前端源代码: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.项目瘦身
删除src目录下的e2e文件夹
删除tests文件夹
1.Idea新建项目:
注意SpringBoot版本不要选择3.0及以上!
选择如图六个依赖:
Lombok
Spring Boot DevTools
Spring Configuration Processor
MySQL Driver
Spring Web
MyBatis Framework
Mybatis-Plus
com.baomidou
mybatis-plus-boot-starter
3.5.2
Junit
junit
junit
4.13.2
test
注意:添加依赖后需更新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目录下添加mapper和model软件包
后端初始化完毕!
然后在该架构中创建新表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 '用户表';
在Idea的设置=》插件里搜索并安装MyBatisX插件(安装后需要重启Idea)
然后再新建的user表上右键选择MybatisX-Generator
然后如下图填写:
点击Finish来自动生成基础文件,然后将生成的文件移动至应属目录下
domin移至model目录下
UserMapper移至mapper目录下
UserService移至service目录下
UserServiceImpl移至service.impl目录下
注意更改文件内的相关路径
在启动类UserCenterApplication里添加注解**@MapperScan(“com.example.mapper”)**
在pom.xml文件里添加commons-lang3依赖(依赖网站:https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 )
org.apache.commons
commons-lang3
3.12.0
在UserService接口里添加接口方法userRegister
long userRegister(String userAccount,String userPassword,String checkPassword);
@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 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;
}
在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);
}
新增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;
}
在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文件里的路径引用
删除其它新生成的文件
新增model.domin.request.UserLoginRequest文件
/**
* 用户登录请求体
*
* @author lwy
*/
@Data
public class UserLoginRequest implements Serializable {
private static final long serialVersionUID= -7188479473325271448L;
private String userAccount;
private String userPassword;
}
在UserContent文件里添加用户登录状态键:
String USER_LOGIN_STATE="userLoginState";
在userService文件里添加userLogin方法声明
User userLogin(String userAccount, String userPassword , HttpServletRequest request);
在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 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;
}
在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;
}
在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 searchUsers(String username,HttpServletRequest request){
if(!isAdmin(request)){
return new ArrayList<>();
}
QueryWrapper queryWrapper=new QueryWrapper<>();
if (StringUtils.isNotBlank(username)){
// 模糊查询
queryWrapper.like("username",username);
}
List 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);
}
找到**Footer **部分
按住Ctrl键并鼠标左键点进去
更改登录界面的样式
更改defaultMessage为Uncommen出品(可自定义修改)
更改DefaultFooter部分
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 CSDN = "https://blog.csdn.net/Uncommen?spm=1010.2135.3001.5343"
回到src.pages.user.Login.index.tsx文件
添加导入
import {CSDN, 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内的字段username和password为userAccount和userPassword
找到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文件内全局替换username为userAccount
全局替换password为userPassword(注意区分大小写)
在ProFormText.Password部分添加密码校验规则
全局替换msg为user
更改登录成功条件 user.status ==== ‘ok’ 为 user
启动前后端项目
使用真实的数据登录
显示登陆成功(按F12打开开发者工具)
并且得到后端传回的响应数据
测试成功!
进入该文件src/pages/user/Login/index.tsx,新增 新用户注册 选项并添加分隔符:
}>
自动登录
新用户注册
忘记密码
复制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();
和
自动登录
和
和
const [userLoginState, setUserLoginState] = useState({});
和
const { status, type: loginType } = userLoginState;
和
{status === 'error' && loginType === 'account' && (
)}
和
const LoginMessage: React.FC<{
content: string;
}> = ({ content }) => (
);
可以按住 Alt + Ctrl + O 删除所有未使用到的引用
按住Ctrl + R替换文件内的Login为Register,注意LoginForm无需更改
在LoginForm下添加代码:
submitter={{
searchConfig: {
submitText: '注册'
}
}}
添加 校验密码 代码:
,
}}
placeholder="请再次输入密码"
rules={[
{
required: true,
message: '确认密码是必填项!',
},
{
min: 8,
type: "string",
message: '密码长度不小于 8!',
},
]}
/>
删除代码:
}>
自动登录
新用户注册
忘记密码
进入src/services/ant-design-pro/typings.d.ts文件内,添加注册参数:
type RegisterParams = {
userAccount?: string;
userPassword?: string;
checkPassword?: string;
type?: string;
};
进入src/services/ant-design-pro/api.ts文件内,添加注册接口代码:
export async function register(body: API.RegisterParams, options?: { [key: string]: any }) {
return request('/api/user/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
export async function searchUsers(options?: { [key: string]: any }) {
return request('/api/user/search', {
method: 'GET',
...(options || {}),
});
}
添加(更改)注册成功后跳转回登录页面代码:
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}`);
}
在config/routes.ts内添加注册路由:
{name: '注册', path: '/user/register', component: './user/Register'},
删除并更改src/pages/Admin/UserManage/index.tsx为:
import React from 'react';
const UserManage: React.FC = () => {
return (
);
};
export default UserManage;
在浏览器进入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[] = [
{
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) => (
{record.labels.map(({ name, color }) => (
{name}
))}
),
},
{
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) => [
{
action?.startEditable?.(record.id);
}}
>
编辑
,
查看
,
action?.reload()}
menus={[
{ key: 'copy', name: '复制' },
{ key: 'delete', name: '删除' },
]}
/>,
],
},
];
export default () => {
const actionRef = useRef();
return (
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={() => [
}
onClick={() => {
actionRef.current?.reload();
}}
type="primary"
>
新建
,
,
]}
/>
);
};
删除代码:
{
disable: true,
title: '标签',
dataIndex: 'labels',
search: false,
renderFormItem: (_, { defaultRender }) => {
return defaultRender(_);
},
render: (_, record) => (
{record.labels.map(({ name, color }) => (
{name}
))}
),
},
和
disable: true,
title: '状态',
dataIndex: 'state',
filters: true,
onFilter: true,
ellipsis: true,
和
和
toolBarRender={() => [
}
onClick={() => {
actionRef.current?.reload();
}}
type="primary"
>
新建
,
]}
和
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,
});
改写列信息:
{
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
return
},
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',
},
进入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 || {}),
});
}
进入src/app.tsx文件内
添加常量:
const NO_NEED_WHITE_LIST=['/user/register',loginPath];
在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相关字段内容:
新增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;
}
}
新增src/main/java/com/example/common/BaseResponse.java通用返回类
package com.example.common;
import lombok.Data;
import java.io.Serializable;
/**
* 通用返回类
*
* @param
* @author Uncommon
*/
@Data
public class BaseResponse 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());
}
}
新增src/main/java/com/example/common/ResultUtils.java工具类:
package com.example.common;
/**
* 统一返回工具类
*
* @author Uncommon
*/
public class ResultUtils {
/**
* 成功
*
* @param data
* @return
* @param
*/
public static BaseResponse success(T data) {
return new BaseResponse<>(0,data,"OK");
}
/**
* 失败
*
* @param errorCode
* @return
* @param
*/
public static BaseResponse 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);
}
}
新增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;
}
}
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(),"");
}
}
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;
}
}
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;
}
}
后端优化完成!
<ProFormText
name="planetCode"
fieldProps={{
size: 'large',
prefix: <UserOutlined className={styles.prefixIcon}/>,
}}
placeholder="请输入星球编号"
rules={[
{
required: true,
message: '星球编号是必填项!',
},
]}
/>
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);
}
//注册参数
type RegisterParams = {
userAccount?: string;
userPassword?: string;
checkPassword?: string;
planetCode?: string;
type?: string;
};
添加通用返回参数:
/**
* 通用返回参数
*/
type BaseResponse<T> = {
code?: number;
data?: T;
message?: string;
description?: string;
}
// @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 || {}),
});
}
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;