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: Dw990831- 1 
 2
 3
 4- spring:
 # Project name
 application:
 name: user-center- 1 
 2
 3
 4
 5- # 端口
 server:
 port: 8081
 servlet:
 context-path: /api- 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)- 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 
 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 |  | 
前端获取用户登录态
- 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 |  |