Sky-User-Center开发笔记
初始化项目
1、前端初始化
下载nodejs
下载npm和yarn
开箱即用的中台前端/设计解决方案 - Ant Design Pro,根据文档快速初始化,注意选择简单脚手架(simple)
运行项目
登录界面如图:
登录成功如图:
安装Umi UI
这个工具可以帮助我们快速生成页面
等待安装成功重启项目,发现页面右下角多了这个图标
点击这个图标,生成分析页面
等待编译下载完成,我们就能得到一个分析页面
项目简化(去除不需要的部分)
去除国际化
运行”i18n-remove”脚本
其他根据自身需求删除
2、后端初始化
springboot初始化、maven引入pom依赖、编写yml文件;
pom.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.9</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.twintea</groupId> <artifactId>user-center</artifactId> <version>0.0.1-SNAPSHOT</version> <name>user-center</name> <description>user-center</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.0</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build>
</project>
|
数据库设计
根据实际需求,
- 设计表
- 表的字段
- 字段的类型
- 是否需要给字段添加索引
- 是否需要设计表与表之间的关联
建表语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| create table user ( id bigint auto_increment comment '主键' primary key, user_name varchar(256) null comment '用户名', user_account varchar(256) null comment '用户账号', avatar_url varchar(1024) null comment '用户头像地址', gender tinyint null comment '性别', user_password varchar(512) null comment '用户密码', email varchar(512) null comment '邮箱', user_status int default 0 null comment '用户状态 0-正常', phone varchar(128) null comment '用户手机号码', create_time datetime default CURRENT_TIMESTAMP not null comment '用户创建时间', update_time datetime default CURRENT_TIMESTAMP not null comment '用户更新时间', is_delete tinyint default 0 not null comment '是否被删除 0-正常' user_role int default 0 not null comment '用户角色 0-默认 1-管理员' ) comment '用户';
|
登录、注册的逻辑
注册逻辑
- 用户在前端界面输入账户和密码、校验码(可能是手机验证码TODO)
- 校验用户的账户、输入的密码和二次输入的密码是否符合要求
- 非空
- 账户长度不小于4位
- 密码不小于8位并且不能输入空格
- 账户不能重复
- 账户不包含特殊字符
- 密码和二次输入密码保持一致
- 对密码进行加密(数据库保存加密后的密码,而不是明文密码
- 向数据库插入用户数据
登录逻辑
校验
- 非空
- 账户长度不小于4位
- 密码不小于8位并且不能输入空格
- 账户不包含特殊字符
校验密码是否输入正确,要和数据库中的密文密码去进行对比
用户信息脱敏,隐藏敏感信息,防止数据库中的字段泄露
将用户的登录态(一般位session),将其存储在服务器上
1 2 3 4 5 6 7
| @Override public User userLogin(String userAccount, String userPassword, HttpServletRequest request) { ... request.getSession().setAttribute(USER_LOGIN_STATE,safetyUser); ... }
|
返回脱敏后的用户信息
用户管理
用户管理仅允许管理员操作,所以务必鉴权!!!
- 用户查询
- 根据用户名查询
- 用户删除
1 2 3 4 5 6 7 8 9 10 11 12 13
|
private Boolean isAdmin(HttpServletRequest request) { User user = (User) request.getSession().getAttribute(USER_LOGIN_STATE); if (user == null || user.getUserRole() != ADMIN_ROLE) { return false; } return true; }
|
application.yml指定接口全局api
1 2 3 4
| server: port: 8080 servlet: context-path: /api
|
前后端交互
修整
根据自身需求删减和修改模板的代码
对接后台的接口
找到该文件
定位到 handleSubmit 方法的代码
点击LoginParams会跳转到app.tsx的以下代码:
根据后端的参数名称修改这些字段(注意要使用全局修改!!!
1 2 3 4 5 6
| type LoginParams = { userAccount?: string; userPassword?: string; autoLogin?: boolean; type?: string; };
|
接着回到 handleSubmit 方法
找到如图位置,并根据后端的参数名称修改字段,并且根据需要增加校验规则
定位到 login 方法,修改红框处的 url 为后端接口的地址
AntDesign Pro提供了配置代理的方式,这里我们使用这种方式
在完成上述修改后,请求的地址就变成了http://localhost:8000/api/user/login,
请求的地址通过AntDesign Pro提供的代理自动代理到 http://localhost:8080/api/user/login
注册页面也采用类似方法完成
获取当前用户登录态、信息接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @GetMapping("/current") public BaseResponse<User> getCurrentUser(HttpServletRequest request) { User user = (User) request.getSession().getAttribute(USER_LOGIN_STATE);
if (user == null){ throw new BusinessException(ErrorCode.NO_LOGIN); } User currentUser = userService.getById(user.getId()); if (!Objects.equals(currentUser.getUserStatus(), VALID_STATUS)){ throw new BusinessException(ErrorCode.INVALID_ACCOUNT_ERROR); } User currentSafetyUser = userService.getSafetyUser(currentUser); return ResultUtils.success(currentSafetyUser); }
|
用户管理后台的前端开发
新建管理页面
在page文件夹下新建一个Admin文件,新建一个UserManage文件,到route.ts文件添加一个路由
添加路由之后尝试访问发现无权限,原因是该地址访问有权限管理,观察发现有access:'canAdmin'
,即管理员才能访问
找到access.ts
文件,将判断管理员的逻辑改成我们设置的逻辑
然后,我们在UserManage
文件编写代码发现前端并不能正确显示,而是固定显示某个页面,经过排查发现,问题是在component:'./Admin'
Admin.tsx
内的文本内容就是页面展示的内容,这说明这个页面写死了,我们修改一下,
终于,我们在UserManage
文件编写的代码能够正确显示出来了,
编写管理页面
到官方文档使用使用现有的高级表格ProTable - 高级表格 - Pro Components (ant.design)
参考官方文档修改代码,效果如图:
后端优化
(todo 用户校验)
通用返回对象
目的:给对象补充一些信息,告诉前端这个请求在业务层面上是成功还是失败
200、404、500、502、503
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "code": 0 "data": { "name": "twintea" }, "message": "ok" }
{ "code": 50001 "data": null "message": "用户操作异常、xxx" }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
@Data public class BaseResponse<T> implements Serializable { private static final long serialVersionUID = -27627570252259361L;
private int code;
private T data;
private String msg;
private String description;
public BaseResponse(int code, T data, String msg,String description) { this.code = code; this.data = data; this.msg = msg; this.description = description; } public BaseResponse(int code, T data, String msg){ this.code = code; this.data = data; this.msg = msg; } public BaseResponse(int code, T data) { this(code, data, "",""); }
public BaseResponse(ErrorCode errorCode){ this(errorCode.getCode(),null,errorCode.getMsg(),errorCode.getDescription()); }
}
|
自定义错误码,返回类支持返回正常和错误
前端的HTTP状态码默认的值比较少,不够精准
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Getter public enum ErrorCode { SUCCESS(0, "ok", ""), PARAMS_ERROR(40000, "请求参数错误", "请求参数错误"), NULL_ERROR(40001, "请求数据为空", "请求数据为空"), NO_LOGIN(40100, "未登录", "未登录"), NO_AUTH(40100, "无权限", "无权限"), SYSTEM_ERROR(50000, "系统内部异常", "系统内部异常"), INVALID_ACCOUNT_ERROR(40300, "账号异常,请联系管理员", "账号异常,请联系管理员");
private final int code; private final String msg; private final String description;
ErrorCode(int code, String msg, String description) { this.code = code; this.msg = msg; this.description = description; } }
|
自定义返回工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public class ResultUtils {
public static <T> BaseResponse<T> success(T data){ return new BaseResponse<>(0,data,"ok",""); }
public static BaseResponse error(ErrorCode errorCode){ return new BaseResponse(errorCode); } public static BaseResponse error(int code,String msg,String descpriton){ return new BaseResponse(code,null,msg,descpriton); }
public static BaseResponse error(ErrorCode errorCode,String msg,String descpriton){ return new BaseResponse(errorCode.getCode(),null,msg,descpriton); } public static BaseResponse error(ErrorCode errorCode,String descpriton){ return new BaseResponse(errorCode.getCode(),null,errorCode.getMsg(),descpriton); } }
|
封装全局异常处理器
定义业务异常类
- 相对于 java 的异常类,支持更多字段
- 自定义构造函数,更灵活 / 快捷的设置字段
编写全局异常处理器(利用 Spring AOP,在调用方法前后进行额外的处理)
作用
- 捕获代码中所有的异常,内部消化,让前端得到更详细的业务报错 / 信息
- 同时屏蔽掉项目框架本身的异常(不暴露服务器内部状态)
- 集中处理,比如记录日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
@Getter public class BusinessException extends RuntimeException{
private final int code; private final String descriptionn;
public BusinessException(String message, int code, String descriptionn) { super(message); this.code = code; this.descriptionn = descriptionn; }
public BusinessException(ErrorCode errorCode) { super(errorCode.getMsg()); this.code = errorCode.getCode(); this.descriptionn = errorCode.getDescription(); }
public BusinessException(ErrorCode errorCode,String descriptionn) { super(errorCode.getMsg()); this.code = errorCode.getCode(); this.descriptionn = descriptionn; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class) public BaseResponse businessExceptionHandler(BusinessException e){ log.error("businessException: "+e.getMessage(),e); return ResultUtils.error(e.getCode(),e.getMessage(), e.getDescriptionn()); }
@ExceptionHandler(RuntimeException.class) public BaseResponse runtimeException(RuntimeException e){ log.error("runtimeException",e); return ResultUtils.error(ErrorCode.SYSTEM_ERROR,e.getMessage(),"系统内部错误"); } }
|
前端优化
全局响应处理
应用场景:我们需要对接口的 通用响应 进行统一处理,比如从 response 中取出 data;或者根据 code 去集中处理错误,比如用户未登录、没权限之类的。
优势:不用在每个接口请求中都去写相同的逻辑
实现:参考你用的请求封装工具的官方文档,比如 umi-request(https://github.com/umijs/umi-request#interceptor、https://blog.csdn.net/huantai3334/article/details/116780020)。如果你用 axios,参考 axios 的文档。
创建新的文件,在该文件中配置一个全局请求类。在发送请求时,使用我们自己的定义的全局请求类。