刘帅
2026-09-17 45dccc43c06c4fe96c406f24a8da331e7c816971
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/UserMgrController.java
@@ -1,7 +1,9 @@
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.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dsh.course.mapper.UserMapper;
@@ -34,10 +36,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 +63,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 +87,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 +108,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);
    }
    /**
@@ -126,9 +152,23 @@
        }
        assertAuth(userId);
        User user = this.userService.getById(userId);
        Role role = roleService.getById(user.getRoleid());
        Dept dept = deptService.getById(user.getDeptid());
        user.setPhone(maskPhone(user.getPhone()));
        user.setIdCard(maskIdCard(user.getIdCard()));
        model.addAttribute(user);
        model.addAttribute("roleName", ConstantFactory.me().getRoleName(user.getRoleid()));
        model.addAttribute("deptName", ConstantFactory.me().getDeptName(user.getDeptid()));
        if(role==null){
            model.addAttribute("roleName", "");
        }else {
            model.addAttribute("roleName", role.getName());
        }
        if(dept==null){
            model.addAttribute("deptName","");
        }else {
            model.addAttribute("deptName", dept.getFullname());
        }
        LogObjectHolder.me().set(user);
        LambdaQueryWrapper<Role> roleLambdaQueryWrapper = new LambdaQueryWrapper<>();
        if(UserExt.getUser().getObjectType()!=1){
@@ -141,8 +181,21 @@
        // 部门列表
        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);
    }
    /**
@@ -155,9 +208,18 @@
            throw new GunsException(BizExceptionEnum.REQUEST_NULL);
        }
        User user = this.userService.getById(userId);
        Role role = roleService.getById(user.getRoleid());
        Dept dept = deptService.getById(user.getDeptid());
        user.setPhone(maskPhone(user.getPhone()));
        user.setIdCard(maskIdCard(user.getIdCard()));
        model.addAttribute(user);
        model.addAttribute("roleName", ConstantFactory.me().getRoleName(user.getRoleid()));
        model.addAttribute("deptName", user.getDeptid()==0?"顶级":ConstantFactory.me().getDeptName(user.getDeptid()));
        model.addAttribute("roleName", role.getName());
        if(dept==null){
            model.addAttribute("deptName", "无");
        }else{
            model.addAttribute("deptName", user.getDeptid()==0?"顶级":dept.getFullname());
        }
        LogObjectHolder.me().set(user);
        return PREFIX + "user_view.html";
    }
@@ -194,6 +256,8 @@
    @Autowired
    private IStoreService storeService;
    @Autowired
    private UserMapper userMapper;
    /**
     * 查询管理员列表
     */
@@ -218,14 +282,20 @@
                return res;
            case 2:
                // 运营商查询运营商账号的员工和属于该运营商门店的员工
                userQuery.setObjectType(2);
                userQuery.setObjectId(objectId);
                // 获取属于这个运营商的门店的店长ids
                List<Integer> storeStaffIds = storeService.list(new QueryWrapper<TStore>().eq("operatorId", objectId)
                        .ne("state", 3)).stream().map(TStore::getStoreStaffId).collect(Collectors.toList());
                userQuery.setStoreIds(storeStaffIds);
//                userQuery.setObjectType(2);
//                userQuery.setObjectId(objectId);
//                // 获取属于这个运营商的门店的店长ids
//                List<Integer> storeStaffIds = storeService.list(new QueryWrapper<TStore>().eq("operatorId", objectId)
//                        .ne("state", 3)).stream().map(TStore::getStoreStaffId).collect(Collectors.toList());
//                userQuery.setStoreIds(storeStaffIds);
//                List<UserListVO> res1= userService.searchUsers(userQuery);
                List<UserListVO> res1= userService.searchUsers(userQuery);
                return res1;
                List<UserListVO> filteredList = res1.stream()
                        .filter(vo -> Objects.equals(vo.getVersion(), UserExt.getUser().getId()))
                        .collect(Collectors.toList());
//                return res1;
                return filteredList;
            case 3:
                // 门店只查询门店员工
                TStore store = storeService.getById(objectId);
@@ -263,17 +333,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;
        }
@@ -282,7 +356,13 @@
        user.setStatus(ManagerStatus.OK.getCode());
        user.setCreatetime(new Date());
        User objectUser = UserFactory.createUser(user);
        objectUser.setObjectType(Integer.valueOf(user.getRoleid()));
        if (StringUtils.isEmpty(user.getRoleid())){
            objectUser.setObjectType(2);
        }else {
            objectUser.setObjectType(Integer.valueOf(user.getRoleid()));
        }
        objectUser.setObjectId(UserExt.getUser().getObjectId());
        objectUser.setRoleid(user.getRoleid());
        objectUser.setAccount(user.getPhone());
@@ -295,8 +375,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());
@@ -307,7 +407,12 @@
            objectUser.setRoleid("3");
            objectUser.setObjectId(UserExt.getUser().getObjectId());
        }
        objectUser.setVersion(UserExt.getUser().getId());
        this.userService.save(objectUser);
        if (!assignedStores.isEmpty()) {
            replaceStoreRelations(objectUser.getId(), assignedStores);
            syncStoreManagers(objectUser, Collections.<Integer>emptyList(), assignedStores);
        }
        return SUCCESS_TIP;
    }
@@ -319,13 +424,17 @@
    @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 (user.getPhone() != null && user.getPhone().contains("*")) {
            // The edit view displays a masked phone. Treat an unchanged masked
            // value as "keep original", never as a value to persist.
        } 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());
@@ -335,16 +444,102 @@
        }
        oldUser.setName(user.getName());
        oldUser.setBirthday(user.getBirthday());
        oldUser.setRoleid(user.getRoleid());
        oldUser.setDeptid(user.getDeptid());
        oldUser.setIdCard(user.getIdCard());
        if (user.getIdCard() == null || !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()) {
                oldUser.setObjectType(3);
                oldUser.setObjectId(assignedStores.get(0).getId());
                oldUser.setRoleid("3");
            } else if (Integer.valueOf(3).equals(oldUser.getObjectType())) {
                // 清除旧字段关联,避免已移除的首个门店仍被当作该员工所属门店。
                oldUser.setObjectId(null);
            }
            syncStoreManagers(oldUser, previousStoreIds, assignedStores);
        }
        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 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());
        }
    }
    /**
     * 同步员工与门店的负责人信息。门店移除时,只清理仍由该员工负责的门店,
     * 防止并发或后续操作已为门店设置新店长时被错误覆盖。
     */
    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);
        }
    }
    /**
     * 删除管理员(逻辑删除)
     * Delete administrator (logical deletion)