yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
@@ -17,10 +17,12 @@
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.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.event.SocialLoginEvent;
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;
@@ -35,10 +37,13 @@
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Objects;
import java.util.UUID;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
@@ -53,8 +58,15 @@
@Slf4j
public class AdminAuthServiceImpl implements AdminAuthService {
    /**
     * 企业微信社交类型,对应 {@link cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum#WECHAT_ENTERPRISE}
     */
    private static final Integer WECHAT_ENTERPRISE_TYPE = 30;
    @Resource
    private AdminUserService userService;
    @Resource
    private AdminUserMapper userMapper;
    @Resource
    private LoginLogService loginLogService;
    @Resource
@@ -69,6 +81,10 @@
    private CaptchaService captchaService;
    @Resource
    private SmsCodeApi smsCodeApi;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    /**
     * 验证码的开关,默认为 true
@@ -113,7 +129,15 @@
                    reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
        }
        // 创建 Token 令牌,记录登录日志
        return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
        AuthLoginRespVO result = createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
        // 发布社交登录事件(绑定新社交账号时)
        if (reqVO.getSocialType() != null) {
            eventPublisher.publishEvent(new SocialLoginEvent(
                    reqVO.getSocialType(), user.getId(), user.getUsername(), "账号密码绑定"));
        }
        return result;
    }
    @Override
@@ -169,22 +193,81 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AuthLoginRespVO socialLogin(AuthSocialLoginReqVO reqVO) {
        // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
        // 1. 使用 code 授权码,获取社交用户信息
        SocialUserRespDTO socialUser = socialUserService.getSocialUserByCode(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
                reqVO.getCode(), reqVO.getState());
        if (socialUser == null || socialUser.getUserId() == null) {
        if (socialUser == null) {
            throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
        }
        // 获得用户
        AdminUserDO user = userService.getUser(socialUser.getUserId());
        // 2. 判断是否已有绑定的系统用户
        Long bindUserId = socialUser.getUserId();
        AdminUserDO user;
        if (bindUserId == null) {
            // 2.1 首次登录:自动注册新用户
            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);
        }
        }
        // 创建 Token 令牌,记录登录日志
        return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL);
        // 3. 创建 Token 令牌,记录登录日志
        AuthLoginRespVO result = createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL);
        // 4. 发布社交登录事件(供其他模块监听,如发送企业微信通知)
        eventPublisher.publishEvent(new SocialLoginEvent(
                reqVO.getType(), user.getId(), user.getUsername(), "社交扫码"));
        return result;
    }
    /**
     * 社交登录自动注册用户
     *
     * 当用户首次通过社交账号(如企业微信)扫码且未绑定时,自动创建系统用户
     *
     * @param socialUser 社交用户信息
     * @param socialType 社交类型
     * @return 新创建的用户
     */
    private AdminUserDO autoRegisterUser(SocialUserRespDTO socialUser, Integer socialType) {
        // 1. 自动生成用户名(wx_ + 8位随机十六进制)
        String rawUsername = "wx_" + UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
        // 2. 确保用户名唯一(如果冲突则重试)
        String username = rawUsername;
        int retry = 0;
        while (userService.getUserByUsername(username) != null && retry < 5) {
            username = "wx_" + UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
            retry++;
        }
        if (retry >= 5) {
            throw exception(USER_USERNAME_EXISTS);
        }
        // 3. 生成随机密码
        String password = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
        // 4. 创建用户
        AdminUserDO user = new AdminUserDO();
        user.setUsername(username);
        user.setNickname(socialUser.getNickname() != null ? socialUser.getNickname() : username);
        user.setPassword(passwordEncoder.encode(password));
        user.setStatus(CommonStatusEnum.ENABLE.getStatus());
        userMapper.insert(user);
        return user;
    }
    @VisibleForTesting