> 文档中心 > SpringBoot日记本系统全程直播03:把登录后台接起来撒~~

SpringBoot日记本系统全程直播03:把登录后台接起来撒~~

上一节:SpringBoot日记本系统全程直播02:登录页面搞起来撒~~

大家好,我是今天晚上的主讲老师,我是兔哥。

上一讲,我们学习了登录和注册页面,以及Controller访问页面的方法,还有如何处理静态资源。

今天我们继续来学习SpringBoot日记本系统,任务是进行后台的对接。就是说,用户注册和登录的功能,需要完善起来啦。还会涉及到很多企业级开发技术哦。

总之,这一节的内容非常之多,也比较丰富。视频我后期会补上,如果跟着做做不出来,一定要下载源码慢慢比对哈。

ok,那么我们现在开整!

目录

1.数据库建表

1.1 为什么不用UUID?

1.2 假如以后分库分表,ID重复怎么办?

2. 登录/注册页面访问后台

3.后端准备工作

3.1 系统核心包

3.1.1 BizException 业务异常

3.1.2 GlobalExceptionHandler 全局异常处理

3.1.3 ExceptionCodeEnum 通用异常枚举

3.1.4 CommonResponseDataAdvice 统一封账响应结果

3.1.5 IgnoreCosmoResult 忽略统一结果返回封装

3.2 用户类和mybatisplus引入

3.3 redis引入(为了ID自增)

4. sa-token 鉴权框架

5. 用户接口和服务类

6. 如何下载源码?


1.数据库建表

navicat大家应该都有吧,或者类似的软件也可以,创建一个数据库,名字叫diary。

然后创建用户表:

CREATE TABLE `user_base` (`uid`  bigint(20) NOT NULL COMMENT '用户ID' ,`user_role`  tinyint(2) UNSIGNED NOT NULL DEFAULT 1 ,`register_source`  tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '注册来源:1手机号 2邮箱 3用户名 4qq 5微信 6腾讯微博 7新浪微博' ,`user_name`  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户账号,必须唯一' ,`password`  varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '密码' ,`nick_name`  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户昵称' ,`gender`  tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户性别 0-female 1-male' ,`birthday`  bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户生日' ,`signature`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户个人签名' ,`mobile`  varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号码(唯一)' ,`mobile_bind_time`  varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '手机号码绑定时间' ,`email`  varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '邮箱(唯一)' ,`email_bind_time`  varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '邮箱绑定时间' ,`face`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '头像' ,`face200`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '头像 200x200x80' ,`srcface`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '原图头像' ,`create_time`  varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建时间' ,`update_time`  varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '修改时间' ,PRIMARY KEY (`uid`))ENGINE=InnoDBDEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ciCOMMENT='用户基础信息表'ROW_FORMAT=COMPACT;

把上面的sql拖到navicat中执行,建表成功。

 

关于UID,我们用bigint类型。

1.1 为什么不用UUID?

因为UUID是看不出顺序的,我们用int类型可以看出用户创建的先后顺序。

1.2 假如以后分库分表,ID重复怎么办?

我们知道mysql没有sequence,字段不是自增的。如果用自增长ID,分库分表的话,很难保证ID不重复和连续。因此,我们可以采用redis的ID自增长策略,这样不管你有几个数据库,只要redis是一个,就没问题啦。(本节后面会给出具体的方案)

2. 登录/注册页面访问后台

回顾下注册页面

这一步,我们需要对接后台的登录注册接口。

引入vue,axios

 更改HTML

没有账号,去注册日记本

其实用jquery是完全可以的,不过现在jq基本没人用了,于是本项目我们也赶一下潮流吧。

vue代码:

let vue = new Vue({    el:'#app',    data: { //注册 userName1:'', password1:'', password11:'', //登录 userName2:'', password2:''    },    methods:{ reg(){     if(this.password1 != this.password11){  layer.msg('两次输入密码不一致',{icon:2});  return;     }     let data = {  userName: this.userName1,  password: this.password1     }     axios.post('${basePath}/user/register',data).then(r =>{  if(r.data.code != '0000'){      layer.msg(r.data.message,{icon:2});      return;  }  layer.msg('注册成功!',{icon:2});     }); }, login(){     let data = {  userName: this.userName2,  password: this.password2     }     axios.post('${basePath}/user/login',data).then(r =>{  if(r.data.code != '0000'){      layer.msg(r.data.message,{icon:2});      return;  }  layer.msg('登录成功!',{icon:1});  //登录成功后,就缓存账号密码  localStorage.setItem('userName2',this.userName2);  localStorage.setItem('password2',this.password2);  setTimeout(()=>{      location.href="/";  },1000)     }); }, //填充缓存的账号和密码 fillAccount(){     let userName2 = localStorage.getItem('userName2');     let password2 = localStorage.getItem('password2');     if(userName2){  this.userName2 = userName2;     }     if(password2){  this.password2 = password2;     }     if(userName2 && password2){  setTimeout(()=>{      document.querySelector('.login-title').click();  },1000)     } }    },    created(){ this.fillAccount();    }});

登录流程:获取用户名和密码,发送 /user/login 

返回报文格式是这样的:

 这个格式后台是怎么返回的,下面会说到。登录成功后,会把用户名密码缓存到本地,缓存技术用的是localStorage。代码如下:

login(){    let data = { userName: this.userName2, password: this.password2    }    axios.post('${basePath}/user/login',data).then(r =>{ if(r.data.code != '0000'){     layer.msg(r.data.message,{icon:2});     return; } layer.msg('登录成功!',{icon:1}); //登录成功后,就缓存账号密码 localStorage.setItem('userName2',this.userName2); localStorage.setItem('password2',this.password2); setTimeout(()=>{     location.href="/"; },1000)    });},

 注册流程:校验两次密码输入是否一致,然后发送用户名和密码到 /user/register 

代码如下:

reg(){    if(this.password1 != this.password11){ layer.msg('两次输入密码不一致',{icon:2}); return;    }    let data = { userName: this.userName1, password: this.password1    }    axios.post('${basePath}/user/register',data).then(r =>{ if(r.data.code != '0000'){     layer.msg(r.data.message,{icon:2});     return; } layer.msg('注册成功!',{icon:2});    });},

如果已经发现缓存中存在用户名和密码,就自动填充,并且显示登陆页面:

fillAccount(){    let userName2 = localStorage.getItem('userName2');    let password2 = localStorage.getItem('password2');    if(userName2){ this.userName2 = userName2;    }    if(password2){ this.password2 = password2;    }    if(userName2 && password2){ setTimeout(()=>{     document.querySelector('.login-title').click(); },1000)    }}

如果你已经登录过,那就会看到一个向上滑动的效果,然后系统会自动填充用户名和密码。

OK,以上就是前端的逻辑了。

3.后端准备工作

接下来,我们需要做一点准备工作。代码有点多,我们一个个来看。

3.1 系统核心包

创建上图所示核心包,位置:

com.rabbit.diary.bean.core

3.1.1 BizException 业务异常类

 这是我们系统自定义的业务异常类,所有业务上的报错,比如【用户名密码错误】之类的,我们就需要抛出这个异常。

package com.rabbit.diary.bean.core;/** * 业务异常 * biz是business的缩写 * * @author sunting * @see ExceptionCodeEnum */public class BizException extends RuntimeException {    private ExceptionCodeEnum error;    /**     * 构造器,有时我们需要将第三方异常转为自定义异常抛出,但又不想丢失原来的异常信息,此时可以传入cause     *     * @param error     * @param cause     */    public BizException(ExceptionCodeEnum error, Throwable cause) { super(cause); this.error = error;    }    /**     * 构造器,只传入错误枚举     *     * @param error     */    public BizException(ExceptionCodeEnum error) { this.error = error;    }public ExceptionCodeEnum getError() {return error;}public void setError(ExceptionCodeEnum error) {this.error = error;} }

3.1.2 GlobalExceptionHandler 全局异常处理

作用是捕获异常,当发生异常的时候,就会进入对应的handler。

package com.rabbit.diary.bean.core;import cn.dev33.satoken.exception.NotLoginException;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;/** * 全局异常处理 * 一般来说,全局异常处理只是一种兜底的异常处理策略,也就是说提倡自己处理异常。 * 但现在其实很多人都喜欢直接在代码中抛异常,全部交给@RestControllerAdvice处理 */@RestControllerAdvicepublic class GlobalExceptionHandler {/**     * 权限异常 (注意,参数一定要是需要捕获的异常,否则进不来)     * @param     * @return     */    @ExceptionHandler(NotLoginException.class)    public com.rabbit.diary.bean.core.Result handleNotLoginException(NotLoginException bizException) { return com.rabbit.diary.bean.core.Result.error(com.rabbit.diary.bean.core.ExceptionCodeEnum.NEED_LOGIN,bizException.getMessage());    }    /**     * 业务异常(需要主动抛出)     *     * @param     * @return     */    @ExceptionHandler(com.rabbit.diary.bean.core.BizException.class)    public com.rabbit.diary.bean.core.Result handleBizException(com.rabbit.diary.bean.core.BizException bizException) { return com.rabbit.diary.bean.core.Result.error(bizException.getError());    }    /**     * 运行时异常     *     * @param e     * @return     */    @ExceptionHandler(RuntimeException.class)    public com.rabbit.diary.bean.core.Result handleRunTimeException(RuntimeException e) { return com.rabbit.diary.bean.core.Result.error(com.rabbit.diary.bean.core.ExceptionCodeEnum.ERROR);    }}

3.1.3 ExceptionCodeEnum 通用异常枚举

package com.rabbit.diary.bean.core;/** * 通用异常枚举 * @author Administrator * */public enum ExceptionCodeEnum {SUCCESS("0000","返回成功!"),ERROR("1111","与服务方通讯失败,请联系管理员!"),NEED_LOGIN("9999", "用户未登录!"),ERROR_PARAM("1000", "参数送错了!"),EMPTY_PARAM("2000", "参数为空!"),;private String code;private String desc;private ExceptionCodeEnum(String code, String desc) {this.code = code;this.desc = desc;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getDesc() {return desc;}//为了封装自定义信息,做特殊处理public ExceptionCodeEnum setDesc(String desc) {this.desc = desc;return this;}}

3.1.4 CommonResponseDataAdvice 统一封账响应结果

作用是设置通用的返回,以后在RestController里面写方法,就不需要专门设置返回结果了。

/** * 统一封账响应结果 * @author Administrator * */@RestControllerAdvicepublic class CommonResponseDataAdvice implements ResponseBodyAdvice {    @Override    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter> aClass) { // 标注了@RestController,且类及方法上都没有标注@IgnoreCosmoResult的方法才进行包装    return methodParameter.getDeclaringClass().isAnnotationPresent(RestController.class)  && !methodParameter.getDeclaringClass().isAnnotationPresent(com.rabbit.diary.bean.core.IgnoreCosmoResult.class)  && !methodParameter.getMethod().isAnnotationPresent(com.rabbit.diary.bean.core.IgnoreCosmoResult.class);    }    @Override    public Object beforeBodyWrite(Object o,      MethodParameter methodParameter,      MediaType mediaType,      Class<? extends HttpMessageConverter> aClass,      ServerHttpRequest serverHttpRequest,      ServerHttpResponse serverHttpResponse) {    // 已经包装过的,不再重复包装 if (o instanceof com.rabbit.diary.bean.core.Result) {     return o; } // 改一行代码即可:把Object返回值用Result封装 return com.rabbit.diary.bean.core.Result.success(o);    }}

3.1.5 IgnoreCosmoResult 忽略统一结果返回封装

和上一个类配套使用,如果有的方法或者类不希望被统一封装,就加上这个注解。

/** * 如果有的方法不希望被统一封装结果,就用这个注解 * @author Administrator * */@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})public @interface IgnoreCosmoResult {}

3.1.6 Result 通用返回结果

public class Result implements Serializable{ private String code;    private String message;    private T data;    private Result(String code, String message, T data) { this.code = code; this.message = message; this.data = data;    }    private Result(String code, String message) { this.code = code; this.message = message; this.data = null;    }    /**     * 带数据成功返回     *     * @param data     * @param      * @return     */    public static  Result success(T data) { return new Result(ExceptionCodeEnum.SUCCESS.getCode(), ExceptionCodeEnum.SUCCESS.getDesc(), data);    }    /**     * 不带数据成功返回     *     * @return     */    public static  Result success() { return success(null);    }    /**     * 通用错误返回     *     * @param exceptionCodeEnum     * @return     */    public static  Result error(ExceptionCodeEnum exceptionCodeEnum) { return new Result(exceptionCodeEnum.getCode(), exceptionCodeEnum.getDesc());    }    /**     * 通用错误返回     *     * @param exceptionCodeEnum     * @param msg     * @return     */    public static  Result error(ExceptionCodeEnum exceptionCodeEnum, String msg) { return new Result(exceptionCodeEnum.getCode(), msg);    }    /**     * 通用错误返回     *     * @param exceptionCodeEnum     * @param data     * @param      * @return     */    public static  Result error(ExceptionCodeEnum exceptionCodeEnum, T data) { return new Result(exceptionCodeEnum.getCode(), exceptionCodeEnum.getDesc(), data);    }public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public T getData() {return data;}public void setData(T data) {this.data = data;}    }

以上6个类的作用很大,帮我们解决了编写Controller方法的数据返回问题,和异常抛出的问题。有了他们,可以极大地方便我们写后面的代码。

3.2 用户类和mybatisplus引入

在第一小节,我们已经建好了表,接下来我们需要做一些Java后端的工作。首先是配置mybatis-plus:

pom.xml增加依赖

com.baomidoumybatis-plus-boot-starter3.3.1.tmp

application.yml配置myabtis

mybatis-plus:  mapper-locations: classpath:mybatis/*.xml  type-aliases-package: com.rabbit.diary.bean

注意就是配置xml文件的存放位置,和JavaBean的路径,JavaBean就是和数据库表对应的类。

User.java编写

@TableName("user_base")@Data@Servicepublic class User {    @TableId(type = IdType.ASSIGN_UUID)    private Long uid;    private Integer userRole;    private Integer registerSource;    private String userName;    private String password;    private String nickName;    private Integer gender;    private String birthday;    private String signature;    private String mobile;    private String mobileBindTime;    private String email;    private String emailBindTime;    private String face;    private String face200;    private String srcface;    private String createTime;    private String updateTime;}

我用了lombok,这个需要安装对应的idea插件,和maven依赖,当然你也可以选择手动生成get,set方法。

lombok依赖:

org.projectlomboklombok1.18.16

因为使用了mybatis-plus,我们大部分情况, 其实是不需要再手动编写xml了。

创建包:com.rabbit.diary.dao

编写UserMapper.java

package com.rabbit.diary.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.rabbit.diary.bean.User;import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface UserMapper extends BaseMapper {}

 这样就有基本的增删改查方法了。

3.3 redis引入(为了ID自增)

上面已经说过,为了应对分库分表的情况(虽然这个项目用不到哈),我们将采用redis的方式。redis大家可以自行去百度下载windows版的,安装很方便。

双击这个exe就可以打开了。

增加maven依赖:

org.springframework.bootspring-boot-starter-data-redis

springboot有对应的starter,引入了就行。

然后是application.yml配置

spring:  redis:    host: 127.0.0.1    port: 6379    password:    jedis:      pool: max-active: 8 max-wait: -1 max-idle: 500 min-idle: 0    lettuce:      shutdown-timeout: 0    timeout: 2s

注意是放在spring节点下,和mvc,datasource节点是同级的。如果还是不对,请下载源码自行比较。一般而言,我们刚下载的redis是没有密码的。

redis辅助工具类:

IRedisService

public interface IRedisService {    // 加入元素    void setValue(String key, Map value);    // 加入元素    void setValue(String key, String value);    // 加入元素    void setValue(String key, Object value);    // 获取元素    Object getMapValue(String key);    // 获取元素    Object getValue(String key);}
RedisServiceImpl
package com.rabbit.diary.redis;import com.rabbit.diary.util.SpringUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.ValueOperations;import org.springframework.data.redis.support.atomic.RedisAtomicLong;import org.springframework.stereotype.Service;import java.util.Map;import java.util.concurrent.TimeUnit;@Servicepublic class RedisServiceImpl implements IRedisService {    @Autowired    private StringRedisTemplate stringRedisTemplate;    @Autowired    private RedisTemplate redisTemplate;    /**     * @Description: 获取自增长值 (每调用1次,ID自增1)     * @param key key     * @return     */    public  Long getIncr(String key) { RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); Long increment = entityIdCounter.getAndIncrement(); //entityIdCounter.expire(0, TimeUnit.SECONDS); return increment;    }    /**     * @Description: 初始化自增长值     * @param key key     * @param value 当前值     */    public void setIncr(String key, int value) { RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); counter.set(value); //counter.expire(0, TimeUnit.SECONDS);    }    @Override    public void setValue(String key, Map value) { ValueOperations vo = redisTemplate.opsForValue(); vo.set(key, value); redisTemplate.expire(key, 1, TimeUnit.HOURS); // 这里指的是1小时后失效    }    @Override    public Object getValue(String key) { ValueOperations vo = redisTemplate.opsForValue(); return vo.get(key);    }    @Override    public void setValue(String key, String value) { ValueOperations vo = redisTemplate.opsForValue(); vo.set(key, value); redisTemplate.expire(key, 1, TimeUnit.HOURS); // 这里指的是1小时后失效    }    @Override    public void setValue(String key, Object value) { ValueOperations vo = redisTemplate.opsForValue(); vo.set(key, value); redisTemplate.expire(key, 1, TimeUnit.HOURS); // 这里指的是1小时后失效    }    @Override    public Object getMapValue(String key) { ValueOperations vo = redisTemplate.opsForValue(); return vo.get(key);    }}

其中包含了

getIncr
setIncr

这两个方法可以用来设置、获取自增长值。

4. sa-token 鉴权框架

用户权限问题是一个系统不可缺少的部分,本项目采用sa-token框架。倒不是我给作者打广告哈,而是我觉得确实很好用,shiro太重了,我更倾向于选择sa-token,用我们国人自己的框架。

官网:Sa-Token

引入依赖:

cn.dev33sa-token-spring-boot-starter1.28.0

创建包

com.rabbit.diary.config

编写

SaTokenConfigure
@Configurationpublic class SaTokenConfigure implements WebMvcConfigurer {    // 注册Sa-Token的注解拦截器,打开注解式鉴权功能    @Override    public void addInterceptors(InterceptorRegistry registry) { // 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关) registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");    }}

写这个config是为了开启sa-token注解。

5. 用户接口和服务类

终于到用户接口编写啦,我们已经做了很多的准备工作,这个项目已经初步具备企业级工程化的标准了。很多企业小项目的架构杂乱无章,甚至都没有这么完备的体系。

用户服务类,我们采用接口+实现类的方式。

UserService
package com.rabbit.diary.service;import com.rabbit.diary.bean.User;import org.springframework.stereotype.Service;public interface UserService {    User getByUserName(String userName);    void save(User user);}
UserServiceImpl
package com.rabbit.diary.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.rabbit.diary.bean.User;import com.rabbit.diary.dao.UserMapper;import com.rabbit.diary.redis.RedisServiceImpl;import com.rabbit.diary.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.lang.annotation.Annotation;import java.util.List;@Servicepublic class UserServiceImpl implements UserService {    @Autowired    UserMapper userMapper;    @Autowired    RedisServiceImpl redisServiceImpl;    /**     * 根据用户名获取用户     * @param userName     * @return     */    @Override    public User getByUserName(String userName) { List users = userMapper.selectList(new QueryWrapper().eq("user_name", userName)); if(users.size() > 0){     return users.get(0); } return null;    }    /**     * 保存用户     * @param user     */    @Override    public void save(User user) { userMapper.insert(user);    }}

很简单吧,就两个方法。

UserController:

 

package com.rabbit.diary.web;import cn.dev33.satoken.stp.StpUtil;import cn.hutool.core.date.DateUtil;import cn.hutool.core.util.StrUtil;import cn.hutool.crypto.SecureUtil;import com.rabbit.diary.bean.User;import com.rabbit.diary.bean.core.BizException;import com.rabbit.diary.bean.core.ExceptionCodeEnum;import com.rabbit.diary.bean.core.Result;import com.rabbit.diary.redis.RedisServiceImpl;import com.rabbit.diary.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/user")public class UserController {    @Autowired    UserService userService;    @Autowired    RedisServiceImpl redisServiceImpl;    String salt = "diary188";    @RequestMapping("register")    public Result register(@RequestBody User user){ if(StrUtil.isEmpty(user.getUserName())){     throw new BizException(ExceptionCodeEnum.ERROR_PARAM.setDesc("用户名不允许为空!")); } if(StrUtil.isEmpty(user.getPassword())){     throw new BizException(ExceptionCodeEnum.ERROR_PARAM.setDesc("密码不允许为空!")); } //检查用户名是否重复 if(userService.getByUserName(user.getUserName()) != null){     throw new BizException(ExceptionCodeEnum.ERROR_PARAM.setDesc("用户名"+user.getUserName()+"重复!")); } //拼装userBean user.setUid(redisServiceImpl.getIncr("userId")); //redis自增ID user.setPassword(SecureUtil.md5(user.getPassword() + salt)); user.setCreateTime(DateUtil.now()); user.setUpdateTime(DateUtil.now()); userService.save(user); return Result.success();    }    @RequestMapping("login")    public Result login(@RequestBody User user){ User user1 = userService.getByUserName(user.getUserName()); if(user1 == null){     throw new BizException(ExceptionCodeEnum.ERROR_PARAM.setDesc("不存在的用户名:" + user.getUserName())); } String password = SecureUtil.md5(user.getPassword() + salt); if(!user1.getPassword().equals(password)){     throw new BizException(ExceptionCodeEnum.ERROR_PARAM.setDesc("账号和密码不匹配:" + user.getUserName())); } /**登录维持ID* */ StpUtil.login(user1.getUid(),"PC"); return Result.success();    }}

下面我们挑几个需要注意的点

01. 如何实现redis自增长ID?

 user.setUid(redisServiceImpl.getIncr("userId")); //redis自增ID

02.如何保持登录ID?

StpUtil.login(user1.getUid(),"PC");
PageController
@Controllerpublic class PageController {    @RequestMapping("login")    public String login(){ return "login";    }    @RequestMapping("/")    @SaCheckLogin    public String index(){ return "index";    }}

注意,我们给index加上了saCheckLogin注解,这表示进入这个方法必须得是登录状态。

index.jsp是项目首页

    Title            首页

启动项目,让我们访问:http://localhost/

看到:

{"code":"9999","message":"Token无效:3fd99f78-10aa-4690-bd83-5fae198d1b87","data":null}

 这表示sa-token的配置已经生效了。

现在,我们去注册几个账号:

这就顺利进入了index.jsp。

6. 如何下载源码?

关注下方公众号,回复“日记本”即可。我把每一节的源码都单独打包给你准备好了。