上一节:SpringBoot日记本系统全程直播02:登录页面搞起来撒~~
大家好,我是今天晚上的主讲老师,我是兔哥。
上一讲,我们学习了登录和注册页面,以及Controller访问页面的方法,还有如何处理静态资源。
今天我们继续来学习SpringBoot日记本系统,任务是进行后台的对接。就是说,用户注册和登录的功能,需要完善起来啦。还会涉及到很多企业级开发技术哦。
总之,这一节的内容非常之多,也比较丰富。视频我后期会补上,如果跟着做做不出来,一定要下载源码慢慢比对哈。
ok,那么我们现在开整!
目录
3.1.2 GlobalExceptionHandler 全局异常处理
3.1.3 ExceptionCodeEnum 通用异常枚举
3.1.4 CommonResponseDataAdvice 统一封账响应结果
3.1.5 IgnoreCosmoResult 忽略统一结果返回封装
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=InnoDB
DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='用户基础信息表'
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
<script src="${basePath}/js/vue.js"></script>
<script src="${basePath}/js/axios.min.js"></script>
更改HTML
<div class="container" id="app">
<div class="register-box">
<h2 class="register-title">
<span>没有账号,去</span>注册日记本
</h2>
<div class="input-box">
<input type="text" v-model="userName1" placeholder="用户名" autocomplete="off">
<input type="password" v-model="password1" placeholder="密码" autocomplete="off">
<input type="password" v-model="password11" placeholder="确认密码" autocomplete="off">
</div>
<button @click="reg">注册</button>
</div>
<div class="login-box slide-up">
<div class="center">
<h2 class="login-title">
<span>已有账号,去</span>登录日记本
</h2>
<div class="input-box">
<input type="text" v-model="userName2" placeholder="用户名" autocomplete="off">
<input type="password" v-model="password2" placeholder="密码" autocomplete="off">
</div>
<button @click="login">登录</button>
</div>
</div>
</div>
其实用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处理
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 权限异常 (注意,参数一定要是需要捕获的异常,否则进不来)
* @param
* @return
*/
@ExceptionHandler(NotLoginException.class)
public com.rabbit.diary.bean.core.Result<com.rabbit.diary.bean.core.ExceptionCodeEnum> 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<com.rabbit.diary.bean.core.ExceptionCodeEnum> 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<com.rabbit.diary.bean.core.ExceptionCodeEnum> 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
*
*/
@RestControllerAdvice
public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object> {
@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<T> 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 <T>
* @return
*/
public static <T> Result<T> success(T data) {
return new Result<>(ExceptionCodeEnum.SUCCESS.getCode(), ExceptionCodeEnum.SUCCESS.getDesc(), data);
}
/**
* 不带数据成功返回
*
* @return
*/
public static <T> Result<T> success() {
return success(null);
}
/**
* 通用错误返回
*
* @param exceptionCodeEnum
* @return
*/
public static <T> Result<T> error(ExceptionCodeEnum exceptionCodeEnum) {
return new Result<>(exceptionCodeEnum.getCode(), exceptionCodeEnum.getDesc());
}
/**
* 通用错误返回
*
* @param exceptionCodeEnum
* @param msg
* @return
*/
public static <T> Result<T> error(ExceptionCodeEnum exceptionCodeEnum, String msg) {
return new Result<>(exceptionCodeEnum.getCode(), msg);
}
/**
* 通用错误返回
*
* @param exceptionCodeEnum
* @param data
* @param <T>
* @return
*/
public static <T> Result<T> 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增加依赖
<!-- mybatis plus 支持 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
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
@Service
public 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依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
因为使用了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;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
这样就有基本的增删改查方法了。
3.3 redis引入(为了ID自增)
上面已经说过,为了应对分库分表的情况(虽然这个项目用不到哈),我们将采用redis的方式。redis大家可以自行去百度下载windows版的,安装很方便。
双击这个exe就可以打开了。
增加maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
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<String, Object> 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;
@Service
public 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<String, Object> value) {
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
vo.set(key, value);
redisTemplate.expire(key, 1, TimeUnit.HOURS); // 这里指的是1小时后失效
}
@Override
public Object getValue(String key) {
ValueOperations<String, String> vo = redisTemplate.opsForValue();
return vo.get(key);
}
@Override
public void setValue(String key, String value) {
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
vo.set(key, value);
redisTemplate.expire(key, 1, TimeUnit.HOURS); // 这里指的是1小时后失效
}
@Override
public void setValue(String key, Object value) {
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
vo.set(key, value);
redisTemplate.expire(key, 1, TimeUnit.HOURS); // 这里指的是1小时后失效
}
@Override
public Object getMapValue(String key) {
ValueOperations<String, String> vo = redisTemplate.opsForValue();
return vo.get(key);
}
}
其中包含了
getIncr
setIncr
这两个方法可以用来设置、获取自增长值。
4. sa-token 鉴权框架
用户权限问题是一个系统不可缺少的部分,本项目采用sa-token框架。倒不是我给作者打广告哈,而是我觉得确实很好用,shiro太重了,我更倾向于选择sa-token,用我们国人自己的框架。
官网:Sa-Token
引入依赖:
<!-- Sa-Token 权限认证-->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.28.0</version>
</dependency>
创建包
com.rabbit.diary.config
编写
SaTokenConfigure
@Configuration
public 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;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Autowired
RedisServiceImpl redisServiceImpl;
/**
* 根据用户名获取用户
* @param userName
* @return
*/
@Override
public User getByUserName(String userName) {
List<User> users = userMapper.selectList(new QueryWrapper<User>().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
@Controller
public class PageController {
@RequestMapping("login")
public String login(){
return "login";
}
@RequestMapping("/")
@SaCheckLogin
public String index(){
return "index";
}
}
注意,我们给index加上了saCheckLogin注解,这表示进入这个方法必须得是登录状态。
index.jsp是项目首页
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<c:set var="basePath" value="${pageContext.request.contextPath}"></c:set>
<html>
<head>
<title>Title</title>
<script src="${basePath}/layui/layui.all.js" charset="utf-8"></script>
<link rel="stylesheet" href="${basePath}/layui/css/layui.css" media="all">
</head>
<body>
首页
</body>
</html>
启动项目,让我们访问:http://localhost/
看到:
{"code":"9999","message":"Token无效:3fd99f78-10aa-4690-bd83-5fae198d1b87","data":null}
这表示sa-token的配置已经生效了。
现在,我们去注册几个账号:
这就顺利进入了index.jsp。
6. 如何下载源码?
关注下方公众号,回复“日记本”即可。我把每一节的源码都单独打包给你准备好了。