找实习、找实习、找实习 求内推 有没有大佬们愿意给我一次机会。求求啦!
前言📕
本文章循序渐进 从而实现一个企业级别 的用户管理系统
写在前面 我们知道 登录功能 作为 我们开发的项目必备的一个功能,也是最基本的初始化功能!
但是 如何实现、功能全不全 这个里面学问就很大了
比如 倘若我们仅仅是一张表的crud 这固然简单
但 真正的登录或者注册功能 考虑的肯定会更多 比如 如何防止被恶意调用接口、唯一ID保证事务性、有没有集成三方登录、用户名的敏感词汇的检索 等等
那么就跟随作者一起去看看吧~
前置技术栈: 首先 您需要 会 创建 springBoot 项目 以及拥有 mysql (操作数据库)的基础!
作者水平有限、请多多包涵~
初学者💻
以 注册、和登录 举例子就好了
- 首先 我们先构造一个SpringBoot项目
- 接下来 我们先引入 slf4j (测试文档)的依赖 方便测试
<!-- https://doc.xiaominfo.com/knife4j/documentation/get_start.html-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
- yaml 中配置一下 swagger
# 支持 swagger3
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
- 我们就可以再项目中 去编写 实现功能啦
先搞一个用户表
create table user
(
username varchar(256) null comment '用户昵称',
id bigint auto_increment comment 'id'
primary key,
userAccount varchar(256) null comment '账号',
avatarUrl varchar(1024) null comment '用户头像',
gender tinyint null comment '性别',
userPassword varchar(512) not null comment '密码',
phone varchar(128) null comment '电话',
email varchar(512) null comment '邮箱',
userStatus int default 0 not null comment '状态 0 - 正常',
createTime datetime default CURRENT_TIMESTAMP null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP,
isDelete tinyint default 0 not null comment '是否删除',
userRole int default 0 not null comment '用户角色 0 - 普通用户 1 - 管理员',
planetCode varchar(512) null comment '掘金编号'
)
comment '用户';
- 编写实体类 (这里我们可以使用MybatisX 插件 来进行一键生成)
package com.lizhi.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 用户
* @TableName user
*/
@TableName(value ="user") //告诉MP 这是用户表
// 下面三个是Lombok的注解 作用是不需要 写 get set 了
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
/**
* id
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户昵称
*/
private String username;
/**
* 账号
*/
private String userAccount;
/**
* 用户头像
*/
private String avatarUrl;
/**
* 性别
*/
private Integer gender;
/**
* 密码
*/
private String userPassword;
/**
* 电话
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 状态 0 - 正常
*/
private Integer userStatus;
/**
* 创建时间
*/
private Date createTime;
/**
*
*/
private Date updateTime;
/**
* 是否删除
*/
@TableLogic
private Integer isDelete;
/**
* 用户角色 0 - 普通用户 1 - 管理员
*/
private Integer userRole;
/**
* 掘金编号
*/
private String planetCode;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
- 所以看到 上面 其实我们还需要 引入 mysql 连接池 依赖 spring mybatis 依赖 mybatis-plus 依赖 以及 lombok 依赖
<!-- 这个依赖是快速构建 启动项目 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
mapper 层 我就不搞了 (MP帮你封装好了)
- controller 控制层
package com.lizhi.controller;
import com.lizhi.entity.User;
import com.lizhi.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* @author <a href="https://github.com/lizhe-0423">荔枝</a>
*/
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@PostMapping("/register")
public String userRegister(@RequestBody User userRegisterRequest) {
String userAccount = userRegisterRequest.getUserAccount();
String userPassword = userRegisterRequest.getUserPassword();
String planetCode = userRegisterRequest.getPlanetCode();
long result = userService.userRegister(userAccount, userPassword, planetCode);
return "注册成功";
}
@PostMapping("/login")
public String userLogin(@RequestBody User userLoginRequest, HttpServletRequest request) {
String userAccount = userLoginRequest.getUserAccount();
String userPassword = userLoginRequest.getUserPassword();
User user = userService.userLogin(userAccount, userPassword, request);
return "登录成功";
}
}
可以看到 控制层 更多的是进行一个交互 我们把具体的逻辑封装在 service层 (处理事务)
- service层
接口
package com.lizhi.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.lizhi.entity.User;
import javax.servlet.http.HttpServletRequest;
/**
* 用户服务
*
* @author 荔枝
*/
public interface UserService extends IService<User> {
/**
* 用户注册
*
* @param userAccount 用户账户
* @param userPassword 用户密码
* @param planetCode 掘金编号
* @return 新用户 id
*/
long userRegister(String userAccount, String userPassword, String planetCode);
/**
* 用户登录
*
* @param userAccount 用户账户
* @param userPassword 用户密码
* @param request
* @return 用户信息
*/
User userLogin(String userAccount, String userPassword, HttpServletRequest request);
}
- 实现
package com.lizhi.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lizhi.entity.User;
import com.lizhi.mapper.UserMapper;
import com.lizhi.service.UserService;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 用户服务实现类
*
* @author 荔枝
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
@Resource
private UserMapper userMapper;
@Override
public long userRegister(String userAccount, String userPassword, String planetCode) {
// 3. 插入数据
User user = new User();
user.setUserAccount(userAccount);
user.setUserPassword(userPassword);
user.setPlanetCode(planetCode);
boolean saveResult = this.save(user);
if (!saveResult) {
return -1;
}
return user.getId();
}
@Override
public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
val user = new User();
user.setUserAccount(userAccount);
user.setUserPassword(userPassword);
// 4. 记录用户的登录态
request.getSession().setAttribute("user", user);
return user;
}
}
访问下面这个地址
http://127.0.0.1:2318/api/doc.html
测试:
收获
上面 只是一个简单的 demo 但demo 就是 demo 在真正开发中 先不说安全问题的隐患 就说这些代码格式 想必各位应该就受不了吧!
记得 我 还在上大二的时候 那个时候 身边有一个经常带着我做项目的"好哥哥" 当时就是他教给我代码的书写规范
其实 这个东西 你说他重要吧 好像很重要 不重要 也不重要 因为这个东西 更多的就是”潜规“则一样 以后早晚会懂得!!!
那么 让我们来说说上面有什么问题吧
首先从代码本身的角度来看:
拿注册来看 @requestBody 我们知道 获取的json 数据 自动 转换成对应的实体类 也就是说这个地方是用来接收前端用户传来的 数据的 那么这个地方就有问题了 我们最好不要直接用一个User实体类来接受数据
因为 一个完整实体类 少了 还好 太多了话 前端 就不知道他要给你传什么了
- 所以在Java中 我们专门有一种说法叫 DTO 也就是请求 表示前端 向 后端 传递的数据 理解成request
/**
* 用户注册请求体
*
* @author 荔枝
*/
@Data
public class UserRegisterRequest implements Serializable {
private static final long serialVersionUID = 3191241716373120793L;
private String userAccount;
private String userPassword;
private String checkPassword;
private String planetCode;
}
- 我们仅仅把用到的数据 封装成一个实体类 既然有了前端向后端 传递的数据了 那么是不是有后端向前端传递的数据呢? 没错 我们管这种叫 VO
比如 前端 展示的用户信息 (在User 实体类中包含了ID、创建时间、更新时间等等 这些是不给前端用户看的 所以对于其他的数据 我们再封装成VO进行返回)
- 不要出现魔法值
比如 我session 那里 存储的数据 要用常量
package com.yupi.usercenter.contant;
/**
* 用户常量
*
* @author 荔枝
*/
public interface UserConstant {
/**
* 用户登录态键
*/
String USER_LOGIN_STATE = "userLoginState";
// ------- 权限 --------
/**
* 默认权限
*/
int DEFAULT_ROLE = 0;
/**
* 管理员权限
*/
int ADMIN_ROLE = 1;
}
- 不要返回前端 字符串 自定义通用返回类进行返回
安全性/功能性:
- 首先 传递过来的数据 我们应该先进行校验(前端也需要进行校验)
- 一个完整的用户流程或许还包括 删除用户、注销登录 等常见功能
进阶者👨
当你能考虑上述 的东西的话 那么在我的印象中 你的实力应该有大二的真实水平啦
然后就让我们来实现它吧!
接下来我只 举例 一些重要的功能 完整的代码我会附上GitHub地址
用户控制层
package com.lizhi.usercenter.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lizhi.usercenter.common.BaseResponse;
import com.lizhi.usercenter.common.ErrorCode;
import com.lizhi.usercenter.common.ResultUtils;
import com.lizhi.usercenter.exception.BusinessException;
import com.lizhi.usercenter.model.domain.User;
import com.lizhi.usercenter.model.domain.request.UserLoginRequest;
import com.lizhi.usercenter.model.domain.request.UserRegisterRequest;
import com.lizhi.usercenter.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;
import static com.lizhi.usercenter.contant.UserConstant.ADMIN_ROLE;
import static com.lizhi.usercenter.contant.UserConstant.USER_LOGIN_STATE;
/**
* 用户接口
*
* @author 荔枝
*/
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Resource
private UserService userService;
@PostMapping("/register")
public BaseResponse<Long> userRegister(@RequestBody UserRegisterRequest userRegisterRequest) {
if (userRegisterRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
String userAccount = userRegisterRequest.getUserAccount();
String userPassword = userRegisterRequest.getUserPassword();
String checkPassword = userRegisterRequest.getCheckPassword();
String planetCode = userRegisterRequest.getPlanetCode();
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) {
return null;
}
long result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
return ResultUtils.success(result);
}
@PostMapping("/login")
public BaseResponse<User> userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
if (userLoginRequest == null) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR);
}
String userAccount = userLoginRequest.getUserAccount();
String userPassword = userLoginRequest.getUserPassword();
if (StringUtils.isAnyBlank(userAccount, userPassword)) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR);
}
User user = userService.userLogin(userAccount, userPassword, request);
return ResultUtils.success(user);
}
@PostMapping("/logout")
public BaseResponse<Integer> userLogout(HttpServletRequest request) {
if (request == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
int result = userService.userLogout(request);
return ResultUtils.success(result);
}
@GetMapping("/current")
public BaseResponse<User> getCurrentUser(HttpServletRequest request) {
Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
User currentUser = (User) userObj;
if (currentUser == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN);
}
long userId = currentUser.getId();
// TODO 校验用户是否合法
User user = userService.getById(userId);
User safetyUser = userService.getSafetyUser(user);
return ResultUtils.success(safetyUser);
}
@GetMapping("/search")
public BaseResponse<List<User>> searchUsers(String username, HttpServletRequest request) {
if (!isAdmin(request)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(username)) {
queryWrapper.like("username", username);
}
List<User> userList = userService.list(queryWrapper);
List<User> list = userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
return ResultUtils.success(list);
}
@PostMapping("/delete")
public BaseResponse<Boolean> deleteUser(@RequestBody long id, HttpServletRequest request) {
if (!isAdmin(request)) {
throw new BusinessException(ErrorCode.NO_AUTH);
}
if (id <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
boolean b = userService.removeById(id);
return ResultUtils.success(b);
}
/**
* 是否为管理员
*
* @param request
* @return
*/
private boolean isAdmin(HttpServletRequest request) {
// 仅管理员可查询
Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
User user = (User) userObj;
return user != null && user.getUserRole() == ADMIN_ROLE;
}
}
不知道童鞋们看到 这里有没有蒙 不过不要慌 就像 我说的 那些多出来的代码只不过是一些规则校验
当然了 如果是正式 或者 负责的规则校验 我们应该抽取成一个 代码片段
比如在 service层中接口 verfy接口 (校验接口)
自定义的返回类
package com.lizhi.usercenter.common;
import lombok.Data;
import java.io.Serializable;
/**
* 通用返回类
*
* @param <T>
* @author 荔枝
*/
@Data
public class BaseResponse<T> implements Serializable {
private int code;
private T data;
private String message;
private String description;
public BaseResponse(int code, T data, String message, String description) {
this.code = code;
this.data = data;
this.message = message;
this.description = description;
}
public BaseResponse(int code, T data, String message) {
this(code, data, message, "");
}
public BaseResponse(int code, T data) {
this(code, data, "", "");
}
public BaseResponse(ErrorCode errorCode) {
this(errorCode.getCode(), null, errorCode.getMessage(), errorCode.getDescription());
}
}
自定义返回信息
package com.lizhi.usercenter.common;
/**
* 返回工具类
*
* @author 荔枝
*/
public class ResultUtils {
/**
* 成功
*
* @param data
* @param <T>
* @return
*/
public static <T> BaseResponse<T> success(T data) {
return new BaseResponse<>(0, data, "ok");
}
/**
* 失败
*
* @param errorCode
* @return
*/
public static BaseResponse error(ErrorCode errorCode) {
return new BaseResponse<>(errorCode);
}
/**
* 失败
*
* @param code
* @param message
* @param description
* @return
*/
public static BaseResponse error(int code, String message, String description) {
return new BaseResponse(code, null, message, description);
}
/**
* 失败
*
* @param errorCode
* @return
*/
public static BaseResponse error(ErrorCode errorCode, String message, String description) {
return new BaseResponse(errorCode.getCode(), null, message, description);
}
/**
* 失败
*
* @param errorCode
* @return
*/
public static BaseResponse error(ErrorCode errorCode, String description) {
return new BaseResponse(errorCode.getCode(), errorCode.getMessage(), description);
}
}
此时 我们再来看到 控制层
类型 就是我们自己定义的通用返回类型
我们返回数据的时候 就是自己定义的返回工具类
自定义错误代码
package com.lizhi.usercenter.common;
/**
* 错误码
*
* @author 荔枝
*/
public enum ErrorCode {
SUCCESS(0, "ok", ""),
PARAMS_ERROR(40000, "请求参数错误", ""),
NULL_ERROR(40001, "请求数据为空", ""),
NOT_LOGIN(40100, "未登录", ""),
NO_AUTH(40101, "无权限", ""),
SYSTEM_ERROR(50000, "系统内部异常", "");
private final int code;
/**
* 状态码信息
*/
private final String message;
/**
* 状态码描述(详情)
*/
private final String description;
ErrorCode(int code, String message, String description) {
this.code = code;
this.message = message;
this.description = description;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}
}
这里的用法就是 再 进行 权限校验的 时候 如果报错 自定义返回的错误
下面 注册 为例
- 参数校验 如果不符合 自动抛出我们自定义的错误
- 账号校验有没有特殊字符
- 账号有无与数据库重复
- 进行密码的加密
- 返回用户id 如果注册失败 返回-1
/**
* 盐值,混淆密码
*/
private static final String SALT = "lizhi";
@Override
public long userRegister(String userAccount, String userPassword, String checkPassword, String planetCode) {
// 1. 校验
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
}
if (userAccount.length() < 4) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
}
if (userPassword.length() < 8 || checkPassword.length() < 8) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
}
if (planetCode.length() > 5) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号过长");
}
// 账户不能包含特殊字符
String validPattern = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
if (matcher.find()) {
return -1;
}
// 密码和校验密码相同
if (!userPassword.equals(checkPassword)) {
return -1;
}
// 账户不能重复
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount", userAccount);
long count = userMapper.selectCount(queryWrapper);
if (count > 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
}
// 星球编号不能重复
queryWrapper = new QueryWrapper<>();
queryWrapper.eq("planetCode", planetCode);
count = userMapper.selectCount(queryWrapper);
if (count > 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "编号重复");
}
// 2. 加密
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
// 3. 插入数据
User user = new User();
user.setUserAccount(userAccount);
user.setUserPassword(encryptPassword);
user.setPlanetCode(planetCode);
boolean saveResult = this.save(user);
if (!saveResult) {
return -1;
}
return user.getId();
}
收获
当你看到此刻下来 我想你应该对 一个项目 尤其是登录项目 应该做什么 有了一个清晰的认知
首先
-
你知道了 VO 和 DTO
-
真实的项目 自定义 通用返回 与 错误代码
我们此刻 再来考虑一些东西 以及 上述的代码 是不是 可以再进行简化
问题如下
- 敏感词汇的检索 比如用户 账号 有无敏感词汇(辱骂、种族歧视 等等)
- 同一时刻 两个用户同时登陆 是否会同时登录 如果此刻进行 签到、打卡 (会不会产生双倍积分?)
- 如何防止被爬虫 然后进行接口的调用
- 有没有实现找回密码、记住密码的功能?
以及 更丰富的功能 或者更 高级的技术
- jwt 和 session + redis
- springSecurity 或者 Sa-Token 登录框架 辅助开发
- 查询用户 追求性能 我们直接走Redis缓存
……
感悟者🤔
针对上述 列举的问题
1 .敏感词汇的检索 比如用户 账号 有无敏感词汇(辱骂、种族歧视 等等)
解决方法:
数据库建立这么一张词汇表 然后注册账号的时候 用户名 去匹配 如果匹配到 就 返回用户名非法信息,否则注册成功
- 同一时刻 确实 有可能 会登录成功
解决方法: 锁操作(这只是单机锁 单机锁的意思 如果处于分布式环境就会失效)
synchronized (unionId.intern()) {
// 查询用户是否已存在
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("unionId", unionId);
User user = this.getOne(queryWrapper);
// 被封号,禁止登录
if (user != null && UserRoleEnum.BAN.getValue().equals(user.getUserRole())) {
throw new BusinessException(ErrorCode.FORBIDDEN_ERROR, "该用户已被封,禁止登录");
}
// 用户不存在则创建
if (user == null) {
user = new User();
user.setUnionId(unionId);
user.setMpOpenId(mpOpenId);
user.setUserAvatar(wxOAuth2UserInfo.getHeadImgUrl());
user.setUserName(wxOAuth2UserInfo.getNickname());
boolean result = this.save(user);
if (!result) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败");
}
}
// 记录用户的登录态
request.getSession().setAttribute(USER_LOGIN_STATE, user);
return getLoginUserVO(user);
- 使用 验证码 每次 登录 或者 注册 的时候 让用户 进行 验证码 验证
解决方法: 使用hutool 引入 hutool工具库的验证码功能:
package com.lizhi.stpspringbootinit.util;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.ShearCaptcha;
import lombok.val;
/**
* @author <a href="https://github.com/lizhe-0423">荔枝</a>
* 图片验证码
*/
public class Captcha {
/**
* 生成 扭曲干扰验证码
* @param str 传入 密码进行验证
* @return true 验证码正确 false 验证码错误
*/
public boolean shearCaptcha(String str){
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 100, 4, 4);
//图形验证码写出,可以写出到文件,也可以写出到流
// captcha.write("d:/shear.png");
boolean verify = captcha.verify(str);
return verify;
}
}
- 找回密码 通过密保找回 (数据表操作) 而 记住密码 其实 就是 登录 用户 的时候 将用户信息 存入 session 上面演示的功能中 自动实现了此功能
- jwt 和 session+redis 具体百度吧 简单来说是两种不同的登录手段 jwt 更加简单 但 扩展性差 session+redis的方式 扩展性强
前面 我们知道 用户将信息 存入session 实现 记住用户的功能
但这样有弊端 我们知道session表示一次会话 所以关闭浏览器 或者 重启应用 session就会消失
而所谓的session+redis 不过就是 将session信息存储到redis中而已 这样就可以进行长久的保存了!
而jwt 其实 就是将用户信息 携带在jwt(字符串)中(前后端传递jwt)
6.引入 Sa-Token
可以看我的另一篇笔记 玩转Sa-Token 2023年,不会还有人用SpringSecurity登录鉴权吧 - 掘金 (juejin.cn)
收获
如果你看到这里 如果你读到这里 那这就是真爱 你难以抗拒~~~
好了 此刻 你已经会自己思考了 你的实力已经不仅仅局限在代码本身 我承认 你的实力有大三的水平了(大三强者、恐怖如斯 😱)
随笔🖊
过度思考
所谓过度思考 在码农身上就是指过度优化 对于你我新手来说 就是 只懂熟悉的技术选型 如果叫你实现什么功能 你必须依赖它的前置 代码 而不能自身 具有编写代码的能力
我把 新手 分为两个阶段
第一个阶段 萌新
学习就是看视频 你是一个真真正正的代码 小白 你获取信息 学习的方式 就是看视频 如果遇到没有视频的新技术 就会捉襟见肘 同时 在编程中 如果遇到的错误 的地方 ,也是 百度 如果百度不到 也和前面一样!
你敲代码 只是本能的模仿
第二个阶段 初窥门径
你尝试 开始阅读源码
你学习的方式 开始变得讨厌看视频 因为你觉得这样获取信息的速度太慢了
你可以尝试着写点功能 并且有自己间接 比如这篇文章 用户管理功能
第三个阶段 高级新手
你已经 会看源码 并且 看得懂 大部分源码
你获取信息的途径 也不是 纸质书籍教程 而是阅读官方文档
你觉得自己什么都会 又好像只是什么都会一点
你 年轻气盛 你 迈入了编程的第一步
所以 道阻且长 我们都应该努力✊
关于成为怎样的人?
其实 作者 我 没考上高中 读的也是职高 我费劲努力 上的也不过是个三本
我讨厌这个大学 我认为这是一个没有学术氛围 并且官僚横行的学校 我与朋友参加比赛看别的学校导师带着一起答辩 我也很羡慕
我高中 常常想 上了 大学就好了
我上了 大学 还真的是这样 大一我课余时间打篮球 报吉他社
我经常看到 网上有人说 如果大一就开始学习编程就好了 如果大一就看到这篇文章就好了
我也在想 如果我大一学习 就好了
但仔细一想 我真的后悔码?
不管怎样 这就是青春 不是吗? 大学就应该干点大学的事不是吗? 恋爱、散步、live house 、聚餐、运动 ……
****等等等等 记得一句话 我们不应该站在现在的高度去批评当时的自己 如果站在当时的角度 或许又是一次重蹈覆辙 但这一切真的只是重蹈覆辙吗?
结尾 📕
找实习找实习找实习、求内推~~~~
项目地址:
hutool(三方工具库):
MybatisPlus(数据库):
Sa-Token(登录框架)