Memory 用户中心中心-开发文档
本文最后更新于:1 年前
前言
开发项目前的分析
企业做项目流程
1 |
|
需求分析
- 登录 / 注册
- ⽤户管理(仅管理员可⻅)对⽤户的查询或者修改
- ⽤户校验( 仅星球⽤户 )
技术选型
前端:
三件套 + 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 |
|
MybatisX插件快速生成domain entity service 以及 serviceImpl
- 选择生成目录
- 生成选项
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: Dw9908311
2
3
4spring:
# Project name
application:
name: user-center1
2
3
4
5# 端口
server:
port: 8081
servlet:
context-path: /api1
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)1
2
3# session存活时间 一天
session:
timeout: 86640
后端开发
Service层
登录+校验
1 |
|
1 |
|
注册+校验
1 |
|
1 |
|
封装脱敏用户信息
1 |
|
1 |
|
Controller层
登录
1 |
|
注册
1 |
|
查询用户+权限校验
1 |
|
删除用户+权限校验
1 |
|
获取用户登录态
1 |
|
封装校验管理员逻辑
1 |
|
constant层
封装常量
1 |
|
model/request层
封装login/register实体接收类
1 |
|
1 |
|
前端开发
修改登录页面
熟悉登录流程 请求地址 返回数据
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
2const 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
5history.push({
pathname: 'user/login',
query,
})
跳转到登录页面注册失败 提示”注册失败”
1
2const defaultLoginFailureMessage = '注册失败,请重试!';
message.error(defaultLoginFailureMessage);“登录” 修改为 “注册” (了解源码)
1
2
3
4
5submitter={{
searchConfig: {
submitText: '注册'
}
}}添加注册校验 简单的逻辑 根据返回的数据 解构出密码和二次密码 判断二者是否相等
1
2
3
4
5
6const {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 |
|
前端获取用户登录态
- app.tsx 前端服务入口 每次打开页面, 都会执行查询
1 |
|
1 |
|
- 修改CurrentUser, 将返回的字段全部修改为对应数据库中的字段
1 |
|
- 设置白名单, 登录注册页面不会返回查询到的用户登录态, 其余页面会返回查询到的用户登录态
1 |
|
开发欢迎页面
- 设置欢迎页面的水印 头像
1 |
|
- 头像的话在/src/components/RightContext/AvatarDropdown.tsx里有个引用
1 |
|
- 我这边头像刷新不出来是因为数据库里字段名写成avatarUtil了,一直没发现,改了正确的字段名以及映射实体类属性名 Mapper.xml 文件后 头像映射正常了
开发用户管理页面
新建一个管理界面
他奶奶的我这边出问题了
我新建了一个/Pages/Admin/UserManage 把Register文件夹复制过去打算修改, 结果它给我把Register的路由给替换了
1 |
|
- 然后前端直接挂掉了, 报错报了这个玩意儿 妈的找了半天 终于发现了 把路由改回来了
1 |
|
- 给新增的用户管理页面加个路由
path: ‘/admin/user-manager’ 是访问路径
component: ‘./Admin/UserManage’ 是资源路径
仿照下面的写就行了
1 |
|
- 访问http://localhost:8000/admin/user-manager发现无权访问 好像存在访问权限 访问不到
项目全局入口
app.tsx是项目全局入口 里面包含了访问页面时, 就会调用的方法, 重定向到Login页 查询用户登录态
1 |
|
看到那个access了吗 通过校验’canAdmin’的真假 判断是否具有管理员权限 这就是控制了这个路由的访问权限 怎么实现的?
访问权限管理
access.ts是访问权限管理 在查询到用户登录态后 通过返回结果CurrentUser来校验 这段逻辑非常简单 我们可以修改为自己的逻辑
1 |
|
- 修改访问路由的管理员权限的校验规则
1 |
|
- 找一个管理员账号登录, 发现http://localhost:8000/admin/user-manager页面可以访问了 因为我们由管理员权限了
正确显示管理页面
原本的页面显示组件是 Admin.tsx
1 |
|
组件里面这么写:
1 |
|
这样管理页面就能显示我们定义的组件 Admin/UserManage/index.tsx 了
我们上ProComponents的高级表格里找一个高级表格, 作为管理页面
直接找一个漂亮有用的, 粘贴到/UserManage/index.tsx里
接下来就是对该页面的改造了
改造新的组件(管理页面)
改造表格数据(数据如何展示)
- 改造返回数据类型 (API.CurrentUser) 和各列名
1 |
|
改造访问路径(数据从何而来)
1 |
|
- 在api.ts下编写自定义函数searchUsers, 并设置访问路径
1 |
|
- 后端返回所有用户数据, 并展示在表格中 展示成功了
1 |
|
项目全局命名空间, 把一组TS类型全部定义到了这个命名空间下, 即定义了一组返回数据对象, 取的时候就不需要import了, 直接API.TS类型就可以取到
src/services/ant-design-pro/typings.d.ts
1 |
|
src/services/ant-design-pro/api.ts 这里定义了许多请求接口 根据请求地址 请求方式发出请求
1 |
|
修改表格显示细节
通过columns定义表格有哪些列
column属性
dataIndex 对应返回数据对象的属性
title 表格列名
copyable 是否允许复制
ellipsis 是否允许缩略
valueType 用于声明这一列的类型
- 头像
1 |
|
- 性别 角色 状态
1 |
|
1 |
|
1 |
|
代码优化
新增注销功能
- service层新增userLogout
1 |
|
1 |
|
- controller层新增userLogout
1 |
|
- 前端src/RightContent/AvatarDropdown.tsx下有注销功能
1 |
|
- 修改注销接口 请求路径
1 |
|
注销功能优化完毕
用户必填信息新增星球编号
- user表新增字段planet_code
- 重新生成对应实体类 domain Mapper.xml
- 注册接收类新增planetCode
1 |
|
service层
- 注册校验新增
1 |
|
1 |
|
1 |
|
- 用户信息脱敏新增
1 |
|
controller层
- controller参数校验
1 |
|
- 注册页面新增星球编号填写和校验
1 |
|
- 测试类新增planetCode
1 |
|
- currentUser新增planetCode
1 |
|
- 管理页用户信息新增planetCode
1 |
|
新增星球编号注册必填项完成, 管理页用户信息正常显示
返回通用对象
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
返回成功的话, 就是返回成功状态, 即返回状态码 + 数据 + “ok”(message) + “”(description)
1 |
|
返回失败的话, 就是返回失败状态, 即返回状态码 + null(data) + message + description
实现思路: 这里我们打算封装自定义异常类和全局异常处理器, 当业务中出现错误, 不会返回失败结果, 而是抛出相应的异常, 并由全局异常处理器捕获, 再由全局异常处理器来返回对应失败状态
封装自定义异常BusinessException
自定义异常成员属性
1 |
|
这里我们会发现, 自定义异常里没有定义message属性. 这是因为自定义异常继承了RuntimeException, 在执行构造方法时, 执行super(message)即可设置自定义的异常信息, 再通过e.getMessage()方法来获取异常信息
自定义异常多种构造器
局部业务代码下自定义的异常信息
1 |
|
1 |
|
1 |
|
补充: 封装全局自定义异常信息 ErrorCode (枚举类)
1 |
|
1 |
|
1 |
|
1 |
|
自定义异常 getter 方法
1 |
|
封装全局异常处理器
1 |
|
1 |
|
1 |
|
修改后端返回给前端的数据, 全部更新为直接返回通用对象(成功状态)或抛出业务异常再由全局异常处理器返回通用对象(失败状态)
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
失败状态
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
自定义异常并由全局异常处理器成功处理, 功能基本完成
前端适配后端的通用返回对象
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
修改管理员权限的校验 - 解决了无法正确访问到管理员页面的问题
1 |
|
修改查询在线用户信息 - 解决了在线用户信息页面无法正常显示的问题
1 |
|
1 |
|
1 |
|
前端封装全局请求/响应拦截器
umi其实已经给我们封装好了request了(目录:src/.umi/plugin-request/request.ts), 在这里我们也可以在该目录下自定义全局请求/响应拦截器, 但是一方面破坏了封装组件的完整性, 不好直接修改封装好的组件. 我们的想法是定义自己的requert来封装axios, 再自定义全局请求/响应拦截器
新建目录: src/plugins/globalRequest.ts
在该目录下我们封装自己的request
1 |
|
1 |
|
1 |
|
1 |
|
最后不要忘了, 在src/services/ant-design-pro/api.ts目录下引入我们自己实现的request, 覆盖掉umi封装的request
1 |
|