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. 如何下载源码?
关注下方公众号,回复“日记本”即可。我把每一节的源码都单独打包给你准备好了。