From 9bcbf4398202139b31de1df6599e362915f15682 Mon Sep 17 00:00:00 2001
From: 刘帅 <rr.iii@qq.com>
Date: 星期二, 22 九月 2026 10:05:21 +0800
Subject: [PATCH] bug:数据返回问题
---
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/UserMgrController.java | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 170 insertions(+), 9 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 d20430b..90bbaa7 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
@@ -1,6 +1,7 @@
package com.dsh.guns.modular.system.controller.system;
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.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -34,10 +35,14 @@
import com.dsh.guns.modular.system.util.ResultUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
+import org.springframework.util.StringUtils;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.beans.propertyeditors.CustomNumberEditor;
import javax.naming.NoPermissionException;
import javax.servlet.http.HttpServletResponse;
@@ -57,6 +62,12 @@
private static String PREFIX = "/system/user/";
+ /** 多选框未选择时会提交空字符串;将其转换为空值,避免绑定 List<Integer> 时抛 NumberFormatException。 */
+ @InitBinder
+ public void initBinder(WebDataBinder binder) {
+ binder.registerCustomEditor(Integer.class, new CustomNumberEditor(Integer.class, true));
+ }
+
@Autowired
private GunsProperties gunsProperties;
@@ -75,6 +86,7 @@
@RequestMapping("")
public String index(Model model) {
model.addAttribute("language",UserExt.getLanguage());
+ model.addAttribute("isSuperAdmin", "admin".equalsIgnoreCase(UserExt.getUser().getAccount()));
return PREFIX + "user.html";
}
@@ -95,7 +107,20 @@
// 部门列表
List<Dept> depts = deptService.list(new QueryWrapper<>());
model.addAttribute("depts",depts);
+ model.addAttribute("stores", availableStores());
return PREFIX + "user_add.html";
+ }
+
+ 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);
+ }
+ return storeService.list(query);
}
/**
@@ -128,6 +153,13 @@
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);
if(role==null){
model.addAttribute("roleName", "");
@@ -148,13 +180,31 @@
}
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());
// 部门列表
List<Dept> depts = deptService.list(new QueryWrapper<>());
model.addAttribute("depts",depts);
+ model.addAttribute("stores", availableStores());
+ // 回显同时兼容新关联表、sys_user 旧归属字段和 t_store.storeStaffId 旧数据。
+ List<Integer> selectedStoreIds = userMapper.getStoreIdsByUserId(userId);
+ model.addAttribute("selectedStoreIds", selectedStoreIds.stream()
+ .map(String::valueOf).collect(Collectors.joining(",")));
model.addAttribute("type",type);
return PREFIX + "user_edit.html";
+ }
+
+ /** 编辑页异步读取已关联门店,避免模板渲染集合造成多选回显丢失。 */
+ @GetMapping("/storeIds/{userId}")
+ @ResponseBody
+ public List<Integer> getStoreIds(@PathVariable Integer userId) {
+ assertAuth(userId);
+ return userMapper.getStoreIdsByUserId(userId);
}
/**
@@ -169,6 +219,13 @@
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("roleName", role.getName());
if(dept==null){
@@ -213,6 +270,8 @@
@Autowired
private IStoreService storeService;
+ @Autowired
+ private UserMapper userMapper;
/**
* 查询管理员列表
*/
@@ -288,17 +347,21 @@
@BussinessLog(value = "添加管理员", key = "account", dict = UserDict.class)
@Permission(Const.ADMIN_NAME)
@ResponseBody
+ @Transactional(rollbackFor = Exception.class)
public Object add(@Valid UserDto user, BindingResult result) {
if (result.hasErrors()) {
throw new GunsException(BizExceptionEnum.REQUEST_NULL);
}
- // 判断账号是否重复
- User theUser = new User();
- if (user.getPhone()!=""){
- theUser = userService.getByAccount(user.getPhone());
+ // 手机号既是登录账号,必须在 sys_user 中唯一;使用内容判断,不能使用 != 比较字符串引用。
+ if (StringUtils.isEmpty(user.getPhone())) {
+ return 5002;
}
- if (theUser != null){
+ // 已逻辑删除的员工(status=3)不占用手机号,可用同一手机号重新创建员工。
+ User theUser = userService.getOne(new QueryWrapper<User>()
+ .eq("phone", user.getPhone())
+ .ne("status", ManagerStatus.DELETED.getCode()));
+ if (theUser != null || userService.getByAccount(user.getPhone()) != null){
return 5001;
}
@@ -308,7 +371,7 @@
user.setCreatetime(new Date());
User objectUser = UserFactory.createUser(user);
- if (user.getRoleid().equals("")){
+ if (StringUtils.isEmpty(user.getRoleid())){
objectUser.setObjectType(2);
}else {
objectUser.setObjectType(Integer.valueOf(user.getRoleid()));
@@ -326,8 +389,28 @@
objectUser.setStatus(1);
objectUser.setPassword(SecureUtil.md5("a123456"));
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);
+ }
+ // 首个门店同步写入旧字段,保证既有权限、数据查询和登录逻辑无需改造。
+ TStore primaryStore = assignedStores.get(0);
+ objectUser.setObjectType(3);
+ objectUser.setRoleid("3");
+ objectUser.setObjectId(primaryStore.getId());
// 运营商添加员工
- if (UserExt.getUser().getObjectType() == 2){
+ } else if (UserExt.getUser().getObjectType() == 2){
objectUser.setObjectType(UserExt.getUser().getObjectType());
objectUser.setRoleid("2");
objectUser.setObjectId(UserExt.getUser().getObjectId());
@@ -340,6 +423,9 @@
}
objectUser.setVersion(UserExt.getUser().getId());
this.userService.save(objectUser);
+ if (!assignedStores.isEmpty()) {
+ replaceStoreRelations(objectUser.getId(), assignedStores);
+ }
return SUCCESS_TIP;
}
@@ -351,13 +437,20 @@
@RequestMapping("/edit")
@BussinessLog(value = "修改管理员", key = "account", dict = UserDict.class)
@ResponseBody
+ @Transactional(rollbackFor = Exception.class)
public Object edit(@Valid UserDto user, BindingResult result) throws NoPermissionException {
if (result.hasErrors()) {
throw new GunsException(BizExceptionEnum.REQUEST_NULL);
}
User oldUser = userService.getById(user.getId());
// 判断是否更改电话
- if (!oldUser.getPhone().equals(user.getPhone())){
+ if (StringUtils.isEmpty(user.getPhone()) || user.getPhone().contains("*")) {
+ // The edit view displays a masked phone. Treat an unchanged masked
+ // 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){
oldUser.setPhone(user.getPhone());
@@ -368,15 +461,83 @@
oldUser.setName(user.getName());
oldUser.setBirthday(user.getBirthday());
oldUser.setDeptid(user.getDeptid());
- 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<TStore> assignedStores = validateStores(user.getStoreIds());
+ if (assignedStores == null) return ResultUtil.error("所选门店不存在、已停用或不在当前账号管理范围内");
+ replaceStoreRelations(oldUser.getId(), assignedStores);
+ if (!assignedStores.isEmpty()) {
+ oldUser.setObjectType(3);
+ oldUser.setObjectId(assignedStores.get(0).getId());
+ oldUser.setRoleid("3");
+ } else if (Integer.valueOf(3).equals(oldUser.getObjectType())) {
+ // 清除旧字段关联,避免已移除的首个门店仍被当作该员工所属门店。
+ oldUser.setObjectId(null);
+ }
+ }
+ // 门店关联会维护员工归属信息,但不能覆盖编辑页明确选择的角色。
+ // 必须在关联处理后写入,否则有门店的员工会被上面的兼容逻辑强制改回角色 3。
+ // 个人资料页不提供 roleid_ 回显控件时,旧版前端可能提交字符串
+ // "undefined";该值绝不能覆盖 sys_user.roleid。
+ if (isValidRoleId(user.getRoleid())) {
+ oldUser.setRoleid(user.getRoleid());
+ }
this.userService.updateById(oldUser);
return SUCCESS_TIP;
}
+ private boolean isValidRoleId(String roleId) {
+ return !StringUtils.isEmpty(roleId)
+ && !"undefined".equalsIgnoreCase(roleId)
+ && !"null".equalsIgnoreCase(roleId);
+ }
+
+ private String maskPhone(String value) {
+ if (value == null) return null;
+ return value.length() <= 7 ? stars(value.length()) : value.substring(0, 3) + "****" + value.substring(7);
+ }
+
+ private String maskIdCard(String value) {
+ if (value == null) return null;
+ return value.length() <= 10 ? stars(value.length()) : value.substring(0, 6) + stars(value.length() - 10) + value.substring(value.length() - 4);
+ }
+
+ private String stars(int size) {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < size; i++) result.append('*');
+ return result.toString();
+ }
+
+ private List<TStore> validateStores(List<Integer> storeIds) {
+ List<TStore> stores = new ArrayList<TStore>();
+ Integer currentType = UserExt.getUser().getObjectType();
+ Integer currentObjectId = UserExt.getUser().getObjectId();
+ 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;
+ stores.add(store);
+ }
+ return stores;
+ }
+
+ /** 每条关联记录单独生成雪花主键,避免 MyBatis foreach 复用同一个绑定变量。 */
+ private void replaceStoreRelations(Integer userId, List<TStore> stores) {
+ userMapper.deleteStoreRelations(userId);
+ for (TStore store : stores) {
+ userMapper.insertStoreRelation(IdWorker.getId(), userId, store.getId());
+ }
+ }
+
/**
* 删除管理员(逻辑删除)
* Delete administrator (logical deletion)
--
Gitblit v1.7.1