(Java项目)用户管理中心

项目地址

前端源代码:gitee
后端源代码:gitee

需求分析

 写一个通用型用户管理中心项目,以后如果写其他项目需要用到用户管理系统,可以直接套用该项目代码。

注意: 由于部分代码过长故而部分代码截图未截全

技术栈

前端:三件套(HTML,CSS,JavaScript),Ant Design + Umi + Ant Design Pro
后端:Java,Spring,SpringMVC,SpringBoot,Mybatis,Mybatis-Plus,Junit,MySQL

项目初始化

- 项目初始化方法
	* Github上搜现成的代码(由于是他人写的代码,直接拿来使用可能会出现未预料的错误,所以不推荐)
	* SpringBoot官方模板生成器(https://start.spring.io/)
	* 使用Idea上面集成的生成器(推荐)

前端初始化

1.浏览器搜索Ant Design Pro,点击开始使用后跳转到该页面
在这里插入图片描述
2.根据教程进行初始化

打开cmd命令行输入
>npm i @ant-design/pro-cli -g
>pro create myapp
来初始化脚手架
	umi版本选择 umi@3
	使用简单的脚手架 simple
在命令行输入
>cd myapp && tyarn
	进入项目并安装依赖,tyarn需要提前安装
安装依赖时可能会发生依赖冲突错误,可尝试降低NodeJs的版本至16.13.0或更低,这里推荐使用NVM来自动管理Node版本,便于满足不同项目对Node版本的不同需求。

3.依赖安装完成后,使用WebStorm打开该文件夹myapp,在WebStorm终端里输入 yarn 来再次安装依赖。
4.在package.json文件中找到start并点击左侧三角启动按钮来启动项目进行测试。
在这里插入图片描述
项目成功启动:
在这里插入图片描述
我们在浏览器打开下方显示的网址(http://localhost:8000 )
出现下面的界面代表项目运行成功!
在这里插入图片描述
5.项目瘦身

  • package.json文件中找到"i18n-remove"并点击左侧的三角启动按钮来移除国际化
    在这里插入图片描述

  • 删除src目录下的locales文件夹(是用来国际化的)
    在这里插入图片描述

  • 删除src目录下的e2e文件夹
    在这里插入图片描述

  • 删除src目录下的services目录下的swagger文件夹
    在这里插入图片描述

  • 删除config目录下的oneapi.json文件
    在这里插入图片描述
    进入config.ts文件,删除里面的“openAPI”部分
    在这里插入图片描述

  • 删除tests文件夹
    在这里插入图片描述

  • 删除jest.config.js文件
    在这里插入图片描述

  • 删除playwright.config.ts文件
    在这里插入图片描述
    前端初始化完毕!

后端初始化

1.Idea新建项目:
在这里插入图片描述
注意SpringBoot版本不要选择3.0及以上!
选择如图六个依赖:

	Lombok
	Spring Boot DevTools
	Spring Configuration Processor
	MySQL Driver
	Spring Web
	MyBatis Framework

在这里插入图片描述2.在pom.xml文件里添加依赖:

  • Mybatis-Plus

      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-boot-starter</artifactId>
          <version>3.5.2</version>
      </dependency>
    
  • Junit

      <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.13.2</version>
          <scope>test</scope>
      </dependency>
    

注意:添加依赖后需更新maven

3.将application.properties文件名改为application.yml并进行配置(注意将数据库,密码,用户名等改为自己的)

spring:
  application:
    name: user_center
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/user_center
    username: root
    password: 2469
server:
  port: 8080

在这里插入图片描述
4.在com.example目录下添加mappermodel软件包
在这里插入图片描述
后端初始化完毕!

添加后端登录功能

  1. 点击Idea右侧数据库按钮并选择数据源为MySQL
    在这里插入图片描述
    然后链接数据库
    在这里插入图片描述

  2. 使用MySQL数据库建立架构 user_center
    在这里插入图片描述
    在这里插入图片描述

  3. 然后在该架构中创建新表user,建表语句:

     create table user
     (
     id           bigint auto_increment comment 'id'
         primary key,
     username     varchar(256)                       null comment '用户昵称',
     userAccount  varchar(256)                       null comment '用户账号',
     avatarUrl    varchar(1024)                      null comment '用户头像',
     gender       tinyint                            null comment '性别',
     userPassword varchar(512)                       not null comment '密码',
     phone        varchar(128)                       null comment '电话',
     email        varchar(512)                       null comment '邮箱',
     userStatus   int      default 0                 not null comment '状态 0-正常',
     createTime   datetime default CURRENT_TIMESTAMP null comment '创建时间',
     updateTime   datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新时间',
     isDelete     tinyint  default 0                 not null comment '是否删除'
     )
     comment '用户表';
    
  4. Idea设置=》插件里搜索并安装MyBatisX插件(安装后需要重启Idea)
    在这里插入图片描述
    在这里插入图片描述
    然后再新建的user表上右键选择MybatisX-Generator
    在这里插入图片描述
    然后如下图填写:
    在这里插入图片描述
    在这里插入图片描述
    点击Finish来自动生成基础文件,然后将生成的文件移动至应属目录下
    domin移至model目录下
    UserMapper移至mapper目录下
    UserService移至service目录下
    UserServiceImpl移至service.impl目录下
    注意更改文件内的相关路径

  5. 在启动类UserCenterApplication里添加注解**@MapperScan(“com.example.mapper”)**
    在这里插入图片描述

  6. pom.xml文件里添加commons-lang3依赖(依赖网站:https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 )

     <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
         <version>3.12.0</version>
     </dependency>
    
  7. UserService接口里添加接口方法userRegister

     long userRegister(String userAccount,String userPassword,String checkPassword);
    

在这里插入图片描述
并在UserServiceImpl类里实现该方法

@Override
public long userRegister(String userAccount, String userPassword, String checkPassword) {
    // 密码,账号,二次校验密码 不能为空
    if(StringUtils.isAnyBlank(userPassword,userAccount,checkPassword)){
        return -1;
    }
    // 账号长度不小于4
    if(userAccount.length()<4){
        return -1;
    }
    // 密码和二次校验密码 长度不小于8
    if (userPassword.length()<8||checkPassword.length()<8){
        return -1;
    }
    // 账号不能含有特殊字符
    String validPattern="[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、? ]";
    Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
    if (matcher.find()){
        return -1;
    }
    // 密码与二次校验密码需一致
    if (!userPassword.equals(checkPassword)){
        return -1;
    }
    // 账号不可重复
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    queryWrapper.eq("userAccount",userAccount);
    long count = this.count(queryWrapper);
    if (count>0){
        return -1;
    }
    // 对密码进行加盐加密(加盐就是让密码加密后更复杂)
    final String SALT="lwy";
    String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
    // 建立用户实体
    User user =new User();
    user.setUserAccount(userAccount);
    user.setUserPassword(encryptPassword);
    // 存储用户信息
    boolean result = this.save(user);
    // 若返回null则不利于后续处理
    if(!result){
        return -1;
    }
    // 用户注册成功
    return 0;
}

在这里插入图片描述

  1. test.java.com.example.service目录下新建UserServiceTest
    在这里插入图片描述
    在类上添加**@SpringBootTest**注解
    在这里插入图片描述
    注入 UserService
    在这里插入图片描述
    编写测试方法 testUserRegister

     @Test
     void testUserRegister(){
         String userAccount = "";
         String userPassword = "12345678";
         String checkPassword= "12345678";
         long result = userService.userRegister(userAccount, userPassword, checkPassword);
         Assertions.assertEquals(-1, result);
         userAccount="lwy";
         result = userService.userRegister(userAccount, userPassword, checkPassword);
         Assertions.assertEquals(-1, result);
         userAccount="liwy";
         userPassword="123456";
         result = userService.userRegister(userAccount, userPassword, checkPassword);
         Assertions.assertEquals(-1, result);
         userAccount="li wy";
         userPassword="12345678";
         checkPassword="12345678";
         result = userService.userRegister(userAccount, userPassword, checkPassword);
         Assertions.assertEquals(-1, result);
         userAccount="liwy";
         checkPassword="123456789";
         result = userService.userRegister(userAccount, userPassword, checkPassword);
         Assertions.assertEquals(-1, result);
         userAccount="test";
         checkPassword="12345678";
         result = userService.userRegister(userAccount, userPassword, checkPassword);
         Assertions.assertEquals(-1, result);
         userAccount="1234";
         result = userService.userRegister(userAccount, userPassword, checkPassword);
         Assertions.assertTrue(result>=0);
     }
    

在这里插入图片描述
测试成功即可

  1. 新增model.domin.request.UserRegisterRequest文件

    /**
     * 用户注册请求体
     *
     * @author lwy
     */
    @Data
    public class UserRegisterRequest implements Serializable {
        private static final long serialVersionUID= 504966900305465423L;
    
        private String userAccount;
    
        private String userPassword;
    
        private String checkPassword;
    }
    

在这里插入图片描述

  1. controller.UserController文件里添加register方法

    @PostMapping("/register")
    public Long register(@RequestBody UserRegisterRequest userRegisterRequest){
        if (userRegisterRequest==null) {
            return null;
        }
        String userAccount = userRegisterRequest.getUserAccount();
        String userPassword = userRegisterRequest.getUserPassword();
        String checkPassword = userRegisterRequest.getCheckPassword();
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)){
            return null;
        }
        return userService.userRegister(userAccount, userPassword, checkPassword);
    }
    

在这里插入图片描述

在数据库里添加字段

application.yml配置文件里添加逻辑删除配置

  global-config:
    db-config:
      logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

在这里插入图片描述

Idea的数据库里右键点击我们的表user,点击修改表
在这里插入图片描述
新增userRole
在这里插入图片描述
点击确定
再次通过MybatisX-Generator生成文件
在这里插入图片描述
在这里插入图片描述
用新生成的User文件覆盖原本的User文件,并在isDelete字段上添加**@TableLogic**逻辑删除注解
在这里插入图片描述
更改UserMapper.xml文件里的路径引用
在这里插入图片描述
删除其它新生成的文件

完成后端登录功能

  1. 新增model.domin.request.UserLoginRequest文件

     /**
      * 用户登录请求体
      *
      * @author lwy
      */
     @Data
     public class UserLoginRequest implements Serializable {
         private static final long serialVersionUID= -7188479473325271448L;
     
         private String userAccount;
     
         private String userPassword;
     
     }
    

在这里插入图片描述

  1. 新建com.example.content.UserContent文件
    在这里插入图片描述

  2. UserContent文件里添加用户登录状态键:

    String USER_LOGIN_STATE="userLoginState";
    

在这里插入图片描述

  1. 将密码盐提至文件前端以使文件内其它方法可调用
    在这里插入图片描述

  2. userService文件里添加userLogin方法声明

     User userLogin(String userAccount, String userPassword , HttpServletRequest request);
    

在这里插入图片描述

  1. userServiceImpl文件里实现该方法

     @Override
     public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
     // 密码,账号不能为空
     if(StringUtils.isAnyBlank(userPassword,userAccount)){
         return null;
     }
     // 账号长度不小于4
     if(userAccount.length()<4){
         return null;
     }
     // 密码和二次校验密码 长度不小于8
     if (userPassword.length()<8){
         return null;
     }
     // 账号不能含有特殊字符
     String validPattern="[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”		 “’。,、? ]";
     Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
     if (matcher.find()){
         return null;
     }
     String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
     // 账号不可重复
     QueryWrapper<User> queryWrapper=new QueryWrapper<>();
     queryWrapper.eq("userAccount",userAccount);
     queryWrapper.eq("userPassword",encryptPassword);
     User user = userMapper.selectOne(queryWrapper);
     //用户不存在
     if(user == null){
         log.info("User login failed,userAccount cannot match userPassword");
         return null;
     }
     //用户脱敏
     User safetyUser=getSafetyUser(user);
     //记录用户的登录状态
     request.getSession().setAttribute(USER_LOGIN_STATE,safetyUser);
    
     return safetyUser;
    }
    

在这里插入图片描述

  1. UserService文件里声明getSafetyUser方法

       User getSafetyUser(User originUser);
    

在这里插入图片描述

UserServiceImpl文件里新增getSafetyUser方法

	   /**
     * 用户脱敏
     *
     * @param originUser 脱敏前的用户
     * @return 脱敏后用户信息
     */
   	 @Override
  	 	 public User getSafetyUser(User originUser) {
        User safetyUser=new User();
        safetyUser.setId(originUser.getId());
        safetyUser.setUsername(originUser.getUsername());
        safetyUser.setUserAccount(originUser.getUserAccount());
        safetyUser.setAvatarUrl(originUser.getAvatarUrl());
        safetyUser.setGender(originUser.getGender());
        safetyUser.setPhone(originUser.getPhone());
        safetyUser.setEmail(originUser.getEmail());
        safetyUser.setUserRole(originUser.getUserRole());
        safetyUser.setUserStatus(originUser.getUserStatus());
        safetyUser.setCreateTime(originUser.getCreateTime());
        return safetyUser;
    }	  

在这里插入图片描述

  1. UserController文件里新增login方法

     @PostMapping("/login")
     public User login(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request){
         if (userLoginRequest==null) {
             return null;
         }
         String userAccount = userLoginRequest.getUserAccount();
         String userPassword = userLoginRequest.getUserPassword();
         if (StringUtils.isAnyBlank(userAccount, userPassword)){
             return null;
         }
         return userService.userLogin(userAccount, userPassword,request);
     }
    

在这里插入图片描述

在后端添加查询方法

UserController文件里添加isAdmin方法来判断是否为管理员

 /**
     * 是否为管理员
     *
     * @param request
     * @return 若是管理员则返回true
     */
    private boolean isAdmin(HttpServletRequest request){
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user=(User) userObj;
        return user != null && user.getUserRole() == ADMIN_USER;
    }

在这里插入图片描述

UserController文件里添加searchUsers方法

@GetMapping("/search")
public List<User> searchUsers(String username,HttpServletRequest request){
    if(!isAdmin(request)){
        return new ArrayList<>();
    }
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    if (StringUtils.isNotBlank(username)){
        // 模糊查询
        queryWrapper.like("username",username);
    }
    List<User> list = userService.list(queryWrapper);
    return list.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
}

在这里插入图片描述

在后端添加删除功能

UserController文件里添加deleteUser方法

@PostMapping("/delete")
public boolean deleteUser(@RequestBody Long id,HttpServletRequest request){
    if (!isAdmin(request)){
        return false;
    }
    if (id<=0){
        return false;
    }
    return userService.removeById(id);
}

在这里插入图片描述

完成前端登录功能

  1. package.json文件里找到start:dev并点击其左侧的三角启动按钮来启动前端项目
    在这里插入图片描述

  2. 找到src.pages.user.Login.index.tsx文件
    在这里插入图片描述

找到**Footer **部分
在这里插入图片描述
按住Ctrl键并鼠标左键点进去
更改登录界面的样式
更改defaultMessageUncommen出品(可自定义修改)
在这里插入图片描述

更改DefaultFooter部分

<DefaultFooter
      copyright={`${currentYear} ${defaultMessage}`}
      links={[
        {
          key: '优快云',
          title: '优快云',
          href: 优快云,
          blankTarget: true,//在新页面打开
        },
        {
          key: 'github',
          title: <><GithubOutlined />Uncommon github</>,
          href: 'https://github.com/15222039615',
          blankTarget: true,
        },
        {
          key: 'gitee',
          title: 'Uncommon gitee',
          href: 'https://gitee.com/Uncommen',
          blankTarget: true,
        },
      ]}
    />

在这里插入图片描述
效果:
在这里插入图片描述
新建src.constants.index.ts文件
在这里插入图片描述
在该文件里添加要用到的常量

export const SYSTEM_LOG0 = "https://www.toopic.cn/public/uploads/small/163756299623163756299625.png"

export const 优快云 = "https://blog.youkuaiyun.com/Uncommen?spm=1010.2135.3001.5343"

在这里插入图片描述

回到src.pages.user.Login.index.tsx文件
添加导入

import {优快云, SYSTEM_LOG0} from "@/constants";

在这里插入图片描述

找到LoginForm部分,修改Logo信息
在这里插入图片描述
效果:
在这里插入图片描述
删除LoginForm部分的actions(删除其它登录方式)
在这里插入图片描述
删除LoginForm部分的Tabs内的手机号登录
在这里插入图片描述
将下面的LoginMessage改为错误的账号和密码在这里插入图片描述
在该文件内按Ctrl+R键全局替换用户名账号
placeholder改为“请输入账号”
在这里插入图片描述
placeholder改为“请输入密码”
在这里插入图片描述
ProFormText部分的mobile类型删除
在这里插入图片描述
这部分也删除
在这里插入图片描述
更改后面的忘记密码信息
在这里插入图片描述
效果:
在这里插入图片描述
找到src/services/ant-design-pro/typings.d.ts文件,重构LoginParams内的字段usernamepassworduserAccountuserPassword
在这里插入图片描述
在这里插入图片描述
找到src/services/ant-design-pro/api.ts文件,更改其中的登录路径为*/api/user/login*:
在这里插入图片描述
找到src/app.tsx文件,添加请求配置

export const request: RequestConfig = {
  timeout: 10000,
}

在这里插入图片描述
找到config/proxy.ts文件,修改代理为 http://localhost:8080
在这里插入图片描述
在后端application.yml文件里添加配置

  servlet:
    context-path: /api

在这里插入图片描述
在前端src/pages/user/Login/index.tsx文件内全局替换usernameuserAccount
在这里插入图片描述
全局替换passworduserPassword(注意区分大小写)
在这里插入图片描述
ProFormText.Password部分添加密码校验规则
在这里插入图片描述
全局替换msguser
更改登录成功条件 user.status ==== ‘ok’user
在这里插入图片描述

测试登录功能

启动前后端项目
使用真实的数据登录
显示登陆成功(按F12打开开发者工具)
在这里插入图片描述
并且得到后端传回的响应数据
在这里插入图片描述
测试成功!

完成前端注册功能

  1. 进入该文件src/pages/user/Login/index.tsx,新增 新用户注册 选项并添加分隔符:

     <div
         style={{
           marginBottom: 24,
         }}
       >
         <Space split={<Divider type={"vertical"}/>}>
           <ProFormCheckbox noStyle name="autoLogin">
             自动登录
           </ProFormCheckbox>
           <Link to="/user/register">新用户注册</Link>
           <a
             style={{
               float: 'right',
             }}
             href={优快云}
             target="_blank"
             rel="noreferrer"
           >
             忘记密码
           </a>
         </Space>
       </div>
    

在这里插入图片描述
效果图:
在这里插入图片描述

  1. 复制src/pages/user/Login该文件并粘贴到src/pages/user路径下,注意改名为 Register
    在这里插入图片描述
    删除src/pages/user/Register/index.tsx文件内的:

     const fetchUserInfo = async () => {
       const userInfo = await initialState?.fetchUserInfo?.();
       if (userInfo) {
         await setInitialState((s) => ({
           ...s,
           currentUser: userInfo,
         }));
       }
     };
    

     const { initialState, setInitialState } = useModel('@@initialState');
    

     await fetchUserInfo();
    

     <ProFormCheckbox noStyle name="autoLogin">
       自动登录
     </ProFormCheckbox>
    

     	<div
     	style={{
    	 marginBottom: 24,
     	 }}
     	>
      	 <a
         style={{
           float: 'right',
         }}
         href={优快云}
         target="_blank"
         rel="noreferrer"
       	>
         忘记密码请联系Uncommon
      	 </a>
     	</div>
    

     const [userLoginState, setUserLoginState] = useState<API.LoginResult>({});
    

     const { status, type: loginType } = userLoginState;
    

     {status === 'error' && loginType === 'account' && (
       <LoginMessage content={'错误的账号和密码'} />
     )}
    

     const LoginMessage: React.FC<{
       content: string;
     }> = ({ content }) => (
       <Alert
         style={{
           marginBottom: 24,
         }}
         message={content}
         type="error"
         showIcon
       />
     );
    

    可以按住 Alt + Ctrl + O 删除所有未使用到的引用

  2. 按住Ctrl + R替换文件内的LoginRegister,注意LoginForm无需更改

  3. LoginForm下添加代码:

     submitter={{
        searchConfig: {
          submitText: '注册'
        }
       }}
    

在这里插入图片描述

  1. 添加 校验密码 代码:

     <ProFormText.Password
             name="checkPassword"
             fieldProps={{
               size: 'large',
               prefix: <LockOutlined className={styles.prefixIcon} />,
             }}
             placeholder="请再次输入密码"
             rules={[
               {
                 required: true,
                 message: '确认密码是必填项!',
               },
               {
                 min: 8,
                 type: "string",
                 message: '密码长度不小于 8!',
               },
             ]}
           />
    

在这里插入图片描述

  1. 删除代码:

      <div
         style={{
           marginBottom: 24,
         }}
       >
         <Space split={<Divider type={"vertical"}/>}>
           <ProFormCheckbox noStyle name="autoLogin">
             自动登录
           </ProFormCheckbox>
           <Link to="/user/register">新用户注册</Link>
           <a
             style={{
               float: 'right',
             }}
             href={优快云}
             target="_blank"
             rel="noreferrer"
           >
             忘记密码
           </a>
         </Space>
       </div>
    

在这里插入图片描述

  1. 进入src/services/ant-design-pro/typings.d.ts文件内,添加注册参数:

      type RegisterParams = {
     userAccount?: string;
     userPassword?: string;
     checkPassword?: string;
     type?: string;
     };
    

在这里插入图片描述

  1. 进入src/services/ant-design-pro/api.ts文件内,添加注册接口代码:

     export async function register(body: API.RegisterParams, options?: { [key: string]: any }) {
       return request<API.RegisterResult>('/api/user/register', {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json',
         },
         data: body,
         ...(options || {}),
       });
     }
    

    在这里插入图片描述
    添加搜索用户接口代码:

     export async function searchUsers(options?: { [key: string]: any }) {
       return request<API.CurrentUser[]>('/api/user/search', {
         method: 'GET',
         ...(options || {}),
       });
     }
    

在这里插入图片描述

  1. 添加(更改)注册成功后跳转回登录页面代码:

      const id  = await register(values);
       if (id > 0) {
         const defaultLoginSuccessMessage = '注册成功!';
         message.success(defaultLoginSuccessMessage);
         /** 此方法会跳转到 redirect 参数所在的位置 */
         if (!history) return;
         const { query } = history.location;
         history.push({
           pathname: '/user/login',
           query,
         });
         return;
       } else {
         throw new Error(`redirect error id = ${id}`);
       }
    

在这里插入图片描述

  1. config/routes.ts内添加注册路由:

      {name: '注册', path: '/user/register', component: './user/Register'},
    

在这里插入图片描述

  1. 修改后端 UserServiceImpl 类里的 UserRegister 方法的返回值为 user.getId()
    在这里插入图片描述

  2. 删除并更改src/pages/Admin/UserManage/index.tsx为:

    import React from 'react';
    
    const UserManage: React.FC = () => {
      return (
        <div id="userManage">
        </div>
      );
    };
    export default UserManage;
    
  3. 在浏览器进入https://procomponents.ant.design/网站,在组件里找到高级表格
    在这里插入图片描述
    展开并复制上图表格的代码:

    import { EllipsisOutlined, PlusOutlined } from '@ant-design/icons';
    import type { ActionType, ProColumns } from '@ant-design/pro-components';
    import { ProTable, TableDropdown } from '@ant-design/pro-components';
    import { Button, Dropdown, Space, Tag } from 'antd';
    import { useRef } from 'react';
    import request from 'umi-request';
    export const waitTimePromise = async (time: number = 100) => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(true);
        }, time);
      });
    };
    
    export const waitTime = async (time: number = 100) => {
      await waitTimePromise(time);
    };
    
    type GithubIssueItem = {
      url: string;
      id: number;
      number: number;
      title: string;
      labels: {
        name: string;
        color: string;
      }[];
      state: string;
      comments: number;
      created_at: string;
      updated_at: string;
      closed_at?: string;
    };
    
    const columns: ProColumns<GithubIssueItem>[] = [
      {
        dataIndex: 'index',
        valueType: 'indexBorder',
        width: 48,
      },
      {
        title: '标题',
        dataIndex: 'title',
        copyable: true,
        ellipsis: true,
        tip: '标题过长会自动收缩',
        formItemProps: {
          rules: [
            {
              required: true,
              message: '此项为必填项',
            },
          ],
        },
      },
      {
        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',
          },
        },
      },
      {
        disable: true,
        title: '标签',
        dataIndex: 'labels',
        search: false,
        renderFormItem: (_, { defaultRender }) => {
          return defaultRender(_);
        },
        render: (_, record) => (
          <Space>
            {record.labels.map(({ name, color }) => (
              <Tag color={color} key={name}>
                {name}
              </Tag>
            ))}
          </Space>
        ),
      },
      {
        title: '创建时间',
        key: 'showTime',
        dataIndex: 'created_at',
        valueType: 'date',
        sorter: true,
        hideInSearch: true,
      },
      {
        title: '创建时间',
        dataIndex: 'created_at',
        valueType: 'dateRange',
        hideInTable: true,
        search: {
          transform: (value) => {
            return {
              startTime: value[0],
              endTime: value[1],
            };
          },
        },
      },
      {
        title: '操作',
        valueType: 'option',
        key: 'option',
        render: (text, record, _, action) => [
          <a
            key="editable"
            onClick={() => {
              action?.startEditable?.(record.id);
            }}
          >
            编辑
          </a>,
          <a href={record.url} target="_blank" rel="noopener noreferrer" key="view">
            查看
          </a>,
          <TableDropdown
            key="actionGroup"
            onSelect={() => action?.reload()}
            menus={[
              { key: 'copy', name: '复制' },
              { key: 'delete', name: '删除' },
            ]}
          />,
        ],
      },
    ];
    
    export default () => {
      const actionRef = useRef<ActionType>();
      return (
        <ProTable<GithubIssueItem>
          columns={columns}
          actionRef={actionRef}
          cardBordered
          request={async (params = {}, sort, filter) => {
            console.log(sort, filter);
            await waitTime(2000);
            return request<{
              data: GithubIssueItem[];
            }>('https://proapi.azurewebsites.net/github/issues', {
              params,
            });
          }}
          editable={{
            type: 'multiple',
          }}
          columnsState={{
            persistenceKey: 'pro-table-singe-demos',
            persistenceType: 'localStorage',
            onChange(value) {
              console.log('value: ', value);
            },
          }}
          rowKey="id"
          search={{
            labelWidth: 'auto',
          }}
          options={{
            setting: {
              listsHeight: 400,
            },
          }}
          form={{
            // 由于配置了 transform,提交的参与与定义的不同这里需要转化一下
            syncToUrl: (values, type) => {
              if (type === 'get') {
                return {
                  ...values,
                  created_at: [values.startTime, values.endTime],
                };
              }
              return values;
            },
          }}
          pagination={{
            pageSize: 5,
            onChange: (page) => console.log(page),
          }}
          dateFormatter="string"
          headerTitle="高级表格"
          toolBarRender={() => [
            <Button
              key="button"
              icon={<PlusOutlined />}
              onClick={() => {
                actionRef.current?.reload();
              }}
              type="primary"
            >
              新建
            </Button>,
            <Dropdown
              key="menu"
              menu={{
                items: [
                  {
                    label: '1st item',
                    key: '1',
                  },
                  {
                    label: '2nd item',
                    key: '1',
                  },
                  {
                    label: '3rd item',
                    key: '1',
                  },
                ],
              }}
            >
              <Button>
                <EllipsisOutlined />
              </Button>
            </Dropdown>,
          ]}
        />
      );
    };
    
  4. 删除代码:

     {
       disable: true,
    	title: '标签',
       dataIndex: 'labels',
     search: false,
      renderFormItem: (_, { defaultRender }) => {
         return defaultRender(_);
       },
       render: (_, record) => (
         <Space>
           {record.labels.map(({ name, color }) => (
             <Tag color={color} key={name}>
               {name}
         </Tag>
           ))}
         </Space>
       ),
     },
    

    	disable: true,
    	title: '状态',
    	dataIndex: 'state',
    	filters: true,
    	onFilter: true,
    	ellipsis: true,
    

    	<Dropdown
      key="menu"
      menu={{
        items: [
          {
            label: '1st item',
            key: '1',
          },
          {
            label: '2nd item',
            key: '1',
          },
          {
            label: '3rd item',
            key: '1',
          },
        ],
      }}
    >
      <Button>
        <EllipsisOutlined />
      </Button>
    </Dropdown>
    

    toolBarRender={() => [
      <Button
        key="button"
        icon={<PlusOutlined />}
        onClick={() => {
          actionRef.current?.reload();
        }}
        type="primary"
      >
        新建
      </Button>,
    
    ]}
    

    	formItemProps: {
      rules: [
        {
          required: true,
          message: '此项为必填项',
        },
      ],
    },
    

    {
      title: '创建时间',
      key: 'showTime',
      dataIndex: 'created_at',
      valueType: 'date',
      sorter: true,
      hideInSearch: true,
    },
    {
      title: '创建时间',
      dataIndex: 'created_at',
      valueType: 'dateRange',
      hideInTable: true,
      search: {
        transform: (value) => {
          return {
            startTime: value[0],
            endTime: value[1],
          };
        },
      },
    },
    

    	return request<{
      data: CurrentUser[];
    }>('https://proapi.azurewebsites.net/github/issues', {
      params,
    });
    
  5. 改写列信息:

    	  {
        dataIndex: 'id ',
        valueType: 'indexBorder',
        width: 48,
      },
      {
        title: '用户名',
        dataIndex: 'username',
        copyable: true,
      },
      {
        title: '用户账号',
        dataIndex: 'userAccount',
        copyable: true,
      },
      {
        title: '头像',
        dataIndex: 'avatarUrl',
        render: (_, record) => {
          if (record.avatarUrl)
            return <Image src={record.avatarUrl} width={100} height={100}/>
          return <Image
            width={100}
            height={100}
            src="error"
            fallback="data:image/png;base64,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"
          />
        },
        copyable: true,
      },
      {
        title: '性别',
        dataIndex: 'gender',
      },
      {
        title: '电话',
        dataIndex: 'phone',
        copyable: true,
      },
      {
        title: '邮件',
        dataIndex: 'email',
        copyable: true,
      },
      {
        title: '状态',
        dataIndex: 'userStatus',
      },
      {
        title: '角色',
        dataIndex: 'userRole',
        valueType: 'select',
        valueEnum: {
          0: {
            text: '普通用户',
            status: 'Default',
          },
          1: {
            text: '管理员',
            status: 'Success',
          },
        },
      },
      {
        title: '创建时间',
        dataIndex: 'createTime',
        valueType: 'date',
      },
    
  6. 进入src/services/ant-design-pro/api.ts文件内,添加获取当前用户接口:

    	/** 获取当前的用户 GET /api/user/current */
    export async function currentUser(options?: { [key: string]: any }) {
      return request< API.CurrentUser >('/api/user/current', {
        method: 'GET',
        ...(options || {}),
      });
    }
    
  7. 进入src/app.tsx文件内
    添加常量:

    const NO_NEED_WHITE_LIST=['/user/register',loginPath];
    

在这里插入图片描述
这边也要改写一下:
在这里插入图片描述
达到此效果便OK了:
在这里插入图片描述

完成后端基本功能

  1. user表中新增planetCode
    在这里插入图片描述
    再次通过MybatisX-Generator功能生成代码
    在这里插入图片描述
    注意修改src/main/resources/mapper/UserMapper.xml文件内容的路径引用
    在这里插入图片描述
    将新生成的实体类User里的字段复制粘贴到我们自己的User实体类里

      /**
      * 星球编号
      */
     private String planetCode;
    

在这里插入图片描述
删除新生成的generator文件包
在文件src/main/java/com/example/model/domain/request/UserRegisterRequest.java里添加字段planetCode
在这里插入图片描述
注意在src/test/java/com/example/service/UserServiceTest.java测试类里添加planetCode相关字段内容:
在这里插入图片描述

  1. 新增src/main/java/com/example/common/ErrorCode.java错误码枚举类

     package com.example.common;
     
     /**
      * 错误码
      *
      * @author Uncommon
      */
     public enum ErrorCode {
         SUCCESS(0,"OK",""),
         PARAMS_ERROR(40000,"请求参数错误",""),
         NULL_ERROR(40001,"请求参数为空",""),
         NOT_LOGIN(40100,"未登录",""),
         NO_AUTH(40101,"无权限",""),
         SYSTEM_ERROR(50000,"系统内部异常","");
         private final int code;
     
         // 状态码信息
         private final String message;
     
         // 状态码详情
         private final String 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;
         }
     }
    

在这里插入图片描述

  1. 新增src/main/java/com/example/common/BaseResponse.java通用返回类

     package com.example.common;
     
     import lombok.Data;
     
     import java.io.Serializable;
     
     /**
      * 通用返回类
      *
      * @param <T>
      * @author Uncommon
      */
     @Data
     public class BaseResponse<T> implements Serializable {
         // 返回码
         private int code;
     
         // 返回值信息
         private String message;
     
         // 返回值数据
         private T data;
     
         // 返回值描述
         private String description;
     
         public BaseResponse(int code, T data, String message,String description) {
             this.code = code;
             this.message = message;
             this.data = data;
             this.description = description;
         }
         public BaseResponse(int code, T data,String message){
             this(code, data,message,"");
         }
         public BaseResponse(int code, T data){
             this(code, data,"","");
         }
         public BaseResponse(ErrorCode errorCode){
             this(errorCode.getCode(),null,errorCode.getMessage(),errorCode.getDescription());
         }
     
     }
    

在这里插入图片描述

  1. 新增src/main/java/com/example/common/ResultUtils.java工具类:

     package com.example.common;
     
     /**
      *  统一返回工具类
      *
      * @author Uncommon
      */
     public class ResultUtils {
     
         /**
          * 成功
          *
          * @param data
          * @return
          * @param <T>
          */
         public static <T> BaseResponse<T> success(T data) {
             return new BaseResponse<>(0,data,"OK");
         }
     
         /**
          * 失败
          *
          * @param errorCode
          * @return
          * @param <T>
          */
         public static <T> BaseResponse<T> error(ErrorCode errorCode) {
             return new BaseResponse<>(errorCode);
         }
     
         /**
          * 失败
          *
          * @param errorCode
          * @param message
          * @param description
          * @return
          */
         public static BaseResponse error(ErrorCode errorCode, String message,String description) {
             return new BaseResponse(errorCode.getCode(),null,message,description);
         }
     
         /**
          * 失败
          *
          * @param errorCode
          * @param description
          * @return
          */
         public static BaseResponse error(ErrorCode errorCode,String description) {
             return new BaseResponse(errorCode.getCode(),null,errorCode.getMessage(),description);
         }
     
         /**
          * 失败
          *
          * @param code
          * @param message
          * @param description
          * @return
          */
         public static BaseResponse error(int code, String message,String description) {
             return new BaseResponse(code,null,message,description);
         }
     }
    

在这里插入图片描述

  1. 新增src/main/java/com/example/exception/BusinessException.java异常类:

     package com.example.exception;
     
     import com.example.common.ErrorCode;
     
     /**
      *  自定义异常类
      *
      * @author Uncommon
      */
     public class BusinessException extends RuntimeException{
         private final int code;
     
         private final String description;
     
         public BusinessException(int code,String message ,String description) {
             super(message);
             this.code = code;
             this.description = description;
         }
         public BusinessException(ErrorCode errorCode) {
             super(errorCode.getMessage());
             this.code = errorCode.getCode();
             this.description = errorCode.getDescription();
         }
         public BusinessException(ErrorCode errorCode,String description) {
             super(errorCode.getMessage());
             this.code = errorCode.getCode();
             this.description = description;
         }
     
         public int getCode() {
             return code;
         }
     
         public String getDescription() {
             return description;
         }
     }
    

在这里插入图片描述

  1. 新增src/main/java/com/example/exception/GlobalExceptionHandler.java全局异常处理器:
package com.example.exception;

import com.example.common.BaseResponse;
import com.example.common.ErrorCode;
import com.example.common.ResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理器
 *
 * @author Uncommon
 */
@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.getDescription());
    }

    @ExceptionHandler(RuntimeException.class)
    public BaseResponse runtimeExceptionHandler(RuntimeException e){
        log.error("runtimeException: ",e);
        return ResultUtils.error(ErrorCode.SYSTEM_ERROR,e.getMessage(),"");
    }
}

在这里插入图片描述

  1. 更改src/main/java/com/example/service/impl/UserServiceImpl.java类的异常返回(注意接口UserService内的相应参数也要改)
package com.example.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.common.ErrorCode;
import com.example.common.ResultUtils;
import com.example.exception.BusinessException;
import com.example.mapper.UserMapper;
import com.example.model.domain.User;
import com.example.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.example.content.UserContent.USER_LOGIN_STATE;

/**
 * 用户服务实现类
 *
* @author lwy
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    private UserMapper userMapper;
    /**
     * 对密码进行加盐加密(加盐就是让密码加密后更复杂)
     */
    private static final String SALT="lwy";


    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword,String planetCode) {
        // 密码,账号,二次校验密码 不能为空
        if(StringUtils.isAnyBlank(userPassword,userAccount,checkPassword,planetCode)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"参数为空");
        }
        // 账号长度不小于4
        if(userAccount.length()<4){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账号过短");
        }
        // 密码和二次校验密码 长度不小于8
        if (userPassword.length()<8||checkPassword.length()<8){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"密码过短");
        }
        // 星球编号长度不能大于5
        if (planetCode.length() > 5){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"星球编号过长");
        }
        // 账号不能含有特殊字符
        String validPattern="[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、? ]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账号不合法");
        }
        // 密码与二次校验密码需一致
        if (!userPassword.equals(checkPassword)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"密码不一致");
        }
        // 账号不可重复
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("userAccount",userAccount);
        long count = this.count(queryWrapper);
        if (count>0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账号重复");
        }
        // 星球编号不可重复
        queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("planetCode",planetCode);
        count = this.count(queryWrapper);
        if (count>0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"新球编号重复");
        }
        // 加密密码
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 建立用户实体
        User user =new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setPlanetCode(planetCode);
        // 存储用户信息
        boolean result = this.save(user);
        // 若返回null则不利于后续处理
        if(!result){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"注册失败");
        }
        // 用户注册成功
        return user.getId();
    }

    @Override
    public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 密码,账号不能为空
        if(StringUtils.isAnyBlank(userPassword,userAccount)){
            throw new BusinessException(ErrorCode.NULL_ERROR,"账号或密码不能为空");
        }
        // 账号长度不小于4
        if(userAccount.length()<4){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账号过短");
        }
        // 密码和二次校验密码 长度不小于8
        if (userPassword.length()<8){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"密码过短");
        }
        // 账号不能含有特殊字符
        String validPattern="[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、? ]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账号不合法");
        }
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 账号不可重复
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("userAccount",userAccount);
        queryWrapper.eq("userPassword",encryptPassword);
        User user = userMapper.selectOne(queryWrapper);
        //用户不存在
        if(user == null){
            log.info("User login failed,userAccount cannot match userPassword");
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户不存在");
        }
        //用户脱敏
        User safetyUser=getSafetyUser(user);
        //记录用户的登录状态
        request.getSession().setAttribute(USER_LOGIN_STATE,safetyUser);

        return safetyUser;
    }

    @Override
    public int userLogout(HttpServletRequest request) {
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return 1;
    }

    /**
     * 用户脱敏
     *
     * @param originUser 脱敏前的用户
     * @return 脱敏后用户信息
     */
    @Override
    public User getSafetyUser(User originUser) {
        if (originUser == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户为空");
        }
        User safetyUser=new User();
        safetyUser.setId(originUser.getId());
        safetyUser.setUsername(originUser.getUsername());
        safetyUser.setUserAccount(originUser.getUserAccount());
        safetyUser.setAvatarUrl(originUser.getAvatarUrl());
        safetyUser.setGender(originUser.getGender());
        safetyUser.setPhone(originUser.getPhone());
        safetyUser.setEmail(originUser.getEmail());
        safetyUser.setUserRole(originUser.getUserRole());
        safetyUser.setPlanetCode(originUser.getPlanetCode());
        safetyUser.setUserStatus(originUser.getUserStatus());
        safetyUser.setCreateTime(originUser.getCreateTime());
        return safetyUser;
    }
}
  1. 更改src/main/java/com/example/controller/UserController.java控制类里的异常返回值:
package com.example.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.common.BaseResponse;
import com.example.common.ErrorCode;
import com.example.common.ResultUtils;
import com.example.exception.BusinessException;
import com.example.model.domain.User;
import com.example.model.domain.request.UserLoginRequest;
import com.example.model.domain.request.UserRegisterRequest;
import com.example.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;

import static com.example.content.UserContent.ADMIN_USER;
import static com.example.content.UserContent.USER_LOGIN_STATE;

/**
 * 用户服务控制类
 *
 * @author lwy
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    UserService userService;

    @PostMapping("/register")
    public BaseResponse<Long> register(@RequestBody UserRegisterRequest userRegisterRequest){
        if (userRegisterRequest==null) {
            throw new BusinessException(ErrorCode.NULL_ERROR,"参数为空");
        }
        String userAccount = userRegisterRequest.getUserAccount();
        String userPassword = userRegisterRequest.getUserPassword();
        String checkPassword = userRegisterRequest.getCheckPassword();
        String planetCode = userRegisterRequest.getPlanetCode();
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword,planetCode)){
            throw new BusinessException(ErrorCode.NULL_ERROR,"参数为空");
        }
        Long result= userService.userRegister(userAccount, userPassword, checkPassword,planetCode);
        return ResultUtils.success(result);
    }
    @PostMapping("/login")
    public BaseResponse<User> login(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request){
        if (userLoginRequest==null) {
            throw new BusinessException(ErrorCode.NULL_ERROR,"参数为空");
        }
        String userAccount = userLoginRequest.getUserAccount();
        String userPassword = userLoginRequest.getUserPassword();
        if (StringUtils.isAnyBlank(userAccount, userPassword)){
            throw new BusinessException(ErrorCode.NULL_ERROR,"账号或密码为空");
        }
        User user = userService.userLogin(userAccount, userPassword, request);
        return ResultUtils.success(user);
    }

    @PostMapping("/logout")
    public BaseResponse<Integer> logout(HttpServletRequest request){
        if (request==null) {
            throw new BusinessException(ErrorCode.NULL_ERROR,"参数为空");
        }
        int result = userService.userLogout(request);
        return ResultUtils.success(result);
    }

    @GetMapping("/current")
    public BaseResponse<User> getCurrentUser(HttpServletRequest request){
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User currentUser = (User) userObj;
        if (currentUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN,"用户未登录");
        }
        Long userId = currentUser.getId();
        User user = userService.getById(userId);
        User safetyUser = userService.getSafetyUser(user);
        return ResultUtils.success(safetyUser);
    }

    @GetMapping("/search")
    public BaseResponse<List<User>> searchUsers(String username,HttpServletRequest request){
        if(!isAdmin(request)){
            throw new BusinessException(ErrorCode.NO_AUTH,"用户不是管理员");
        }
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        if (StringUtils.isNotBlank(username)){
            // 模糊查询
            queryWrapper.like("username",username);
        }
        List<User> list = userService.list(queryWrapper);
        List<User> collect = list.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
        return ResultUtils.success(collect);
    }

    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteUser(@RequestBody Long id,HttpServletRequest request){
        if (!isAdmin(request)){
            throw new BusinessException(ErrorCode.NO_AUTH,"用户不是管理员");
        }
        if (id<=0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"参数错误");
        }
        boolean result = userService.removeById(id);
        return ResultUtils.success(result);
    }

    /**
     * 是否为管理员
     *
     * @param request
     * @return 若是管理员则返回true
     */
    private boolean isAdmin(HttpServletRequest request){
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user=(User) userObj;
        return user != null && user.getUserRole() == ADMIN_USER;
    }
}

后端优化完成!

完成前端基本功能

  1. 在文件src/pages/user/Register/index.tsx内添加planetCode输入框:
 <ProFormText
                name="planetCode"
                fieldProps={{
                  size: 'large',
                  prefix: <UserOutlined className={styles.prefixIcon}/>,
                }}
                placeholder="请输入星球编号"
                rules={[
                  {
                    required: true,
                    message: '星球编号是必填项!',
                  },
                ]}
              />

在这里插入图片描述

  1. 修改 注册成功 相应逻辑:
try {
      // 注册
      const id = await register(values);
      // @ts-ignore
      // 忽略res.data可能未定义的报错
      if (id) {
        const defaultLoginSuccessMessage = '注册成功!';
        message.success(defaultLoginSuccessMessage);
        /** 此方法会跳转到 redirect 参数所在的位置 */
        if (!history) return;
        const {query} = history.location;
        history.push({
          pathname: '/user/login',
          query,
        });
        return;
      }
    } catch (error: any) {
      const defaultLoginFailureMessage = '注册失败,请重试!';
      message.error(defaultLoginFailureMessage);
    }

在这里插入图片描述

  1. src/services/ant-design-pro/typings.d.ts文件内修改注册参数:
  //注册参数
  type RegisterParams = {
    userAccount?: string;
    userPassword?: string;
    checkPassword?: string;
    planetCode?: string;
    type?: string;
  };

添加通用返回参数:

 /**
   * 通用返回参数
   */
  type BaseResponse<T> = {
    code?: number;
    data?: T;
    message?: string;
    description?: string;
  }

在这里插入图片描述

  1. 修改src/services/ant-design-pro/api.ts文件内的各接口返回值:
// @ts-ignore
/* eslint-disable */
import request from '@/plugins/globalRequest';

/** 获取当前的用户 GET /api/user/current */
export async function currentUser(options?: { [key: string]: any }) {
  return request<API.BaseResponse< API.CurrentUser >>('/api/user/current', {
    method: 'GET',
    ...(options || {}),
  });
}

/** 退出登录接口 POST /api/user/logout */
export async function outLogin(options?: { [key: string]: any }) {
  return request<API.BaseResponse<number>>('/api/user/logout', {
    method: 'POST',
    ...(options || {}),
  });
}

/** 登录接口 POST /api/user/login */
export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
  return request<API.BaseResponse<API.LoginResult>>('/api/user/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    data: body,
    ...(options || {}),
  });
}

/** 注册接口 POST /api/user/register */
export async function register(body: API.RegisterParams, options?: { [key: string]: any }) {
  return request<API.BaseResponse<API.RegisterResult>>('/api/user/register', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    data: body,
    ...(options || {}),
  });
}

/** 搜索用户 GET /api/user/search */
export async function searchUsers(options?: { [key: string]: any }) {
  return request<API.BaseResponse<API.CurrentUser[]>>('/api/user/search', {
    method: 'GET',
    ...(options || {}),
  });
}

/** 此处后端没有提供注释 GET /api/notices */
export async function getNotices(options?: { [key: string]: any }) {
  return request<API.NoticeIconList>('/api/notices', {
    method: 'GET',
    ...(options || {}),
  });
}

/** 获取规则列表 GET /api/rule */
export async function rule(
  params: {
    // query
    /** 当前的页码 */
    current?: number;
    /** 页面的容量 */
    pageSize?: number;
  },
  options?: { [key: string]: any },
) {
  return request<API.RuleList>('/api/rule', {
    method: 'GET',
    params: {
      ...params,
    },
    ...(options || {}),
  });
}

/** 新建规则 PUT /api/rule */
export async function updateRule(options?: { [key: string]: any }) {
  return request<API.RuleListItem>('/api/rule', {
    method: 'PUT',
    ...(options || {}),
  });
}

/** 新建规则 POST /api/rule */
export async function addRule(options?: { [key: string]: any }) {
  return request<API.RuleListItem>('/api/rule', {
    method: 'POST',
    ...(options || {}),
  });
}

/** 删除规则 DELETE /api/rule */
export async function removeRule(options?: { [key: string]: any }) {
  return request<Record<string, any>>('/api/rule', {
    method: 'DELETE',
    ...(options || {}),
  });
}

  1. 新增src/plugins/globalRequest.ts全局请求拦截器:
import {extend} from 'umi-request';
import {message} from "antd";
import {history} from "@@/core/history";
import {stringify} from "querystring";

/**
 * 配置request请求时的默认参数
 */
const request = extend({
  credentials: 'include', // 默认请求是否带上cookie
  // requestType: 'form',
});

/**
 * 所以请求拦截器
 */
request.interceptors.request.use((url, options): any => {
  console.log(`do request url = ${url}`)

  return {
    url,
    options: {
      ...options,
      headers: {},
    },
  };
});

/**
 * 所有响应拦截器
 */
request.interceptors.response.use(async (response, options): Promise<any> => {

  const res = await response.clone().json();
  if (res.code === 0) {
    return res.data;
  }
  if (res.code === 40100) {
    message.error("请先登录");
    history.replace({
      pathname: '/user/login',
      search: stringify({
        redirect: location.pathname,
      }),
    });
  } else {
    message.error(res.description);
  }
  return res.data;
});

export default request;

在这里插入图片描述

  1. 在文件src/components/RightContent/AvatarDropdown.tsx内添加退出登录相关代码:
    在这里插入图片描述
    前端优化完成!
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值