Memory 用户中心中心-开发文档

本文最后更新于:1 年前

让我们以一颗平和的心,去面对生活的起起伏伏,因为内心的宁静才是最大的力量。

前言

  • 本文档记录了用户中心开发的基本流程, 包含了前期的框架搭建、数据库设计,以及后期的后端接口实现、前端页面开发,实现了基础的登录注册功能,以及查看在线用户状态和删除用户的功能(管理员)

开发项目前的分析

企业做项目流程

1
2
3
需求分析 => 设计(概要设计、详细设计)=> 技术选型 => 
初始化 / 引⼊需要的技术 => 写 Demo => 写代码(实现业务逻辑) =>
测试(单元测试)=> 代码提 交 / 代码评审 => 部署=> 发布

需求分析

  1. 登录 / 注册
  2. ⽤户管理(仅管理员可⻅)对⽤户的查询或者修改
  3. ⽤户校验( 仅星球⽤户 )

技术选型

前端:

三件套 + React + 组件库 Ant Design + Umi + Ant Design Pro(现成的管理系统)

后端:
  • java
  • spring(依赖注⼊框架,帮助你管理 Java 对象,集成⼀些其他的内容)
  • springmvc(web 框架,提供接⼝访问、restful接⼝等能⼒)
  • mybatis(Java 操作数据库的框架,持久层框架,对 jdbc 的封装)
  • mybatis-plus(对 mybatis 的增强,不⽤写 sql 也能实现增删改查)
  • springboot(快速启动 / 快速集成项⽬。不⽤⾃⼰管理 spring 配置,不⽤⾃⼰整合各种框架)
  • junit 单元测试库
  • mysql

部署:服务器 / 容器(平台)

框架搭建

前端框架搭建

搭建AntDesignPro脚手架

后端框架搭建

数据库设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
create table user
(
id bigint not null comment 'id' primary key,
user_account varchar(256) null comment '账号',
username varchar(256) null comment '昵称',
user_password varchar(128) not null comment '密码',
avatar_url varchar(512) null comment '头像',
gender varchar(128) default '0' null comment '邮箱',
phone varchar(128) null comment '电话',
email varchar(128) null comment '邮箱',
user_status int default 0 not null comment '状态 0 - 正常',
create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
update_time datetime default CURRENT_TIMESTAMP not null comment '更新时间',
is_delete tinyint default 0 null comment '是否删除 0 - 正常',
user_role int(1) default 0 not null comment '用户权限 0 - 管理员 1 - 普通用户',
planet_code varchar(512) not null comment '星球编号'
)
comment '用户';
  • MybatisX插件快速生成domain entity service 以及 serviceImpl
    • 选择生成目录

    image-20230324211359229

    • 生成选项

    image-20230324211329077

  • application.yaml 项目配置文件
    • 连接到数据库
    1
    2
    3
    4
    5
    6
    # DataSource Config
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/memory
    username: root
    password: Dw990831
    • 项目名称
    1
    2
    3
    4
    spring:
    # Project name
    application:
    name: user-center
    • 项目端口 | 项目所有API路径上下文
    1
    2
    3
    4
    5
    # 端口
    server:
    port: 8081
    servlet:
    context-path: /api
    • 逻辑删除(MybatisPlus提供)
    1
    2
    3
    4
    5
    6
    7
    # 逻辑删除
    mybatis-plus:
    global-config:
    db-config:
    logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
    logic-delete-value: 1 # 逻辑已删除值(默认为 1)
    logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    • session
    1
    2
    3
      # session存活时间 一天
    session:
    timeout: 86640

后端开发

Service层

登录+校验

1
User userLogin(String userAccount, String userPassword, HttpServletRequest request);
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
@Override
public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
// 1.校验
// 1.1.账户, 密码不能为空
if (StringUtils.isAnyBlank(userAccount, userPassword))
return null;

// 1.2.账户不小于4位
if (userAccount.length() < 4)
return null;

// 1.3.用户密码不小于8位
if (userPassword.length() < 8)
return null;

// 1.4.账户不包含特殊字符
String pattern = ".*[\\s`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?\\\\]+.*";
if (Pattern.matches(pattern, userAccount))
return null;

// 1.5.检验该用户是否注册
User user = new User();

user.setUserAccount(userAccount);

String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
user.setUserPassword(encryptPassword);

QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("user_account", userAccount).eq("user_password", encryptPassword);
User one = this.getOne(qw);

// 1.5.1.用户未注册(包含了MP自带的逻辑删除校验)
if (one == null)
return null;

// 2.脱敏用户信息
User safetyUser = getSafetyUser(one);

// 3.记录用户登录态
request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);

// 4.返回用户信息
return safetyUser;
}

注册+校验

1
long userRegister(String userAccount, String userPassword, String checkPassword);
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
/**
* 用户注册
*
* @param userAccount 账户
* @param userPassword 密码
* @param checkPassword 二次密码
* @return 用户id
*/
@Override
public long userRegister(String userAccount, String userPassword, String checkPassword) {
// 1.校验
// 1.1.账户, 密码, 二次密码不能为空
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword))
return -1;

// 1.2.账户不小于4位
if (userAccount.length() < 4)
return -1;

// 1.3.账户不能重复
QueryWrapper<User> lqw = new QueryWrapper<>(); // LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
lqw.eq("user_account", userAccount); // userLambdaQueryWrapper.eq(User::getUserAccount, userAccount);
Long count = userMapper.selectCount(lqw); // long count = this.count(lqw);

if (count > 0)
return -1;

// 1.4.用户密码不小于8位
if (userPassword.length() < 8)
return -1;

// 1.5.账户不包含特殊字符
String pattern = ".*[\\s`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?\\\\]+.*";
if (Pattern.matches(pattern, userAccount))
return -1;

// 1.6.二次密码与密码相同
if (!userPassword.equals(checkPassword))
return -1;


// 2.对密码进行加密
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
// System.out.println(encryptPassword);


// 3.向数据库中插入用户数据
User user = new User();
//
user.setUserAccount(userAccount);
user.setUserPassword(encryptPassword);
boolean save = this.save(user);
//插入失败
if (!save)
return -1;

return user.getId();
}

封装脱敏用户信息

1
User getSafetyUser(User originUser);
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
/**
* 用户信息脱敏
*
* @param originUser 原始用户
* @return 脱敏后的用户
*/
public User getSafetyUser(User originUser) {
if (originUser == null)
return null;

User safetyUser = new User();
safetyUser.setId(originUser.getId());
safetyUser.setUserAccount(originUser.getUserAccount());
safetyUser.setUsername(originUser.getUsername());
safetyUser.setAvatarUrl(originUser.getAvatarUrl());
safetyUser.setGender(originUser.getGender());
safetyUser.setPhone(originUser.getPhone());
safetyUser.setEmail(originUser.getEmail());
safetyUser.setUserStatus(originUser.getUserStatus());
safetyUser.setCreateTime(originUser.getCreateTime());
safetyUser.setIsDelete(originUser.getIsDelete());
safetyUser.setUserRole(originUser.getUserRole());

return safetyUser;
}
Controller层

登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 用户登录
*
* @param userLoginRequest 登录信息封装
* @param request request
* @return User
*/
@PostMapping("/login")
public User userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
String userAccount = userLoginRequest.getUserAccount();
String userPassword = userLoginRequest.getUserPassword();
//controller对参数的校验
if (StringUtils.isAnyBlank(userAccount, userPassword))
return null;

return userService.userLogin(userAccount, userPassword, request);
}

注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 用户注册
*
* @param userRegisterRequest 注册信息封装类
* @return id
*/
@PostMapping("/register")
public Long userRegister(@RequestBody UserRegisterRequest userRegisterRequest) {
String userAccount = userRegisterRequest.getUserAccount();
String userPassword = userRegisterRequest.getUserPassword();
String checkPassword = userRegisterRequest.getCheckPassword();
//controller对参数的校验
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword))
return null;

return userService.userRegister(userAccount, userPassword, checkPassword);
}

查询用户+权限校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 查询用户
*
* @param username 用户名
* @return 查到的用户
*/
@GetMapping("/search")
public List<User> userSearch(String username, HttpServletRequest request) {
// 1.校验权限
if (!isAdmin(request))
return new ArrayList<>();
// 2.判空, 默认查询全部
QueryWrapper<User> qw = new QueryWrapper<>();
if (StringUtils.isNotBlank(username))
qw.like("username", username);
// 3.查询
List<User> userList = userService.list(qw);
// 4.返回脱敏的用户信息
return userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
}

删除用户+权限校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 删除用户
*
* @param id 用户id
* @return true/false
*/
@DeleteMapping("/delete")
public Boolean userDelete(Long id, HttpServletRequest request) {
// 1.校验权限
if (!isAdmin(request))
return false;

if (id <= 0)
return false;

// 2.删除用户(只要配置MP的逻辑删除的话, 该删除为逻辑删除)
return userService.removeById(id);
}

获取用户登录态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 查询用户
*
* @param username 用户名
* @return 查到的用户
*/
@GetMapping("/search")
public List<User> userSearch(String username, HttpServletRequest request) {
// 1.校验权限
if (!isAdmin(request))
return new ArrayList<>();
// 2.判空, 默认查询全部
QueryWrapper<User> qw = new QueryWrapper<>();
if (StringUtils.isNotBlank(username))
qw.like("username", username);
// 3.查询
List<User> userList = userService.list(qw);
// 4.返回脱敏的用户信息
return userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
}

封装校验管理员逻辑

1
2
3
4
5
6
7
8
9
10
11
/**
* 校验是否为管理员
*
* @param request request
* @return 校验成功与否
*/
public Boolean isAdmin(HttpServletRequest request) {
//校验是否为管理员
User user = (User) request.getSession().getAttribute(USER_LOGIN_STATE);
return user != null && user.getUserRole() == ADMIN_ROLE;
}
constant层

封装常量

1
2
3
4
5
6
//登录用户session Key
public static final String USER_LOGIN_STATE = "userLoginState";
//管理员权限
public static final int ADMIN_ROLE = 1;
//普通用户权限
public static final int DEFAULT_ROLE = 1;
model/request层

封装login/register实体接收类

1
2
3
4
5
@Data
public class UserLoginRequest {
private String userAccount;
private String userPassword;
}
1
2
3
4
5
6
@Data
public class UserRegisterRequest {
private String userAccount;
private String userPassword;
private String checkPassword;
}

前端开发

修改登录页面

  • 熟悉登录流程 请求地址 返回数据

    1
    2
    3
    4
    5
    // 登录
    const user = await login({
    ...values,
    type,
    });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /** 登录接口 POST /api/login/account */
    export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
    return request<API.LoginResult>('/api/user/login', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    },
    data: body,
    ...(options || {}),
    });
    }
  • 登录表单的校验逻辑(账号 密码)

  • 登录校验 成功则提示登录成功 重定向到welcome页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 登录成功
    if (user) {
    const defaultLoginSuccessMessage = '登录成功!';
    message.success(defaultLoginSuccessMessage);
    await fetchUserInfo();
    /** 此方法会跳转到 redirect 参数所在的位置 */
    if (!history) return;
    const {query} = history.location;
    const {redirect} = query as {
    redirect: string;
    };
    // { path: '/', redirect: '/welcome' },
    // 跳转到欢迎页面
    history.push(redirect || '/');
    return;
    }
  • 登录校验 失败则提示登录失败

    1
    2
    const defaultLoginFailureMessage = '登录失败,请重试!';
    message.error(defaultLoginFailureMessage);

开发注册页面

  • 登录页的复制粘贴

  • 路由的理解 设置注册页的路由

    1
    { name: '注册', path: '/user/register', component: './user/Register' },
  • 注册表单的校验逻辑(账号 密码)

  • 熟悉注册流程 请求地址 返回数据

    1
    2
    3
    4
    5
    // 发起请求
    const id = await register({
    ...values,
    type,
    });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /** 注册接口 POST /api/login/account */
    export async function register(body: API.LoginParams, options?: { [key: string]: any }) {
    return request<API.LoginResult>('/api/user/register', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    },
    data: body,
    ...(options || {}),
    });
    }
  • 解决访问注册页面 重定向到Login问题 业务逻辑 白名单内, 无需重定向到登录页

    1
    2
    3
    4
    5
    6
    7
    //白名单内无需重定向
    if (NO_NEED_LOGIN_WHITE_LIST.includes(location.pathname))
    return;
    // 白名单外, 如果没有登录, 重定向到 login
    if (!initialState?.currentUser) {
    history.push(loginPath);
    }
  • 注册成功 提示”注册成功” 跳转登录页面

    1
    2
    3
    4
    5
    history.push({
    pathname: 'user/login',
    query,
    })
    跳转到登录页面
  • 注册失败 提示”注册失败”

    1
    2
    const defaultLoginFailureMessage = '注册失败,请重试!';
    message.error(defaultLoginFailureMessage);
  • “登录” 修改为 “注册” (了解源码)

    1
    2
    3
    4
    5
    submitter={{
    searchConfig: {
    submitText: '注册'
    }
    }}
  • 添加注册校验 简单的逻辑 根据返回的数据 解构出密码和二次密码 判断二者是否相等

    1
    2
    3
    4
    5
    6
    const {userPassword, checkPassword} = values;
    //校验
    if (userPassword != checkPassword) {
    message.error('两次输入的密码不一致!')
    return;
    }
  • 添加登录页跳转到注册页的链接”新用户注册” 仿照 “忘记密码”

    1
    2
    3
    4
    5
    6
    7
    <a
    href="/user/register"
    target="_blank"
    rel="noreferrer"
    >
    新用户注册
    </a>

获取当前用户登录态

后端实现接口

返回当前用户的当前信息(重新查询过数据库)

1
2
3
4
5
6
7
8
9
10
@GetMapping("/currentUser")
public User getCurrentUser(HttpServletRequest request) {
User currentUser = (User) request.getSession().getAttribute(USER_LOGIN_STATE);
if (currentUser == null)
return null;

Long id = currentUser.getId();
// 查询数据库, 获取最新信息, 而非登录时记录的信息
return userService.getById(id);
}

前端获取用户登录态

  • app.tsx 前端服务入口 每次打开页面, 都会执行查询
1
2
// 查询用户登录态信息
return await queryCurrentUser();
1
2
3
4
5
// 请求
return request<API.CurrentUser>('/api/user/currentUser', {
method: 'GET',
...(options || {}),
});
  • 修改CurrentUser, 将返回的字段全部修改为对应数据库中的字段
1
2
3
4
5
6
7
8
9
10
11
type CurrentUser = {
id?: number;
userAccount?: string;
username?: string;
avatarUtil?: string;
gender?: string;
phone?: string;
email?: string;
userStatus?: string;
userRole?: string;
};
  • 设置白名单, 登录注册页面不会返回查询到的用户登录态, 其余页面会返回查询到的用户登录态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 白名单内(登陆注册), 不返回用户登录态信息
if (NO_NEED_LOGIN_WHITE_LIST.includes(history.location.pathname)) {
return {
fetchUserInfo,
settings: defaultSettings,
};
}
// 登陆注册后, 页面返回用户登录态信息
const currentUser = await fetchUserInfo();
return {
fetchUserInfo,
currentUser,
settings: defaultSettings,
};

开发欢迎页面

  • 设置欢迎页面的水印 头像
1
2
3
4
waterMarkProps: {
// 添加水印
content: initialState?.currentUser?.username, // 以用户昵称作为水印
},
  • 头像的话在/src/components/RightContext/AvatarDropdown.tsx里有个引用
1
<Avatar size="small" className={styles.avatar} src={currentUser.avatarUrl} alt="avatar"/>
  • 我这边头像刷新不出来是因为数据库里字段名写成avatarUtil了,一直没发现,改了正确的字段名以及映射实体类属性名 Mapper.xml 文件后 头像映射正常了

开发用户管理页面

新建一个管理界面

  • 他奶奶的我这边出问题了

  • 我新建了一个/Pages/Admin/UserManage 把Register文件夹复制过去打算修改, 结果它给我把Register的路由给替换了

1
{ name: '注册', path: '/user/register', component: './user/UserManage' },
  • 然后前端直接挂掉了, 报错报了这个玩意儿 妈的找了半天 终于发现了 把路由改回来了
1
Cannot find module 'D:/Project/星球项目/ClientCenter/myapp/src/pages/user/UserManage'
  • 给新增的用户管理页面加个路由

​ path: ‘/admin/user-manager’ 是访问路径

​ component: ‘./Admin/UserManage’ 是资源路径

​ 仿照下面的写就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
{
path: '/admin',
name: '管理页',
icon: 'crown',
access: 'canAdmin',
component: './Admin',
routes: [
// 用户管理
{ path: '/admin/user-manager', name: '用户管理', icon: 'smile', component: './Admin/UserManage' },
{ path: '/admin/sub-page', name: '二级管理页', icon: 'smile', component: './Welcome' },
{ component: './404' },
],
},

项目全局入口

app.tsx是项目全局入口 里面包含了访问页面时, 就会调用的方法, 重定向到Login页 查询用户登录态
1
2
3
4
5
6
7
8
9
10
11
12
13
{
path: '/admin',
name: '管理页',
icon: 'crown',
// 管理员权限校验
access: 'canAdmin',
component: './Admin',
routes: [
{ path: '/admin/user-manager', name: '用户管理', icon: 'smile', component: './Admin/UserManage' },
{ path: '/admin/sub-page', name: '二级管理页', icon: 'smile', component: './Welcome' },
{ component: './404' },
],
},
看到那个access了吗 通过校验’canAdmin’的真假 判断是否具有管理员权限 这就是控制了这个路由的访问权限 怎么实现的?

访问权限管理

access.ts是访问权限管理 在查询到用户登录态后 通过返回结果CurrentUser来校验 这段逻辑非常简单 我们可以修改为自己的逻辑
1
2
3
4
5
6
7
8
export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) {
const { currentUser } = initialState ?? {};
// 权限校验
return {
// 校验管理员权限
canAdmin: currentUser && currentUser.access === 'admin'
};
}
  • 修改访问路由的管理员权限的校验规则
1
2
// 校验管理员权限
canAdmin: currentUser && currentUser.userRole === 1,

正确显示管理页面

原本的页面显示组件是 Admin.tsx
1
2
3
4
5
6
7
8
9
10
// 父路由
path: '/admin',
name: '管理页',
icon: 'crown',
component: './Admin',
// 子路由
routes: [
{ path: '/admin/user-manager', name: '用户管理', icon: 'smile', component: './Admin/UserManage' },
{ component: './404' },
],
组件里面这么写:
1
2
3
4
5
6
7
8
onst Admin: React.FC = ({children}) => {
return (
<PageHeaderWrapper content={' 这个页面只有 admin 权限才能查看'}>
// 展示子路由组件
{children}
</PageHeaderWrapper>
);
};
这样管理页面就能显示我们定义的组件 Admin/UserManage/index.tsx 了
  • 我们上ProComponents的高级表格里找一个高级表格, 作为管理页面

  • 直接找一个漂亮有用的, 粘贴到/UserManage/index.tsx里

  • 接下来就是对该页面的改造了

改造新的组件(管理页面)

改造表格数据(数据如何展示)

  • 改造返回数据类型 (API.CurrentUser) 和各列名
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
// 返回的数据类型改造为Api.currentUser
const columns: ProColumns<API.CurrentUser>[] = [
{
title: '账号',
dataIndex: 'userAccount',
copyable: true,
},
{
title: '昵称',
dataIndex: 'username',
copyable: true,
},
{
title: '头像',
dataIndex: 'avatarUrl',
copyable: true,
},
{
title: '性别',
dataIndex: 'gender',
copyable: true,
},
{
title: '电话',
dataIndex: 'phone',
copyable: true,
},
{
title: '邮件',
dataIndex: 'email',
copyable: true,
},
{
title: '角色',
dataIndex: 'userRole',
copyable: true,
},
{
title: '创建时间',
dataIndex: 'createTime',
copyable: true,
},
{
disable: true,
title: '状态',
dataIndex: 'state',
filters: true,
onFilter: true,
ellipsis: true,
valueType: 'select',
valueEnum: {
all: {text: '超长'.repeat(50)},
open: {
text: '未解决',
status: 'Error',
},
closed: {
text: '已解决',
status: 'Success',
disabled: true,
},
processing: {
text: '解决中',
status: 'Processing',
},
},
},

];

改造访问路径(数据从何而来)

1
2
3
4
5
6
7
8
9
10
11
12
columns={columns}
actionRef={actionRef}
cardBordered
request={async (params = {}, sort, filter) => {
console.log(sort, filter);
// 返回userList
// 自定义函数
const userList = await searchUsers();
return {
data: userList
}
}}
  • 在api.ts下编写自定义函数searchUsers, 并设置访问路径
1
2
3
4
5
6
7
8
/** 此处后端没有提供注释 GET /api/notices */
export async function searchUsers(options?: { [key: string]: any }) {
// 返回数据格式为API.CurrentUser
return request<API.CurrentUser>('/api/user/search', {
method: 'GET',
...(options || {}),
});
}
  • 后端返回所有用户数据, 并展示在表格中 展示成功了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 查询用户
*
* @param username 用户名
* @return 查到的用户
*/
@GetMapping("/search")
public List<User> userSearch(String username, HttpServletRequest request) {
// 1.校验权限
if (!isAdmin(request))
return new ArrayList<>();
// 2.判空, 默认查询全部
QueryWrapper<User> qw = new QueryWrapper<>();
if (StringUtils.isNotBlank(username))
qw.like("username", username);
// 3.查询
List<User> userList = userService.list(qw);
// 4.返回脱敏的用户信息
return userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
}
项目全局命名空间, 把一组TS类型全部定义到了这个命名空间下, 即定义了一组返回数据对象, 取的时候就不需要import了, 直接API.TS类型就可以取到
src/services/ant-design-pro/typings.d.ts
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
declare namespace API {
type CurrentUser = {
.............
};

type LoginResult = {
.............
};

type RegisterResult = number;

type PageParams = {
.............
};

type RuleListItem = {
.............
};

type RuleList = {
.............
};

type FakeCaptcha = {
.............
};

type LoginParams = {
.............
};

type RegisterParams = {
.............
};

type ErrorResponse = {
.............
};

type NoticeIconList = {
.............
};

type NoticeIconItemType = 'notification' | 'message' | 'event';

type NoticeIconItem = {
.............
};
}
src/services/ant-design-pro/api.ts 这里定义了许多请求接口 根据请求地址 请求方式发出请求
1
import {request} from 'umi';

修改表格显示细节

  • 通过columns定义表格有哪些列

  • column属性

dataIndex 对应返回数据对象的属性

title 表格列名

copyable 是否允许复制

ellipsis 是否允许缩略

valueType 用于声明这一列的类型

  • 头像
1
2
3
4
5
6
7
8
9
10
{
title: '头像',
dataIndex: 'avatarUrl',
copyable: true,
render: (_, record) => (
<div>
<img src={record.avatarUrl} width={100}/>
</div>
)
},
  • 性别 角色 状态
1
2
3
4
5
6
7
8
9
10
11
12
13
{
title: '性别',
dataIndex: 'gender',
valueType: 'select',
valueEnum: {
0: {
text: '女',
},
1: {
text: '男',
},
},
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
title: '角色',
dataIndex: 'userRole',
valueType: 'select',
valueEnum: {
0: {
text: '普通用户',
status: 'Default',
},
1: {
text: '管理员',
status: 'Success',
},
},
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
title: '状态',
dataIndex: 'userStatus',
valueType: 'select',
valueEnum: {
0: {
text: '正常',
status: 'Success',
},
1: {
text: '异常',
status: 'Error',
},
},
},

代码优化

新增注销功能

  • service层新增userLogout
1
int userLogout(HttpServletRequest request);
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 用户注销
*
* @param request
* @return
*/
@Override
public int userLogout(HttpServletRequest request) {
// 移除session
request.getSession().removeAttribute(USER_LOGIN_STATE);
return 1;
}
  • controller层新增userLogout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 用户登录
*
* @param request request
* @return int
*/
@PostMapping("/logout")
public Integer userLogout(HttpServletRequest request) {
//controller对参数的校验
if (request == null)
return null;

return userService.userLogout(request);
}
  • 前端src/RightContent/AvatarDropdown.tsx下有注销功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 退出登录,并且将当前的 url 保存
*/
const loginOut = async () => {
await outLogin();
const {query = {}, search, pathname} = history.location;
const {redirect} = query;
// Note: There may be security issues, please note
if (window.location.pathname !== '/user/login' && !redirect) {
history.replace({
pathname: '/user/login',
search: stringify({
redirect: pathname + search,
}),
});
}
};
  • 修改注销接口 请求路径
1
2
3
4
5
6
7
/** 退出登录接口 POST /api/user/logout */
export async function outLogin(options?: { [key: string]: any }) {
return request<Record<string, any>>('/api/user/logout', {
method: 'POST',
...(options || {}),
});
}

注销功能优化完毕

用户必填信息新增星球编号

  • user表新增字段planet_code
  • 重新生成对应实体类 domain Mapper.xml
  • 注册接收类新增planetCode
1
2
3
4
5
6
7
@Data
public class UserRegisterRequest {
private String userAccount;
private String userPassword;
private String checkPassword;
private String planetCode;
}

service层

  • 注册校验新增
1
long userRegister(String userAccount, String userPassword, String checkPassword, String planetCode);
1
2
3
// 1.6.星球编号不能超过5位
if (planetCode.length() > 5)
return -1;
1
2
3
4
5
6
// 1.8.星球编号不能重复
QueryWrapper<User> pc_lqw = new QueryWrapper<>();
pc_lqw.eq("planet_code", planetCode);
Long pc_count = userMapper.selectCount(pc_lqw);
if (pc_count > 0)
return -1;
  • 用户信息脱敏新增
1
safetyUser.setPlanetCode(originUser.getPlanetCode());

controller层

  • controller参数校验
1
2
3
//controller对参数的校验
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode))
return null;
  • 注册页面新增星球编号填写和校验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<ProFormText
name="planetCode"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={styles.prefixIcon}/>,
}}
placeholder={'请输入星球编号'}
rules={[
{
required: true,
message: '星球编号是必填项!',
},
{
max: 5,
type: 'string',
message: '长度不能大于5位',
},
]}
/>
  • 测试类新增planetCode
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
@Test
public void userRegister() {
String userAccount = "memory";
String userPassword = "";
String checkPassword = "123456";
String planetCode = "17265";

long result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
Assertions.assertEquals(-1, result);
userAccount = "mem";
result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
Assertions.assertEquals(-1, result);
userPassword = "123456";
result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
Assertions.assertEquals(-1, result);
userAccount = "me mory";
userPassword = "12345678";
result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
Assertions.assertEquals(-1, result);
userAccount = "me mory";
checkPassword = "12345678";
result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
Assertions.assertEquals(-1, result);
userAccount = "memory4";
result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
Assertions.assertTrue(result > 0);
}
  • currentUser新增planetCode
1
2
3
4
5
6
7
8
9
10
11
12
13
type CurrentUser = {
id?: number;
userAccount?: string;
username?: string;
avatarUrl?: string;
gender?: string;
phone?: string;
email?: string;
createTime?: Date;
userStatus?: string;
userRole?: number;
planetCode?: string;
};
  • 管理页用户信息新增planetCode
1
2
3
4
5
{
title: '星球编号',
dataIndex: 'planetCode',
copyable: true,
},

新增星球编号注册必填项完成, 管理页用户信息正常显示

返回通用对象

  • 自定义通用返回对象 - BaseResponse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 状态码
*/
private int code;

/**
* 数据
*/
private T data;

/**
* 信息
*/
private String message;

/**
* 描述
*/
private String description;
  • 通用返回对象多种构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 封装执行成功的结果
*
* @param code 状态码
* @param data 数据
* @param message 信息
* @param description 描述
*/
public BaseResponse(int code, T data, String message, String description) {
this.code = code;
this.data = data;
this.message = message;
this.description = description;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 封装全局自定义异常
*
* @param code 状态码
* @param message 信息
* @param description 描述
*/
public BaseResponse(int code, String message, String description) {
this.code = code;
this.data = null;
this.message = message;
this.description = description;
}
1
2
3
4
5
6
7
8
9
10
11
/**
* 封装局部自定义异常
*
* @param errorCode 自定义异常态
*/
public BaseResponse(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.data = null;
this.message = errorCode.getMessage();
this.description = errorCode.getDescription();
}
  • 通用返回对象添加getter, setter方法
  • 这一步一定不要忘记啊, 我就因为没加这俩方法, 测试登录注册就报406错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int getCode() {
return code;
}

public T getData() {
return data;
}

public String getMessage() {
return message;
}

public String getDescription() {
return description;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void setCode(int code) {
this.code = code;
}

public void setData(T data) {
this.data = data;
}

public void setMessage(String message) {
this.message = message;
}

public void setDescription(String description) {
this.description = description;
}

返回成功的话, 就是返回成功状态, 即返回状态码 + 数据 + “ok”(message) + “”(description)

  • 封装返回成功状态下通用对象的方法
1
2
3
4
5
6
7
8
9
10
/**
* 封装调用执行成功
*
* @param data 返回数据
* @param <T> 数据类型
* @return 执行成功信息
*/
public static <T> BaseResponse<T> success(T data) {
return new BaseResponse<>(0, data, "ok", "");
}

返回失败的话, 就是返回失败状态, 即返回状态码 + null(data) + message + description

实现思路: 这里我们打算封装自定义异常类和全局异常处理器, 当业务中出现错误, 不会返回失败结果, 而是抛出相应的异常, 并由全局异常处理器捕获, 再由全局异常处理器来返回对应失败状态

封装自定义异常BusinessException

自定义异常成员属性
1
2
3
4
5
6
7
8
9
10
11
12
public class BusinessException extends RuntimeException {
/**
* 状态码
*/
private final int code;

/**
* 异常描述
*/
private final String description;
................
}
  • 这里我们会发现, 自定义异常里没有定义message属性. 这是因为自定义异常继承了RuntimeException, 在执行构造方法时, 执行super(message)即可设置自定义的异常信息, 再通过e.getMessage()方法来获取异常信息
  • 自定义异常多种构造器
  • 局部业务代码下自定义的异常信息
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 封装局部自定义异常
*
* @param message 异常信息
* @param code 异常状态码
* @param description 异常描述
*/
public BusinessException(String message, int code, String description) {
super(message);
this.code = code;
this.description = description;
}
  • 封装全局自定义异常(无描述)
1
2
3
4
5
6
7
8
9
10
/**
* 封装全局自定义异常(无描述)
*
* @param errorCode 自定义异常态
*/
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.description = errorCode.getDescription();
}
  • 封装全局自定义异常(有描述)
1
2
3
4
5
6
7
8
9
10
11
/**
* 封装全局自定义异常(有描述)
*
* @param errorCode 自定义异常态
* @param description 异常描述
*/
public BusinessException(ErrorCode errorCode, String description) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.description = description;
}

补充: 封装全局自定义异常信息 ErrorCode (枚举类)

1
2
3
public enum ErrorCode {
..............
}
  • 枚举值 (需持续完善更新)
1
2
3
4
5
6
7
PARMS_ERROR(40000, "请求参数错误", ""),
NULL_ERROR(40001, "请求数据为空", ""),
UPDATE_ERROR(40002, "操作数据库失败", ""),
NOT_LOGIN(40003, "未登录", ""),
NOT_REGISTER(40004, "未注册", ""),
NO_AUTH(40005, "无权限", ""),
SYSTEM_ERROR(10000, "系统内部异常", "");
  • 成员属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 状态码
*/
private final int code;

/**
* 状态码信息
*/
private final String message;

/**
* 状态码描述
*/
private final String description;
  • 构造器和 getter 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @param code 状态码
* @param message 信息
* @param 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;
}
自定义异常 getter 方法
1
2
3
4
5
6
7
public int getCode() {
return code;
}

public String getDescription() {
return description;
}

封装全局异常处理器

1
2
3
4
5
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
....................
}
  • 捕获自定义异常对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 捕获自定义异常对象
*
* @param e 自定义异常
* @return 封装返回执行失败异常信息
*/
@ExceptionHandler(BusinessException.class)
public BaseResponse locallyCustomException(BusinessException e) {
log.error("businessException: " + e.getMessage(), e);
log.info("" + e.getCode());
log.info(e.getMessage());
log.info(e.getDescription());
return ResultUtils.error(e.getCode(), e.getMessage(), e.getDescription());
}
  • 捕获其他异常
1
2
3
4
5
6
7
8
9
10
11
/**
* 捕获其他异常
*
* @param e 其他异常
* @return 封装返回请求失败异常信息
*/
@ExceptionHandler(RuntimeException.class)
public BaseResponse globalCustomException(Exception e) {
log.error("runtimeException", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR);
}

修改后端返回给前端的数据, 全部更新为直接返回通用对象(成功状态)或抛出业务异常再由全局异常处理器返回通用对象(失败状态)

  • 成功状态 (controller层)
1
2
3
4
5
6
7
8
9
10
/**
* 用户注册
*
* @param userRegisterRequest 注册信息封装类
* @return id
*/
public BaseResponse<Long> userRegister(@RequestBody UserRegisterRequest userRegisterRequest) {
......................
return ResultUtils.success(userRegister);
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 用户登录
*
* @param userLoginRequest 登录信息封装
* @param request request
* @return User
*/
@PostMapping("/login")
public BaseResponse<User> userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
.........................
return ResultUtils.success(userLogin);
}
1
2
3
4
5
6
7
8
9
10
11
/**
* 用户登录
*
* @param request request
* @return int
*/
@PostMapping("/logout")
public BaseResponse<String> userLogout(HttpServletRequest request) {
..........................
return ResultUtils.success(userLogout);
}
1
2
3
4
5
6
7
8
9
10
11
/**
* 获取当前用户登录态
*
* @param request request
* @return 当前用户信息
*/
@GetMapping("/currentUser")
public BaseResponse<User> getCurrentUser(HttpServletRequest request) {
............................
return ResultUtils.success(user);
}
1
2
3
4
5
6
7
8
9
10
11
/**
* 查询用户
*
* @param username 用户名
* @return 查到的用户
*/
@GetMapping("/search")
public BaseResponse<List<User>> userSearch(String username, HttpServletRequest request) {
.............................
return ResultUtils.success(users);
}
1
2
3
4
5
6
7
8
9
10
11
/**
* 删除用户
*
* @param id 用户id
* @return true/false
*/
@DeleteMapping("/delete")
public BaseResponse<Boolean> userDelete(Long id, HttpServletRequest request) {
..............................
return ResultUtils.success(removeById);
}
1
2
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode))
throw new BusinessException(PARMS_ERROR);
失败状态
  • controller层 register login logout currentUser search delete 下的controller参数校验
1
2
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode))
throw new BusinessException(PARMS_ERROR);
1
2
if (StringUtils.isAnyBlank(userAccount, userPassword))
throw new BusinessException(PARMS_ERROR);
1
2
if (request == null)
throw new BusinessException(PARMS_ERROR);
1
2
if (currentUser == null)
throw new BusinessException(PARMS_ERROR);
1
2
if (!isAdmin(request))
throw new BusinessException(NO_AUTH);
1
2
3
4
if (!isAdmin(request))
throw new BusinessException(NO_AUTH);
if (id <= 0)
throw new BusinessException(PARMS_ERROR);
  • service层 register 下的参数校验
1
2
3
// 1.1.账户, 密码, 二次密码不能为空
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword))
throw new BusinessException(PARMS_ERROR);
1
2
3
// 1.2.账户不小于4位
if (userAccount.length() < 4)
throw new BusinessException("账户不符合要求", 50000, "账户小于4位");
1
2
3
4
// 1.3.账户不包含特殊字符
String pattern = ".*[\\s`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?\\\\]+.*";
if (Pattern.matches(pattern, userAccount))
throw new BusinessException("账户不符合要求", 50001, "账户包含特殊字符");
1
2
3
// 1.4.用户密码不小于8位
if (userPassword.length() < 8)
throw new BusinessException("密码不符合要求", 60000, "用户密码小于8位");
1
2
3
// 1.5.二次密码与密码相同
if (!userPassword.equals(checkPassword))
throw new BusinessException("二次密码不符合要求", 60001, "二次密码与密码不相同");
1
2
3
// 1.6.星球编号不能超过5位
if (planetCode.length() > 5)
throw new BusinessException("星球编号不符合要求", 60002, "星球编号超过5位");
1
2
3
4
5
6
// 1.7.账户不能重复
QueryWrapper<User> ua_lqw = new QueryWrapper<>(); // LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
ua_lqw.eq("user_account", userAccount); // userLambdaQueryWrapper.eq(User::getUserAccount, userAccount);
Long ua_count = userMapper.selectCount(ua_lqw); // long count = this.count(lqw);
if (ua_count > 0)
throw new BusinessException("账户不符合要求", 50002, "账户重复");
1
2
3
4
5
6
// 1.8.星球编号不能重复
QueryWrapper<User> pc_lqw = new QueryWrapper<>();
pc_lqw.eq("planet_code", planetCode);
Long pc_count = userMapper.selectCount(pc_lqw);
if (pc_count > 0)
throw new BusinessException("星球编号不符合要求", 60003, "星球编号重复");
  • service层 login下的参数校验
1
2
3
// 1.1.账户, 密码不能为空
if (StringUtils.isAnyBlank(userAccount, userPassword))
throw new BusinessException(PARMS_ERROR);
1
2
3
// 1.2.账户不小于4位
if (userAccount.length() < 4)
throw new BusinessException("账户不符合要求", 50000, "账户小于4位");
1
2
3
// 1.3.用户密码不小于8位
if (userPassword.length() < 8)
throw new BusinessException("密码不符合要求", 60000, "用户密码小于8位");
1
2
3
4
// 1.4.账户不包含特殊字符
String pattern = ".*[\\s`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?\\\\]+.*";
if (Pattern.matches(pattern, userAccount))
throw new BusinessException("账户不符合要求", 50001, "账户包含特殊字符");
  • service层 logout下的返回信息
1
2
request.getSession().removeAttribute(USER_LOGIN_STATE);
return "注销成功";
自定义异常并由全局异常处理器成功处理, 功能基本完成

前端适配后端的通用返回对象

  • 前端定义通用返回对象(services/ant-design-pro/typings.d.ts)
1
2
3
4
5
6
7
8
9
/**
* 通用返回类
*/
type BaseResponse<T> = {
code: number,
data: T,
message: string,
description: string
}
  • 封装各接口响应类型(services/ant-design-pro/api.ts)
1
2
3
4
return request<API.BaseResponse<API.CurrentUser>>('/api/user/currentUser', {
method: 'GET',
...(options || {}),
});
1
2
3
4
return request<API.BaseResponse<API.CurrentUser>>('/api/user/logout', {
method: 'POST',
...(options || {}),
});
1
2
3
4
5
6
7
8
return request<API.BaseResponse<API.CurrentUser>>('/api/user/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
1
2
3
4
5
6
7
8
return request<API.BaseResponse<API.CurrentUser>>('/api/user/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
1
2
3
4
return request<API.BaseResponse<API.CurrentUser>>('/api/user/search', {
method: 'GET',
...(options || {}),
});
  • 修改登录/注册成功的校验逻辑
1
2
3
4
5
6
7
8
9
10
// 登录
const data = await login({
...values,
type,
});
// 登录成功
if (data) {
const defaultLoginSuccessMessage = '登录成功!';
................
}
1
2
3
4
5
6
7
8
9
10
11
// 注册
// 返回id
const data = await register({
...values,
type,
});
// 注册成功
if (data) {
const defaultLoginSuccessMessage = '注册成功!';
....................
}
  • 这边还要记得修改两个地方:
修改管理员权限的校验 - 解决了无法正确访问到管理员页面的问题
1
2
3
4
5
6
7
8
export default function access(initialState: { baseResponse?: API.BaseResponse<API.CurrentUser> } | undefined) {
const { baseResponse } = initialState ?? {};
// 权限校验
return {
// 校验管理员权限
canAdmin: baseResponse && baseResponse.data.userRole === 1,
};
}
修改查询在线用户信息 - 解决了在线用户信息页面无法正常显示的问题
1
2
3
const columns: ProColumns<API.BaseResponse<API.CurrentUser>>[] = [
...............
]
1
2
3
<ProTable<API.BaseResponse<API.CurrentUser>>
columns={columns}
............
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 // 弹出异常信息
message.error(res.message)
// 弹出异常描述
message.error(res.description)
// 跳转至登录页
history.replace({
pathname: 'user/login',
search: stringify({
redirect: location.pathname,
}),
});request={async (params = {}, sort, filter) => {
console.log(sort, filter);
// 返回userList
const baseResponse = await searchUsers();
return {
data: baseResponse,
}
}}

前端封装全局请求/响应拦截器

umi其实已经给我们封装好了request了(目录:src/.umi/plugin-request/request.ts), 在这里我们也可以在该目录下自定义全局请求/响应拦截器, 但是一方面破坏了封装组件的完整性, 不好直接修改封装好的组件. 我们的想法是定义自己的requert来封装axios, 再自定义全局请求/响应拦截器
新建目录: src/plugins/globalRequest.ts
在该目录下我们封装自己的request
1
2
3
4
import {extend} from 'umi-request';
import {message} from "antd";
import {history} from 'umi';
import {stringify} from "querystring";
1
2
3
4
5
6
7
/**
* 配置request请求时的默认参数
*/
const request = extend({
credentials: 'include', // 默认请求是否带上cookie
// requestType: 'form',
});
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 全局请求拦截器
*/
request.interceptors.request.use((url, options): any => {
console.log(`do request url = ${url}`)
return {
url,
options: {
...options,
headers: {},
},
};
});
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
/**
* 全局响应拦截器
*/
request.interceptors.response.use(async (response, options): Promise<any> => {
const res = await response.clone().json();
// 1.返回成功状态
if (res.code === 0 && res.data > 0) {
console.log(res.message);
return res.data;
}
// 2.返回失败状态
if (res.code === 50001) {
console.log("...")
// 弹出异常信息
message.error(res.message)
// 弹出异常描述
// message.error(res.description)
// 跳转至登录页
history.replace({
pathname: 'user/login',
search: stringify({
redirect: location.pathname,
}),
});
console.log("code: " + res.code)
}

return res.data;

});
最后不要忘了, 在src/services/ant-design-pro/api.ts目录下引入我们自己实现的request, 覆盖掉umi封装的request
1
import request from '@/plugins/globalRequest';
我们实现了自定义的前端全局请求/响应拦截器

至此, 用户中心系统基本功能已经完成

未来的持续优化:

  • 完善框架搭建和数据库设计的文档编写
  • 注册的新用户会有默认的头像和昵称
  • 用户登录后可以更改个人信息
  • 完善前端全局异常处理器所处理的异常类型
  • 项目的部署和上线

废话不多说, 开启 伙伴匹配系统 的学习 (2023/04/21)

项目部署

前端多环境配置

后端多环境配置

项目上线


Memory 用户中心中心-开发文档
https://test.atomgit.net/blog/2023/03/17/Memory 用户中心系统-开发文档/
作者
Memory
发布于
2023年3月17日
更新于
2023年4月21日
许可协议