上一节: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=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> {@Overridepublic 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);}@Overridepublic 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/*.xmltype-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.1port: 6379password:jedis:pool:max-active: 8max-wait: -1max-idle: 500min-idle: 0lettuce:shutdown-timeout: 0timeout: 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 {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate 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);}@Overridepublic 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小时后失效}@Overridepublic Object getValue(String key) {ValueOperations<String, String> vo = redisTemplate.opsForValue();return vo.get(key);}@Overridepublic void setValue(String key, String value) {ValueOperations<String, Object> vo = redisTemplate.opsForValue();vo.set(key, value);redisTemplate.expire(key, 1, TimeUnit.HOURS); // 这里指的是1小时后失效}@Overridepublic void setValue(String key, Object value) {ValueOperations<String, Object> vo = redisTemplate.opsForValue();vo.set(key, value);redisTemplate.expire(key, 1, TimeUnit.HOURS); // 这里指的是1小时后失效}@Overridepublic 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的注解拦截器,打开注解式鉴权功能@Overridepublic 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 {@AutowiredUserMapper userMapper;@AutowiredRedisServiceImpl redisServiceImpl;/*** 根据用户名获取用户* @param userName* @return*/@Overridepublic 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*/@Overridepublic 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 {@AutowiredUserService userService;@AutowiredRedisServiceImpl 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()+"重复!"));}//拼装userBeanuser.setUid(redisServiceImpl.getIncr("userId")); //redis自增IDuser.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("/")@SaCheckLoginpublic 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. 如何下载源码?

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

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

  1. SpringBoot日记本系统全程直播01:先把框架搞起来撒~~

    那么大家晚上好,我是今天晚上的主讲老师,我是兔哥. 一直有粉丝私信我,能不能专门做一期项目实战的教程.这让我不禁想起了之前的文章发布系统系列教程,说实话,写项目类的教程真的很累.不过,看到粉丝们这么热 ...

  2. SpringBoot日记本系统全程直播09:项目一期完结

    大家好,我是兔哥呀.我又来啦,这个SpringBoot日记本系统已经接近尾声了,目前已经完成的模块有日记本的增删改查,类型的增删改查,头像上传,还有个人中心. 最后一节呢,我们来做一个收尾工作哈. 目 ...

  3. JavaWeb项目实战 - SpringBoot日记本系统(第一期)

    哈喽,我是兔哥呀,终于啊,这个SpringBoot日记本系统完成啦.(虽然留了一堆BUG) 撒花,写教程是真的很累的一件事情. 第一期教程一共9个小节: 大家可以直接关注这个专栏: SpringBoo ...

  4. springboot教学系统毕业设计-附源码191733

    目  录 摘要 1 绪论 1.1 研究意义 1.2开发现状 1.3论文结构与章节安排 2教学系统 系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1数据增加流程 2.3.2数据修改流程 2 ...

  5. (附源码)springboot养老院系统 毕业设计 645488

    springboot养老院系统 摘 要 随着互联网趋势的到来,各行各业都在考虑利用互联网将自己推广出去,最好方式就是建立自己的互联网系统,并对其进行维护和管理.在现实运用中,应用软件的工作规则和开发步 ...

  6. springboot+教学系统 毕业设计-附源码191733

    目  录 摘要 1 绪论 1.1 研究意义 1.2开发现状 1.3论文结构与章节安排 2教学系统 系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1数据增加流程 2.3.2数据修改流程 2 ...

  7. Springboot平衡膳食系统小程序 计算机毕设源码27190

    摘 要 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,平衡膳食系统小程序被用户普遍使用,方便用户能够 ...

  8. 详细说明如何配置蜻蜓Q系统的直播功能完美实现直播和观赏直播,推拉流配置完整说明一颗优雅草科技伊凡写

    详细说明如何配置蜻蜓Q系统的直播功能完美实现直播和观赏直播,推拉流配置完整说明一颗优雅草科技伊凡写 一.登录后台找到直播配置 依次在站点配置-直播配置 ​ 编辑切换为居中 添加图片注释,不超过 140 ...

  9. jsp servlet mysql项目_JSP+Servlet+JDBC+mysql实现的个人日记本系统

    项目简介 本系统基于JSP+Servlet+Mysql 一个基于JSP+Servlet+Jdbc的个人日记本系统.涉及技术少,易于理解,适合JavaWeb初学者学习使用. 难度等级:入门 技术栈 编辑 ...

最新文章

  1. java十个整数相反顺序_编写程序,对输入的一个整数,按相反顺序输出该数。例如,输入为 3578, 输出为 8753。...
  2. Datawhale第四期组队学习团队成员
  3. Google开源新TensorFlow运行时TFRT,将取代现有进行时
  4. 拿什么兜住网络信息安全网底
  5. Android Jetpack组件之 LiveData使用-源码
  6. 大牛的距离(笑cry)精简算法
  7. 百练2815 城堡问题
  8. stylus之运算符(Operators)
  9. 设计模式(四)行为型模式
  10. JAVAFX-1 开发应用
  11. Flutter BottomAppBar
  12. idea中maven项目,输出在控制台上的中文乱码问题
  13. 【五校联考5day2】光棍
  14. Github常用英文翻译
  15. HTML+CSS+JS的基础知识笔记
  16. excel 画散点图 怎么设置图片的分辨率_双代号时标网络图用Excel画
  17. 前端遇到的那些技术难点
  18. WIN10笔记本屏幕亮度无法调节,一直是最高亮度
  19. Using getResponseBodyAsStream instead is recommended
  20. 使用GPS坐标来计算距离和方位角

热门文章

  1. Java的匿名内部类
  2. 缄默期过后6小时,搜狗王小川回应一切(他说:会叫的狗不咬人)
  3. CCF-CSP 201812-1小明上学 简单思路 满分题解
  4. JavaScript中getElementById()方法怎么用?
  5. Windows Server 服务器实现PPT转pdf
  6. 一定要看!SD卡格式化数据丢失?简单几步轻松找回!
  7. ESP32 ESP-IDF ftpServer
  8. 一款类似于mac dock栏的windows软件
  9. 人脸表情识别数据集:CK+
  10. 第 十 六 天 : 关 于 root 和 用 户 登 陆 的 问 题