文章目录
- 计划
- 登录逻辑
- 接口
- 简单说明cookie和session
- 写代码流程
- 后端
- 逻辑层
- 控制层
- 测试
- 用户管理接口
- 前端
- 简化代码
- 对接后端
- 代理
计划
- 开发完成后端登录功能 (单机登录 => 后续改造为分布式 / 第三方登录)✔
- 开发后端用户的管理接口 (用户的查询 / 状态更改)✔
- 后端接口测试 ✔
- 开发前端用户登录注册功能
- 讨论如何校验用户(星球的小伙伴可以使用)
登录逻辑
接口
接受参数:用户账户、密码
请求类型:POST
请求体:JSON 格式的数据
请求参数很长,或者无法预料的情况,不建议用 GET
返回值:用户信息(脱敏)
逻辑
- 校验用户和密码是否合法
- 非空
- 账户长度不小于 4 位
- 密码长度不小于 8 位
- 账户不包含特殊字符
- 校验密码是否输入正确,要和数据库中的密文密码对比
- 用户信息脱敏,隐藏敏感信息,防止数据库中的字段泄露
- 记录用户的登录态(我们用 Session),将其存到服务器上(用后端 SpringBoot 框架封装的服务器 Tomcat 记录即可)
如何知道是哪个用户登录了?
1连接上服务器后,得到一个 session 状态(匿名会话),返回给前端
2登录成功后,得到了登录成功的 session,并且给该 session 设置一些值(比如用户信息),返回给前端一个设置 cookie 的“命令”
3前端接收到后端的命令后,设置 cookie,保存到浏览器内
4前端再次请求后端的时候(相同的域名),在请求头中带上 cookie 去请求
5后端拿到前端传来的 cookie,找到对应的 session
6后端从 session 中可以取出基于该 session 存储的变量(用户的登录信息、登录名)
- 返回用户信息(脱敏后的)
简单说明cookie和session
首先,cookie是一种缓存机制,session是会话机制
:::info
🪔以最常见的登陆案例讲解cookie的使用过程:
(1)首先用户在客户端浏览器向服务器首次发起登陆请求
(2)登陆成功后,服务端会把登陆的用户信息设置在cookie 中,并将cookie返回给客户端浏览器
(3)客户端浏览器接收到 cookie 请求后,会把 cookie 保存到本地(可能是内存,也可能是磁盘,看具体使用情况而定)
(4)以后再次访问该 web 应用时,客户端浏览器就会把本地的 cookie 带上,这样服务端就能根据 cookie 获得用户信息了
:::
🪔同样以登陆案例为例子讲解 session 的使用过程:
(1)首先用户在客户端浏览器发起登陆请求
(2)登陆成功后,服务端会把用户信息保存在服务端,并返回一个唯一的 session 标识给客户端浏览器。
(3)客户端浏览器会把这个唯一的 session 标识保存在起来
(4)以后再次访问 web 应用时,客户端浏览器会把这个唯一的 session 标识带上,这样服务端就能根据这个唯一标识找到用户信息。
看到这里可能会引起疑问:把唯一的 session 标识返回给客户端浏览器,然后保存起来,以后访问时带上,这难道不是 cookie 吗?
没错,session 只是一种会话机制,在许多 web 应用中,session 机制就是通
过 cookie 来实现的。也就是说它只是使用了 cookie 的功能,并不是使用 cookie
完成会话保存。与 cookie 在保存客户端保存会话的机制相反,session 通过 cookie
的功能把会话信息保存到了服务端。
session和cookie有什么区别?
(1)cookie 是浏览器提供的一种缓存机制,它可以用于维持客户端与服务端之间的会话
(2)session 指的是维持客户端与服务端会话的一种机制,它可以通过 cookie 实现,也可以
通过别的手段实现。
(3)如果用 cookie 实现会话,那么会话会保存在客户端浏览器中
(4)而 session 机制提供的会话是保存在服务端的。
🦥举个小例子说明Cookie和Session之间的区别和联系
假如一个咖啡店有喝五杯赠一杯咖啡的优惠,但是一次性消费5杯咖啡的客人很少,这时就需要某种方式来记录某位顾客的消费数量。无外乎下面的几种方案:
1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。但是http协议本身是无状态的。
2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态,也就是cookie,顾客就相当于浏览器。
3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的记录本上找到这个卡号对应的记录添加一些消费信息。这种做法就是在服务器端保持状态。
源于星球炎大佬,解释的很清晰
写代码流程
先做设计
代码实现
持续优化!!!(代码复用性,提取公共逻辑/常量)
后端
逻辑层
UserService
public interface UserService extends IService<User> {
/**
*用户注册
* @param userAccount 用户账户
* @param userPassword 用户密码
* @param checkPassword 校验密码
* @return 用户id
*/
long userRegister(String userAccount, String userPassword, String checkPassword);
/**
* 用户登录
* @param userAccount 用户账户
* @param userPassword 用户密码
* @param request 请求
* @return 脱敏后的用户信息
*/
User userLogin(String userAccount, String userPassword, HttpServletRequest request);
}
UserServiceImpl
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService{
@Autowired
private UserMapper userMapper;
/**
* 盐值, 混淆密码
*/
private static final String SALT = "yupi";
private static final String USER_LOGIN_STATE = "userLoginState";
@Override
public long userRegister(String userAccount, String userPassword, String checkPassword) {
//1.校验
if(StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)){
//TO do
return -1;
}
//账户不小于4位
if(userAccount.length() < 4){
return -1;
}
//密码不小于 8 位
if(userPassword.length() < 8){
return -1;
}
//账户不包含特殊字符
String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\\\\\[\\\\\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?\\s]";
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){
return -1;
}
//2.加密
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
//3.插入数据
User user = new User();
user.setUserAccount(userAccount);
user.setUserPassword(encryptPassword);
boolean saveResult = this.save(user);
if (!saveResult){
return -1;
}
return user.getId();
}
@Override
public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
//1.校验
if(StringUtils.isAnyBlank(userAccount, userPassword)){
//TO do
return null;
}
//账户不小于4位
if(userAccount.length() < 4){
return null;
}
//密码不小于 8 位
if(userPassword.length() < 8){
return null;
}
//账户不包含特殊字符
String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\\\\\[\\\\\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?\\s]";
Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
if(matcher.find()){
return null;
}
//2.加密
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
//查询用户是否存在
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount", userAccount);
queryWrapper.eq("userPassword", encryptPassword);
User user = userMapper.selectOne(queryWrapper);
// 用户不存在
if(user == null) {
log.info("user login failed, userAccount cannot match userPassword");
return null;
}
//3. 用户脱敏
User safetyUser = new User();
safetyUser.setId(user.getId());
safetyUser.setUsername(user.getUsername());
safetyUser.setUserAccount(user.getUserAccount());
safetyUser.setAvatarUrl(user.getAvatarUrl());
safetyUser.setGender(user.getGender());
safetyUser.setEmail(user.getEmail());
safetyUser.setUserStatus(user.getUserStatus());
safetyUser.setPhone(user.getPhone());
safetyUser.setCreateTime(user.getCreateTime());
//4. 记录用户的登录态
request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);
return safetyUser;
}
}
逻辑删除,MyBatis-Plus 能自动实现逻辑删除,Mybatis-Plus 逻辑删除
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
这里的 flag 我们是 isDelete
还要在 User 这个类中找到这个属性,然后我们加上注解 @TableLogic
/**
* 是否删除
*/
@TableLogic
private Integer isDelete;
控制层
创建 UserController
记得加上注解 @RestController,适用于编写 Restful 风格的 API,返回值默认为 JSON 类型
还有 @RequestMapping 请求路径
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@PostMapping("/register")
public Long userRegister(@RequestBody UserRegisterRequest userRegisterRequest) {
if (userRegisterRequest == null)
{
return null;
}
String userAccount = userRegisterRequest.getUserAccount();
String userPassword = userRegisterRequest.getUserPassword();
String checkPassword = userRegisterRequest.getCheckPassword();
if(StringUtils.isAnyBlank(userAccount,userPassword,checkPassword)) {
return null;
}
return userService.userRegister(userAccount, userPassword, checkPassword);
}
@PostMapping("/login")
public User userLogin(@RequestBody UserLoginrequest userLoginrequest, HttpServletRequest request) {
if (userLoginrequest == null)
{
return null;
}
String userAccount = userLoginrequest.getUserAccount();
String userPassword = userLoginrequest.getUserPassword();
if(StringUtils.isAnyBlank(userAccount,userPassword)) {
return null;
}
return userService.userLogin(userAccount, userPassword, request);
}
}
我们这里需要建立封装一个请求对象,接收请求
在 model 包下建立 request 包,新增对象 UserRegisterRequest、UserLoginRequest(这里列举一个)
这里实现序列化接口(具体详细可以百度)应用较为广泛
private static final long serialVersionUID = -2406654025944442247L; 是一个序列化版本号,用于标识序列化类的版本。当类的结构发生变化时,如果没有显式指定 serialVersionUID,Java 编译器会自动生成一个版本号,如果类的结构发生了变化,反序列化时可能会导致版本不一致的问题。因此,显式地指定 serialVersionUID 可以确保在类结构发生变化时,版本号保持一致,从而避免反序列化时的问题。
@Data
public class UserRegisterRequest implements Serializable {
private static final long serialVersionUID = -2406654025944442247L;
private String userAccount;
private String userPassword;
private String checkPassword;
}
为什么在逻辑层已经做过参数校验这里控制层也要做呢?
控制层倾向于对请求参数本身的校验,不涉及业务逻辑本身
而逻辑层是对业务逻辑的校验,有可能被除了控制层以外的类调用
测试
这里可以用IDEA自带的接口测试,也可以借助其他软件Postman等
或者直接点击这个按钮也可以
POST http://localhost:8080/user/login
Content-Type: application/json
{
"userAccount": "yupi",
"userPassword": 12345678
}
可以debug 模式感受session 登录态的记录,以及数据的传递
用户管理接口
必须要鉴权!
- 查询用户
- 允许根据用户名查询
- 删除用户
这里偷懒了,直接在控制层写了,但其实合理点的话应该在逻辑层写
较为简单的查询等操作可以直接在控制层写(不规范)
@GetMapping("/search")
public List<User> searchUsers(String username, HttpServletRequest request) {
if (!isAdmin(request)) {
return new ArrayList<>();
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(username)) {
queryWrapper.like("username", username);
}
List<User> userList = userService.list();
return userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
}
@PostMapping("/delete")
public boolean deleteUser(@RequestBody Long id , HttpServletRequest request) {
if (!isAdmin(request)) {
return false;
}
if(id <= 0) {
return false;
}
return userService.removeById(id);
}
这里将相同的代码逻辑摘出来(即鉴权)
private boolean isAdmin(HttpServletRequest request) {
// 鉴权,只有管理员可以查询
Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
User user = (User) userObj;
if(user == null || user.getUserRole() != ADMIN_ROLE) {
return false;
}
return true;
}
注意:只是这么写还不行,因为这么写的话,什么人都能够调用,就很危险了
而我们此时的 user 表里面没有管理员这个字段,因此我们加一个
/**
* 用户角色 0-普通用户 1-管理员
*/
private Integer role;
注意其他相关地方都要改, 数据库相关的可以用MybatisX插件重写
由于常量过多调用,这里定义用户常量类
public interface UserConstant {
//用户登录态键
String USER_LOGIN_STATE = "userLoginState";
/**
* 用户权限
*/
int DEFAULT_ROLE = 0;//普通用户,默认权限
int ADMIN_ROLE = 1;//管理员
}
定义session 失效时间,减少记录登录态,方便测试
spring:
application:
name: user-center
datasource:
# 驱动类名称
driver-class-name: com.mysql.cj.jdbc.Driver
# 数据库连接的url
url: jdbc:mysql://localhost:3306/ania
# 连接数据库的用户名
username: root
# 连接数据库的密码
password: root
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
# session 失效时间
session:
timeout: 86400
server:
port: 8080
mybatis-plus:
configuration:
map-underscore-to-camel-case: false
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
为了避免用户密码被返回, 过滤优化代码
UserService
/**
* 用户脱敏
* @param originUser 起始用户
* @return 安全用户
*/
User getSafetyUser(User originUser);
UserServiceImpl
/**
* 用户脱敏
* @param originUser 起始用户
* @return 安全用户
*/
@Override
public User getSafetyUser(User originUser) {
User safetyUser = new User();
safetyUser.setId(originUser.getId());
safetyUser.setUsername(originUser.getUsername());
safetyUser.setUserAccount(originUser.getUserAccount());
safetyUser.setAvatarUrl(originUser.getAvatarUrl());
safetyUser.setGender(originUser.getGender());
safetyUser.setEmail(originUser.getEmail());
safetyUser.setUserRole(originUser.getUserRole());
safetyUser.setUserStatus(originUser.getUserStatus());
safetyUser.setPhone(originUser.getPhone());
safetyUser.setCreateTime(originUser.getCreateTime());
return safetyUser;
}
UserController
@GetMapping("/search")
public List<User> searchUsers(String username, HttpServletRequest request) {
if (!isAdmin(request)) {
return new ArrayList<>();
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(username)) {
queryWrapper.like("username", username);
}
List<User> userList = userService.list();
// 调用getSafetyUser方法,得到脱敏后的User也实现了返回结果过滤
return userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
}
前端
简化代码
页脚部分·
修改页脚链接,title等,换成想要的
标题,logo部分
删除不用功能(代码)
修改显示内容
增添密码长度提示
定义全局常量包constants,便于使用常量(如logo的链接,编程导航链接等)
export const SYSTEM_LOGO = "data:image/png;base64,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";
export const PLANTE_LINK = "https://wx.zsxq.com/dweb2/index/group/51122858222824"
精简后页面
对接后端
调整参数名称一致,便于进行类型检查和参数传递
这里可以Ctrl + r 全局修改
这里改成user,如果user存在的话,就显示登录成功,并且设置用户的登录状态为user
修改接口
这里返回结果不太一样,后面会再调
前端需要向后端发送请求
前端用 ajax 来请求后端,axios 封装了 ajax,request 是 ant design 项目又封装了一次
追踪 request 源码:用到了 umi 插件,requestConfig 是一个配置
按照官网的提示,我们修改 request 后面的请求地址,还有修改一下配置Ant Design Pro的官方文档,搜索请求
import { RequestConfig } from 'umi';
export const request: RequestConfig = {
timeout: 1000,
errorConfig: {},
middlewares: [],
requestInterceptors: [],
responseInterceptors: [],
errorHandler,
};
此时访问的请求地址就是
但此时后端是用 8080,我们前端使用 8000,这样就对不上,要么跨域,要么代理,后者比较简便一些
代理
正向代理:替客户端向服务器发送请求
反向代理:替服务器接收请求
怎么弄?
通过 Nginx 服务器、Node.js 服务器
后端 application.yml 指定接口全局 api
修改超时时间为10s
debug启动UserCenterApplication
后端成功拿到数据
前端也有相关响应