近期做了一个最常用的系统——用户中心系统,简单实现了用户注册、登录、查询等基础功能。整个项目流程:需求分析 => 设计(概要设计、详细设计)=> 技术选型 => 初始化 / 引入需要的技术 => 写 Demo => 写代码(实现业务逻辑) => 测试(单元测试)=> 代码提交 / 代码评审 => 部署=> 发布。本篇文章主要记录这个项目后端的实现。成品图如下:
一、需求分析:
1.登录/注册
2.用户管理(仅管理员可见)对用户的查询或者修改
3.用户校验
二、技术选型
后端:
- java
- spring(依赖注入框架,帮助你管理 Java 对象,集成一些其他的内容)
- springmvc(web 框架,提供接口访问、restful接口等能力)
- mybatis(Java 操作数据库的框架,持久层框架,对 jdbc 的封装)
- mybatis-plus(对 mybatis 的增强,不用写 sql 也能实现增删改查)
- springboot(快速启动 / 快速集成项目。不用自己管理 spring 配置,不用自己整合各种框 架)
- junit 单元测试库
- mysql
部署:服务器/容器(平台)
三、后端初始化
1.mysql前期准备
(1)下载并安装
(2)验证Mysql是否安装成功
2.初始化springboot项目(直接在IDEA开发工具中生成)
3.引入mybatis-plus
通过查看官方文档,书写 MyBatis-Plus (baomidou.com) demo 完成引入
四、数据库设计
1.用户表设计
字段 | 说明 | 类型 |
id | 主键,唯一标识 | bigint |
username | 昵称 | varchar |
userAccount | 登录账号 | varchar |
avatarUrl | 头像 | varchar |
gender | 性别 | tinyint |
userPassword | 密码 | varchar |
phone | 电话 | varchar |
邮箱 | varchar | |
userStatus | 用户状态;0-正常 | int |
userRole | 用户权限;0-普通用户、1-管理员 | int |
code | 编号 | varchar |
createTime | 创建时间(数据插入时间) | datetime |
updateTime | 更新时间 | datetime |
isDelete | 是否删除(逻辑删除) | tinyint |
2.创建用户表
(1)方式一:傻瓜式创建
(2)方式二:SQL语句
-- auto-generated definition
create table user
(
id bigint auto_increment comment 'id' primary key,
username varchar(256) null comment '用户昵称',
userAccount varchar(256) null comment '账号',
avatarUrl varchar(1024) default
'https://img1.baidu.com/it/u=1966616150,2146512490&fm=253&fmt=auto&app=138&f=JPE
G?w=751&h=500' 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 comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除(逻辑删除)',
userRole int default 0 not null comment '用户权限 0 - 普通用户 1 - 管理员',
code varchar(512) null comment '编号'
);
五、登录功能
1.实现基本数据库操作(操作user表)
(1)模型user对象=>和数据库的字段关联,自动生成
使用插件mybatisX:MyBatisX 插件,自动根据数据库生成:
- domain 实体对象
- mapper(操作数据库的对象)
- mapper.xml(定义了 mapper对象和数据库的关联,可以在里面自己写 SQL)
- service(包含常用的增删改查)
- serviceImpl(具体实现 service)
步骤:
tips:在配置文件 application.yml 中添加如下配置:
# mybatis-plus的配置
mybatis-plus:
configuration:
# 解决 mybatis-plus 框架中 “ Unknown column 'user_account' in 'field list'”
map-underscore-to-camel-case: false
(2)测试mybatisX生成的代码;
package com.example.backend.service;
import java.util.Date;
import com.example.backend.model.domain.User;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
/*
* 用户服务测试
* */
@SpringBootTest
class UserServiceTest {
@Resource
private UserService userService;
@Test
public void testAddUser(){
User user=new User();
user.setUsername("");
user.setUserAccount("wangP");
user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png");
user.setGender(0);
user.setUserPassword("12345678");
user.setPhone("123");
user.setEmail("456");
user.setUserRole(1);
boolean result = userService.save(user);
System.out.println(user.getId());
assertTrue(result);
}
}
2.登录逻辑
(1)校验用户账户和密码是否合法
- 非空
- 账户长度 不小于 4 位
- 密码就 不小于 8 位吧
- 账户不包含特殊字符
(2)校验密码是否输入正确,要和数据库中的密文密码去对比
(3)用户信息脱敏,隐藏敏感信息,防止数据库中的字段泄露
(4)我们要记录用户的登录态(session),将其存到服务器上(用后端 SpringBoot 框架封装的服务器 tomcat 去记录)cookie
(5) 返回脱敏后的用户信息
3.代码实现:
/**
* 用户登录
* @param userAccount 用户账号
* @param userPassword 用户密码
* @param request
* @return
*/
@Override
public User userLogin(@RequestBody String userAccount, String userPassword, HttpServletRequest request) {
//1.校验
if(StringUtils.isAnyBlank(userAccount,userPassword)){
return null;
}
//2.校验账户
if(userAccount.length()<4){
return null;
}
if(userPassword.length()<8 ){
return null;
}
//账户不能包含特殊字符
String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
if (matcher.find()) {
return null;
}
//加密
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;
}
//用户脱敏
User safetyUser=getSafetyUser(user);
//记录用户登录态
request.getSession().setAttribute(USER_LOGIN_STATE,safetyUser);
return safetyUser;
}
4.问题以及解决方案;
问题:
以上第二步检验密码是否正确时,查询数据库时,会把逻辑删除的用户数据也一并查出 来,所以需要对 mybatis-plus 框架进行设置
解决:
-
在 application.yml 配置文件中配置,可避免这种情况出现
mybatis-plus:
global-config:
db-config:
logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
-
实体类字段上加上@TableLogic注解
@TableLogic
private Integer isDelete;
5.如何知道是哪个用户登录了?
(1)连接服务器端后,得到一个 session 状态(匿名会话),返回给前端
(2)登录成功后,得到了登录成功的 session,并且给该session设置一些值(比如用户信息),返回给 前端一个设置 cookie 的 ”命令“ session => cookie
(3)前端接收到后端的命令后,设置 cookie,保存到浏览器内
(4)前端再次请求后端的时候(相同的域名),在请求头中带上cookie去请求
(5)后端拿到前端传来的 cookie,找到对应的 session
(6)后端从 session 中可以取出基于该 session 存储的变量(用户的登录信息、登录名)
6.控制层Controller封装请求
application.yml 指定接口全局 api
servlet:
context-path: /api
六、注册功能
1.注册逻辑:
(1)校验用户的账户、密码、校验密码,是否符合要求
- 非空
- 账户长度 不小于 4 位
- 密码就 不小于 8 位
- 账户不能重复
- 账户不包含特殊字符
- 密码和校验密码相同
(2)对密码进行加密(密码千万不要直接以明文存储到数据库中)
(3)向数据库插入用户数据
2.代码实现:
/**
* 用户注册
* @param userAccount 用户账号
* @param userPassword 用户密码
* @param checkPassword 检验密码
* @param planetCode 星球编号
* @return
*/
@Override
public long userRegister(@RequestBody String userAccount, String userPassword, String checkPassword,String planetCode) {
//1.校验
if(StringUtils.isAnyBlank(userAccount,userPassword,checkPassword,planetCode)){
throw new BusinessException(ErrorCode.PARAMS_ERROR,"参数为空");
}
//2.校验账户
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, "编号重复");
}
//加密
String encryptPassword = DigestUtils.md5DigestAsHex((salt + userPassword).getBytes());
//插入数据
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();
}
七、用户管理(仅管理员可见)
- 接口设计关键:必须鉴权(允许根据用户名查询、删除用户)
- controller层:
/**
* 管理员查询
* @param username 用户昵称
* @param request springboot内置请求对象,用于存储用户session
* @return 查到得到的所有用户信息
*/
@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.isNoneBlank(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);
}
/**
* 管理员删除
* @param id 用户id
* @param request springboot内置请求对象,用于存储用户session
* @return 是否删除用户,ture表示删除;false表示删除失败
*/
@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);
}
八、后端添加获取当前用户信息的接口
- controller 层
/**
* 获取当前用户
* @param request
* @return
*/
@GetMapping("/current")
public BaseResponse<User> getCurrentUser(HttpServletRequest request) {
Object useObj = request.getSession().getAttribute(USER_LOGIN_STATE);
User currentUser = (User) useObj;
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);
}
九、用户退出登录
- controller 层
/**
* 用户注销
* @param request
* @return
*/
@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);
}
- sevice 层
/**
*用户注销
* @param request
* @return
*/
@Override
public int userLogout(HttpServletRequest request) {
//用户注销
request.getSession().removeAttribute(USER_LOGIN_STATE);
return 1;
}
}
十、部署和上线
1.多环境的简介以及作用:
- 多环境::指同一套项目代码在不同的阶段需要根据实际情况来调整配置并且部署到不同的机器上。
- 为什么需要:
- 每个环境互不影响
- 区分不同阶段:开发/测试/生存
- 对项目进行优化
2.多环境分类:
- 本地环境(自己的电脑)localhost
- 开发环境(远程开发)大家连同一台机器,为了大家开发方便
- 测试环境(测试)开发 / 测试 / 产品,单元测试 / 性能测试 / 功能测试 / 系统集成测试,独立的数 据库、独立的服务器
- 预发布环境(体验服):和正式环境一致,正式数据库,更严谨,查出更多问题
- 正式环境(线上,公开对外访问的项目):尽量不要改动,保证上线前的代码是 “完美” 运行
- 沙箱环境(实验环境):为了做实验
3.后端多环境实战:
- SpringBoot 项目,通过 application.yml 添加不同的后缀来区分配置文件
- 开发环境:application-prod.yml
- 生产环境:application-dev.yml
- 可以在启动项目时传入环境变量:
java -jar .\user-center-backend-0.0.1-SNAPSHOT.jar -- spring.profiles.active=prod
- 主要是改:
- 依赖的环境地址
- 数据库地址
- 缓存地址
- 消息队列地址
- 项目端口号
- 依赖的环境地址
- 服务器配置
4.部署:
- java、maven
- 安装
# 安装 java8
yum install -y java-1.8.0-openjdk*
# 安装 maven
curl -o apache-maven-3.8.5-bin.tar.gz https://dlcdn.apache.org/maven/maven 3/3.8.5/binaries/apache-maven-3.8.5-bin.tar.gz
# 下载源码 git clone xxx 下载代码
# 使用 maven 工具打包项目(跳过测试)
打包构建,跳过测试
mvn package -DskipTests
# 运行 jar 包 java -jar ./user-center-backend-0.0.1-SNAPSHOT.jar --server.port=8080 -- spring.profiles.active=prod
# 后台运行 jar 包
nohup java -jar ./user-center-backend-0.0.1-SNAPSHOT.jar --server.port=8080 -- spring.profiles.active=prod &
# 使用 jobs 查看用 nohup 运行的项目 jobs