yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
@@ -16,6 +16,7 @@
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
@@ -26,6 +27,8 @@
import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
import cn.iocoder.yudao.module.system.service.member.MemberService;
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.permission.RoleService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.anji.captcha.model.common.ResponseModel;
@@ -43,6 +46,7 @@
import org.springframework.transaction.annotation.Transactional;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -62,6 +66,14 @@
     * 企业微信社交类型,对应 {@link cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum#WECHAT_ENTERPRISE}
     */
    private static final Integer WECHAT_ENTERPRISE_TYPE = 30;
    /**
     * 企业微信扫码自动注册的账号,默认绑定的角色编码。
     *
     * 需要在「系统管理 → 角色管理」里建好该编码的角色并勾选菜单,否则新账号登录后没有任何菜单。
     * 编码而非角色编号:角色编号在本地/测试/生产各库不一致,写死会在换库后静默失效。
     */
    private static final String WXCP_DEFAULT_ROLE_CODE = "wxcp_user";
    @Resource
    private AdminUserService userService;
@@ -85,6 +97,10 @@
    private PasswordEncoder passwordEncoder;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Resource
    private RoleService roleService;
    @Resource
    private PermissionService permissionService;
    /**
     * 验证码的开关,默认为 true
@@ -203,23 +219,30 @@
        }
        // 2. 判断是否已有绑定的系统用户
        //
        // 注意:system_social_user_bind 里的 user_id 可能指向「已被删除」的用户 ——
        // AdminUserServiceImpl.deleteUser 只清理了角色和岗位,没有清理这张社交绑定表,
        // 删除后绑定关系会残留成孤儿。所以这里不能只看「有没有绑定」,
        // 还要看「绑定的人还在不在」。
        Long bindUserId = socialUser.getUserId();
        AdminUserDO user;
        AdminUserDO user = bindUserId != null ? userService.getUser(bindUserId) : null;
        if (bindUserId == null) {
            // 2.1 首次登录:自动注册新用户
        if (user == null) {
            // 2.1 首次登录,或原绑定用户已被删除:自动注册新用户并(改)绑定
            //
            // 原绑定用户已删除时必须走这条路:否则该企业成员会永久卡在「用户不存在」——
            // 既登不进来,也不会被重新注册,是一个无法自愈的死锁。
            // bindSocialUser 内部会先删掉该社交用户的旧绑定,所以改绑不会产生重复记录。
            if (bindUserId != null) {
                log.warn("[socialLogin][社交用户({}) 原绑定的用户({}) 已不存在(可能已被删除),按首次登录重新注册]",
                        socialUser.getOpenid(), bindUserId);
            }
            user = autoRegisterUser(socialUser, reqVO.getType());
            // 绑定社交账号
            socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
                    reqVO.getType(), reqVO.getCode(), reqVO.getState()));
            log.info("[socialLogin][社交登录自动注册 userId={} username={} socialType={}]",
                    user.getId(), user.getUsername(), reqVO.getType());
        } else {
            // 2.2 已有绑定:直接获取用户
            user = userService.getUser(bindUserId);
            if (user == null) {
                throw exception(USER_NOT_EXISTS);
            }
        }
        // 3. 创建 Token 令牌,记录登录日志
@@ -267,9 +290,35 @@
        user.setStatus(CommonStatusEnum.ENABLE.getStatus());
        userMapper.insert(user);
        // 5. 绑定默认角色
        //    不绑的话账号能登录,但 get-permission-info 返回空菜单,进去一片空白;
        //    这种"能进但什么都看不到"的状态不会报错,很难定位,所以注册时就绑上
        bindDefaultRole(user);
        return user;
    }
    /**
     * 给自动注册的账号绑定默认角色
     *
     * 角色不存在时只打警告、不抛异常:一个配置遗漏不应该让扫码登录整条链路不可用,
     * 而且警告日志里已经写明了去哪儿补。
     *
     * @param user 自动注册出来的用户
     */
    private void bindDefaultRole(AdminUserDO user) {
        RoleDO role = roleService.getRoleByCode(WXCP_DEFAULT_ROLE_CODE);
        if (role == null) {
            log.warn("[bindDefaultRole][角色编码({}) 不存在,用户({}) 将没有任何菜单权限;" +
                            "请在「系统管理 → 角色管理」新建编码为 {} 的角色并分配菜单]",
                    WXCP_DEFAULT_ROLE_CODE, user.getUsername(), WXCP_DEFAULT_ROLE_CODE);
            return;
        }
        permissionService.assignUserRole(user.getId(), Set.of(role.getId()));
        log.info("[bindDefaultRole][用户({}) 已绑定默认角色({}/{})]",
                user.getUsername(), role.getCode(), role.getName());
    }
    @VisibleForTesting
    void validateCaptcha(AuthLoginReqVO reqVO) {
        ResponseModel response = doValidateCaptcha(reqVO);