修改用户角色,添加管理员相关逻辑
Some checks failed
构建并部署 Spring Boot 应用 / build-and-deploy (push) Has been cancelled

This commit is contained in:
2025-10-17 02:15:49 +08:00
parent c83db8d927
commit 1540fe6dab
5 changed files with 100 additions and 43 deletions

View File

@@ -35,6 +35,11 @@ Light Delivery 是一个为小程序配送服务设计的后端系统,提供
- REST API位置查询接口 - REST API位置查询接口
- 位置过期处理机制 - 位置过期处理机制
### 5. 员工管理(新增)
- 员工信息维护(仅限管理员)
- 员工增删改查功能
- 员工角色管理(管理员/配送员)
## 项目结构 ## 项目结构
``` ```
@@ -93,9 +98,20 @@ src
- 所有用户首先以游客身份静默登录创建User记录 - 所有用户首先以游客身份静默登录创建User记录
- 用户签到或注册时系统根据手机号在Employee表中查找匹配记录 - 用户签到或注册时系统根据手机号在Employee表中查找匹配记录
- 如果匹配成功将用户的openid写入Employee表表示该员工已成为系统用户 - 如果匹配成功将用户的openid写入Employee表表示该员工已成为系统用户
- 根据Employee中的角色信息更新User的角色 - 根据Employee中的角色信息动态确定User的角色
- 对于配送员还会创建或更新DeliveryPerson记录 - 对于配送员还会创建或更新DeliveryPerson记录
### 用户角色管理优化(新增)
为了解决数据一致性问题,我们对用户角色管理进行了优化:
1. 移除了User表中的role字段避免在User表和Employee表中重复维护角色信息
2. 用户角色现在完全通过Employee表进行管理
3. 当需要获取用户角色时系统会根据用户的手机号在Employee表中查找对应记录
4. 如果用户未注册没有手机号或在Employee表中找不到对应记录则默认为游客角色
这种设计确保了数据的一致性,避免了在多个表中维护相同信息可能带来的问题。
## 本地开发与测试 ## 本地开发与测试
### 环境配置 ### 环境配置
@@ -103,7 +119,7 @@ src
项目支持多种运行环境配置: 项目支持多种运行环境配置:
1. **本地开发环境** (`local`) 1. **本地开发环境** (`local`)
- 端口: 8443 - 端口: 8080
- 数据库: 远程MySQL (与生产环境相同) - 数据库: 远程MySQL (与生产环境相同)
- SSL: 禁用 - SSL: 禁用
- 日志级别: DEBUG - 日志级别: DEBUG
@@ -132,7 +148,7 @@ src
```bash ```bash
# 使用local配置文件运行 # 使用local配置文件运行
mvn spring-boot:run -Dspring-boot.run.profiles=local mvn spring-boot:run -D"spring.profiles.active"="local"
# 或者使用test配置文件运行使用内存数据库 # 或者使用test配置文件运行使用内存数据库
mvn spring-boot:run -Dspring-boot.run.profiles=test mvn spring-boot:run -Dspring-boot.run.profiles=test
@@ -158,7 +174,7 @@ java -jar target/light-delivery-1.0.0.jar --spring.profiles.active=test
build-local.bat build-local.bat
# 运行容器 # 运行容器
docker run -d --name light-delivery -p 8443:8443 light-delivery-app docker run -d --name light-delivery -p 8080:8080 light-delivery-app
``` ```
## 部署说明 ## 部署说明
@@ -234,11 +250,32 @@ deploy.bat
- `GET /delivery-persons/{id}` - 获取配送员详情 - `GET /delivery-persons/{id}` - 获取配送员详情
- `PUT /delivery-persons/{id}/location` - 更新配送员位置 - `PUT /delivery-persons/{id}/location` - 更新配送员位置
### 员工管理相关(仅限管理员访问)
- `GET /employees` - 获取员工列表
- `POST /employees` - 添加员工
- `PUT /employees/{id}` - 更新员工信息
- `DELETE /employees/{id}` - 删除员工
### 位置同步相关 ### 位置同步相关
- `GET /location-sync/delivery-persons/locations` - 获取所有配送员位置 - `GET /location-sync/delivery-persons/locations` - 获取所有配送员位置
## 最近更新 ## 最近更新
### 用户角色管理优化(新增)
为解决数据一致性问题,我们优化了用户角色管理机制:
- 移除了User表中的role字段
- 用户角色现在完全通过Employee表进行管理
- 系统会根据用户的手机号在Employee表中动态查找角色信息
- 未注册用户默认为游客角色
### 员工管理功能(新增)
为便于管理员维护员工信息,我们新增了员工管理功能:
- 新增EmployeeController控制器提供员工信息的增删改查接口
- 新增EmployeeService服务层处理员工相关的业务逻辑
- 新增EmployeeDto数据传输对象用于前后端数据交互
- 添加权限控制,仅管理员可访问员工管理接口
- 添加全局异常处理机制
### 位置历史记录功能移除 ### 位置历史记录功能移除
为了简化系统架构并提高性能,我们移除了位置历史记录功能。具体变更包括: 为了简化系统架构并提高性能,我们移除了位置历史记录功能。具体变更包括:
- 删除了`LocationHistory`实体类 - 删除了`LocationHistory`实体类
@@ -253,4 +290,6 @@ deploy.bat
1. 系统仅支持微信登录,不再支持用户名密码登录 1. 系统仅支持微信登录,不再支持用户名密码登录
2. 所有敏感接口都需要通过JWT Token认证 2. 所有敏感接口都需要通过JWT Token认证
3. 配送员位置信息具有时效性默认5分钟内有效 3. 配送员位置信息具有时效性默认5分钟内有效
4. 位置历史记录功能已被移除,如有需要可使用第三方服务进行位置追踪 4. 位置历史记录功能已被移除,如有需要可使用第三方服务进行位置追踪
5. 员工管理接口仅限管理员角色访问
6. 用户角色信息现在通过员工表动态获取,确保数据一致性

View File

@@ -1,13 +1,13 @@
package com.light.delivery.controller; package com.light.delivery.controller;
import com.light.delivery.dto.UserInfoResponse; import com.light.delivery.dto.UserInfoResponse;
import com.light.delivery.dto.UserResponse;
import com.light.delivery.model.LoginResponse; import com.light.delivery.model.LoginResponse;
import com.light.delivery.model.RegisterRequest; import com.light.delivery.model.RegisterRequest;
import com.light.delivery.model.User; import com.light.delivery.model.User;
import com.light.delivery.model.UserRole; import com.light.delivery.model.UserRole;
import com.light.delivery.model.WxLoginRequest; import com.light.delivery.model.WxLoginRequest;
import com.light.delivery.service.UserService; import com.light.delivery.service.UserService;
import com.light.delivery.service.impl.UserServiceImpl;
import com.light.delivery.util.JwtUtil; import com.light.delivery.util.JwtUtil;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -39,6 +39,12 @@ public class UserController {
*/ */
@Autowired @Autowired
private JwtUtil jwtUtil; private JwtUtil jwtUtil;
/**
* UserServiceImpl依赖注入用于获取用户角色。
*/
@Autowired
private UserServiceImpl userServiceImpl;
/** /**
* 获取当前用户状态接口。 * 获取当前用户状态接口。
@@ -60,7 +66,8 @@ public class UserController {
// 根据用户角色和信息完整性判断状态 // 根据用户角色和信息完整性判断状态
String status; String status;
if (user.getRole() == null || user.getRole() == UserRole.GUEST) { UserRole userRole = userServiceImpl.getUserRole(user);
if (userRole == null || userRole == UserRole.GUEST) {
// 未注册用户 // 未注册用户
status = "unregistered"; status = "unregistered";
} else if ((user.getName() == null || user.getName().isEmpty()) && } else if ((user.getName() == null || user.getName().isEmpty()) &&
@@ -118,7 +125,9 @@ public class UserController {
dto.setId(user.getId()); dto.setId(user.getId());
dto.setName(user.getName()); dto.setName(user.getName());
dto.setPhone(user.getPhone()); dto.setPhone(user.getPhone());
dto.setRole(user.getRole() != null ? user.getRole().getCode() : null); // 从员工表获取角色信息
UserRole userRole = userServiceImpl.getUserRole(user);
dto.setRole(userRole != null ? userRole.getCode() : null);
dto.setOpenid(user.getOpenid()); dto.setOpenid(user.getOpenid());
return dto; return dto;
} }
@@ -173,7 +182,6 @@ public class UserController {
} }
try { try {
String username = jwtUtil.getUsernameFromToken(token);
User user = userService.getUserInfo(token); User user = userService.getUserInfo(token);
User updatedUser = userService.signIn(user.getId()); User updatedUser = userService.signIn(user.getId());
UserInfoResponse response = toUserInfoResponse(updatedUser); UserInfoResponse response = toUserInfoResponse(updatedUser);

View File

@@ -29,12 +29,6 @@ public class User {
*/ */
@Column(name = "phone") @Column(name = "phone")
private String phone; private String phone;
/**
* 用户角色(如 ADMIN, DELIVERY_PERSON, GUEST
*/
@Enumerated(EnumType.STRING)
private UserRole role;
/** /**
* 微信用户唯一标识 * 微信用户唯一标识

View File

@@ -96,6 +96,7 @@ public class UserServiceImpl implements UserService {
if (user == null) { if (user == null) {
throw new IllegalArgumentException("用户不存在"); throw new IllegalArgumentException("用户不存在");
} }
return user; return user;
} catch (Exception e) { } catch (Exception e) {
// 捕获JWT解析异常等 // 捕获JWT解析异常等
@@ -127,12 +128,10 @@ public class UserServiceImpl implements UserService {
} }
// 如果用户是配送员通知WebSocket处理器清理连接 // 如果用户是配送员通知WebSocket处理器清理连接
if (UserRole.DELIVERY_PERSON.equals(user.getRole())) { // 注意这里需要根据实际业务逻辑获取配送员ID
// 注意:这里需要根据实际业务逻辑获取配送员ID // 可能需要通过其他方式关联用户ID和配送员ID
// 可能需要通过其他方式关联用户ID和配送员ID // 这里假设用户ID和配送员ID相同根据项目实际情况调整
// 这里假设用户ID和配送员ID相同根据项目实际情况调整 // locationWebSocketHandler.removeUserConnection(user.getId());
// locationWebSocketHandler.removeUserConnection(user.getId());
}
} catch (Exception e) { } catch (Exception e) {
// 记录日志但不中断登出流程 // 记录日志但不中断登出流程
System.err.println("清理WebSocket连接时出错: " + e.getMessage()); System.err.println("清理WebSocket连接时出错: " + e.getMessage());
@@ -160,24 +159,15 @@ public class UserServiceImpl implements UserService {
if (user == null) { if (user == null) {
// 首次登录,创建新用户,默认为游客角色 // 首次登录,创建新用户,默认为游客角色
user = new User(); user = new User();
user.setRole(UserRole.GUEST); // 默认为游客角色
user.setOpenid(openid); user.setOpenid(openid);
userRepository.save(user); userRepository.save(user);
} else {
// 检查并修复用户角色
if (user.getRole() == null) {
// 如果用户角色为空,设置为游客角色
user.setRole(UserRole.GUEST);
userRepository.save(user);
}
} }
// 获取用户角色
UserRole userRole = getUserRole(user);
// 确保用户角色不为空防止在生成token时出现空指针异常 String token = jwtUtil.generateToken(user.getOpenid(),
if (user.getRole() == null) { userRole != null ? userRole.getCode() : UserRole.GUEST.getCode());
user.setRole(UserRole.GUEST);
}
String token = jwtUtil.generateToken(user.getOpenid(), user.getRole().getCode());
LoginResponse response = new LoginResponse(); LoginResponse response = new LoginResponse();
response.setToken(token); response.setToken(token);
response.setUser(toUserInfoResponse(user)); response.setUser(toUserInfoResponse(user));
@@ -270,14 +260,13 @@ public class UserServiceImpl implements UserService {
} }
// 将openid写入employee表表示该员工已成为系统用户 // 将openid写入employee表表示该员工已成为系统用户
employee.setOpenid(userOptional.get().getOpenid()); User user = userOptional.get();
employee.setOpenid(user.getOpenid());
employeeRepository.save(employee); employeeRepository.save(employee);
// 更新用户信息 // 更新用户信息
User user = userOptional.get();
user.setName(name); // 设置用户姓名 user.setName(name); // 设置用户姓名
user.setPhone(phone); // 设置用户手机号 user.setPhone(phone); // 设置用户手机号
user.setRole(UserRole.fromCode(employee.getRole())); // 设置为员工对应的权限角色
userRepository.save(user); userRepository.save(user);
return user; return user;
@@ -319,7 +308,17 @@ public class UserServiceImpl implements UserService {
dto.setId(user.getId()); dto.setId(user.getId());
dto.setName(user.getName()); dto.setName(user.getName());
dto.setPhone(user.getPhone()); dto.setPhone(user.getPhone());
dto.setRole(user.getRole() != null ? user.getRole().getCode() : null); // 从员工表获取角色信息
if (user.getPhone() != null && !user.getPhone().isEmpty()) {
Optional<Employee> employee = employeeRepository.findByPhone(user.getPhone());
if (employee.isPresent()) {
dto.setRole(employee.get().getRole());
} else {
dto.setRole(UserRole.GUEST.getCode());
}
} else {
dto.setRole(UserRole.GUEST.getCode());
}
dto.setOpenid(user.getOpenid()); dto.setOpenid(user.getOpenid());
return dto; return dto;
} }
@@ -338,7 +337,9 @@ public class UserServiceImpl implements UserService {
} }
User user = userOptional.get(); User user = userOptional.get();
if (user.getRole() != UserRole.DELIVERY_PERSON) { UserRole userRole = getUserRole(user);
if (userRole != UserRole.DELIVERY_PERSON) {
// 非配送员角色没有签到状态概念 // 非配送员角色没有签到状态概念
return false; return false;
} }
@@ -346,4 +347,20 @@ public class UserServiceImpl implements UserService {
// 检查WebSocket中的签到状态 // 检查WebSocket中的签到状态
return locationWebSocketHandler.isDeliveryPersonSignedIn(userId); return locationWebSocketHandler.isDeliveryPersonSignedIn(userId);
} }
/**
* 获取用户角色
* @param user 用户对象
* @return 用户角色
*/
public UserRole getUserRole(User user) {
// 从员工表获取角色信息
if (user.getPhone() != null && !user.getPhone().isEmpty()) {
Optional<Employee> employee = employeeRepository.findByPhone(user.getPhone());
if (employee.isPresent()) {
return UserRole.fromCode(employee.get().getRole());
}
}
return UserRole.GUEST;
}
} }

View File

@@ -1,9 +1,8 @@
spring.application.name=Light spring.application.name=Light
server.port=443 server.port=443
server.ssl.key-store-type=JKS # 默认禁用SSL通过profile启用
server.ssl.key-store=/etc/ssl/certs/www.doubleyin.cn.jks server.ssl.enabled=false
server.ssl.key-store-password=${KEY_STORE_PASSWORD}
spring.datasource.url=jdbc:mysql://115.190.121.151:3306/light_delivery?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.datasource.url=jdbc:mysql://115.190.121.151:3306/light_delivery?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.username=double spring.datasource.username=double