用户登录的流程:
后台登录时,需要接受的参数:用户账户,密码;
网络请求类型:POST
返回值:返回当前用户的基本信息;数据要脱敏;不要返回相关隐私;返回json格式数据;
与注册逻辑类似,登录的逻辑包括:
1、用户名和密码非空;
2、账户长度不小于4,密码不小于8,账户不包含特殊字符;
3、密码是否正确,需要查询数据库;
4、数据脱敏,敏感信息要隐藏,不能返回;
5、记录登录态,将用户登录状态保存在后端;
书写登录业务代码涉及到文件,与注册类似:
在service中的接口UserService.java中定义登录方法:
public interface UserService extends IService<User> { long userRegister(String userAccount, String userPassword, String checkPassword); User userLogin(String userAccount, String userPassword);}
在service的实现类中UserServiceImpl.java实现userLogin的代码:
xpublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
public long userRegister(String userAccount, String userPassword, String checkPassword) { //业务逻辑代码写在这里; return 0; } public User userLogin(String userAccount, String userPassword) { //业务逻辑代码写在这里; } }
在test/java/com.tfzhang.backend/service/UserServiceTest.java中创建测试文件:
xxxxxxxxxxclass UserServiceTest {
private UserService userService;
public void testAddUser(){ User user = new User(); //... }
void userRegister() { //书写测试代码; } void userLogin(){ //书写登录测试代码; }}
接下来主要在UserServiceImpl.java中实现各种逻辑的代码,其中与注册类似的代码,可以直接借用:
1、用户名和密码非空;
2、账户长度不小于4,密码不小于8,账户不包含特殊字符;
xxxxxxxxxxif(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、密码是否正确,需要查询数据库;
校验密码是否正确,注意数据库中保存的是加密后的数据,所以我们也要对密码数据先加密,然后再与数据库中的对比;
xxxxxxxxxx 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标签;
xxxxxxxxxxprivate UserMapper userMapper;
4、数据脱敏,敏感信息要隐藏,不能返回;
登录返回用户的数据,其中的敏感数据要屏蔽,才能返回数据给前端。所谓的屏蔽只是调用下set方法。
xxxxxxxxxxUser 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、记录登录态,将用户登录状态保存在后端;
后端只有保存用户的登录态,才能知道连接时,该连接来自哪个用户,前端和后端用户登录态的交互过程如下:
连接服务器端后,得到一个 session 状态(匿名会话),返回给前端
登录成功后,得到了登录成功的 session,并且给该session设置一些值(比如用户信息),返回给前端一个设置 cookie 的 ”命令“
session => cookie
前端接收到后端的命令后,设置 cookie,保存到浏览器内
前端再次请求后端的时候(相同的域名),在请求头中带上cookie去请求
后端拿到前端传来的 cookie,找到对应的 session
后端从 session 中可以取出基于该 session 存储的变量(用户的登录信息、登录名)
后端的代码只要添加如下:
xxxxxxxxxxrequest.getSession().setAttribute('userLoginState', safetyUser);其中的request参数来自前端,需要修改userLogin方法的声名:
xxxxxxxxxxUser userLogin(String account, String userPassword, HttpServletRequest request)
将上述所有的业务代码片段整合后,得到如下:
xxxxxxxxxx private UserMapper userMapper; //注意要写在方法外;
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,其中的代码:
xxxxxxxxxxpackage com.tfzhang.backend.constant;
public interface UserConstant { String USER_LOGIN_STATE="userLoginState"; String SALT="tfzhang";}然后再修改登录中的代码:
xxxxxxxxxximport 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方法:
xxxxxxxxxx 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即可。
我们之前在设计数据库中有提到过一个逻辑删除的变量: isDelete
该变量表示当前的数据是否已经逻辑删除,逻辑删除是指:数据仍在数据库中,但是不会被查找;对外不可见。
比如:如果我们登录一个被逻辑删除的账号,那么即使账户名和密码都对,数据库也应该显示无该数据。那么mybatis-plus要做到的这一点,需要进行如下的配置(搜官方文档):
步骤1:配置application.yml
xxxxxxxxxxmybatis-plus global-config db-config logic-delete-fieldflag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) logic-delete-value1 # 逻辑已删除值(默认为 1) logic-not-delete-value0 # 逻辑未删除值(默认为 0)步骤2:实体字段上加注解;
xxxxxxxxxxprivate Integer deleted;对照我们的项目,我们需要在application.yml文件中,添加如下的内容:
xxxxxxxxxxmybatis-plus global-config db-config logic-delete-fieldisDelete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) logic-delete-value1 # 逻辑已删除值(默认为 1) logic-not-delete-value0 # 逻辑未删除值(默认为 0)在User.java中添加注解:
xxxxxxxxxx private Integer isDelete;
登录部分的测试代码我们不写了,因为与注册基本类似,下面我们写controller层的网络访问接口代码。