feat(erp): 添加ERP用户管理系统
- 在ResultCode中新增用户相关错误码(用户不存在、用户已存在、密码错误、Token无效等) - 创建ERP用户实体类ErpUser,包含账号、密码、店铺号等字段 - 实现用户注册功能,支持账号、手机号、邮箱唯一性校验 - 实现用户登录功能,支持密码验证和Token生成 - 添加Token验证机制,支持Bearer和自定义Header方式 - 创建用户管理API文档,包含注册、登录接口说明和错误码说明 - 实现IP地址获取功能,记录用户最后登录IP - 添加MD5密码加密和Token生成解析工具类 - 实现用户状态管理(激活/禁用)功能
This commit is contained in:
253
mt-pay/ERP_USER_API.md
Normal file
253
mt-pay/ERP_USER_API.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
# ERP用户管理API文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
ERP用户管理模块提供了用户注册、登录等基础功能,支持账号、密码、店铺号管理。
|
||||||
|
|
||||||
|
## 数据库表
|
||||||
|
|
||||||
|
### erp_user 表结构
|
||||||
|
|
||||||
|
执行以下SQL创建表:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 文件位置:mt-pay/database/erp_user_schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
表字段说明:
|
||||||
|
- `id`: 主键ID
|
||||||
|
- `username`: 账号(唯一,3-50个字符,只能包含字母、数字和下划线)
|
||||||
|
- `password`: 密码(MD5加密,6-20个字符)
|
||||||
|
- `nick_name`: 用户名称(可选,最大50个字符)
|
||||||
|
- `phone`: 手机号(可选,唯一,格式:1开头11位数字)
|
||||||
|
- `email`: 邮箱(可选,唯一,最大100个字符)
|
||||||
|
- `store_code`: 店铺号(最大50个字符)
|
||||||
|
- `status`: 状态(ACTIVE-激活,DISABLED-禁用)
|
||||||
|
- `last_login_time`: 最后登录时间
|
||||||
|
- `last_login_ip`: 最后登录IP
|
||||||
|
- `create_time`: 创建时间
|
||||||
|
- `update_time`: 更新时间
|
||||||
|
|
||||||
|
## API接口
|
||||||
|
|
||||||
|
### 1. 用户注册
|
||||||
|
|
||||||
|
**接口地址:** `POST /api/erp/user/register`
|
||||||
|
|
||||||
|
**请求头:**
|
||||||
|
```
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求体:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "testuser",
|
||||||
|
"password": "123456",
|
||||||
|
"storeCode": "STORE001",
|
||||||
|
"nickName": "测试用户",
|
||||||
|
"phone": "13800138000",
|
||||||
|
"email": "test@example.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明:**
|
||||||
|
- `username` (必填): 账号,3-50个字符,只能包含字母、数字和下划线
|
||||||
|
- `password` (必填): 密码,6-20个字符
|
||||||
|
- `storeCode` (必填): 店铺号,最大50个字符
|
||||||
|
- `nickName` (可选): 用户名称,最大50个字符
|
||||||
|
- `phone` (可选): 手机号,格式:1开头11位数字(如:13800138000),必须唯一
|
||||||
|
- `email` (可选): 邮箱,最大100个字符,必须唯一
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "0000",
|
||||||
|
"message": "注册成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"username": "testuser",
|
||||||
|
"nickName": "测试用户",
|
||||||
|
"phone": "13800138000",
|
||||||
|
"email": "test@example.com",
|
||||||
|
"storeCode": "STORE001",
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"createTime": "2024-12-24T10:00:00"
|
||||||
|
},
|
||||||
|
"timestamp": 1703412000000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误响应:**
|
||||||
|
- 账号已存在:`{"code": "6000", "message": "账号已存在"}`
|
||||||
|
- 手机号已被注册:`{"code": "6000", "message": "手机号已被注册"}`
|
||||||
|
- 邮箱已被注册:`{"code": "6000", "message": "邮箱已被注册"}`
|
||||||
|
- 参数验证失败:`{"code": "4001", "message": "参数验证失败"}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 用户登录
|
||||||
|
|
||||||
|
**接口地址:** `POST /api/erp/user/login`
|
||||||
|
|
||||||
|
**请求头:**
|
||||||
|
```
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求体:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "testuser",
|
||||||
|
"password": "123456"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明:**
|
||||||
|
- `username` (必填): 账号
|
||||||
|
- `password` (必填): 密码
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "0000",
|
||||||
|
"message": "登录成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"username": "testuser",
|
||||||
|
"nickName": "测试用户",
|
||||||
|
"phone": "13800138000",
|
||||||
|
"email": "test@example.com",
|
||||||
|
"storeCode": "STORE001",
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"token": "MTIzNDU2Nzg5MGFiY2RlZjoxMjM0NTY3ODkwYWJjZGVmOjE3MDM0MTIwMDAwMDA6YWJjZGVmMTIzNDU2Nzg5MA==",
|
||||||
|
"tokenExpireTime": 1704016800000,
|
||||||
|
"lastLoginTime": "2024-12-24T10:00:00",
|
||||||
|
"lastLoginIp": "192.168.1.100"
|
||||||
|
},
|
||||||
|
"timestamp": 1703412000000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误响应:**
|
||||||
|
- 账号或密码错误:`{"code": "4002", "message": "账号或密码错误"}`
|
||||||
|
- 账号已被禁用:`{"code": "4003", "message": "账号已被禁用"}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Token使用说明
|
||||||
|
|
||||||
|
### Token生成
|
||||||
|
|
||||||
|
登录成功后,系统会返回一个`token`字段,该token的有效期为7天。
|
||||||
|
|
||||||
|
### Token验证
|
||||||
|
|
||||||
|
后续需要认证的接口,可以在请求头中携带token:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
或者使用自定义header:
|
||||||
|
|
||||||
|
```
|
||||||
|
X-Auth-Token: {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Token验证方法
|
||||||
|
|
||||||
|
在需要认证的接口中,可以通过以下方式验证token:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Autowired
|
||||||
|
private ErpUserService erpUserService;
|
||||||
|
|
||||||
|
// 验证token
|
||||||
|
ErpUser user = erpUserService.validateToken(token);
|
||||||
|
if (user == null) {
|
||||||
|
// Token无效或已过期
|
||||||
|
return Result.fail(ResultCode.TOKEN_INVALID);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 错误码说明
|
||||||
|
|
||||||
|
| 错误码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| 0000 | 操作成功 |
|
||||||
|
| 4000 | 参数错误 |
|
||||||
|
| 4001 | 参数验证失败 |
|
||||||
|
| 4002 | 未授权(账号或密码错误) |
|
||||||
|
| 4003 | 禁止访问(账号被禁用) |
|
||||||
|
| 6000 | 业务错误(账号已存在等) |
|
||||||
|
| 7001 | 用户不存在 |
|
||||||
|
| 7002 | 用户已存在 |
|
||||||
|
| 7003 | 密码错误 |
|
||||||
|
| 7004 | Token无效或已过期 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 安全说明
|
||||||
|
|
||||||
|
1. **密码加密**:密码使用MD5加密存储,生产环境建议使用BCrypt或Argon2等更安全的加密方式。
|
||||||
|
|
||||||
|
2. **Token安全**:
|
||||||
|
- 当前实现使用简单的MD5+Base64编码,生产环境建议使用JWT(JSON Web Token)
|
||||||
|
- Token包含用户ID、用户名、时间戳和签名
|
||||||
|
- Token有效期为7天,过期后需要重新登录
|
||||||
|
|
||||||
|
3. **IP记录**:系统会记录用户最后登录的IP地址,可用于安全审计。
|
||||||
|
|
||||||
|
4. **账号状态**:支持账号禁用功能,禁用后的账号无法登录。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 后续扩展建议
|
||||||
|
|
||||||
|
1. **JWT Token**:将当前简单的Token实现替换为标准的JWT
|
||||||
|
2. **密码策略**:添加密码复杂度要求、密码过期策略
|
||||||
|
3. **登录限制**:添加登录失败次数限制、IP白名单等功能
|
||||||
|
4. **权限管理**:添加角色和权限管理功能
|
||||||
|
5. **操作日志**:记录用户的操作日志,便于审计
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试示例
|
||||||
|
|
||||||
|
### 使用curl测试注册接口
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/api/erp/user/register \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"username": "testuser",
|
||||||
|
"password": "123456",
|
||||||
|
"storeCode": "STORE001",
|
||||||
|
"nickName": "测试用户",
|
||||||
|
"phone": "13800138000",
|
||||||
|
"email": "test@example.com"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用curl测试登录接口
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/api/erp/user/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"username": "testuser",
|
||||||
|
"password": "123456"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 执行数据库表创建SQL后,才能使用注册和登录功能
|
||||||
|
2. 生产环境建议修改`TokenUtils`中的`TOKEN_SECRET`,使用更复杂的密钥
|
||||||
|
3. 建议在生产环境使用HTTPS协议,保护密码和Token传输安全
|
||||||
|
|
||||||
1
mt-pay/database/erp_user_schema.sql
Normal file
1
mt-pay/database/erp_user_schema.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -88,7 +88,27 @@ public enum ResultCode {
|
|||||||
/**
|
/**
|
||||||
* 业务错误
|
* 业务错误
|
||||||
*/
|
*/
|
||||||
BUSINESS_ERROR("6000", "业务错误");
|
BUSINESS_ERROR("6000", "业务错误"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户不存在
|
||||||
|
*/
|
||||||
|
USER_NOT_FOUND("7001", "用户不存在"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户已存在
|
||||||
|
*/
|
||||||
|
USER_EXISTS("7002", "用户已存在"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码错误
|
||||||
|
*/
|
||||||
|
PASSWORD_ERROR("7003", "密码错误"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token无效或已过期
|
||||||
|
*/
|
||||||
|
TOKEN_INVALID("7004", "Token无效或已过期");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应码
|
* 响应码
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.mtkj.mtpay.controller;
|
||||||
|
|
||||||
|
import com.mtkj.mtpay.common.Result;
|
||||||
|
import com.mtkj.mtpay.dto.request.ErpUserLoginRequestDTO;
|
||||||
|
import com.mtkj.mtpay.dto.request.ErpUserRegisterRequestDTO;
|
||||||
|
import com.mtkj.mtpay.dto.response.ErpUserLoginResponseDTO;
|
||||||
|
import com.mtkj.mtpay.dto.response.ErpUserRegisterResponseDTO;
|
||||||
|
import com.mtkj.mtpay.service.ErpUserService;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERP用户控制器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/erp/user")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ErpUserController {
|
||||||
|
|
||||||
|
private final ErpUserService erpUserService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户注册
|
||||||
|
*/
|
||||||
|
@PostMapping("/register")
|
||||||
|
public Result<ErpUserRegisterResponseDTO> register(@Valid @RequestBody ErpUserRegisterRequestDTO request) {
|
||||||
|
log.info("用户注册请求,账号: {}", request.getUsername());
|
||||||
|
ErpUserRegisterResponseDTO response = erpUserService.register(request);
|
||||||
|
return Result.success("注册成功", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
*/
|
||||||
|
@PostMapping("/login")
|
||||||
|
public Result<ErpUserLoginResponseDTO> login(@Valid @RequestBody ErpUserLoginRequestDTO request,
|
||||||
|
HttpServletRequest httpRequest) {
|
||||||
|
log.info("用户登录请求,账号: {}", request.getUsername());
|
||||||
|
|
||||||
|
// 获取客户端IP
|
||||||
|
String loginIp = getClientIp(httpRequest);
|
||||||
|
|
||||||
|
ErpUserLoginResponseDTO response = erpUserService.login(request, loginIp);
|
||||||
|
return Result.success("登录成功", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端IP地址
|
||||||
|
*/
|
||||||
|
private String getClientIp(HttpServletRequest request) {
|
||||||
|
String ip = request.getHeader("X-Forwarded-For");
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理多个IP的情况,取第一个IP
|
||||||
|
if (ip != null && ip.contains(",")) {
|
||||||
|
ip = ip.split(",")[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.mtkj.mtpay.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERP用户登录请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ErpUserLoginRequestDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "账号不能为空")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.mtkj.mtpay.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERP用户注册请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ErpUserRegisterRequestDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "账号不能为空")
|
||||||
|
@Size(min = 3, max = 50, message = "账号长度必须在3-50个字符之间")
|
||||||
|
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "账号只能包含字母、数字和下划线")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 店铺号
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "店铺号不能为空")
|
||||||
|
@Size(max = 50, message = "店铺号长度不能超过50个字符")
|
||||||
|
private String storeCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名称
|
||||||
|
*/
|
||||||
|
@Size(max = 50, message = "用户名称长度不能超过50个字符")
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
@Email(message = "邮箱格式不正确")
|
||||||
|
@Size(max = 100, message = "邮箱长度不能超过100个字符")
|
||||||
|
private String email;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.mtkj.mtpay.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERP用户登录响应DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ErpUserLoginResponseDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名称
|
||||||
|
*/
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 店铺号
|
||||||
|
*/
|
||||||
|
private String storeCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token(用于后续接口认证)
|
||||||
|
*/
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token过期时间(毫秒时间戳)
|
||||||
|
*/
|
||||||
|
private Long tokenExpireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后登录时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime lastLoginTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后登录IP
|
||||||
|
*/
|
||||||
|
private String lastLoginIp;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.mtkj.mtpay.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERP用户注册响应DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ErpUserRegisterResponseDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名称
|
||||||
|
*/
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 店铺号
|
||||||
|
*/
|
||||||
|
private String storeCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
|
|
||||||
87
mt-pay/src/main/java/com/mtkj/mtpay/entity/ErpUser.java
Normal file
87
mt-pay/src/main/java/com/mtkj/mtpay/entity/ErpUser.java
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package com.mtkj.mtpay.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERP用户实体类
|
||||||
|
*/
|
||||||
|
@TableName(value = "erp_user")
|
||||||
|
@Data
|
||||||
|
public class ErpUser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号(唯一)
|
||||||
|
*/
|
||||||
|
@TableField(value = "username", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码(MD5加密)
|
||||||
|
*/
|
||||||
|
@TableField(value = "password", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名称
|
||||||
|
*/
|
||||||
|
@TableField(value = "nick_name", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
@TableField(value = "phone", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
@TableField(value = "email", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 店铺号
|
||||||
|
*/
|
||||||
|
@TableField(value = "store_code", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||||
|
private String storeCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态:ACTIVE-激活,DISABLED-禁用
|
||||||
|
*/
|
||||||
|
@TableField(value = "status", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后登录时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "last_login_time", jdbcType = org.apache.ibatis.type.JdbcType.TIMESTAMP)
|
||||||
|
private LocalDateTime lastLoginTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后登录IP
|
||||||
|
*/
|
||||||
|
@TableField(value = "last_login_ip", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||||
|
private String lastLoginIp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.mtkj.mtpay.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.mtkj.mtpay.entity.ErpUser;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERP用户Mapper接口
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ErpUserMapper extends BaseMapper<ErpUser> {
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.mtkj.mtpay.service;
|
||||||
|
|
||||||
|
import com.mtkj.mtpay.dto.request.ErpUserLoginRequestDTO;
|
||||||
|
import com.mtkj.mtpay.dto.request.ErpUserRegisterRequestDTO;
|
||||||
|
import com.mtkj.mtpay.dto.response.ErpUserLoginResponseDTO;
|
||||||
|
import com.mtkj.mtpay.dto.response.ErpUserRegisterResponseDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERP用户服务接口
|
||||||
|
*/
|
||||||
|
public interface ErpUserService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户注册
|
||||||
|
* @param request 注册请求
|
||||||
|
* @return 注册响应
|
||||||
|
*/
|
||||||
|
ErpUserRegisterResponseDTO register(ErpUserRegisterRequestDTO request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
* @param request 登录请求
|
||||||
|
* @param loginIp 登录IP
|
||||||
|
* @return 登录响应
|
||||||
|
*/
|
||||||
|
ErpUserLoginResponseDTO login(ErpUserLoginRequestDTO request, String loginIp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户名查询用户
|
||||||
|
* @param username 用户名
|
||||||
|
* @return 用户实体
|
||||||
|
*/
|
||||||
|
com.mtkj.mtpay.entity.ErpUser getUserByUsername(String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证Token
|
||||||
|
* @param token Token
|
||||||
|
* @return 用户实体,如果Token无效则返回null
|
||||||
|
*/
|
||||||
|
com.mtkj.mtpay.entity.ErpUser validateToken(String token);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package com.mtkj.mtpay.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.mtkj.mtpay.common.ResultCode;
|
||||||
|
import com.mtkj.mtpay.dto.request.ErpUserLoginRequestDTO;
|
||||||
|
import com.mtkj.mtpay.dto.request.ErpUserRegisterRequestDTO;
|
||||||
|
import com.mtkj.mtpay.dto.response.ErpUserLoginResponseDTO;
|
||||||
|
import com.mtkj.mtpay.dto.response.ErpUserRegisterResponseDTO;
|
||||||
|
import com.mtkj.mtpay.entity.ErpUser;
|
||||||
|
import com.mtkj.mtpay.exception.BusinessException;
|
||||||
|
import com.mtkj.mtpay.mapper.ErpUserMapper;
|
||||||
|
import com.mtkj.mtpay.service.ErpUserService;
|
||||||
|
import com.mtkj.mtpay.util.MD5;
|
||||||
|
import com.mtkj.mtpay.util.TokenUtils;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERP用户服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ErpUserServiceImpl implements ErpUserService {
|
||||||
|
|
||||||
|
private final ErpUserMapper erpUserMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public ErpUserRegisterResponseDTO register(ErpUserRegisterRequestDTO request) {
|
||||||
|
log.info("用户注册请求,账号: {}, 店铺号: {}", request.getUsername(), request.getStoreCode());
|
||||||
|
|
||||||
|
// 检查用户名是否已存在
|
||||||
|
ErpUser existingUser = erpUserMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<ErpUser>()
|
||||||
|
.eq(ErpUser::getUsername, request.getUsername())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingUser != null) {
|
||||||
|
throw new BusinessException(ResultCode.BUSINESS_ERROR.getCode(), "账号已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查手机号是否已存在(如果提供了手机号)
|
||||||
|
if (request.getPhone() != null && !request.getPhone().trim().isEmpty()) {
|
||||||
|
ErpUser existingPhone = erpUserMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<ErpUser>()
|
||||||
|
.eq(ErpUser::getPhone, request.getPhone())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingPhone != null) {
|
||||||
|
throw new BusinessException(ResultCode.BUSINESS_ERROR.getCode(), "手机号已被注册");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查邮箱是否已存在(如果提供了邮箱)
|
||||||
|
if (request.getEmail() != null && !request.getEmail().trim().isEmpty()) {
|
||||||
|
ErpUser existingEmail = erpUserMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<ErpUser>()
|
||||||
|
.eq(ErpUser::getEmail, request.getEmail())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingEmail != null) {
|
||||||
|
throw new BusinessException(ResultCode.BUSINESS_ERROR.getCode(), "邮箱已被注册");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新用户
|
||||||
|
ErpUser user = new ErpUser();
|
||||||
|
user.setUsername(request.getUsername());
|
||||||
|
// 密码使用MD5加密
|
||||||
|
user.setPassword(MD5.md5(request.getPassword()));
|
||||||
|
user.setNickName(request.getNickName());
|
||||||
|
user.setPhone(request.getPhone());
|
||||||
|
user.setEmail(request.getEmail());
|
||||||
|
user.setStoreCode(request.getStoreCode());
|
||||||
|
user.setStatus("ACTIVE");
|
||||||
|
|
||||||
|
// 保存用户
|
||||||
|
erpUserMapper.insert(user);
|
||||||
|
|
||||||
|
log.info("用户注册成功,用户ID: {}, 账号: {}", user.getId(), user.getUsername());
|
||||||
|
|
||||||
|
// 构建响应
|
||||||
|
ErpUserRegisterResponseDTO response = ErpUserRegisterResponseDTO.builder()
|
||||||
|
.id(user.getId())
|
||||||
|
.username(user.getUsername())
|
||||||
|
.nickName(user.getNickName())
|
||||||
|
.phone(user.getPhone())
|
||||||
|
.email(user.getEmail())
|
||||||
|
.storeCode(user.getStoreCode())
|
||||||
|
.status(user.getStatus())
|
||||||
|
.createTime(user.getCreateTime())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public ErpUserLoginResponseDTO login(ErpUserLoginRequestDTO request, String loginIp) {
|
||||||
|
log.info("用户登录请求,账号: {}", request.getUsername());
|
||||||
|
|
||||||
|
// 查询用户
|
||||||
|
ErpUser user = erpUserMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<ErpUser>()
|
||||||
|
.eq(ErpUser::getUsername, request.getUsername())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
throw new BusinessException(ResultCode.UNAUTHORIZED.getCode(), "账号或密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户状态
|
||||||
|
if (!"ACTIVE".equals(user.getStatus())) {
|
||||||
|
throw new BusinessException(ResultCode.FORBIDDEN.getCode(), "账号已被禁用");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证密码
|
||||||
|
String encryptedPassword = MD5.md5(request.getPassword());
|
||||||
|
if (!encryptedPassword.equals(user.getPassword())) {
|
||||||
|
throw new BusinessException(ResultCode.UNAUTHORIZED.getCode(), "账号或密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新最后登录时间和IP
|
||||||
|
user.setLastLoginTime(LocalDateTime.now());
|
||||||
|
user.setLastLoginIp(loginIp);
|
||||||
|
erpUserMapper.updateById(user);
|
||||||
|
|
||||||
|
// 生成Token
|
||||||
|
String token = TokenUtils.generateToken(user.getId(), user.getUsername());
|
||||||
|
Long tokenExpireTime = TokenUtils.getTokenExpireTime(token);
|
||||||
|
|
||||||
|
log.info("用户登录成功,用户ID: {}, 账号: {}", user.getId(), user.getUsername());
|
||||||
|
|
||||||
|
// 构建响应
|
||||||
|
ErpUserLoginResponseDTO response = ErpUserLoginResponseDTO.builder()
|
||||||
|
.id(user.getId())
|
||||||
|
.username(user.getUsername())
|
||||||
|
.nickName(user.getNickName())
|
||||||
|
.phone(user.getPhone())
|
||||||
|
.email(user.getEmail())
|
||||||
|
.storeCode(user.getStoreCode())
|
||||||
|
.status(user.getStatus())
|
||||||
|
.token(token)
|
||||||
|
.tokenExpireTime(tokenExpireTime)
|
||||||
|
.lastLoginTime(user.getLastLoginTime())
|
||||||
|
.lastLoginIp(user.getLastLoginIp())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ErpUser getUserByUsername(String username) {
|
||||||
|
return erpUserMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<ErpUser>()
|
||||||
|
.eq(ErpUser::getUsername, username)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ErpUser validateToken(String token) {
|
||||||
|
if (token == null || token.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析Token
|
||||||
|
Map<String, Object> userInfo = TokenUtils.parseToken(token);
|
||||||
|
if (userInfo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户
|
||||||
|
Long userId = (Long) userInfo.get("userId");
|
||||||
|
ErpUser user = erpUserMapper.selectById(userId);
|
||||||
|
|
||||||
|
if (user == null || !"ACTIVE".equals(user.getStatus())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user