소스 검색

fix(service): 解决设备导入和微信绑定功能问题

- 修复Excel导入时大屏显示和子设备标识被错误覆盖的问题
- 添加getChildForUpdate和getShowForUpdate方法处理导入时空值情况
- 修复快捷登录认证异常信息被掩盖的问题
- 添加listByNo方法用于检测设备编号重复
- 解决导入时Excel内部重复编号和系统中重复编号的校验问题
- 实现导入时自动创建缺失的设备位置功能
- 修复导入时使用人不存在时的处理逻辑
- 解决移动端扫码启停用报500错误的问题
- 修复微信重新绑定时旧绑定未清理的问题
- 完善用户角色和菜单的客户端过滤逻辑
dazhaxie 2 시간 전
부모
커밋
8580707e4f

+ 4 - 0
.gitignore

@@ -6,6 +6,10 @@
 /.idea
 **/logs
 *.jar
+*.sql
+*.py
+*.md
+*.txt
 ### VisualStudio template
 ## Ignore Visual Studio temporary files, build results, and
 ## files generated by popular Visual Studio add-ons.

+ 8 - 2
platform-common/src/main/java/com/platform/common/util/SecurityUtils.java

@@ -122,13 +122,19 @@ public class SecurityUtils {
 
     /**
      * 检查并获取用户,未登录不抛出异常
+     * 注意:/ignores 免登录请求下 SecurityContextHolder 中是 AnonymousAuthenticationToken(非 null),
+     * principal 为字符串 "anonymousUser",不能直接走 getUser 抛"登录状态异常",需按未登录返回 null
      */
     public OauthUser checkAndGetUser() {
         Authentication authentication = getAuthentication();
-        if (authentication == null) {
+        if (authentication == null || !authentication.isAuthenticated()) {
             return null;
         }
-        return getUser(authentication);
+        Object principal = authentication.getPrincipal();
+        if (principal instanceof OauthUser) {
+            return (OauthUser) principal;
+        }
+        return null;
     }
 
     /**

+ 8 - 0
platform-dao/src/main/java/com/platform/dao/mapper/sb/SbInfoMapper.java

@@ -82,6 +82,14 @@ public interface SbInfoMapper extends MyMapper<SbInfo> {
     SbInfoVO getByNo(String no);
 
     /**
+     * 根据编号查询列表(用于导入时检测编号重复,selectOne 遇到重复数据会抛 TooManyResultsException)
+     *
+     * @param no
+     * @return
+     */
+    List<SbInfoVO> listByNo(String no);
+
+    /**
      * 根据主键查询
      *
      * @param ids :

+ 17 - 0
platform-dao/src/main/java/com/platform/dao/mapper/upms/SysUserMapper.java

@@ -38,6 +38,23 @@ public interface SysUserMapper extends MyMapper<SysUser> {
     List<String> selectUserNameByOpenId(Object id);
 
     /**
+     * 解除某个微信 openId 在全部账号上的旧绑定
+     *
+     * @param openId 微信 openId
+     * @return 更新行数
+     */
+    int clearWxOpenIdByOpenId(@Param("openId") String openId);
+
+    /**
+     * 将微信 openId 绑定到指定账号
+     *
+     * @param userId 账号主键
+     * @param openId 微信 openId
+     * @return 更新行数
+     */
+    int bindWxOpenId(@Param("userId") String userId, @Param("openId") String openId);
+
+    /**
      * 用户查询,包含用户
      *
      * @param sysUserDTO :

+ 36 - 2
platform-dao/src/main/java/com/platform/dao/util/CustomExcelImportUtil.java

@@ -1251,6 +1251,38 @@ public class CustomExcelImportUtil {
     }
 
     /**
+     * 修改导入专用:单元格为空时返回 null(表示“未填写,保持数据库原值不变”),
+     * 不能像 {@link #getShow(String)} 那样把空值默认成 NOT_SHOW(0),
+     * 否则配合 updateByPrimaryKeySelective(只跳过 null、不跳过 0)会把
+     * 已打点、本来在大屏显示的设备重新刷成“不显示”。
+     */
+    private static Integer getShowForUpdate(String trim) {
+        if (StringUtils.isBlank(trim)) {
+            return null;
+        } else if ("不显示".equals(trim)) {
+            return SbInfoShowEnum.NOT_SHOW.getValue();
+        } else {
+            return SbInfoShowEnum.IS_SHOWING.getValue();
+        }
+    }
+
+    /**
+     * 修改导入专用:同 {@link #getShowForUpdate(String)},为空时返回 null,
+     * 避免把已有的子设备标识覆盖成默认值。
+     */
+    private static Integer getChildForUpdate(String trim) {
+        if (StringUtils.isBlank(trim)) {
+            return null;
+        } else if ("子设备".equals(trim)) {
+            return SbInfoChildEnum.IS_CHILD.getValue();
+        } else if ("父设备".equals(trim)) {
+            return SbInfoChildEnum.IS_PARENT.getValue();
+        } else {
+            return SbInfoChildEnum.IS_NORMAL.getValue();
+        }
+    }
+
+    /**
      * 导入点检标准
      *
      * @param inputstream
@@ -1792,8 +1824,10 @@ public class CustomExcelImportUtil {
                 record.setStatus(getSbInfoStatus(getCellValue(row.getCell(18))));
                 record.setRemark(getCellValue(row.getCell(19)));
                 // 主子设备和是否大屏显示、所属车间(直接读取,不判断物理非空单元格数)
-                record.setIsChild(getChild(getCellValue(row.getCell(20))));
-                record.setIsShow(getShow(getCellValue(row.getCell(21))));
+                // 修改导入:Excel 中这两列留空表示"本次不改动",必须传 null 交给 updateByPrimaryKeySelective 跳过,
+                // 不能用 getChild/getShow 的默认值(0),否则会把已有的大屏显示设置、子设备标识整体刷掉
+                record.setIsChild(getChildForUpdate(getCellValue(row.getCell(20))));
+                record.setIsShow(getShowForUpdate(getCellValue(row.getCell(21))));
                 String posName = getCellValue(row.getCell(22));
                 if (StringUtils.isNotBlank(posName)) {
                     record.setPositionName(posName.trim());

+ 10 - 0
platform-dao/src/main/resources/mapper/sb/SbInfoMapper.xml

@@ -568,6 +568,16 @@ user.real_name as saveUserName
         left join t_sys_user user on sb.save_user = user.user_id
         where sb.no = #{value}
     </select>
+    <select id="listByNo" parameterType="java.lang.Object" resultType="com.platform.dao.vo.sb.SbInfoVO">
+        select
+        <include refid="Left_Column"/>
+        from t_sb_info sb
+        left join t_sb_type sbType on sb.type_id = sbType.id
+        left join t_firm_producer producer on sb.producer_id = producer.id
+        left join t_sb_position position on sb.position_id = position.id
+        left join t_sys_user user on sb.save_user = user.user_id
+        where sb.no = #{value}
+    </select>
 
     <select id="getByIds" parameterType="java.util.List" resultType="com.platform.dao.vo.sb.SbInfoVO">
         select

+ 15 - 1
platform-dao/src/main/resources/mapper/upms/SysUserMapper.xml

@@ -86,9 +86,23 @@
     <select id="selectUserNameByOpenId" resultType="java.lang.String" parameterType="java.lang.Object">
         select username
         from t_sys_user
-        where wx_open_id = #{value}
+        where wx_open_id = #{value} and del_flag = 0
     </select>
 
+    <!-- 解除某个微信 openId 在全部账号上的旧绑定,避免账号变更后 openId 残留在老记录上导致快捷登录反查到失效账号 -->
+    <update id="clearWxOpenIdByOpenId" parameterType="java.lang.String">
+        update t_sys_user
+        set wx_open_id = null
+        where wx_open_id = #{openId}
+    </update>
+
+    <!-- 将微信 openId 绑定到指定账号 -->
+    <update id="bindWxOpenId" parameterType="java.util.Map">
+        update t_sys_user
+        set wx_open_id = #{openId}
+        where user_id = #{userId} and del_flag = 0
+    </update>
+
     <select id="selectUserByRole" parameterType="com.platform.dao.dto.upms.SysUserDTO"
             resultType="com.platform.dao.vo.SysUserVO">
         select u.user_id, u.real_name

+ 11 - 2
platform-rest/src/main/java/com/platform/rest/config/security/privacy/authManager/NoPasswordAuthenticationManager.java

@@ -1,6 +1,8 @@
 package com.platform.rest.config.security.privacy.authManager;
 
 import com.platform.rest.config.security.privacy.token.QuicklyLoginAuthenticationToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.InternalAuthenticationServiceException;
 import org.springframework.security.core.Authentication;
@@ -9,6 +11,7 @@ import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 
 public class NoPasswordAuthenticationManager implements AuthenticationManager {
+    private static final Logger log = LoggerFactory.getLogger(NoPasswordAuthenticationManager.class);
     private UserDetailsService userDetailsService;
     public NoPasswordAuthenticationManager() {
     }
@@ -20,10 +23,16 @@ public class NoPasswordAuthenticationManager implements AuthenticationManager {
         try {
             userDetails = userDetailsService.loadUserByUsername(username);
             if (userDetails == null) {
-                throw new InternalAuthenticationServiceException("手机号不存在:" + quicklyAuthenticationToken.getPrincipal());
+                throw new InternalAuthenticationServiceException("账号不存在:" + username);
             }
+        } catch (AuthenticationException e) {
+            // 认证类异常(如用户不存在)保留原始信息透出,不再被掩盖成“手机号不存在”
+            log.warn("快捷登录认证失败,username={},原因:{}", username, e.getMessage());
+            throw e;
         } catch (Exception e) {
-            throw new InternalAuthenticationServiceException("手机号不存在");
+            // 其它异常(如账号已冻结的 BusinessException、DB/缓存异常)透出真实原因并记录完整堆栈
+            log.error("快捷登录加载用户信息异常,username=" + username, e);
+            throw new InternalAuthenticationServiceException("快捷登录失败:" + e.getMessage());
         }
 
         QuicklyLoginAuthenticationToken authenticationToken = new QuicklyLoginAuthenticationToken(userDetails, userDetails.getAuthorities());

+ 12 - 15
platform-rest/src/main/java/com/platform/rest/controller/upms/SysUserController.java

@@ -85,10 +85,10 @@ public class SysUserController {
             return R.error("获取当前用户信息失败");
         }
         // 处理角色与菜单
-        // 处理角色与菜单
         if (CollectionUtil.isNotEmpty(userInfoVO.getSysRoles())) {
-            userInfoVO.setSysRoles(userInfoVO.getSysRoles()
-                    .stream().filter((sysRole) -> clientId.equals(sysRole.getClientId())).collect(Collectors.toList()));
+            // 角色码/角色ID/角色名基于用户的全部角色,不受客户端过滤影响:
+            // 移动端登录客户端(front)与角色所属客户端(end)不一致,若沿用客户端过滤后的空列表,
+            // 前端将拿不到真实 roleCodes,导致"维修任务/审核任务"等按角色控制的入口判断失效
             List<String> roleIds = new ArrayList<>();
             List<String> roleCodes = new ArrayList<>();
             userInfoVO.getSysRoles().forEach(item -> {
@@ -98,9 +98,10 @@ public class SysUserController {
             userInfoVO.setRoles(ArrayUtil.toArray(roleCodes, String.class));
             userInfoVO.getUserInfo().setRoleIds(roleIds);
             userInfoVO.getUserInfo().setRoleCodes(roleCodes);
-            if (CollectionUtil.isNotEmpty(userInfoVO.getSysRoles())) {
-                userInfoVO.getUserInfo().setRoleName(userInfoVO.getSysRoles().get(0).getRoleName());
-            }
+            userInfoVO.getUserInfo().setRoleName(userInfoVO.getSysRoles().get(0).getRoleName());
+            // sysRoles 仅用于前端动态菜单/路由,继续按当前登录客户端过滤
+            userInfoVO.setSysRoles(userInfoVO.getSysRoles()
+                    .stream().filter((sysRole) -> clientId.equals(sysRole.getClientId())).collect(Collectors.toList()));
         }
         if (CollectionUtil.isNotEmpty(userInfoVO.getSysMenus())) {
             userInfoVO.setSysMenus(userInfoVO.getSysMenus()
@@ -493,15 +494,11 @@ public class SysUserController {
     @RequestMapping(value = "/bind/{openId}", method = RequestMethod.GET)
     public R bind(@PathVariable(value = "openId", required = false) String openId) throws IOException {
         log.info("微信的openId: " + openId);
-        if(!StringUtils.isBlank(openId)){
-            SysUser searchUser = new SysUser();
-            searchUser.setWxOpenId(openId);
-            SysUser sysUser = sysUserService.getModelByModel(searchUser);
-            if (sysUser == null) {
-                UserInfo userInfo = SecurityUtils.getUser().getUserInfo();
-                searchUser.setUserId(userInfo.getUserId());
-                sysUserService.modModelByPrimaryKey(searchUser);
-            }
+        if (!StringUtils.isBlank(openId)) {
+            UserInfo userInfo = SecurityUtils.getUser().getUserInfo();
+            // 先清除该 openId 在所有账号上的旧绑定(含账号变更前的老记录),再绑定到当前登录账号,
+            // 避免“已绑定就跳过”导致新账号未真正绑上、快捷登录反查到失效账号
+            sysUserService.rebindWxOpenId(userInfo.getUserId(), openId);
         }
         return R.success("微信绑定成功");
     }

+ 69 - 7
platform-service/src/main/java/com/platform/service/sb/impl/SbInfoServiceImpl.java

@@ -1824,10 +1824,40 @@ public class SbInfoServiceImpl extends BaseServiceImpl<SbInfoMapper, SbInfo, SbI
                 List<String> errors = new ArrayList<>();
                 List<SbInfo> toSave = new ArrayList<>();
                 List<Boolean> isNewFlags = new ArrayList<>();
+                // 用于缓存本次导入新增的设备位置,避免同名位置重复新增
+                Map<String, SbPosition> newPositionMap = new HashMap<>();
+                // 新增顶级位置的编码序号,从现有顶级位置数量之后开始
+                int rootPositionCount = 0;
+                for (SbPosition position : positionList) {
+                    if (StringUtils.isBlank(position.getParentId())) {
+                        rootPositionCount++;
+                    }
+                }
+                // 统计 Excel 内部重复的设备编号,避免同一批次同编号重复入库
+                Map<String, Integer> excelNoCount = new HashMap<>();
+                for (SbInfoVO item : items) {
+                    if (StringUtils.isNotBlank(item.getNo())) {
+                        excelNoCount.merge(item.getNo().trim(), 1, Integer::sum);
+                    }
+                }
                 for (SbInfoVO item : items) {
                     StringBuilder rowErr = new StringBuilder();
+                    String no = item.getNo() == null ? "" : item.getNo().trim();
+                    if (StringUtils.isNotBlank(no) && excelNoCount.getOrDefault(no, 0) > 1) {
+                        errors.add("设备【" + no + "】:Excel 中存在 " + excelNoCount.get(no)
+                                + " 行相同编号的记录,请去掉重复行后重新导入");
+                        continue;
+                    }
                     // 通过设备编号no查找已有记录,获取ID用于更新(与新增导入保持一致,不依赖Excel中的ID列)
-                    SbInfoVO existingInfo = mapper.getByNo(item.getNo());
+                    // 用列表查询避免库内存在重复编号时 selectOne 抛 TooManyResultsException
+                    List<SbInfoVO> existingList = StringUtils.isNotBlank(no)
+                            ? mapper.listByNo(no) : Collections.<SbInfoVO>emptyList();
+                    if (existingList.size() > 1) {
+                        errors.add("设备【" + no + "】:系统中存在 " + existingList.size()
+                                + " 条相同编号的设备记录,请先在设备台账中删除多余记录后再导入");
+                        continue;
+                    }
+                    SbInfoVO existingInfo = existingList.isEmpty() ? null : existingList.get(0);
                     boolean isNew = (existingInfo == null);
                     if (isNew) {
                         item.setId(IdGeneratorUtils.getObjectId());
@@ -1837,26 +1867,42 @@ public class SbInfoServiceImpl extends BaseServiceImpl<SbInfoMapper, SbInfo, SbI
                         item.setId(existingInfo.getId());
                     }
 
-                    // 处理所属车间:填了但设备位置(t_sb_position)中匹配不到则记录错误;未填写则跳过
+                    // 处理所属车间(使用位置):填了但设备位置(t_sb_position)中匹配不到则直接新增一条顶级位置;未填写则跳过
                     if (StringUtils.isNotBlank(item.getPositionName())) {
+                        String positionName = item.getPositionName().trim();
                         boolean findPosition = false;
                         for (SbPosition position : positionList) {
-                            if (position.getName().equalsIgnoreCase(item.getPositionName().trim())) {
+                            if (position.getName().equalsIgnoreCase(positionName)) {
                                 item.setPositionId(position.getId());
                                 findPosition = true;
                                 break;
                             }
                         }
                         if (!findPosition) {
-                            item.setPositionId(null);
-                            rowErr.append("所属车间【").append(item.getPositionName()).append("】在设备位置中未找到;");
+                            // 本次导入已新建过同名位置则直接复用,避免重复新增
+                            SbPosition newPosition = newPositionMap.get(positionName.toLowerCase());
+                            if (newPosition == null) {
+                                newPosition = new SbPosition()
+                                        .setId(IdGeneratorUtils.getObjectId())
+                                        .setName(positionName)
+                                        .setCode(IdGeneratorUtils.getStrNum("", ++rootPositionCount))
+                                        .setDelFlag(DelFlagEnum.NORMAL.getValue())
+                                        .setCreatedUserId(SecurityUtils.getUserInfo().getUserId())
+                                        .setCreatedTime(now)
+                                        .setUpdateTime(now);
+                                sbPositionMapper.insertSelective(newPosition);
+                                positionList.add(newPosition);
+                                newPositionMap.put(positionName.toLowerCase(), newPosition);
+                                log.info("修改导入:使用位置【{}】在设备位置中未找到,已自动新增", positionName);
+                            }
+                            item.setPositionId(newPosition.getId());
                         }
                     }
                     // 是否固定资产:默认为否
                     if (item.getIsFinancing() == null) {
                         item.setIsFinancing(SbInfoFinancingEnum.NOT.getValue());
                     }
-                    // 使用人(保管人):填了但系统中找不到则记录错误
+                    // 使用人(保管人):填了但系统中找不到则用维修人替代,维修人也不存在才记录错误
                     if (StringUtils.isNotBlank(item.getSaveUserName())
                             && !"null".equalsIgnoreCase(item.getSaveUserName())) {
                         boolean findSaveUser = false;
@@ -1869,9 +1915,25 @@ public class SbInfoServiceImpl extends BaseServiceImpl<SbInfoMapper, SbInfo, SbI
                             }
                         }
                         if (!findSaveUser) {
+                            // 使用人不存在则用维修人替代
+                            String repairName = item.getRepairUserName();
+                            if (StringUtils.isNotBlank(repairName) && !"null".equalsIgnoreCase(repairName)) {
+                                for (SysUser user : users) {
+                                    if (user.getRealName().equalsIgnoreCase(repairName.trim())) {
+                                        item.setSaveUser(user.getUserId());
+                                        item.setUseUser(user.getUserId());
+                                        findSaveUser = true;
+                                        log.info("修改导入:设备【{}】使用人【{}】在系统中未找到,已用维修人【{}】替代",
+                                                item.getNo(), item.getSaveUserName(), repairName);
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                        if (!findSaveUser) {
                             item.setSaveUser(null);
                             item.setUseUser(null);
-                            rowErr.append("使用人(保管人)【").append(item.getSaveUserName()).append("】在系统中未找到;");
+                            rowErr.append("使用人(保管人)【").append(item.getSaveUserName()).append("】在系统中未找到,且维修人也不可用;");
                         }
                     }
                     // 维修人:填了但系统中找不到则记录错误

+ 27 - 4
platform-service/src/main/java/com/platform/service/sb/impl/SbStatusLogServiceImpl.java

@@ -1,6 +1,7 @@
 package com.platform.service.sb.impl;
 import com.platform.common.bean.AbstractPageResultBean;
 import com.platform.common.exception.BusinessException;
+import com.platform.common.model.OauthUser;
 import com.platform.common.model.UserInfo;
 import com.platform.common.util.DateUtils;
 import com.platform.common.util.IdGeneratorUtils;
@@ -26,6 +27,8 @@ import org.springframework.transaction.annotation.Transactional;
 import tk.mybatis.mapper.weekend.Weekend;
 import tk.mybatis.mapper.weekend.WeekendCriteria;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
 
 import javax.persistence.Transient;
 import java.math.BigDecimal;
@@ -38,6 +41,7 @@ import java.util.List;
  * @Date 2021-05-15 18:37:43
  * @Version Copyright (c) 2020,北京乾元坤和科技有限公司 All rights reserved.
  */
+@Slf4j
 @AllArgsConstructor
 @Service("sbStatusLogService")
 public class SbStatusLogServiceImpl extends BaseServiceImpl<SbStatusLogMapper, SbStatusLog, SbStatusLogDTO> implements SbStatusLogService {
@@ -55,10 +59,29 @@ public class SbStatusLogServiceImpl extends BaseServiceImpl<SbStatusLogMapper, S
         String userId = null;
         String realName = null;
         if(userInfo == null){
-            SbInfo sbInfo = sbInfoMapper.selectByPrimaryKey(model.getSbId());
-            SysUser sysUser = sysUserMapper.selectByPrimaryKey(sbInfo.getSaveUser());
-            userId = sysUser.getUserId();
-            realName = sysUser.getRealName();
+            // 移动端免登录接口(/ignores)传null,若携带token则优先取登录用户
+            OauthUser oauthUser = SecurityUtils.checkAndGetUser();
+            if (oauthUser != null && oauthUser.getUserInfo() != null) {
+                userInfo = oauthUser.getUserInfo();
+                userId = userInfo.getUserId();
+                realName = userInfo.getRealName();
+            } else {
+                // 未登录兜底:取设备保管人;设备未指定保管人或保管人账号已删除时
+                // 不再直接 selectByPrimaryKey 抛 NPE(移动端扫码启停用报500的根因),
+                // 降级记录前端提交的实际操作人姓名,保证启停用流程可提交
+                SbInfo device = sbInfoMapper.selectByPrimaryKey(model.getSbId());
+                if (device != null && StringUtils.isNotBlank(device.getSaveUser())) {
+                    SysUser sysUser = sysUserMapper.selectByPrimaryKey(device.getSaveUser());
+                    if (sysUser != null) {
+                        userId = sysUser.getUserId();
+                        realName = sysUser.getRealName();
+                    }
+                }
+                if (userId == null) {
+                    realName = StringUtils.isNotBlank(model.getActualUser()) ? model.getActualUser() : null;
+                    log.warn("设备 {} 无有效保管人账号,状态变更记录以操作人姓名兜底:{}", model.getSbId(), realName);
+                }
+            }
         }else{
             userId = userInfo.getUserId();
             realName = userInfo.getRealName();

+ 8 - 0
platform-service/src/main/java/com/platform/service/upms/SysUserService.java

@@ -212,6 +212,14 @@ public interface SysUserService extends IBaseService<SysUser, SysUserDTO> {
 
     List<String> selectUserNameByOpenId(Object id);
 
+    /**
+     * 重新绑定微信 openId:先清除该 openId 在所有账号上的旧绑定,再绑定到目标账号
+     *
+     * @param userId 当前登录账号主键
+     * @param openId 微信 openId
+     */
+    void rebindWxOpenId(String userId, String openId);
+
     List<SysUserVO> getUserByCondition(SysUserDTO sysUserDTO);
 
     List<SysUserVO> selectUserListByRole(SysUserDTO sysUserDTO);

+ 18 - 0
platform-service/src/main/java/com/platform/service/upms/impl/SysUserServiceImpl.java

@@ -837,6 +837,24 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserMapper, SysUser,
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void rebindWxOpenId(String userId, String openId) {
+        // 1. 先解除该 openId 在所有账号上的旧绑定(含账号变更后残留的老记录),保证一个 openId 只对应一个有效账号
+        int cleared = mapper.clearWxOpenIdByOpenId(openId);
+        // 2. 再绑定到目标账号
+        int bound = mapper.bindWxOpenId(userId, openId);
+        log.info("微信重绑 openId={}, 清除旧绑定={}条, 绑定到账号userId={}, 结果={}条", openId, cleared, userId, bound);
+        if (bound <= 0) {
+            throw new BusinessException("微信绑定失败,目标账号不存在或已删除");
+        }
+        // 3. 失效按手机号/账号的用户缓存,避免旧绑定数据继续命中
+        Cache phoneCache = cacheManager.getCache("user_phone_details");
+        if (phoneCache != null) {
+            phoneCache.clear();
+        }
+    }
+
+    @Override
     public List<SysUserVO> getUserByCondition(SysUserDTO sysUserDTO) {
         return mapper.selectByDept(sysUserDTO);
     }