本文开发一用户注册与登录的后端java平台。开发完后的网页效果:http://user-front.66bond.com/

后端主要提供用户的注册、查询、删除等操作。使用到的开发工具:

  • IDEA2021; 关注公众号”青椒工具”,发送”IDEA”,获取windows下的IDEA安装包
  • mysql 5.7;关注公众号”青椒工具”,发送”mysql”,获取windows下的mysql5.7安装包;

用户登录的流程:

后台登录时,需要接受的参数:用户账户,密码;

网络请求类型:POST

返回值:返回当前用户的基本信息;数据要脱敏;不要返回相关隐私;返回json格式数据;

1、登录逻辑

与注册逻辑类似,登录的逻辑包括:

1、用户名和密码非空;

2、账户长度不小于4,密码不小于8,账户不包含特殊字符;

3、密码是否正确,需要查询数据库;

4、数据脱敏,敏感信息要隐藏,不能返回;

5、记录登录态,将用户登录状态保存在后端;

2、代码框架:

书写登录业务代码涉及到文件,与注册类似:

在service中的接口UserService.java中定义登录方法:

1
2
3
4
public interface UserService extends IService<User> {
long userRegister(String userAccount, String userPassword, String checkPassword);
User userLogin(String userAccount, String userPassword);
}

在service的实现类中UserServiceImpl.java实现userLogin的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

@Override
public long userRegister(String userAccount, String userPassword, String checkPassword) {
//业务逻辑代码写在这里;
return 0;
}

@Override
public User userLogin(String userAccount, String userPassword) {
//业务逻辑代码写在这里;
}

}

在test/java/com.tfzhang.backend/service/UserServiceTest.java中创建测试文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class UserServiceTest {

@Resource
private UserService userService;

@Test
public void testAddUser(){
User user = new User();
//...
}

@Test
void userRegister() {
//书写测试代码;
}

@Test
void userLogin(){
//书写登录测试代码;
}
}
3、业务代码实现:

接下来主要在UserServiceImpl.java中实现各种逻辑的代码,其中与注册类似的代码,可以直接借用:

1、用户名和密码非空;

2、账户长度不小于4,密码不小于8,账户不包含特殊字符;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(StringUtils.isAnyBlank(userAccount, userPassword)){
return null;
}

if(userAccount.length() < 4 || userPassword.length()<8){
return null;
}

String regex = "^[a-zA-Z0-9]+$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(userAccount);
if(!matcher.matches()){
return null;
}

3、密码是否正确,需要查询数据库;

校验密码是否正确,注意数据库中保存的是加密后的数据,所以我们也要对密码数据先加密,然后再与数据库中的对比;

1
2
3
4
5
6
7
8
9
10
11
final String SALT = "tfzhang";
String encrptedPassword = DigestUtils.md5DigestAsHex((SALT+userPassword).getBytes());

//用户名和密码查询数据库;
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("userAccount", userAccount);
queryWrapper.eq("userPassword", encrptedPassword);
User user= userMapper.selectOne(queryWrapper);
if(user == null){
return null;
}

此处的userMapper对象要定义为私有类属性,并添加Resource标签;

1
2
@Resource
private UserMapper userMapper;

4、数据脱敏,敏感信息要隐藏,不能返回;

登录返回用户的数据,其中的敏感数据要屏蔽,才能返回数据给前端。所谓的屏蔽只是调用下set方法。

1
2
3
4
5
6
7
8
9
10
User saftyUser=new User();

saftyUser.setId(user.getId());
saftyUser.setUsername(user.getUsername());
saftyUser.setUserAccount(user.getUserAccount());
saftyUser.setAvatarUlr(user.getAvatarUlr());
saftyUser.setGender(user.getGender());
saftyUser.setUserState(user.getUserState());

return saftyUser;

5、记录登录态,将用户登录状态保存在后端;

后端只有保存用户的登录态,才能知道连接时,该连接来自哪个用户,前端和后端用户登录态的交互过程如下:

  1. 连接服务器端后,得到一个 session 状态(匿名会话),返回给前端

  2. 登录成功后,得到了登录成功的 session,并且给该session设置一些值(比如用户信息),返回给前端一个设置 cookie 的 ”命令“

    session => cookie

  3. 前端接收到后端的命令后,设置 cookie,保存到浏览器内

  4. 前端再次请求后端的时候(相同的域名),在请求头中带上cookie去请求

  5. 后端拿到前端传来的 cookie,找到对应的 session

  6. 后端从 session 中可以取出基于该 session 存储的变量(用户的登录信息、登录名)

后端的代码只要添加如下:

1
request.getSession().setAttribute('userLoginState', safetyUser);

其中的request参数来自前端,需要修改userLogin方法的声名:

1
User userLogin(String account, String userPassword, HttpServletRequest request)
4、业务代码的优化:

将上述所有的业务代码片段整合后,得到如下:

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
@Resource
private UserMapper userMapper; //注意要写在方法外;

@Override
public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {

if(StringUtils.isAnyBlank(userAccount, userPassword)){
return null;
}

if(userAccount.length() < 4 || userPassword.length()<8){
return null;
}

String regex = "^[a-zA-Z0-9]+$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(userAccount);
if(!matcher.matches()){
return null;
}

final String SALT = "tfzhang";
String encrptedPassword = DigestUtils.md5DigestAsHex((SALT+userPassword).getBytes());

//用户名和密码查询数据库;
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("userAccount", userAccount);
queryWrapper.eq("userPassword", encrptedPassword);

User user= userMapper.selectOne(queryWrapper);
if(user == null){
return null;
}

User saftyUser=new User();

saftyUser.setId(user.getId());
saftyUser.setUsername(user.getUsername());
saftyUser.setUserAccount(user.getUserAccount());
saftyUser.setAvatarUlr(user.getAvatarUlr());
saftyUser.setGender(user.getGender());
saftyUser.setUserStatus(user.getUserStatus());

/**
* 设置session;
*/
request.getSession().setAttribute("userLoginState", saftyUser);
return saftyUser;
}

仔细审视上述的代码,看看有没有优化的空间?

  • SALT变量不只在登录时用到,注册时也用到了;对于这种公共的变量有必要提取出来,统一管理;

    “userLoginState”是固定的变量,不会有变动,适宜统一管理;

  • 数据脱敏的代码有必要简单封装到一个独立的方法中,这样看起来更优雅;

对于上述的第一点,我们在/src/main/java/com.tfzhang.backend/下创建名为constant的package,其中再创建名为UserConstant的interface,其中的代码:

1
2
3
4
5
6
package com.tfzhang.backend.constant;

public interface UserConstant {
String USER_LOGIN_STATE="userLoginState";
String SALT="tfzhang";
}

然后再修改登录中的代码:

1
2
3
4
5
6
7
import static com.tfzhang.backend.constant.UserConstant.SALT;
import static com.tfzhang.backend.constant.UserConstant.USER_LOGIN_STATE;

// final String SALT = "tfzhang";
String encrptedPassword = DigestUtils.md5DigestAsHex((SALT+userPassword).getBytes());
request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);

对于第2点,我们在当前文件夹下创建一个getSafetyUser方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public User getSafetyUser(User originalUser){
if(originalUser==null){
return null;
}
User user = new User();

user.setId(originalUser.getId());
user.setUsername(originalUser.getUsername());
user.setUserAccount(originalUser.getUserAccount());
user.setAvatarUlr(originalUser.getAvatarUlr());
user.setGender(originalUser.getGender());
user.setUserStatus(originalUser.getUserStatus());

return user;
}

然后原来的代码直接调用上述的getSafetyUser即可。

5、数据库中用户的逻辑删除设置:

我们之前在设计数据库中有提到过一个逻辑删除的变量: isDelete

该变量表示当前的数据是否已经逻辑删除,逻辑删除是指:数据仍在数据库中,但是不会被查找;对外不可见。

比如:如果我们登录一个被逻辑删除的账号,那么即使账户名和密码都对,数据库也应该显示无该数据。那么mybatis-plus要做到的这一点,需要进行如下的配置(搜官方文档):

步骤1:配置application.yml

1
2
3
4
5
6
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

步骤2:实体字段上加注解;

1
2
@TableLogic
private Integer deleted;

对照我们的项目,我们需要在application.yml文件中,添加如下的内容:

1
2
3
4
5
6
mybatis-plus:
global-config:
db-config:
logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

在User.java中添加注解:

1
2
@TableLogic
private Integer isDelete;

登录部分的测试代码我们不写了,因为与注册基本类似,下面我们写controller层的网络访问接口代码。

6、参考资料:

本文参考自如下知识星球中的视频教程,更多的完整的相关视频教程,见如下的收费知识星球,近3万人的学习社区,

编程有人同行,学习不再迷茫

编程导航知识星球