SpringBoot电商项目

1.项目分析

1.项目功能: 登录,注册,热销商品,用户管理(密码,个人信息,头像,收货地址),购物车(展示,增加,删除),订单模块.

2.开发顺序 : 注册,登录,用户管理,购物车,商品,订单模块.

3.某一个模块的开发:

🟩持久层开发: 依据前端页面的设置规划相关的SQL语句,以及进行配置.

🟩业务层开发: 核心功能控制,业务操作以及异常的处理.

🟩控制层开发: 接收请求,处理响应.

🟩前端开发: JS,Query,AJAX这些技术来连接后台.

2.项目开发环境

1.JDK : 1.8版本及以上的版本.

2.maven : 配置到idea,3.6.1版本

3.数据库 : MySql.

4.开发平台 : Idea.

3.搭建项目

1.项目名称 : store(商城)

2.结构: com.cy.store

1
2
3
java web
myabtis
mysql driver

3.资源文件: resource文件夹下(static,templates)

4.单元测试: test.com.ct.store

5.在properties文件中配置数据库的连接信息

1
2
3
4
spring.datasource.url=jdbc:mysql://localhost:3306/store?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/shanghai
spring.datasource.username=root
spring.datasource.password=root

6.创建一个store数据库

1
create database store character set utf8;

7.测试连接:

​ 🟩启动SpringBoot的主类是否有对应的SpringBoot图像输出.

​ 🟩在单元测试类中测试数据库的连接是否可以正常的加载.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootTest
class StoreApplicationTests {

@Autowired //自动装配
private DataSource dataSource;

@Test
void contextLoads() {
}

@Test
void getConnection() throws SQLException {
System.out.println(dataSource.getConnection());
}
}

运行getConnection方法,若成功返回HikariProxyConnection@189194499 wrapping com.mysql.cj.jdbc.ConnectionImpl@2b0e9f30则说明成功连接数据库,其中Hikari是一个连接池,用来管理数据库的连接对象,是springboot默认内部整合的连接池,该连接池号称世界上最快的连接池,底层仍然采用c3p0来管理数据库的连接对象

8.访问静态项目的资源,看能否正常的加载.所有的静态资源复制到static目录下

注意:idea对应js1的代码兼容性较差,编写了JS的代码,但是有的时候不能正常的去加载.

  1. idea的缓存清理
  2. clear - install
  3. rebuild重新构建
  4. 重启idea和操作系统

2.用户注册

1.创建数据表

1.选择数据表

1
use store

2.创建t_user表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
CREATE TABLE t_user (
uid INT AUTO_INCREMENT COMMENT '用户id',
username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',
password CHAR(32) NOT NULL COMMENT '密码',
salt CHAR(36) COMMENT '盐值',
phone VARCHAR(20) COMMENT '电话号码',
email VARCHAR(30) COMMENT '电子邮箱',
gender INT COMMENT '性别:0-女,1-男',
avatar VARCHAR(50) COMMENT '头像',
is_delete INT COMMENT '是否删除:0-未删除,1-已删除',
created_user VARCHAR(20) COMMENT '日志-创建人',
created_time DATETIME COMMENT '日志-创建时间',
modified_user VARCHAR(20) COMMENT '日志-最后修改执行人',
modified_time DATETIME COMMENT '日志-最后修改时间',
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE t_address (
aid INT AUTO_INCREMENT COMMENT '收货地址id',
uid INT COMMENT '归属的用户id',
name VARCHAR(20) COMMENT '收货人姓名',
province_name VARCHAR(15) COMMENT '省-名称',
province_code CHAR(6) COMMENT '省-行政代号',
city_name VARCHAR(15) COMMENT '市-名称',
city_code CHAR(6) COMMENT '市-行政代号',
area_name VARCHAR(15) COMMENT '区-名称',
area_code CHAR(6) COMMENT '区-行政代号',
zip CHAR(6) COMMENT '邮政编码',
address VARCHAR(50) COMMENT '详细地址',
phone VARCHAR(20) COMMENT '手机',
tel VARCHAR(20) COMMENT '固话',
tag VARCHAR(6) COMMENT '标签',
is_default INT COMMENT '是否默认:0-不默认,1-默认',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '修改人',
modified_time DATETIME COMMENT '修改时间',
PRIMARY KEY (aid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

tips:

🟩注册页面的确认密码功能多数开发中交给前端做,如果两次密码输入不同就不能将数据传给后台

🟩创建t-user表时password CHAR(32) NOT NULL COMMENT ‘密码’,因为password是关键字,所以需要用号(不是单引号,是esc下面的那个键)并且后面用到该字段时(比如往表中插入数据)也需要用

🟩创建t_user表时salt CHAR(36) COMMENT ‘盐值’,是为了在用户注册时对用户的密码进行加密操作(后续再讲)

🟩数据库中的性别0代表女,1代表男,数据库中用数字而不是文字是因为前端的性别选项是单选框,提交给后台的是数字

🟩创建t_user表时is_delete INT COMMENT ‘是否删除:0-未删除,1-已删除’,的作用:网站中都有注销账号的功能,大部分的网站并不是真的将用户注销了,而是在下次用户登录时进行验证,如果是0就放行,如果是1就禁止登录

🟩创建t-user表时username VARCHAR(20) NOT NULL UNIQUE COMMENT ‘用户名’,的UNIQUE 作为约束条件使用户名唯一

🟩将来任何一张表都有以下四个字段:

created_user VARCHAR(20) COMMENT ‘创建人’,

created_time DATETIME COMMENT ‘创建时间’,

modified_user VARCHAR(20) COMMENT ‘修改人’,

modified_time DATETIME COMMENT ‘修改时间’,

🟩所以为了开发方便可以把这四个字段作为整个实体类

2.创建用户的实体类

1.通过表的结构提取出表的公共字段,放在一个实体类的基类中,起名BaseEntity基类中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import java.io.Serializable;
import java.util.Date;

/*
* 作为实体类的基类
* */
//@Data
//@ToString

public class BaseEntity implements Serializable {
private String createdUser;
private Date createdTime;
private String modifiedUser;
private Date modifiedTime;

public String getCreatedUser() {
return createdUser;
}

public void setCreatedUser(String createdUser) {
this.createdUser = createdUser;
}

public Date getCreatedTime() {
return createdTime;
}

public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}

public String getModifiedUser() {
return modifiedUser;
}

public void setModifiedUser(String modifiedUser) {
this.modifiedUser = modifiedUser;
}

public Date getModifiedTime() {
return modifiedTime;
}

public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BaseEntity)) return false;

BaseEntity that = (BaseEntity) o;

if (getCreatedUser() != null ? !getCreatedUser().equals(that.getCreatedUser()) : that.getCreatedUser() != null)
return false;
if (getCreatedTime() != null ? !getCreatedTime().equals(that.getCreatedTime()) : that.getCreatedTime() != null)
return false;
if (getModifiedUser() != null ? !getModifiedUser().equals(that.getModifiedUser()) : that.getModifiedUser() != null)
return false;
return getModifiedTime() != null ? getModifiedTime().equals(that.getModifiedTime()) : that.getModifiedTime() == null;
}

@Override
public int hashCode() {
int result = getCreatedUser() != null ? getCreatedUser().hashCode() : 0;
result = 31 * result + (getCreatedTime() != null ? getCreatedTime().hashCode() : 0);
result = 31 * result + (getModifiedUser() != null ? getModifiedUser().hashCode() : 0);
result = 31 * result + (getModifiedTime() != null ? getModifiedTime().hashCode() : 0);
return result;
}

@Override
public String toString() {
return "BaseEntity{" +
"createdUser='" + createdUser + '\'' +
", createdTime=" + createdTime +
", modifiedUser='" + modifiedUser + '\'' +
", modifiedTime=" + modifiedTime +
'}';
}
}

2.创建用户的实体类. 需要继承BaseEntity基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import java.io.Serializable;
/*用户的实体类: SpringBoot约定大于配置*/
//@Component
/*@Data
@AllArgsConstructor
@NoArgsConstructor*/

public class User extends BaseEntity implements Serializable {
private Integer uid;
private String username;
private String password;
private String salt;
private String phone;
private String email;
private Integer gender;
private String avatar;
private Integer isDelete;

//1.get 和 set 方法 , equals 和 hashCode()方法, toString()方法


public Integer getUid() {
return uid;
}

public void setUid(Integer uid) {
this.uid = uid;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getSalt() {
return salt;
}

public void setSalt(String salt) {
this.salt = salt;
}

public String getPhone() {
return phone;
}

public void setPhone(String phone) {
this.phone = phone;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public Integer getGender() {
return gender;
}

public void setGender(Integer gender) {
this.gender = gender;
}

public String getAvatar() {
return avatar;
}

public void setAvatar(String avatar) {
this.avatar = avatar;
}

public Integer getIsDelete() {
return isDelete;
}

public void setIsDelete(Integer isDelete) {
this.isDelete = isDelete;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
if (!super.equals(o)) return false;

User user = (User) o;

if (getUid() != null ? !getUid().equals(user.getUid()) : user.getUid() != null) return false;
if (getUsername() != null ? !getUsername().equals(user.getUsername()) : user.getUsername() != null)
return false;
if (getPassword() != null ? !getPassword().equals(user.getPassword()) : user.getPassword() != null)
return false;
if (getSalt() != null ? !getSalt().equals(user.getSalt()) : user.getSalt() != null) return false;
if (getPhone() != null ? !getPhone().equals(user.getPhone()) : user.getPhone() != null) return false;
if (getEmail() != null ? !getEmail().equals(user.getEmail()) : user.getEmail() != null) return false;
if (getGender() != null ? !getGender().equals(user.getGender()) : user.getGender() != null) return false;
if (getAvatar() != null ? !getAvatar().equals(user.getAvatar()) : user.getAvatar() != null) return false;
return getIsDelete() != null ? getIsDelete().equals(user.getIsDelete()) : user.getIsDelete() == null;
}

@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (getUid() != null ? getUid().hashCode() : 0);
result = 31 * result + (getUsername() != null ? getUsername().hashCode() : 0);
result = 31 * result + (getPassword() != null ? getPassword().hashCode() : 0);
result = 31 * result + (getSalt() != null ? getSalt().hashCode() : 0);
result = 31 * result + (getPhone() != null ? getPhone().hashCode() : 0);
result = 31 * result + (getEmail() != null ? getEmail().hashCode() : 0);
result = 31 * result + (getGender() != null ? getGender().hashCode() : 0);
result = 31 * result + (getAvatar() != null ? getAvatar().hashCode() : 0);
result = 31 * result + (getIsDelete() != null ? getIsDelete().hashCode() : 0);
return result;
}

@Override
public String toString() {
return "User{" +
"uid=" + uid +
", username='" + username + '\'' +
", password='" + password + '\'' +
", salt='" + salt + '\'' +
", phone='" + phone + '\'' +
", email='" + email + '\'' +
", gender=" + gender +
", avatar='" + avatar + '\'' +
", isDelete=" + isDelete +
'}';
}
}

tips:

🟩实体类User因为要在网络中以流的形式传输,所以需要serialize序列化(但因为其继承的父类BaseEntity已经实现序列化,所以就不需要再写implements Serializable)

🟩实体类BaseEntity中自动导入Getter and Setter方法,euqals()方法,hashCode()方法,toString方法,其中euqals()方法,hashCode()方法自动导入步骤:

​ 1.enter+insert
​ 2.点击euqals() and hashCode()
​ 3.勾选Accept…和Use这两段话,并且选择Template为IntelliJ Default
​ 4.一路next到底
🟩ssm框架开发项目的时候需要在实体类上面加@Component然后spring才能自动进行对象的创建维护,而springboot不再需要,因为springboot遵循的原则是约定大于配置,如果字段名称相同那就可以自动完成字段的初始化

3.注册

1.持久层

通过MyBatis来操作数据库.在做mybatis开发的流程

1.规划需要执行的SQL语句

1.用户的注册功能,相当于在做数据的插入操作.

1
insert into t_user(username,password) values (值列表)

2.在用户的注册时首先要去查询当前的用户名是否存在,如果存在则不能进行创建.相当于是一条查询语句

1
select * from t_user where username = ?

注: 在实际开发中不要使用*,会降低开发效率.要查什么就用什么.例如:

1
select username from t_user where username = ?
2.设计接口和抽象方法

1.定义Mapper接口.在项目的目录结构下首先创建一个mapper包,在这个包下根据不同的功能模块来创建mapper接口.创建一个UserMapper的接口.要在接口定义这两个SQL语句的抽象方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//@Mapper    有缺陷,如果设置Mapper,一个项目中有许多Mapper接口,则要一个一个设置,麻烦
/*用户模块的持久层接口*/
public interface UserMapper {
/**
* 插入用户的数据
* @param user 用户的数据
* @return 受影响的行数(增,删,改,都有受影响的行数作为返回值,可以根据返回值来判断是否执行成功)
*/
Integer insert(User user);

/**
* 以用户为对象,根据用户名来查询用户的数据
* @param username 用户名
* @return 如果找到对应的用户则返回用户的数据,如果没有找到则返回null值.
*/
User findByUsername(String username);
}

注:

2.ssm框架开发项目的时候需要在mapper接口上加@Mapper用于自动生成相应的接口实现类,在springboot也可以这样,但是后续会有很多mapper接口,每个接口分别加@Mapper太麻烦了,所以在启动类类里面指定当前项目的mapper接口在哪,然后项目启动的时候会自动加载所有的接口

1
//@Mapper    有缺陷,如果设置Mapper,一个项目中有许多Mapper接口,则要一个一个设置,麻烦

3.在启动类配置mapper接口文件的位置

1
2
//MapperScan注解: 指定当前项目中的mapper接口路径的位置,在项目启动时会自动的加载所有的Mapper接口文件.
@MapperScan("com.cy.store.mapper")
3.编写映射

1.定义xml映射文件,与对应的接口进行关联. 所有的映射文件需要放置在resource目录下,在这个目录下创建一个mapper文件夹,然后在这个文件夹下存放Mappe的映射文件.

2.创建接口对应的映射文件,遵循和接口的名称保存一致即可.创建一个UserMapper.xml文件.

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace属性: 用于指定当前的映射文件和哪个接口进行映射,需要指定接口的文件路径,需要标注包的完整路径接口-->
<mapper namespace="com.cy.store.mapper.UserMapper">

</mapper>

3.配置接口中的方法对应上SQL语句,需要借助标签来完成,insert\update\delete\select,对应的为SQL语句的增删改查操作.

🟩insert into () values (),因为values后面插入的值是动态值,mybatis规定需要用占位符来占位,并给占位符起一个变量的名字,且变量的名字需要在占位符#{}内部.
🟩创建t_user表时uid INT AUTO_INCREMENT COMMENT ‘用户id’,中的AUTO_INCREMENT表示主键uid自增,所以需要useGeneratedKeys和keyProperty.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<mapper namespace="com.cy.store.mapper.UserMapper">
<!--自定义映射规则: 需要借助resultMao标签来完成映射规则的定义 -->
<!--id属性: 标签给这个映射负责分配一个唯一的id值,对应的就是resulMap= "id属性的值" 属性的取值-->
<!--type属性: 对查询的结果与java当中的哪一个类的对象进行映射,取值一个类时,具体表示的是数据库中的查询结果与Java中哪个实体类进行结果集的映射-->
<resultMap id="UserEntityMap" type="com.cy.store.entity.User">
<!--将表的字段和类的属性不一致的字段进行匹配指定,名称一致的字段可以省略不写-->
<!--一对result标签 表示一段映射-->
<!--
配合完成名称不一致的映射:
column 属性: 表示表中字段的名称
property属性 : 表示类中的属性名称
-->
<!--在定义映射规则时,它是根据某y一条记录来去映射的,所有有一个字段是不嫩少的,无论它是不是大小写,名称不一致或一致的都要写上 就是id
主键是不可以省略的,不论名称是否一致, 否则就没有办法把唯一一条记录存放在唯一一个对象中,映射规则就会虚断掉.
-->
<id column="uid" property="uid"></id>
<result column="is_delete" property="isDelete"></result>
<result column="created_user" property="createdUser"></result>
<result column="created_time" property="createdTime"></result>
<result column="modified_user" property="modifiedUser"></result>
<result column="modified_time" property="modifiedTime"></result>
</resultMap>


<!-- id属性 : 表示映射的接口中方法的名称,直接在标签的内部来编写SQL语句-->
<!--大部分标签操作都是这里: 1.id指向方法对应的名称,sql语句以前在其它什么位置上写的,直接在标签内部声明就行了-->
<!--
useGeneratedKeys属性: 表示开启某个字段的值递增(主键设置为递增),
keyProperty属性: 标签将表中的哪个字段作为主键递增.
-->
<insert id="insert" useGeneratedKeys="true" keyProperty="uid">
INSERT INTO t_user(
username, password, salt, phone, email,
gender, avatar, is_delete,
created_user, created_time, modified_user, modified_time) VALUES (
#{username}, #{password}, #{salt}, #{phone},
#{email}, #{gender}, #{avatar}, #{isDelete},
#{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime}

) <!--后面不用分号 : 有时候他的底层会自动添加一些条件-->
</insert>

<!--SELECT语句在执行的时候,查询的结果是一个对象,多个对象-->
<!--
resultType: 表示查询结果的类型,只需要指定对应映射类的类型,并且包含完整包类型,当表的字段和属性的字段一致时,用resultType没什么问题
resultMap:标签当表的资源和类的对象属性的字段名称不一致时,来自定义查询结果集的映射规则,字段不一致时,需要定义结果
resultMap引号中的值为映射的规则
-->
<select id="findByUserName" resultMap="UserEntityMap">
SELECT * FROM t_user WHERE username = #{username}
</select>
</mapper>

注: sql语句匹配规则:如果在insert标签里面写了insert语句,首先将insert语句和某一个方法进行绑定,用到了id=“”,但是和哪里的方法进行绑定呢,就要用到namespace=“”,这两步映射就把唯一的SQL语句和唯一的方法进行了关联,实际上就是jdbc里面dao接口的的:

1
2
3
Integer insert(User user) {
String SQL = "insert into () values ()";
}

用到映射的好处:使SQL语句和java代码分离,解耦了,方便后期代码的维护

4.将mapper文件的文章注册到properties对应的配置文件中

1
mybatis.mapper-locations=classpath:mapper/*.mal

5.单元测试: 每个独立的层编写完毕后,都要编写单元测试方法,来测试当前的功能.

好处: 1.尽量小的来排查代码范围的异常的存在,如果有则捕获处理.

在test包里结构下创建一个mapper包,在这个包下创建一个持久层的功能测试.

​ 1.每个独立的层编写完毕后需要编写单元测试方法来测试当前的功能:在test包结构下创建一个mapper包,在这个包下再创建持久层的功能测试,单元测试方法是独立运行,不用启动整个项目,提高了代码的测试效率

​ 2.因为测试方法要用到mapper层的接口来访问刚刚写的两个方法,所以要在类里面声明UserMapper对象:即private UserMapper userMapper;且需要加上@Autowired完成值的初始化,但此时会发现提示"Could not autowire.No beans of’UserMapper’type found",报错原因是idea有自动检测的功能,在java中接口是不能够直接创建bean的,所以idea认为这个语法不合理,但本质上在项目启动时mybatis自动创建了接口的动态代理实现类,所以从项目的运行角度讲这不能算是错.解决办法:

​ 在Settings里面搜索inspections,依次找到Spring->Spring Core->Code->Autowiring for Bean Class然后将Severity的Error改为Warning.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@SpringBootTest
//@SpringBootTest : 表示标注当前的类是一个测试类,不会随同项目一块打包发送
@RunWith(SpringRunner.class)
//@RunWith : 表示启动这个单元测试类(没有这个注解单元测试类是不能够运行的),需要传递一个参数,参数是固定的,必须是SpringRunner的class(实例)类型
public class UserMapperTests {
//使用@Autowired 报错的:
// idea 有检测的功能,接口是不能够直接创建Bean的(动态代理技术来解决)
@Resource
private UserMapper userMapper;

//以下四点同时满足,才是单元测试方法 : 就可以单独独立运行,不用启动整个项目,就可以做单元测试,提升了代码的测试效率
//1.必须被@Test注解所修饰
//2.返回值类型必须是void
//3.方法的参数类别不指定任何类型
//4.方法的访问修饰符必须是public
@Test
public void insert(){
User user = new User();
user.setUsername("tim1");
user.setPassword("123");
Integer rows = userMapper.insert(user);
System.out.println(rows);

}
@Test
public void findByUserName(){
User user = userMapper.findByUserName("tim1");
System.out.println(user);
}
}

2.业务层

业务层的核心功能:

  • 接受前端从控制器流转过来的数据

  • 结合真实的注册业务来完成功能业务逻辑的调转和流程

  • 所以这里要考虑到真实的业务场景,如果只考虑业务场景的话不完整,因为在整个业务执行的过程中会产生很多问题,从java角度来讲这些都是属于异常,所以在业务开发的时候就要规划相关的异常,以便把项目的错误控制在一定范围内

    service下的目录结构(建议这样):

    service包下创建ex包用来写异常类
    service包下创建impl包用来写接口的实现类
    接口直接写在service包下,不再需要接口包

1.规划异常

🟩为什么会有异常:

比如,用户在进行注册时可能会产生用户名被占用的错误,这时需要抛出一个异常

🟩怎么处理异常:

异常不能用RuntimeException,太笼统了,开发者没办法第一时间定位到具体的错误类型上,我们可以定义具体的异常类型来继承这个异常.
正常的开发中异常又要分等级,可能是在业务层产生异常,可能是在控制层产生异常,所以可以创建一个业务层异常的基类,起名ServiceException异常,并使其继承RuntimeException异常
后期开发业务层时具体的异常可以再继承业务层的异常ServiceException.

1.RuntimeException异常 ,作为这异常的子类,然后在去定义具体的异常类型来检测这个常用.业务层异常的基1类,ServiceException异常. 然这个异常继承RuntimeException异常.异常机制的建立.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*作为业务层异常的基类 : throw new ServiceException("业务层产生未知的异常")*/
public class ServiceException extends RuntimeException{
public ServiceException() {
super();
}

public ServiceException(String message) {
super(message);
}

public ServiceException(String message, Throwable cause) {
super(message, cause);
}

public ServiceException(Throwable cause) {
super(cause);
}

protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

根据业务层不同的功能来详细定义具体的异常类型,统一的去继承ServiceException异常类.

2.用户在进行注册的时候,可能会产生用户名被占用的错误,抛出一个异常 : 异常

  • 用户在进行注册时可能会产生用户名被占用的错误,这时需要抛出一个UsernameDuplicatedException异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.cy.store.service.ex;

/*用户名被占用的异常*/
public class UsernameDuplicatedException extends ServiceException{
//alt + insert ----override methods.


public UsernameDuplicatedException() {
super();
}

public UsernameDuplicatedException(String message) {
super(message);
}

public UsernameDuplicatedException(String message, Throwable cause) {
super(message, cause);
}

public UsernameDuplicatedException(Throwable cause) {
super(cause);
}

protected UsernameDuplicatedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

3.正在执行数据插入的操作时,服务器,数据库宕机.处于正在执行插入的过程中所产生的异常insertException异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.cy.store.service.ex;

/*数据在插入过程中所产生的异常*/
public class insertException extends ServiceException{
public insertException() {
super();
}

public insertException(String message) {
super(message);
}

public insertException(String message, Throwable cause) {
super(message, cause);
}

public insertException(Throwable cause) {
super(cause);
}

protected insertException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

2.设计接口和抽象方法的定义

在service包下创建一个接口IUserService接口.(接口命名的默认规则:I+业务名字+层的名字)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.cy.store.service;

import com.cy.store.entity.User;

/*用户模块业务层接口*/
public interface IUserService {
/**
* 用户注册方法
* @param user 用户的数据对象
*/
void reg(User user);

}

2.创建一个实现类UserServiceImpl类,需要实现这个接口,并且实现抽象方法

因为要将这个实现类交给spring管理,所以需要在类上加@Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package com.cy.store.service.impl;

import com.cy.store.entity.User;
import com.cy.store.mapper.UserMapper;
import com.cy.store.service.IUserService;
import com.cy.store.service.ex.UsernameDuplicatedException;
import com.cy.store.service.ex.insertException;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.Date;
import java.util.UUID;

/*用户模块业务层的实现类*/
@Service
//@Service注解: 将当前类的对象交给Spring管理, 自动创建对象以及对象的维护
public class UserServiceImpl implements IUserService {
@Resource
private UserMapper userMapper;
@Override
public void reg(User user) {
//通过user参数来获取传递过来的username.
String username = user.getUsername();
//调用findByUserName(username) 判断用户是否被注册过
User result = userMapper.findByUserName(username);
//判断结果集是否不为null 则抛出用户名被占用的异常
if(result != null){
//抛出异常
throw new UsernameDuplicatedException("用户名被占用");
}

//密码加密处理的实现 : md5算法的形式
// (串 + password + 串) -----交给 md5算法 进行加密,整体看成是一个结构,连续加载3次.
//盐值 + password + 盐值 ---- 盐就是一个随机的字符串.
String oldPassword = user.getPassword();
//获取盐值(随机生成一个盐值)
String salt = UUID.randomUUID().toString().toUpperCase();
//补全数据,盐值的记录
user.setSalt(salt);
//将密码和盐值作为一个整体进行加密处理,忽略原有密码强度提升了密码的安全性.
String md5Password = getMD5Password(oldPassword, salt);
//将加密之后的密码重新补全到user对象中.
user.setPassword(md5Password);


//补全数据 : is_delete设置为0
user.setIsDelete(0);
//补全数据 : 4个日志字段信息
user.setCreatedUser(user.getUsername());
user.setModifiedUser(user.getUsername());
Date date = new Date();
user.setCreatedTime(date);
user.setModifiedTime(date);

//执行注册业务功能的实现(如果用户名没被占用,则rows == 1)
Integer rows = userMapper.insert(user);
if(rows != 1){
throw new insertException("在用户注册过程中产生了未知的异常");
}
}

/*定义一个md5 算法的加密处理*/
private String getMD5Password(String password,String salt){
/*
*加密规则:
* 1.无视原始密码的强度
* 2.使用UUID作为盐值,在原始密码左右两侧加盐值
* 3.循环加密3次
* */

//md5加密算法方法的调用
for(int i = 1;i<=3;i++){
//将 盐 + password + 盐 作为整体加密, 在 getBytes()转为字节,在toUpperCase()方法 转换为大写.
password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase();
}
//返回加密之后的密码
return password;
}
}

3.在单元测试包下创建一个UserServiceTest类,在这个类中添加单元测试的功能.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void reg(){

try {
User user = new User();
user.setUsername("yuanlin1");
user.setPassword("123");
userService.reg(user);
System.out.println("OK");
} catch (ServiceException e) {
//获取类的对象, 在获取类的名称
System.out.println(e.getClass().getSimpleName());

//获取异常的具体描述信息
System.out.println(e.getMessage());
}

}

3.密码的加密

定义一个md5 算法的加密处理

1
2
3
4
加密规则:
1.无视原始密码的强度
2.使用UUID作为盐值,在原始密码左右两侧加盐值
3.循环加密3次

md5加密算法方法的调用

1
2
3
4
5
6
7
     for(int i = 1;i<=3;i++){
//将 盐 + password + 盐 作为整体加密, 在 getBytes()转为字节,在toUpperCase()方法 转换为大写.
password = DigestUtils.md5DigestAsHex((salt+password+salt).getBytes()).toUpperCase();
}
//返回加密之后的密码
return password;
}

密码加密处理的实现 : md5算法的形式
(串 + password + 串) -----交给 md5算法 进行加密,整体看成是一个结构,连续加载3次.
盐值 + password + 盐值 ---- 盐就是一个随机的字符串.

1
2
3
4
5
6
7
8
9
String oldPassword = user.getPassword();
//获取盐值(随机生成一个盐值)
String salt = UUID.randomUUID().toString().toUpperCase();
//补全数据,盐值的记录
user.setSalt(salt);
//将密码和盐值作为一个整体进行加密处理,忽略原有密码强度提升了密码的安全性.
String md5Password = getMD5Password(oldPassword, salt);
//将加密之后的密码重新补全到user对象中.
user.setPassword(md5Password);

注: 用户的密码进行加密时, 需要将盐值(salt)的值进行保存,以便用户在登录时能够进行密码登录的校验.

3.功能的控制层

1.创建响应

状态码,状态描述信息,数据是所有控制层对应的方法都涉及到的操作,所以把这部分功能封装到一个类JsonResult中,将这个类作为方法的返回值返回给前端浏览器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.cy.store.util;

import java.io.Serializable;

/*
* Json格式的数据进行响应. 那么就需要进行实现接口 Serializable
* */
public class JsonResult<E> implements Serializable {

//状态码
private Integer state;
//描述信息
private String message;
//数据类型不确定, 则使用泛型e 数据
private E data;

public JsonResult() {
}

public JsonResult(Integer state) {
this.state = state;
}

public JsonResult(Throwable e) {
this.message = e.getMessage();
}
public JsonResult(Integer state, E data) {
this.state = state;
this.data = data;
}

public Integer getState() {
return state;
}

public void setState(Integer state) {
this.state = state;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public E getData() {
return data;
}

public void setData(E data) {
this.data = data;
}
}

2.设计请求

接下来该向后端服务器发送请求以把用户数据插入到数据库,设计发送请求模块的第一步就是设计相关的请求

依据当前的业务功能模块进行请求的设计.

1
2
3
4
请求路径 : /users/reg
请求参数 : User user
请求类型 : POST
响应结果 : JsonResult<E>
3.处理请求

1.创建一个控制层对应的类UserController类, 依赖于业务层的接口.编写完成后启动主服务验证一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.cy.store.controller;

import com.cy.store.entity.User;
import com.cy.store.service.IUserService;
import com.cy.store.service.ex.UsernameDuplicatedException;
import com.cy.store.service.ex.insertException;
import com.cy.store.util.JsonResult;
import jakarta.annotation.Resource;
import org.apache.ibatis.annotations.Result;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

//@Controller
@RestController //@Controller + @ResponseBody
@RequestMapping("users")
public class UserController {

@Resource
private IUserService userService;

@RequestMapping("reg")
//@RequestBody //表示此方法的响应结果以json格式进行数据的响应给到前端
public JsonResult<Void> reg(User user){
//创建响应结果对象
JsonResult<Void> result = new JsonResult<>();
try {
userService.reg(user);
result.setState(200);
result.setMessage("用户注册成功");
} catch (UsernameDuplicatedException e) {
result.setState(4000);
result.setMessage("用户名被占用");

}catch (insertException e){
result.setState(5000);
result.setMessage("注册时产生未知的异常");
}
return result;
}
}

4.控制层进行优化控制

凡是业务层抛出的异常我们都在控制层进行了捕获,如果其他的业务模块也抛用户名被占用或者插入时异常,那么抛出异常的代码就要重复编写

在控制层抽离一个父类,在这个父类中统一的去处理关于异常的相关操作,编写一个BaseController类.统一的处理异常.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.cy.store.controller;

import com.cy.store.service.ex.ServiceException;
import com.cy.store.service.ex.UsernameDuplicatedException;
import com.cy.store.service.ex.insertException;
import com.cy.store.util.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;

/*控制层的基类*/
public class BaseController {

/* 操作成功的状态码*/
public static final int OK = 200;

//请求处理方法,这个方法的返回值就是需要传递给前端的数据
//自动将异常对象传递给此方法的参数列表上.

//当项目中产生了异常,被统一拦截到此方法中,这个方法此时就冲当的时请求处理的方法,方法的返回值直接给到前端.

@ExceptionHandler(ServiceException.class) //用于统一处理抛出的异常
//JsonResult类型为 : 把数据封装为Json的数据类型
public JsonResult<Void> handleException(Throwable e){
JsonResult<Void> result = new JsonResult<>(e);
//instanceof 属于
if(e instanceof UsernameDuplicatedException){
result.setState(4000);
result.setMessage("用户名已经被占用");
}else if(e instanceof insertException){
result.setState(5000);
result.setMessage("注册过程中产生了未知的异常");
}
return result;
}
}

重新构建了reg()方法,将UserController中的内容更改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.cy.store.controller;

import com.cy.store.entity.User;
import com.cy.store.service.IUserService;

import com.cy.store.util.JsonResult;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


//@Controller
@RestController //@Controller + @ResponseBody
@RequestMapping("users")
public class UserController extends BaseController{

@Resource
private IUserService userService;
@RequestMapping("reg")
//@RequestBody //表示此方法的响应结果以json格式进行数据的响应给到前端
public JsonResult<Void> reg(User user) {
//创建响应结果对象
userService.reg(user);

return new JsonResult<>(OK);
}
/*@RequestMapping("reg")
//@RequestBody //表示此方法的响应结果以json格式进行数据的响应给到前端
public JsonResult<Void> reg(User user){
//创建响应结果对象
JsonResult<Void> result = new JsonResult<>();
try {
userService.reg(user);
result.setState(200);
result.setMessage("用户注册成功");
} catch (UsernameDuplicatedException e) {
result.setState(4000);
result.setMessage("用户名被占用");

}catch (insertException e){
result.setState(5000);
result.setMessage("注册时产生未知的异常");
}
return result;
}*/
}


4.前端页面

什么是ajax函数?

这是jQuery封装的一个函数,称为$.ajax()函数,通过对象调用ajax()函数用来异步加载相关的请求.依靠的是JavaScript提供的一个对象:XHR(全称XmlHttpResponse).

ajax()函数的语法结构:

​ 🟩使用ajax()时需要传递一个方法体作为方法的参数来使用(一对大括号就是一个方法体)
​ 🟩ajax接受多个参数时,参数与参数之间使用",“分割
​ 🟩每一组参数之间使用”:"进行分割
​ 🟩参数的组成部分一个是参数的名称(不能随便定义),另一个是参数的值(必须用字符串来表示)
​ 🟩参数的声明顺序没有要求

1.在register页面中编写发送请求的方法.点击事件来完成.选选中对应的按钮($(“选择器”)),再去添加点击的事件,$.ajax()函数发送异步请求.

2.JQUery封装了一个函数,称之为$.ajax()函数(JQUery把 $封装为一个对象),通过对象调用ajax函数,可以异步加载相关的请求.依靠的是javascript提供的对象XHR(XmlHttpResponse),封装了这个对象.

3.ajax()使用方式. 需要传递一个方法体作为方法的参数来使用,一对大括号称之为方法体.ajax接收多个参数,参数与参数之间要求使用",(英文)“,来分隔,每一组参数之间使用”: (英文)"进行分割, 参数的组成部分一个是参数的名称( 不能够随意的去定义 ), 是参数的值, 参数的值要求是用字符串来标识, 参数的声明顺序没有要求, 语法结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.ajax({
url : "",
type : "",
data : "",
dataType : "",
success : function(){

},
error : function(){

}
});


4.ajax()函数参数的含义:

参数 功能描述
url 标识请求的地址(url地址), 不能包含参数列表部分的内容. 例如: url : “localhost:8080/users/reg”
type 请求类型 (GET和POST类型), 例如 : type : “POST”
data 向指定的请求url地址提交的数据. 例如 : data:“username = tom & pwd = 132”
dataType 提交的数据的类型.数据类型一般为json类型, dataType : “json”
success 当服务器正常响应客户端时, 会自动调用success参数的方法, 并且将服务器返回的数据以参数的形式传递给这个方法的参数上.
error 当服务器未正常响应客户端时, 会自动调用error参数的方法, 并且将服务器返回的数据以参数的形式传递给这个方法的参数上.

5.js代码可以独立声明在一个js的文件里或者声明在一个script标签里.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<script>
//1.监听注册按钮是否被点击,如果被点击可以执行一个方法(这里不能像ajax函数那样删去function()只留下{},这是官方规定的!)
$("#btn-reg").click(function () {

//let username = $("#username").val();
//let pwd = $("#password").val();
//上面这两行是动态获取表单中控件的数据,但是如果这样获取的话ajax函数中
//就是data: "username="+username + "&password="+pwd,但太麻烦了,如
// 果这个表单提交的是用户的兴趣爱好,那数据就很多了,一个表单20个数据都很正
// 常,如果此时还用这种方式就太麻烦了,所以不建议用这种方式

//2.发送ajax()的异步请求来完成用户的注册功能
$.ajax({
url: "/users/reg",
type: "POST",

//serialize这个API会自动检测该表单有什么控件,每个控件检测后还会获取每个控
// 件的值,拿到这个值后并自动拼接成形如username=Tom&password=123的结构
data: $("#form-reg").serialize(),

dataType: "JSON",
success: function (json) { //1.js是弱数据类型,这个地方不用声明json的数据类型
//2.如果服务器成功响应就会将返回的数据传给形参,比如{state: 4000,message: "用户名
// 已经被占用",data: null}
if (json.state == 200) {
alert("注册成功")
} else {
alert("注册失败")
}
},
error: function (xhr) { //如果问题不在可控范围内,服务器就不会返回自己定
//义的json字符串:{state: 4000,message: "用户名已经被占用",data: null}
//而是返回一个XHR类型的对象,该对象也有一个状态码名字是status
alert("注册时产生未知的错误!"+xhr.status);
}
});
});
</script>

6.js代码无法正常被服务器解析成功, 体现在点击页面中的按钮没有任何响应, 解决方案:

​ 🟩在项目的maven下clear清理项目-install重新部署

​ 🟩在项目的file选项下-cash清理缓存

​ 🟩重新的去构建项目 : buid 选项下 - rebuild选项

​ 🟩重启idea

​ 🟩重启电脑


3.用户登录

当用户输入用户名和密码将数据提交给后台数据库进行查询, 如果存在对应的用户名和密码, 则表示登录成功, 登录成功之后跳转到系统的主页就是 index.html页面, 跳转在前端使用jquery来完成.


1.登录

1.持久层

1规划需要执行的SQL语句

依据用户提交的用户名和密码做select查询, 密码的比较在业务层执行.

select * from t_user where username=? and password=?这种不太好,这种相当于在查询用户名时直接判断了用户和密码是否一致了,如果持久层把判断做了那业务层就没事干了,所以这里我们只查询用户名,判断用户名和密码是否一致交给业务层做

1
select * from t_user where username = ? 

说明: 如果在分析过程中发现某个模块已经被开发完成, 所以就可以省略当前的开发步骤, 这个分析过程不能省略.(在这里 sql语句被开发完成,也就意味着xml映射文件也不需要进行开发, 也意味着这个接口所映射的findByuserName也不需要在进行开发了.)

1
2
3
<select id="findByUserName" resultMap="UserEntityMap">
SELECT * FROM t_user WHERE username = #{username}
</select>
2.接口文件的设计和抽象方法

不需要重复开发. 单元测试也是无需单独去执行的.


2.业务层

1.规划异常

1.用户名对应的密码错误, 密码匹配失败的异常: PasswordNotMatchException异常, 运行时异常,业务异常.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.cy.store.service.ex;

/*密码验证失败的异常*/
public class PasswordNotMatchException extends ServiceException{
public PasswordNotMatchException() {
}

public PasswordNotMatchException(String message) {
super(message);
}

public PasswordNotMatchException(String message, Throwable cause) {
super(message, cause);
}

public PasswordNotMatchException(Throwable cause) {
super(cause);
}

public PasswordNotMatchException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

2.用户名没有找到的,抛出异常: UsernameNotFoundEeception. 运行时异常,业务异常.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.cy.store.service.ex;

/*用户名不存在的异常*/
public class UsernameNotFoundException extends ServiceException{
public UsernameNotFoundException() {
}

public UsernameNotFoundException(String message) {
super(message);
}

public UsernameNotFoundException(String message, Throwable cause) {
super(message, cause);
}

public UsernameNotFoundException(Throwable cause) {
super(cause);
}

public UsernameNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

3.异常的编写:

​ 🟩业务层异常需要继承ServiceException异常类.

​ 🟩在具体的异常类中定义构造方法(可以使用快捷键来生成, 有5 个构造方法).


2.设计业务层接口和抽象方法

1.直接在IUserService 接口中编写抽象方法, 在 login(String username, String password).

login(User user)也是可以的.

细说一个事:登录成功某一个网站后,右上角会展示头像,昵称甚至电话号码等等,这些信息依赖于登陆成功后的信息,也就意味着一旦登录成功后在页面中切换到任意一个子页面写右上角都会展示这些信息.本质上就是查询出来这些信息,然后展示在右上角,但是这里实现查询不太现实:js中虽然打开一个html页面就自动发送一个请求,但这样就需要把这个查询的代码写在每一个html页面,显然不现实.

将当前登录成功的用户数据以当前用户对象的形式返回. 状态管理: 可以将数据保存在cookie 或 session中, 可以避免重复度很高的数据多次频繁操作数据进行获取.(这里我们用session存放用户名和用户id,用cookie存放用户头像,其中用户id是为因为有的页面展示依赖于id,用户头像也可以放在session中,而这里放在cookie是为了回顾一下cookie)

1
2
3
4
5
6
7
/**
* 用户登录的功能
* @param username 用户名
* @param password 用户的密码
* @return 当前匹配的用户数据 , 如果没有则返回null
*/
User login(String username,String password);

2.需要在实现类UserServiceImpl中实现父类接口的抽象方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Override
public User login(String username, String password) {
//根据用户的名称来查询用户的数据是否存在, 如果不存在则抛出异常
User result = userMapper.findByUserName(username);
if(result == null){
throw new UsernameNotFoundException("用户数据不存在");
}

//检测用户的密码是否匹配
//1. 先获取到数据库进行加密后的密码
String oldPassword = result.getPassword();

//2.和用户传递过来的密码进行比较

//2.1 先获取盐值: 上一次注册所自动生成的盐值.
String salt = result.getSalt();
//2.2 将用户输入的密码进行相同的md5算法加密的规则进行加密.
String newMd5Password = getMD5Password(password,salt);
//3. 将密码进行比较
if(!newMd5Password.equals(oldPassword)){
throw new PasswordNotMatchException("用户密码错误");
}

//判断is_delete字段的值是否为 1 表示标记被删除
if(result.getIsDelete() == 1){
throw new UsernameNotFoundException("用户数据不存在");
}

//调用usermapper层的findByUserName来查询用户的数据, 提升了系统的性能
User user = new User();
user.setUid(result.getUid());
user.setUsername(result.getUsername());
user.setAvatar(result.getAvatar());

//将当前的用户数据返回, 返回的数据是为了辅助其它页面做数据展示使用(uid, username,avatar)

return user;
}

3.在测试类中测试业务层登录的方法是否可以执行通过.

1
2
3
4
5
6
@Test
public void login(){
User user = userService.login("test01","123");
System.out.println(user);

}

4.如果一个类没有手动创建直接将这个类复制到项目中, idea找不到这个类. 之前的缓存导致不能够正常找到这类的符号. 重新构建项目.

3.抽象方法的实现

3.控制层

1.处理异常

业务层抛出的异常是什么, 需要在统一异常处理类中进行统一的捕获和处理. 如果业务层抛出的异常类型已经在统一异常处理类中曾经处理过, 则不需要重复添加.

为什么在service层和controller层都需要抛异常

业务层抛的异常被控制层捕捉并处理,设置相应的提示信息,作为结果响应给前端

因为controller层调用了service层,所以需要处理异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@ExceptionHandler(ServiceException.class) //用于统一处理抛出的异常
//JsonResult类型为 : 把数据封装为Json的数据类型
public JsonResult<Void> handleException(Throwable e){
JsonResult<Void> result = new JsonResult<>(e);
//instanceof 属于
if(e instanceof UsernameDuplicatedException){
result.setState(4000);
result.setMessage("用户名已经被占用");
}else if(e instanceof insertException){
result.setState(5000);
result.setMessage("注册过程中产生了未知的异常");
}else if(e instanceof UsernameNotFoundException){
result.setState(5001);
result.setMessage("用户数据不存在");
}else if(e instanceof PasswordNotMatchException){
result.setState(5002);
result.setMessage("用户密码错误");
}
return result;
}
}

2.设计请求
1
2
3
4
请求路径: /users/login
请求方式: POST
请求数据: String username, String password, HttpSession session
响应结果: JsonResult<User>

3.处理请求

在UserController类中编写处理请求的方法.编写完成后启动主服务验证一下

1
2
3
4
5
6
7
8
9
10
11
/*
*
* 2.接收数据方式: 请求处理方法的参数列表设置为非pojo类型
* SpringBoot会直接将请求的参数名和方法的参数名直接进行比较,如果名称
* 相同则自动完成值的依赖注入
* */
@RequestMapping("login")
public JsonResult<User> login(String username, String password){
User user = userService.login(username, password);
return new JsonResult<User>(OK,user);
}

注意,控制层方法的参数是用来接收前端数据的,接收数据方式有两种:

请求处理方法的参数列表设置为非pojo类型:

SpringBoot会直接将请求的参数名和方法的参数名直接进行比较,如果名称相同则自动完成值的依赖注入

请求处理方法的参数列表设置为pojo类型:

SpringBoot会将前端的url地址中的参数名和pojo类的属性名进行比较,如果这两个名称相同,则将值注入到pojo类中对应的属性上

这两种方法都没有使用注解等等花里胡哨的,却能正常使用,原因是springboot是约定大于配置的,省略了大量配置以及注解的编写


4.前端页面

1.在login.html页面依据前面所设置的请求来发送ajax请求.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script type="text/javascript">
$("#btn-login").click(function () {
$.ajax({
url: "/users/login",
type: "POST",
data: $("#form-login").serialize(),
dataType: "JSON",
success:function (json) {
if(json.state == 200){
alert("登录成功");
//跳转到系统页面index.html
//相对路径来确定跳转的页面
location.hash = "index.html";
}else {
alert("登录失败");
}
},
error:function (xhr) {
alert("登录时产生未知的异常"+xhr.message);
}
})
})
</script>

2.访问页面进行用户的登录操作.


5.用户登录会话Session

在用户登录成功后要保存下来用户的id,username,avatar,并且需要在任何类中都可以访问存储下来的数据,也就是说存储在一个全局对象中,会话session可以实现
把首次登录所获取的用户数据转移到session对象即可
获取session对象的属性值用session.getAttribute(“key”),因为session对象的属性值在很多页面都要被访问,这时用session对象调用方法获取数据就显得太麻烦了,解决办法是将获取session中数据的这种行为进行封装
考虑一下封装在哪里呢?放在一个干净的工具类里肯定可以,但就这个项目目录结构而言,只有可能在控制层使用session,而控制层里的类又继承BaseController,所以可以封装到BaseController里面

session对象主要存放于服务器端, 可以用于保存服务器的临时数据的对象, 所保存的数据可以在整个项目中都可以通过访问来获取, 把session的数据看出一个共享的数据, 首次登录的时候所获取到用户的数据, 转移到session对象即可.

session.getAttrbue(“key”)可以将session中的数据这种行为进行封装, 封装在BaseController类中.

综上所述,该功能的实现需要两步:

1.封装session对象中数据的获取(封装父类中), 数据的设置(当用户登录成功后进行数据的设置, 设置到全局的seeion对象).

2.在父类中封装两个数据: 获取 uid 和 username 对应的两个方法. 用户头像暂时不考虑,将来封装到cookie中用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 获取到Session中的uid
* @param session Session对象
* @return 当前登录的用户uid的值
*/
protected final Integer getuidFromSession(HttpSession session){
return Integer.valueOf(session.getAttribute("uid").toString());
}

/**
* 获取当前登录用户的username
* @param session Session对象
* @return 当前登录用户的用户名
* 在实现类中重新父类中的toString()方法, 不是句柄信息的输出.
*/
protected final String getUserNameFromSession(HttpSession session){
return session.getAttribute("username").toString();
}

3.服务器本身自动创建有session对象,已经是一个全局的session对象,所以我们需要想办法获取session对象:如果直接将HttpSession类型的对象作为请求处理方法的参数,这时springboot会自动将全局的session对象注入到请求处理方法的session形参上:

  • 将登录模块的设计请求中的请求参数:String username,String password加上HttpSession session
  • 将登录模块的处理请求中login方法加上参数HttpSession session并修改代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping("login")
public JsonResult<User> login(String username, String password, HttpSession session) {
User data = userService.login(username, password);

//向session对象中完成数据的绑定(这个session是全局的,项目的任何位置都可以访问)
session.setAttribute("uid",data.getUid());
session.setAttribute("username",data.getUsername());

//测试能否正常获取session中存储的数据
System.out.println(getUidFromSession(session));
System.out.println(getUsernameFromSession(session));

return new JsonResult<User>(OK,data);
}


6.拦截器

  • 拦截器的作用是将所有的请求统一拦截到拦截器中,可以在拦截器中定义过滤的规则,如果不满足系统设置的过滤规则,该项目统一的处理是重新去打开login.html页面(重定向和转发都可以,推荐使用重定向)
  • 拦截器在springboot中本质是依靠springMVC完成的.springMVC提供了一个HandlerInterceptor接口用于表示定义一个拦截器

(重定向 你访问 让后我让你自己重新去访问别的,转发 你访问 我带你去访问别的),

重定向 你访问 让后我让你自己重新去访问别的,转发 你访问 我带你去访问别的

重定向:发送请求url,根据服务器的返回信息重新发送新的请求url。转发:发送url,服务器内部解析后进行的新的请求,整个过程发生在服务器内部,外部不知

在SpringBoot项目中拦截器的定义和使用, SpringBoot是依靠SpringMVC来完成的. SpringMVC提供了一个Handledinterceptor接口, 用于表示定义一个拦截器, 首先定义一个类, 在这个类实现这个接口.

1.首先自定义一个类, 在这个类实现这个Handledinterceptor接口.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.cy.store.interceptor;


import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;

/*定义一个拦截器*/
public class LoginInterceptor implements HandlerInterceptor {


/**
* 检测全局session对象中是否有uid的值, 如果有则放行, 如果没有则重定向到登录页面
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器(做 url + controller 的映射)
* @return 如果返回值为true则表示放行当前的请求, 如果返回值为false则表示拦截当前的请求.
* @throws Exception
*/
@Override
//在DispatcherServlet调用所有处理请求的方法前被自动调用执行的方法
//springboot会自动把请求对象给到request,响应对象给到response,适配器给到handler
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//HttpServletRequest对象来获取session对象
Object obj = request.getSession().getAttribute("uid");

if(obj == null){
//说明用户没有登录过系统, 则重定向到login.html页面
//不能用相对路径,因为这里是要告诉前端访问的新页面是在哪个目录下的新
//页面,但前面的localhost:8080可以省略,因为在同一个项目下 response.sendRedirect("web/login.html");

//结束后续的调用
return false;
}

//请求放行
return true;
}
}
//在ModelAndView对象返回给DispatcherServlet之后被自动调用的方法
// @Override
// public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// }
//在整个请求所有关联的资源被执行完毕后所执行的方法
// @Override
// public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// }

2.注册过滤器的技术:借助WebMvcConfigure接口将用户定义的拦截器进行注册.所以想要注册过滤器需要定义一个类使其实现WebMvcConfigure接口并在其内部添加黑名单(在用户登录的状态下才可以访问的页面资源)和白名单(哪些资源可以在不登录的情况下访问:①register.html②login.html③index.html④/users/reg⑤/users/login⑥静态资源):

WebMvcConfigure是配置信息,建议在store包下建config包,再定义类LoginInterceptorConfigure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.cy.store.config;


import com.cy.store.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

/* 处理器拦截器的注册*/
@Configuration //自动加载当前的类并进行拦截器的注册,如果没有@Configuration就相当于没有写类LoginInterceptorConfigure
public class LoginInterceptorConfigure implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {

HandlerInterceptor interceptor = new LoginInterceptor();
//配置白名单 : 存放在一个List集合
List<String> patterns = new ArrayList<>();
patterns.add("/bootstrap3/**");
patterns.add("/css/**");
patterns.add("/images/**");
patterns.add("/js/**");
patterns.add("/web/register.html");
patterns.add("/web/login.html");
patterns.add("/web/index.html");
patterns.add("/web/product.html");
patterns.add("/users/reg");
patterns.add("/users/login");

//完成拦截器的注册
//registry.addInterceptor(interceptor);完成拦截
// 器的注册,后面的addPathPatterns表示拦截哪些url
//这里的参数/**表示所有请求,再后面的excludePathPatterns表
// 示有哪些是白名单,且参数是列表
registry.addInterceptor(interceptor)
.addPathPatterns("/**") //表示要拦截的url
.excludePathPatterns(patterns); //白名单


}
}

3.注册过滤器的技术 : 借助WebMvcConfigure接口. 可以将用户定义的拦截器进行注册, 才可以保证拦截器能够生效和使用. 定义一个类, 然后让这个类实现WebMvcConfigure接口, 配置信息, 建议存放在项目的config包结构下.

1
2
3
4
5
6
public interface WebMvcConfigurer {

//将自定义拦截器进行注册
default void addInterceptors(InterceptorRegistry registry) {
}
}

Handledinterceptor源码解析

这里讲错了 拦截器是在doDispatch中生效的,也就是说是请求进入中央调度器后在进入自定义controller之前进行拦截

拦截器是在Controller之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface HandlerInterceptor {
//在调用所有处理请求的方法之前被自动调用的方法
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}

//在Model 和 View对象返回之后被调用的方法
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}

//在整个请求所有关联的资源被执行完毕最后所执行的方法
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}

image-20230515165414852

4.修改密码

初步分析:需要用户提交原始密码和新密码,再根据当前登录的用户进行信息的修改操作

修改密码


1.持久层

1.规划需要执行的SQL语句

根据用户的uid修改用户password值

1
update t_user set password=?,modified_user=?, modified_time=? WHERE uid=?

在执行修改密码之前,还应检查用户数据是否存在或者用户数据是否被标记为"已删除"(比如登录账号后的几分钟在和朋友聊天,没有看页面,管理员错误删除了你的账号或者错误设置is_delete为1)、并检查原密码是否正确,这些检查都可以通过查询用户数据来辅助完成:

1
select * from t_user where uid = ?

2.设计接口和抽象方法

UserMapper接口,将以上的两个方法的抽象定义出来, 将来映射到sql语句.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 根据用户的uid来修改用户的密码
* @param uid 用户的id
* @param password 用户输入的新密码
* @param modifiedUser 表示修改密码的执行者
* @param modifiedTime 表示修改数据的时间
* @return 返回值为受影响的行数
*/
Integer updatePasswordByUid(Integer uid, String password, String modifiedUser, Date modifiedTime);

/**
* 根据用户的uid来查询用户的数据
* @param uid 用户的id
* @return 如果找到则返回对象, 没有则返回null.
*/
User findByUid(Integer uid);

3.SQL的映射

配置到映射文件UserMapper.xml中

1
2
3
4
5
6
7
8
9
10
11
<update id="updatePasswordByUid">
SELECT t_user SET
password = #{password},
modified_user = #{modifiedUser},
modified_time = #{modifiedTime}
where uid = #{uid}
</update>

<select id="findByUid"resultMap="UserEntityMap">
SELECT * FROM t_user WHERE uid = {uid}
</select>

做单元测试功能测试.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 @Test
public void updatePasswordByUid(){
userMapper.updatePasswordByUid(
10,
"321",
"管理员",
new Date());
}

@Test
public void findByUid(){
System.out.println(userMapper.findByUid(10));
}


2.业务层

1.规划异常
  • 用户的原密码错误,抛PasswordNotMatchException异常(前面已创建)

  • 检测到is_delete字段为1和uid找不到都是抛出用户没有找到的异常,UsernameNotFoundException(前面已创建)

  • update在更新的时候,有可能产生未知的异常,抛UpdateException异常.

  • /**用户在更新数据时产生的未知异常*/
    public class UpdateException extends ServiceException{
        /**重写ServiceException的所有构造方法*/
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    ------



    ##### 2.设计接口和抽象方法

    执行用户修改密码的核心方法

    ```java
    /**
    * changePassword方法需要什么参数:
    * 要先看底层持久层需要什么参数:uid,password,modifiedUser,modifiedTime
    * 1.修改人其实就是username,已经保存到session当中,通过控制层传递过来就行了
    * 2.在更新数据之前需要先根据uid查这个数据存不存在,uid也可以通过控制层传递
    * 3.新密码需要有
    * 4.修改时间不需要在参数列表,直接在方法内部new Date()就可以了
    * 5.旧密码
    * */
    void changePassword(Integer uid,
    String username,
    String oldPassword,
    String newPassword);

在实现类中实现当前的抽象方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*修改密码*/
@Override
public void changePassword(Integer uid,
String username,
String oldPassword,
String newPassword) {
User result = userMapper.findByUid(uid);
/**
* 用户没找到:比如登录账号后的几分钟在和朋友聊天,没
* 有看页面,管理员错误删除了你的账号或者错误设置is_delete为1)
*/
if (result ==null || result.getIsDelete() == 1) {
throw new UsernameNotFoundException("用户数据不存在");
}

//原始密码和数据库中密码进行比较
String oldMd5Password = getMD5Password(oldPassword,result.getSalt());
if (!result.getPassword().equals(oldMd5Password)) {
throw new PasswordNotMatchException("密码错误");
}

//将新的密码加密后设置到数据库中(只要曾经注册过就用以前的盐值)
String newMd5Password = getMD5Password(newPassword, result.getSalt());
Integer rows = userMapper.updatePasswordByUid(uid, newMd5Password, username, new Date());

if (rows != 1) {
throw new UpdateException("更新数据产生未知的异常");
}
}

在单元测试类中编写单元测试方法

1
2
3
4
@Test
public void changePassword() {
userService.changePassword(11,"管理员","123","321");
}

3.控制层

1.处理异常

UsernameNotFoundException异常和PasswordNotMatchException异常在前面的章节中已经处理过,现在只需要把UpdateException异常配置到统一的异常处理方法中

1
2
3
4
5
else if(e instanceof UpdateException)
{
result.setState(5003);
result.setMessage("更新数据时产生的异常");
}

2.设计请求
1
2
3
4
5
6
7
8
/users/change_password

post

String oldPassword,String newPassword,HttpSession session(uid和username可以通过session获取到,在处理方法的内部获取就可以了)//如果参数名用的是非pojo类型,就需要和表单中的name属性值保持一致

JsonResult<void>


3.处理请求
1
2
3
4
5
6
7
8
@RequestMapping("change_password")
public JsonResult<Void> changePassword(String oldPassword, String newPassword,HttpSession session){

Integer uid = getuidFromSession(session);
String username = getUserNameFromSession(session);
userService.changePassword(uid,username,oldPassword,newPassword);
return new JsonResult<>(OK);
}

启动服务,先登录账号然后在地址栏输入http://localhost:8080/users/change_password?oldPassword=321&newPassword=123看看是否成功.


4.前端页面

password.html中添加ajax请求的处理, 不再手动去编写ajax请求, 直接复制, 然后在微调修改参数即可.

在password.html中添加ajax请求的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
$("#btn-change-password").click(function () {
$.ajax({
url: "/users/change_password",
type: "POST",
data: $("#form-change-password").serialize(),
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
alert("密码修改成功")
} else {
alert("密码修改失败")
}
},
error: function (xhr) {
//xhr.message可以获取未知异常的信息
alert("修改密码时产生未知的异常!"+xhr.message);
}
});
});
</script>

5.个人资料

1.个人资料

1.持久层

1.需要规划SQL语句

1.根据用户信息的SQL语句

1
update t_user set phone = ? ,email=?,gender=?,modified_user=?,modified_time=?where uid = ?

2.根据用户名来查询用户的数据:

1
select * from t_user where uid = ?

查询用户的数据不需要在重复开发了.

2.接口与抽象方法

更新用户的信息方法的定义.

1
2
3
4
5
6
/**
* 更新用户的数据信息
* @param user 用户的数据
* @return 返回值为受影响的行数
*/
Integer updateInfoByUid(User user);//也可以用三个String的形参接收电话,邮箱,性别,但不如直接写个user省事
3.抽象方法的映射.

在UserMapper.xml文件中进行映射编写.

1
2
3
4
5
6
7
8
9
10
11
12
<update id="updateInfoByUid">
UPDATE t_user SET
<!--if是条件判断标签,属性test接受的是一个返回值为boolean类型的条件,
如果test条件的结果为true则执行if标签内部的语句,注意逗号也要在标签内-->
<if test="phone!null">phone = #{phone},</if>
<if test="email!= null"> email = #{email},</if>
<if test="gender!= null"> gender = #{gender},</if>

modified_user = #{modifiedUser},
modified_time = #{modifiedTime}
WHERE uid = #{uid}
</update>

在测试类中完成功能的测试.

1
2
3
4
5
6
7
8
9
@Test
public void updateInfoByUid(){
User user = new User();
user.setUid(4);
user.setPhone("10086");
user.setEmail("313590@qq.com");
user.setGender(1);
userMapper.updateInfoByUid(user);
}

2.业务层

1.设计两个功能:

  • 当打开页面时是获取用户的信息并且填充到对应的文本框中,
  • 检测用户是否点击了修改按钮, 如果检测到则执行修改用户信息的操作.
1.异常的规划
  • 点击个人资料页面时可能找不到用户的数据
  • 点击修改按钮时可能找不到用户数据,也可能修改时出现未知错误

2.打开页面的时候可能找不到用户的数据, 点击删除按钮需要再次检测用户信息数据是否存在.

2.接口和抽象方法

mapper是针对数据库操作接口,service是针对controller的接口

1.业务层有两个功能模块,对应的是两个抽象方法的设计,并且这两个功能都涉及到用户是否存在的查询操作,所以需要在业务层设计根据用户uid查询数据的方法(持久层已经设计过该方法,但是没有在业务层实现该方法的调用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 根据用户的id查询用户的信息数据
* @param uid 用户id
* @return 用户的数据
*/
User getByUid(Integer uid);


/**uid通过控制层在session中获取然后传递给业务层,并在业务层封装到User对象中
* 更新用户的数据操作
* @param uid 用户的id
* @param username 用户的名称
* @param user 用户对象的名称
*/
void changeInfo(Integer uid, String username, User user);
3.实现抽象方法

在UserSerivceImpl类添加两个抽象方法的具体实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 @Override
public User getByUid(Integer uid) {
//查询用户是否存在
User result = userMapper.findByUid(uid);
if (result == null || result.getIsDelete() == 1) {
throw new UsernameNotFoundException("用户数据不存在");
}

//可以直接返回result给控制层,但是太臃肿了
User user = new User();
user.setUsername(result.getUsername());
user.setPhone(result.getPhone());
user.setEmail(result.getEmail());
user.setGender(result.getGender());

return user;
}

/**
*User对象中的数据只有phone,email,gender,username,因为springboot进行依赖
* 注入的时候只注入表单中数据的值,所以需要手动将uid封装到user中
*/
@Override
public void changeInfo(Integer uid, String username, User user) {
User result = userMapper.findByUid(uid);
if(result == null || result.getIsDelete() == 1){
throw new UsernameNotFoundException("用户数据不存在");
}

user.setUid(uid);
user.setUsername(username);
user.setModifiedUser(username);
user.setModifiedTime(new Date());

Integer rows = userMapper.updateInfoByUid(user);
if(rows != 1){
throw new UpdateException("更新数据时产生未知的异常");
}
}

在测试类中进行功能单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void getByUid(){
//err是为了让输出信息为红色
System.err.println(iUserService.getByUid(4));
}

@Test
public void changeInfo(){
//这四个属性值在真实开发中都是在控制层就已经自动封装在User对象中了
//而uid需要由控制层传给业务层并在业务层封装到user中
User user = new User();
user.setPhone("17448461");
user.setEmail("yun@qq.com");
user.setGender(0);


iUserService.changeInfo(21,"test01",user);
}

3.控制层

1.处理异常

没有新的异常,所以这里不需要有操作

2.设计请求

1.设置 一打开页面就能自动查询用户的信息

1
2
3
4
5
6
7
/users/get_by_uid

Get

HttpSession session

JsonResult<Void>

2.点击修改按钮发送用户的数据修改操作请求的设计.

1
2
3
4
/users/change_info
POST
User user, HttpSession session(用于获取uid)
JsonResult<Void>
3.处理请求

1.一打开页面就发送当前用户数据

1
2
3
4
5
6
7
@RequestMapping("get_by_uid")
public JsonResult<User> getbyuid(HttpSession session){

User date = userService.getByUid(getuidFromSession(session));
return new JsonResult<>(OK,date);
}

启动服务,先登录账号然后在地址栏输入http://localhost:8080/users/get_by_uid看看状态码是否为200并且看data值是否不为null

2.点击修改按钮更改用户数据

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("change_info")
public JsonResult<Void> changeinfo(User user, HttpSession session){

//User对象有四种属性 username, phone , email, gender
//uid数据再次封装到user对象中

Integer uid = getuidFromSession(session);
String username = getUserNameFromSession(session);
userService.changeInfo(uid,username,user);
return new JsonResult<>(OK);
}

启动服务,先登录账号然后在地址栏输入http://localhost:8080/users/change_info?phone=175726&email=6695@qq.com&username=张9&gender=1观察状态码是否为200

4.前端页面

1.在打开userdate.html页面自动发送ajax请求, 查询到的数据填充到这个页面.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<script>
/**
* 一旦检测到当前的页面被加载就会触发ready方法
* $(document).ready(function(){
* //编写业务代码
* });
*/
//点击"个人资料"四个字加载userdata.html页面时$(document).ready(function(){});就会起作用发送ajax请求
$(document).ready(function() {
$.ajax({
url: "/users/get_by_uid",
type: "GET",
//data为null也可以,因为这里get是从数据库拉取数据,不需要data
data: $("#form-change-info").serialize(),
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
//将查询到的数据设置到控件中
$("#username").val(json.data.username);
$("#phone").val(json.data.phone);
$("#email").val(json.data.email);
var radio = json.data.gender == 0 ?
$("#gender-female") : $("#gender-male");
//prop()表示给某个元素添加属性及属性的值
radio.prop("checked","checked");
} else {
alert("用户的数据不存在")
}
},
error: function (xhr) {
//xhr.message可以获取未知异常的信息
alert("查询用户信息时产生未知的异常!"+xhr.message);
}
});
});
</script>

2.在检测到用户点击了修改按钮后发送一个ajax请求(change_info)

该ajax函数需要和上一个ajax同级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$("#btn-change-info").click(function () {
$.ajax({
url: "/users/change_info",
type: "POST",
data: $("#form-change-info").serialize(),
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
alert("用户信息修改成功")
//修改成功后重新加载当前的页面
location.href = "userdata.html";
} else {
alert("用户信息修改失败")
}
},
error: function (xhr) {
//xhr.message可以获取未知异常的信息
alert("用户信息修改时产生未知的异常!"+xhr.message);
}
});
});