YML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
port: 8080
servlet:
session:
timeout: 60
spring:
datasource:
url: "jdbc:mysql://localhost:3306/icesdemo?characterEncoding=utf-8&&useSSL=false&serverTimezone=GMT"
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
thymeleaf:
prefix: classpath:/templates/
cache: false
suffix: .html
check-template-location: true
encoding: utf-8
mode: HTML
servlet:
content-type: text/html
mybatis:
type-aliases-package: com.cy.store.pojo //需修改
mapper-locations: classpath:mapper/*.xml

pojo

user.java实体类

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
package com.cy.store.pojo;
import java.io.Serializable;
public class user implements Serializable{

private Integer id;
private String username;

private String password;
private String salt;

private String email;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

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 getEmail() {
return email;
}

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

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

if (getId() != null ? !getId().equals(user.getId()) : user.getId() != 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;
return getEmail() != null ? getEmail().equals(user.getEmail()) : user.getEmail() == null;
}

@Override
public int hashCode() {
int result = getId() != null ? getId().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 + (getEmail() != null ? getEmail().hashCode() : 0);
return result;
}

@Override
public String toString() {
return "user{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", salt='" + salt + '\'' +
", email='" + email + '\'' +
'}';
}
}

注册

持久层

通过mybatis操作数据库

sql语句

1.用户注册,相当于插入

1
insert into t_user(username,password)values()

2.先查询用户名是否存在

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

定义Mapper接口

创建一个Mapper包

创建UserMapper接口,定义sql的抽象方法

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


import com.cy.store.pojo.User;
import org.apache.ibatis.annotations.Mapper;


public interface UserMapper {
Integer insert(User user);
User findByUsername(String username);
}

1
@MapperScan("com.cy.store.mapper")
编写映射

1.定义xml映射文件,与接口关联,所有映射文件在resources,创建一个mapper文件夹,存放Mapper的映射文件.

2.创建接口对应的映射文件

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

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
<?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">
<!-- id:给映射一个唯一的id值
type:实体类-->
<resultMap id="userEntityMap" type="com.cy.store.pojo.User">

</resultMap>
<!-- id:表示接口中方法名称
userGeneratedKeys:开启递增
keyProperty:以某字段为主键-->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user(username,password,salt,email) VALUES
{
#{username},#{password},#{salt},#{email}
}
</insert>
<!--selcect 查询的结果是一个对象,多个对象-->
<!--resultType:表和类一致-->
<!--resultMap:不一致-->
<select id="findByUsername" resultType="UserEntityMap">
SELECT * FROM t_user WHERE username = #{username}
</select>
</mapper>

4.将mapper注册到yml里

5.单元测试:每个独立层编写完成编写单元测试方法,来测试功能.在test下创建mapper包

业务层

规划异常

1.RuntimeException,作为异常的子类,再定义个异常类来继承.

业务层异常基类,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
24
25
26
27
28
package com.cy.store.service.ex;

/*
作为业务层异常的基类:thows 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);
}
}


根据业务层不同的功能来定义异常类型,统一去继承

2.用户在进行时产生用户名被占用的错误,抛出异常:UsernameDuplicstedException异常.

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
package com.cy.store.service.ex;

/*
用户名被占用
*/
public class UsernameDuplicstedException extends ServiceException{
//alt+insert --overide methods

public UsernameDuplicstedException() {
super();
}

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

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

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

protected UsernameDuplicstedException(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
26
27
28
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);
}
}


设计接口和抽象方法

在service包下创建UserService接口.

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

import com.cy.store.pojo.User;

/*
用户模块业务层接口
*/
public interface UserService {
/*
用户注册方法
*/
void reg(User user);

}

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.cy.store.service.impl;

import com.cy.store.mapper.UserMapper;
import com.cy.store.pojo.User;
import com.cy.store.service.UserService;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.UsernameDuplicstedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.UUID;

@Service//将当前类交给spring管理,自动创建对象以及对象的维护
public class UserServiceImpl implements UserService {
@Autowired
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 UsernameDuplicstedException("用户名被占用");

}
//密码加密:md5
//串+password+串 --md5算法加密,连续三次
//盐值+password+盐值 --盐值是随机串
String oldPassword = user.getPassword();
String salt = UUID.randomUUID().toString().toUpperCase();
//将密码和盐值加密处理
String md5Password = getMD5Password(oldPassword,salt);
//忽略原密码强度提升安全性
user.setPassword(md5Password);
user.setSalt(salt);
//执行注册功能(rows==1)
Integer rows = userMapper.insert(user);
if(rows != 1){
throw new InsertException("用户注册过程中产生未知异常");

}
}

/*
定义一个md5算法加密
*/
private String getMD5Password(String password,String salt){
//md5加密
for(int i = 0;i < 3;i++){
password = DigestUtils.md5DigestAsHex((salt+password+salt).getBytes()).toUpperCase();
}
return password;
}
}


3.在`单元测试包下常见UserServiceTests类,加入单元测试

控制层

创建响应

状态码,状态描述信息,数据封装在一个类里,并作为返回值返回给前端

1
2
3
4
5
6
7
8
9
10
11
12
/*
json格式响应
*/
public class JasonResult<E> implements Serializable {
//响应状态码
private Integer state;
//描述信息
private String message;
//数据
private E data;
}

设计请求
1
2
3
4
5
请求路径: users/reg
请求参数: User user
请求类型: post
响应结果: JavaResult<void>

处理请求

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
package com.cy.store.controller;

import com.cy.store.pojo.User;
import com.cy.store.service.UserService;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.UsernameDuplicstedException;
import com.cy.store.util.JasonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController //controller + response
@RequestMapping("users")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("reg")
public JasonResult<Void> reg(User user){
//创建响应对象
JasonResult<Void> result =new JasonResult<>();
try {
userService.reg(user);
result.setState(200);
result.setMessage("注册成功");
} catch (UsernameDuplicstedException e) {
result.setState(4000);
result.setMessage("用户名被占用");
} catch (InsertException e) {
result.setState(5000);
result.setMessage("注册时产生未知异常");
}
return result;
}
}


控制层优化设计

在控制层抽象一个父类,统一处理关于异常的操作,编写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
package com.cy.store.controller;

import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.ServiceException;
import com.cy.store.service.ex.UsernameDuplicstedException;
import com.cy.store.util.JasonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;

/*
控制层类的基类
*/
public class BaseController {
//操作成功的状态码
public static final int OK = 200;

//请求处理方法,这个方法的返回值为传递给前端的数据
//自动将异常对象传递给参数列表上
//项目产生异常自动拦截到此方法中,方法返回值直接给到前端
@ExceptionHandler(ServiceException.class) //用于统一处理抛出的异常
public JasonResult<Void> handleException(Throwable e){
JasonResult<Void> result = new JasonResult<>(e);
if(e instanceof UsernameDuplicstedException){
result.setState(4000);
result.setMessage("用户名被占用");
}else if (e instanceof InsertException){
result.setState(5000);
result.setMessage("注册时产生未知异常");
}
return result;
}
}


重构reg()

1
2
3
4
5
6
7
8
9
10
@RequestMapping("reg")
public JasonResult<Void> reg(User user){
//创建响应对象
JasonResult<Void> result =new JasonResult<>();

userService.reg(user);

return new JasonResult<>(OK);
}

前端页面

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

2.JQUery封装一个函数,称为$.ajax(),通过对象调用ajax()函数,异步加载相关请求.依靠XHR(XmlHttp Response)

3.ajax()使用方式,需要传递方法作为方法参数,一对大括号称为方法体,

ajax接收多个参数,要求以”,”分割,每组参数以”:”分割,参数组成部分为参数名称(固定的),是参数的值,用字符串标识,无顺序

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

},
error: function(){

}
});



参数 功能描述
url 标识请求地址ex : url: “localhost:8080/users/reg”
type 请求类型,ex: type:”POST”
data 提交的数据 data: “username=tom&pwd=123456”
datatype 数据类型,一般为json dataType: “json”
sucess 服务器正常响应时调用sucess方法,并将服务器返回的数据传递到这个方法
error 不正常时调用error
  1. 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
<script type="text/javascript">
//监听注册按钮被点击
$("#btn-reg").click(function (){
let username = $("#username").val();
let passwd = $("#passwd").val();
let mail = $("#email").val();
//发送ajax异步请求
$.ajax({
url: "/users/reg",
type: "POST",
data: "username="+username+"&password="+passwd+"&mail="+mail,
dataType: "JSON",
success: function (json){
if(json.state == 200) {
alert("注册成功");
}else {
alert("注册失败");
}
},
error: function (){
alert("注册时产生未知错误!"+xhr.status);
}
});
});
</script>

用户登录

用户输入用户名密码,将数据交给后台查询,若查到登陆成功,条状到主页

持久层

规划sql语句

密码比较在业务层

1
2
select * from t_user where username = ?

某一个功能模块已经被开发完成,省略当前开发步骤

接口设计与方法

业务层

规划异常

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

2.用户名没有被找到,抛出异常:UsernameNotFoundException.

3.异常的编写:

业务层继承ServiceException异常类

在具体异常类中定义构造方法

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

1.直接在UserService接口编写抽象方法login(username,password), 将当前数据以当前用户对象形式返回.

状态管理,将数据保存在cookie或session中,避免重复多次请求

id存放在session中,头像在cookie

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

3.测试

抽象方法实现

控制层

处理异常

业务层抛出的异常是什么,需要在统一异常处理类中统一捕获,处理.已经处理过不必添加

1
2
3
4
5
6
7
8
else if (e instanceof UsernameNotFoundException){
result.setState(5001);
result.setMessage("用户数据不存在异常");
}else if (e instanceof PasswordNotMatchException){
result.setState(5002);
result.setMessage("用户名密码错误异常");
}

设计请求
1
2
3
4
5
请求路径: users/login
请求参数: String username,String password
请求类型: post
响应结果: JavaResult<void>

处理请求

在UserControl中编写方法

前端页面

用户会话session

session存储在服务器端,保存临时数据对象,在整个项目都可以访问,把session看作一个共享的数据.首次登录获取的用户数据,转移到session.

session.getAttrbute(“key”)获取session数据进行封装,在BaseController.

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

2.在父类中封装两个数据:获取uid和username对应两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
获取session的uid
*/
protected final Integer getidFromSession(HttpSession session){
return Integer.valueOf(session.getAttribute("id").toString());
}
/*
获取username

*/
protected final String getUsernameFromSession(HttpSession session){
return session.getAttribute("username").toString();
}

3.在登录中将数据封装在session对象中,服务器自动创建session.

SpringBoot直接使用session,直接将HttpSession类型对象作为请求处理方法参数,自动注入到形参上

拦截器

拦截器:将所有请求统一拦截到拦截器中,可以在拦截器中定义过滤规则,不满足过滤规则,重新打开login.html页面(重定向,转发)

Springboot项目拦截器定义与使用.

springMVC提供HandlerInterceptor表示拦截器

1
2
3
4
5
6
7
8
9
10
11
12
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}

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 {
}
}

1.LoginInterceptor

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.interceptor;

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

public class LoginInterceptor implements HandlerInterceptor {
/*
检测session中是否有id,有则放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object obj = request.getSession().getAttribute("id");
if(obj == null){
//未登录,不放
response.sendRedirect("/web/login.html");
return false;
}
//请求放行
return true;
}
}


2.添加白名单(login.html/register.html/index.html),添加黑名单

3.注册过滤器:WebMvcConfigure,将用户定义的拦截器注册,定义一个类,实现该接口.存放在config上

1
2
3
default void addInterceptors(InterceptorRegistry registry) {
}

4.重定向次数过多

test

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;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.SQLException;

@SpringBootTest
class StoreApplicationTests {
@Autowired
private DataSource dataSource;

@Test
void contextLoads() {
}
@Test
void getConnection() throws SQLException {
System.out.println(dataSource.getConnection());//测试数据库连接
}
}


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
package com.cy.store.mapper;

import com.cy.store.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

//@springboottest:标注为测试类
@SpringBootTest
//@RunWith :启动单元测试类,需传递一个参数,必须是springRunner的实例
@RunWith(SpringRunner.class)
public class UserMapperTests {
@Autowired
private UserMapper userMapper;
/*
1.必须被@test修饰
2.返回值void
3.参数列表不指定任何类型
4.方法访问修饰符:public
*/
@Test
public void insert(){
User user = new User();
user.setUsername("tim3");
user.setPassword("123");
Integer rows = userMapper.insert(user);
System.out.println(rows);
}
@Test
public void findByUsername()
{
User user = userMapper.findByUsername("tim");
System.out.println(user);
}
}


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
package com.cy.store.service;

import com.cy.store.mapper.UserMapper;
import com.cy.store.pojo.User;
import com.cy.store.service.ex.ServiceException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

//@springboottest:标注为测试类
@SpringBootTest
//@RunWith :启动单元测试类,需传递一个参数,必须是springRunner的实例
@RunWith(SpringRunner.class)
public class UserServiceTests {
@Autowired
private UserService userService;
/*
1.必须被@test修饰
2.返回值void
3.参数列表不指定任何类型
4.方法访问修饰符:public
*/
@Test
public void reg(){
try {
User user = new User();
user.setUsername("timmd5.2");
user.setPassword("123");
userService.reg(user);
System.out.println("ok");
} catch (ServiceException e) {
//获取类的对象和名称
System.out.println(e.getClass().getSimpleName());
//获取异常信息
System.out.println(e.getMessage());
}
}

}