From d41e0da5df20d559b0b9b8c180d1bf70ba7c1845 Mon Sep 17 00:00:00 2001
From: 刘帅 <rr.iii@qq.com>
Date: 星期三, 07 十月 2026 17:49:26 +0800
Subject: [PATCH] 员工管理权限修改(新需求)
---
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/UserMgrController.java | 191 ++++++++++++++++++++++++++++++-----------------
1 files changed, 120 insertions(+), 71 deletions(-)
diff --git a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/UserMgrController.java b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/UserMgrController.java
index eb4a78f..2e78cf6 100644
--- a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/UserMgrController.java
+++ b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/UserMgrController.java
@@ -3,7 +3,6 @@
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dsh.course.mapper.UserMapper;
@@ -114,14 +113,21 @@
private List<TStore> availableStores() {
LambdaQueryWrapper<TStore> query = new LambdaQueryWrapper<TStore>().eq(TStore::getState, 1);
- Integer objectType = UserExt.getUser().getObjectType();
- Integer objectId = UserExt.getUser().getObjectId();
- if (Integer.valueOf(2).equals(objectType)) {
- query.eq(TStore::getOperatorId, objectId);
- } else if (Integer.valueOf(3).equals(objectType)) {
- query.eq(TStore::getId, objectId);
+ User currentUser = UserExt.getUser();
+ // 超级管理员可维护全部启用门店;其他员工仅能看到自己被分配的门店。
+ // getStoreIdsByUserId 同时兼容 sys_user_store、多门店改造前的 objectId,及历史店长数据。
+ if (!isSuperAdmin(currentUser)) {
+ List<Integer> storeIds = userMapper.getStoreIdsByUserId(currentUser.getId());
+ if (storeIds == null || storeIds.isEmpty()) {
+ return Collections.emptyList();
+ }
+ query.in(TStore::getId, storeIds);
}
return storeService.list(query);
+ }
+
+ private boolean isSuperAdmin(User user) {
+ return user != null && "admin".equalsIgnoreCase(user.getAccount());
}
/**
@@ -154,9 +160,16 @@
User user = this.userService.getById(userId);
Role role = roleService.getById(user.getRoleid());
Dept dept = deptService.getById(user.getDeptid());
+ // 兼容历史账号:部分账号的登录手机号仅保存在 account 字段。
+ // 个人资料页必须能回显该绑定手机号,避免前端提交空值。
+ if (StringUtils.isEmpty(user.getPhone()) && !StringUtils.isEmpty(user.getAccount())) {
+ user.setPhone(user.getAccount());
+ }
user.setPhone(maskPhone(user.getPhone()));
user.setIdCard(maskIdCard(user.getIdCard()));
- model.addAttribute(user);
+ // 显式指定模型名称和身份证回显字段,避免模板按隐式参数名解析失败。
+ model.addAttribute("user", user);
+ model.addAttribute("idCard", user.getIdCard());
if(role==null){
model.addAttribute("roleName", "");
}else {
@@ -176,6 +189,11 @@
}
roleLambdaQueryWrapper.eq(Role::getObjType,UserExt.getUser().getObjectType());
List<Role> list = roleService.list(roleLambdaQueryWrapper);
+ // 员工可能是由平台账号创建、后续归属到门店的,原角色不一定属于当前登录人的角色范围。
+ // 编辑时仍需保留该角色作为可选项,避免下拉框无法回显。
+ if (role != null && list.stream().noneMatch(item -> Objects.equals(item.getId(), role.getId()))) {
+ list.add(role);
+ }
model.addAttribute("list",list);
model.addAttribute("language",UserExt.getLanguage());
// 部门列表
@@ -210,6 +228,11 @@
User user = this.userService.getById(userId);
Role role = roleService.getById(user.getRoleid());
Dept dept = deptService.getById(user.getDeptid());
+ // 兼容历史账号:部分账号的登录手机号仅保存在 account 字段。
+ // 个人资料页必须能回显该绑定手机号,避免前端提交空值。
+ if (StringUtils.isEmpty(user.getPhone()) && !StringUtils.isEmpty(user.getAccount())) {
+ user.setPhone(user.getAccount());
+ }
user.setPhone(maskPhone(user.getPhone()));
user.setIdCard(maskIdCard(user.getIdCard()));
model.addAttribute(user);
@@ -297,10 +320,14 @@
// return res1;
return filteredList;
case 3:
- // 门店只查询门店员工
- TStore store = storeService.getById(objectId);
- List<Integer> storeIds = userQuery.getStoreIds();
- storeIds.add(store.getStoreStaffId());
+ // 门店员工可同时归属多个门店,员工列表应汇总这些门店下的所有员工。
+ List<Integer> currentStoreIds = userMapper.getStoreIdsByUserId(UserExt.getUser().getId());
+ List<Integer> employeeIds = currentStoreIds == null || currentStoreIds.isEmpty()
+ ? Collections.<Integer>emptyList()
+ : userMapper.getUserIdsByStoreIds(currentStoreIds);
+ // searchUsers 在集合为空时不会拼接 IN 条件;用不存在的 ID 明确返回空结果,避免越权看到全部员工。
+ userQuery.setStoreIds(employeeIds == null || employeeIds.isEmpty()
+ ? Collections.singletonList(-1) : employeeIds);
List<UserListVO> res2= userService.searchUsers(userQuery);
return res2;
}
@@ -377,19 +404,8 @@
objectUser.setCreatetime(new Date());
List<TStore> assignedStores = new ArrayList<TStore>();
if (user.getStoreIds() != null && !user.getStoreIds().isEmpty()) {
- List<Integer> storeIds = user.getStoreIds().stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
- if (storeIds.isEmpty()) return ResultUtil.error("请选择有效门店");
- Integer currentType = UserExt.getUser().getObjectType();
- Integer currentObjectId = UserExt.getUser().getObjectId();
- for (Integer storeId : storeIds) {
- TStore store = storeService.getById(storeId);
- if (store == null || !Integer.valueOf(1).equals(store.getState())) return ResultUtil.error("所选门店不存在或已停用");
- if ((Integer.valueOf(2).equals(currentType) && !currentObjectId.equals(store.getOperatorId()))
- || (Integer.valueOf(3).equals(currentType) && !currentObjectId.equals(store.getId()))) {
- return ResultUtil.error("所选门店不在当前账号的管理范围内");
- }
- assignedStores.add(store);
- }
+ assignedStores = validateStores(user.getStoreIds());
+ if (assignedStores == null || assignedStores.isEmpty()) return ResultUtil.error("所选门店不存在、已停用或不在当前账号的管理范围内");
// 首个门店同步写入旧字段,保证既有权限、数据查询和登录逻辑无需改造。
TStore primaryStore = assignedStores.get(0);
objectUser.setObjectType(3);
@@ -411,7 +427,6 @@
this.userService.save(objectUser);
if (!assignedStores.isEmpty()) {
replaceStoreRelations(objectUser.getId(), assignedStores);
- syncStoreManagers(objectUser, Collections.<Integer>emptyList(), assignedStores);
}
return SUCCESS_TIP;
}
@@ -431,9 +446,12 @@
}
User oldUser = userService.getById(user.getId());
// 判断是否更改电话
- if (user.getPhone() != null && user.getPhone().contains("*")) {
+ if (StringUtils.isEmpty(user.getPhone()) || user.getPhone().contains("*")) {
// The edit view displays a masked phone. Treat an unchanged masked
- // value as "keep original", never as a value to persist.
+ // value, or an omitted field, as "keep original", never as a
+ // value to persist. Some edit forms do not submit disabled or
+ // hidden fields; allowing an empty value through would erase the
+ // stored phone after deployment.
} else if (!Objects.equals(oldUser.getPhone(), user.getPhone())) {
User phone = userService.getOne(new QueryWrapper<User>().eq("phone", user.getPhone()));
if (phone == null){
@@ -445,35 +463,57 @@
oldUser.setName(user.getName());
oldUser.setBirthday(user.getBirthday());
oldUser.setDeptid(user.getDeptid());
- if (user.getIdCard() == null || !user.getIdCard().contains("*")) oldUser.setIdCard(user.getIdCard());
+ // 身份证号为空说明本次表单没有提交该字段,不能覆盖数据库中的历史值;
+ // 脱敏值同样只用于回显,不能写回数据库。
+ if (!StringUtils.isEmpty(user.getIdCard()) && !user.getIdCard().contains("*")) {
+ oldUser.setIdCard(user.getIdCard());
+ }
oldUser.setSex(user.getSex());
oldUser.setEmail(user.getEmail());
if (user.getAvatar()!=null && (!user.getAvatar().equals("")))
oldUser.setAvatar(user.getAvatar());
if (user.getStoreIds() != null) {
- List<Integer> previousStoreIds = userMapper.getStoreIdsByUserId(oldUser.getId());
List<TStore> assignedStores = validateStores(user.getStoreIds());
if (assignedStores == null) return ResultUtil.error("所选门店不存在、已停用或不在当前账号管理范围内");
- replaceStoreRelations(oldUser.getId(), assignedStores);
- if (!assignedStores.isEmpty()) {
+ // 非超级管理员的页面只会展示自己有权限的门店。保存时仅更新这个范围,
+ // 被编辑员工在其他门店(当前账号无权查看)的归属必须原样保留。
+ List<TStore> effectiveStores = mergeEditableAndProtectedStores(oldUser.getId(), assignedStores);
+ replaceStoreRelations(oldUser.getId(), effectiveStores);
+ if (!effectiveStores.isEmpty()) {
oldUser.setObjectType(3);
- oldUser.setObjectId(assignedStores.get(0).getId());
+ oldUser.setObjectId(effectiveStores.get(0).getId());
oldUser.setRoleid("3");
} else if (Integer.valueOf(3).equals(oldUser.getObjectType())) {
// 清除旧字段关联,避免已移除的首个门店仍被当作该员工所属门店。
oldUser.setObjectId(null);
}
- syncStoreManagers(oldUser, previousStoreIds, assignedStores);
+ }
+ // 门店关联会维护员工归属信息,但不能覆盖编辑页明确选择的角色。
+ // 必须在关联处理后写入,否则有门店的员工会被上面的兼容逻辑强制改回角色 3。
+ // 个人资料页不提供 roleid_ 回显控件时,旧版前端可能提交字符串
+ // "undefined";该值绝不能覆盖 sys_user.roleid。
+ if (isValidRoleId(user.getRoleid())) {
+ oldUser.setRoleid(user.getRoleid());
+ // 仅内置平台管理员(1)和门店管理员(3)切换数据归属类型;
+ // 其他自定义角色只控制菜单权限,不改变账号归属。objectId 始终保留。
+ syncObjectTypeWithBuiltInRole(oldUser, user.getRoleid());
}
this.userService.updateById(oldUser);
- // 门店表仅保存店长 ID 和联系电话;店长名称由 storeStaffId 关联 sys_user.name 展示。
- // 员工资料修改但门店未修改时,也要将其手机号同步给当前负责的门店。
- if (user.getStoreIds() == null) {
- storeService.update(new LambdaUpdateWrapper<TStore>()
- .eq(TStore::getStoreStaffId, oldUser.getId())
- .set(TStore::getPhone, oldUser.getPhone()));
- }
return SUCCESS_TIP;
+ }
+
+ private boolean isValidRoleId(String roleId) {
+ return !StringUtils.isEmpty(roleId)
+ && !"undefined".equalsIgnoreCase(roleId)
+ && !"null".equalsIgnoreCase(roleId);
+ }
+
+ private void syncObjectTypeWithBuiltInRole(User user, String roleId) {
+ if ("1".equals(roleId)) {
+ user.setObjectType(1);
+ } else if ("3".equals(roleId)) {
+ user.setObjectType(3);
+ }
}
private String maskPhone(String value) {
@@ -494,16 +534,51 @@
private List<TStore> validateStores(List<Integer> storeIds) {
List<TStore> stores = new ArrayList<TStore>();
- Integer currentType = UserExt.getUser().getObjectType();
- Integer currentObjectId = UserExt.getUser().getObjectId();
+ User currentUser = UserExt.getUser();
+ if (storeIds == null) return stores;
+ Set<Integer> manageableStoreIds = null;
+ if (!isSuperAdmin(currentUser)) {
+ List<Integer> assignedStoreIds = userMapper.getStoreIdsByUserId(currentUser.getId());
+ manageableStoreIds = assignedStoreIds == null ? Collections.<Integer>emptySet() : new HashSet<>(assignedStoreIds);
+ }
for (Integer storeId : storeIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList())) {
TStore store = storeService.getById(storeId);
if (store == null || !Integer.valueOf(1).equals(store.getState())) return null;
- if ((Integer.valueOf(2).equals(currentType) && !currentObjectId.equals(store.getOperatorId()))
- || (Integer.valueOf(3).equals(currentType) && !currentObjectId.equals(store.getId()))) return null;
+ if (manageableStoreIds != null && !manageableStoreIds.contains(storeId)) return null;
stores.add(store);
}
return stores;
+ }
+
+ /**
+ * 合并本次可编辑范围内的选择与原有不可编辑门店,避免局部管理员覆盖其他门店的归属。
+ */
+ private List<TStore> mergeEditableAndProtectedStores(Integer userId, List<TStore> editableStores) {
+ if (isSuperAdmin(UserExt.getUser())) {
+ return editableStores;
+ }
+
+ List<Integer> currentUserStoreIds = userMapper.getStoreIdsByUserId(UserExt.getUser().getId());
+ Set<Integer> editableStoreIds = currentUserStoreIds == null
+ ? Collections.<Integer>emptySet() : new HashSet<>(currentUserStoreIds);
+ Map<Integer, TStore> effectiveStoreMap = new LinkedHashMap<>();
+ for (TStore store : editableStores) {
+ effectiveStoreMap.put(store.getId(), store);
+ }
+
+ List<Integer> previousStoreIds = userMapper.getStoreIdsByUserId(userId);
+ if (previousStoreIds != null) {
+ for (Integer storeId : previousStoreIds) {
+ if (storeId == null || editableStoreIds.contains(storeId)) {
+ continue;
+ }
+ TStore store = storeService.getById(storeId);
+ if (store != null) {
+ effectiveStoreMap.put(storeId, store);
+ }
+ }
+ }
+ return new ArrayList<>(effectiveStoreMap.values());
}
/** 每条关联记录单独生成雪花主键,避免 MyBatis foreach 复用同一个绑定变量。 */
@@ -511,32 +586,6 @@
userMapper.deleteStoreRelations(userId);
for (TStore store : stores) {
userMapper.insertStoreRelation(IdWorker.getId(), userId, store.getId());
- }
- }
-
- /**
- * 同步员工与门店的负责人信息。门店移除时,只清理仍由该员工负责的门店,
- * 防止并发或后续操作已为门店设置新店长时被错误覆盖。
- */
- private void syncStoreManagers(User user, List<Integer> previousStoreIds, List<TStore> assignedStores) {
- Set<Integer> assignedStoreIds = assignedStores.stream()
- .map(TStore::getId)
- .collect(Collectors.toSet());
-
- for (Integer previousStoreId : previousStoreIds) {
- if (!assignedStoreIds.contains(previousStoreId)) {
- storeService.update(new LambdaUpdateWrapper<TStore>()
- .eq(TStore::getId, previousStoreId)
- .eq(TStore::getStoreStaffId, user.getId())
- .set(TStore::getStoreStaffId, null)
- .set(TStore::getPhone, null));
- }
- }
-
- for (TStore store : assignedStores) {
- store.setStoreStaffId(user.getId());
- store.setPhone(user.getPhone());
- storeService.updateById(store);
}
}
--
Gitblit v1.7.1