Sky-User-Center开发笔记

初始化项目

1、前端初始化

  1. 下载nodejs

  2. 下载npm和yarn

  3. 开箱即用的中台前端/设计解决方案 - Ant Design Pro,根据文档快速初始化,注意选择简单脚手架(simple)

  4. 运行项目

    登录界面如图:

    image-20230302190745831

    登录成功如图:

image-20230302191010574
  1. 安装Umi UI

    这个工具可以帮助我们快速生成页面

    1
    yarn

    等待安装成功重启项目,发现页面右下角多了这个图标

image-20230302192605487

  1. 点击这个图标,生成分析页面

    image-20230302193042687

​ 等待编译下载完成,我们就能得到一个分析页面

image-20230302194212642
  1. 项目简化(去除不需要的部分)

    • 去除国际化

      运行”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 '用户';

登录、注册的逻辑

注册逻辑

  1. 用户在前端界面输入账户和密码、校验码(可能是手机验证码TODO)
  2. 校验用户的账户、输入的密码和二次输入的密码是否符合要求
    1. 非空
    2. 账户长度不小于4位
    3. 密码不小于8位并且不能输入空格
    4. 账户不能重复
    5. 账户不包含特殊字符
    6. 密码和二次输入密码保持一致
  3. 对密码进行加密(数据库保存加密后的密码,而不是明文密码
  4. 向数据库插入用户数据

登录逻辑

  1. 校验

    1. 非空
    2. 账户长度不小于4位
    3. 密码不小于8位并且不能输入空格
    4. 账户不包含特殊字符
  2. 校验密码是否输入正确,要和数据库中的密文密码去进行对比

  3. 用户信息脱敏,隐藏敏感信息,防止数据库中的字段泄露

  4. 将用户的登录态(一般位session),将其存储在服务器上

    1
    2
    3
    4
    5
    6
    7
    @Override
    public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
    ...
    //4.记录用户的登录态
    request.getSession().setAttribute(USER_LOGIN_STATE,safetyUser);
    ...
    }
  5. 返回脱敏后的用户信息

用户管理

用户管理仅允许管理员操作,所以务必鉴权!!!

  1. 用户查询
    1. 根据用户名查询
  2. 用户删除
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 是否为管理员
*
* @param request
* @return 是或者不是管理员
*/
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

前后端交互

修整

根据自身需求删减和修改模板的代码

对接后台的接口

找到该文件

image-20230308000151748

定位到 handleSubmit 方法的代码

image-20230308000244451

点击LoginParams会跳转到app.tsx的以下代码:

根据后端的参数名称修改这些字段(注意要使用全局修改!!!

1
2
3
4
5
6
type LoginParams = {
userAccount?: string;
userPassword?: string;
autoLogin?: boolean;
type?: string;
};

接着回到 handleSubmit 方法

找到如图位置,并根据后端的参数名称修改字段,并且根据需要增加校验规则

image-20230308004235516 image-20230308004319099

定位到 login 方法,修改红框处的 url 为后端接口的地址

image-20230308000824801

AntDesign Pro提供了配置代理的方式,这里我们使用这种方式

image-20230308004500773

在完成上述修改后,请求的地址就变成了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);
/*用户的数据可能会发生改变,所以不能直接返回从session取到的user
1. 可以利用缓存更新更新用户的数据,从缓存中去取
2.直接查库(这里选用这种方式
*/
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文件添加一个路由

image-20230310161520724

添加路由之后尝试访问发现无权限,原因是该地址访问有权限管理,观察发现有access:'canAdmin',即管理员才能访问

找到access.ts文件,将判断管理员的逻辑改成我们设置的逻辑

image-20230310161929226

然后,我们在UserManage文件编写代码发现前端并不能正确显示,而是固定显示某个页面,经过排查发现,问题是在component:'./Admin'

Admin.tsx内的文本内容就是页面展示的内容,这说明这个页面写死了,我们修改一下,

image-20230310162448822

终于,我们在UserManage文件编写的代码能够正确显示出来了,

编写管理页面

到官方文档使用使用现有的高级表格ProTable - 高级表格 - Pro Components (ant.design)

参考官方文档修改代码,效果如图:

image-20230310163150236

后端优化

(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
/**
* 通用返回类
* @param <T>
*/
@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 {

/**
* 成功
* @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);
}
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);
}
}

封装全局异常处理器

  1. 定义业务异常类

    1. 相对于 java 的异常类,支持更多字段
    2. 自定义构造函数,更灵活 / 快捷的设置字段
  2. 编写全局异常处理器(利用 Spring AOP,在调用方法前后进行额外的处理)

作用

  1. 捕获代码中所有的异常,内部消化,让前端得到更详细的业务报错 / 信息
  2. 同时屏蔽掉项目框架本身的异常(不暴露服务器内部状态)
  3. 集中处理,比如记录日志
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 的文档。

创建新的文件,在该文件中配置一个全局请求类。在发送请求时,使用我们自己的定义的全局请求类。