diff --git a/intc-admin/src/main/java/com/intc/web/controller/AuthController.java b/intc-admin/src/main/java/com/intc/web/controller/AuthController.java index 21564ad..ff61f94 100644 --- a/intc-admin/src/main/java/com/intc/web/controller/AuthController.java +++ b/intc-admin/src/main/java/com/intc/web/controller/AuthController.java @@ -40,6 +40,7 @@ import com.intc.web.service.SysLoginService; import com.intc.web.service.SysRegisterService; import com.intc.weixin.domain.bo.ReqWxLogin; import com.intc.weixin.service.WxLoginService; +import com.intc.weixin.config.WxMaProperties; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -83,6 +84,9 @@ public class AuthController { @Autowired(required = false) private WxLoginService wxLoginService; + @Autowired(required = false) + private WxMaProperties wxMaProperties; + /** * 登录方法 @@ -250,14 +254,27 @@ public class AuthController { /** * 微信小程序登录 * - * @param request 微信登录请求 + * @param body 微信登录请求JSON字符串 * @return 结果 */ @ApiEncrypt @PostMapping("/wechat_login") - public R wechatLogin(@Validated @RequestBody ReqWxLogin request) { + public R wechatLogin(@RequestBody String body) { try { - log.info("收到微信登录请求: clientId={}, tenantId={}", request.getClientId(), request.getTenantId()); + ReqWxLogin request = JsonUtils.parseObject(body, ReqWxLogin.class); + ValidatorUtils.validate(request); + + // 如果未传递 clientId,使用默认配置的 clientId + if (StringUtils.isBlank(request.getClientId()) && wxMaProperties != null) { + request.setClientId(wxMaProperties.getDefaultClientId()); + // 更新 body 中的 clientId + body = JsonUtils.toJsonString(request); + } + + String clientId = request.getClientId(); + String grantType = "wechat"; // 微信登录的授权类型 + + log.info("收到微信登录请求: clientId={}, tenantId={}", clientId, request.getTenantId()); // 1. 检查服务是否可用 if (wxLoginService == null) { @@ -266,43 +283,33 @@ public class AuthController { } // 2. 校验客户端 - SysClientVo client = clientService.queryByClientId(request.getClientId()); + SysClientVo client = clientService.queryByClientId(clientId); if (ObjectUtil.isNull(client)) { - log.error("客户端不存在: clientId={}", request.getClientId()); + log.error("客户端不存在: clientId={}", clientId); + return R.fail(MessageUtils.message("auth.grant.type.error")); + } + // 校验 client 内是否包含 grantType + if (!StringUtils.contains(client.getGrantType(), grantType)) { + log.error("客户端不支持该授权类型: clientId={}, grantType={}", clientId, grantType); return R.fail(MessageUtils.message("auth.grant.type.error")); } if (!SystemConstants.NORMAL.equals(client.getStatus())) { - log.error("客户端已被禁用: clientId={}, status={}", request.getClientId(), client.getStatus()); + log.error("客户端已被禁用: clientId={}, status={}", clientId, client.getStatus()); return R.fail(MessageUtils.message("auth.grant.type.blocked")); } // 3. 校验租户 if (StringUtils.isNotBlank(request.getTenantId())) { - try { - loginService.checkTenant(request.getTenantId()); - } catch (Exception e) { - log.error("租户校验失败: tenantId={}", request.getTenantId(), e); - return R.fail("租户校验失败: " + e.getMessage()); - } + loginService.checkTenant(request.getTenantId()); } - // 4. 执行微信登录 - AquUser aquUser = wxLoginService.loginByWeChat( - request.getCode(), - request.getJsCode(), - request.getTenantId() - ); + // 4. 使用标准的 IAuthStrategy 登录方法 + LoginVo loginVo = IAuthStrategy.login(body, client, grantType); + + log.info("微信登录成功: userId={}, accessToken={}", + loginVo.getUserId(), loginVo.getAccessToken() != null ? "***" : "null"); - if (aquUser == null) { - log.error("微信登录失败,未返回用户信息"); - return R.fail("登录失败,请重试"); - } - - // 5. 返回用户信息 - log.info("微信登录成功: userId={}, mobilePhone={}", - aquUser.getId(), aquUser.getMobilePhone()); - - return R.ok(aquUser); + return R.ok(loginVo); } catch (ServiceException e) { log.error("微信登录业务异常: {}", e.getMessage(), e); diff --git a/intc-admin/src/main/java/com/intc/web/service/impl/WechatAuthStrategy.java b/intc-admin/src/main/java/com/intc/web/service/impl/WechatAuthStrategy.java new file mode 100644 index 0000000..eef6e8a --- /dev/null +++ b/intc-admin/src/main/java/com/intc/web/service/impl/WechatAuthStrategy.java @@ -0,0 +1,136 @@ +package com.intc.web.service.impl; + +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.stp.parameter.SaLoginParameter; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.intc.common.core.constant.SystemConstants; +import com.intc.common.core.domain.model.LoginUser; +import com.intc.common.core.exception.ServiceException; +import com.intc.common.core.exception.user.UserException; +import com.intc.common.core.utils.StringUtils; +import com.intc.common.core.utils.ValidatorUtils; +import com.intc.common.json.utils.JsonUtils; +import com.intc.common.satoken.utils.LoginHelper; +import com.intc.common.tenant.helper.TenantHelper; +import com.intc.fishery.domain.AquUser; +import com.intc.system.domain.SysUser; +import com.intc.system.domain.vo.SysClientVo; +import com.intc.system.domain.vo.SysUserVo; +import com.intc.system.mapper.SysUserMapper; +import com.intc.web.domain.vo.LoginVo; +import com.intc.web.service.IAuthStrategy; +import com.intc.web.service.SysLoginService; +import com.intc.weixin.domain.bo.ReqWxLogin; +import com.intc.weixin.service.WxLoginService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 微信小程序认证策略 + * + * @author intc + */ +@Slf4j +@Service("wechat" + IAuthStrategy.BASE_NAME) +@RequiredArgsConstructor +public class WechatAuthStrategy implements IAuthStrategy { + + private final WxLoginService wxLoginService; + private final SysLoginService loginService; + private final SysUserMapper userMapper; + + @Override + public LoginVo login(String body, SysClientVo client) { + ReqWxLogin loginBody = JsonUtils.parseObject(body, ReqWxLogin.class); + ValidatorUtils.validate(loginBody); + + String tenantId = loginBody.getTenantId(); + String code = loginBody.getCode(); + String jsCode = loginBody.getJsCode(); + + // 在租户上下文中执行登录 + AquUser[] aquUserHolder = new AquUser[1]; // 用于保存 AquUser + LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { + // 1. 调用微信登录服务获取或创建渔业用户(用于获取手机号和openId) + AquUser aquUser = wxLoginService.loginByWeChat(code, jsCode, tenantId); + + if (aquUser == null) { + log.error("微信登录失败,未返回用户信息"); + throw new ServiceException("登录失败,请重试"); + } + + aquUserHolder[0] = aquUser; // 保存以便后续使用 + + log.info("获取微信用户信息成功: aquUserId={}, mobilePhone={}", + aquUser.getId(), aquUser.getMobilePhone()); + + // 2. 根据手机号查询系统用户 + String mobilePhone = aquUser.getMobilePhone(); + if (StringUtils.isBlank(mobilePhone)) { + log.error("微信用户手机号为空: aquUserId={}", aquUser.getId()); + throw new ServiceException("获取手机号失败,请重新授权"); + } + + SysUserVo sysUser = loadUserByPhone(mobilePhone); + + log.info("找到对应的系统用户: sysUserId={}, userName={}", + sysUser.getUserId(), sysUser.getUserName()); + + // 3. 使用系统用户构建 LoginUser(包含完整的权限信息) + return loginService.buildLoginUser(sysUser); + }); + + // 3. 设置客户端信息 + loginUser.setClientKey(client.getClientKey()); + loginUser.setDeviceType(client.getDeviceType()); + + // 4. 配置登录参数 + SaLoginParameter model = new SaLoginParameter(); + model.setDeviceType(client.getDeviceType()); + model.setTimeout(client.getTimeout()); + model.setActiveTimeout(client.getActiveTimeout()); + model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId()); + + // 5. 执行登录,生成 token + LoginHelper.login(loginUser, model); + + // 6. 构建返回对象 + LoginVo loginVo = new LoginVo(); + loginVo.setAccessToken(StpUtil.getTokenValue()); + loginVo.setExpireIn(StpUtil.getTokenTimeout()); + loginVo.setClientId(client.getClientId()); + loginVo.setOpenid(aquUserHolder[0].getWxOpenId()); // 从 AquUser 中获取 openId + loginVo.setUserId(loginUser.getUserId()); + loginVo.setUserName(loginUser.getUsername()); + loginVo.setNickName(loginUser.getNickname()); + + return loginVo; + } + + /** + * 根据手机号加载系统用户 + * + * @param phone 手机号 + * @return 系统用户信息 + */ + private SysUserVo loadUserByPhone(String phone) { + SysUserVo user = userMapper.selectVoOne( + new LambdaQueryWrapper() + .eq(SysUser::getPhonenumber, phone) + ); + + if (ObjectUtil.isNull(user)) { + log.error("系统中不存在该手机号的用户: phone={}", phone); + throw new UserException("user.not.exists", phone); + } + + if (SystemConstants.DISABLE.equals(user.getStatus())) { + log.error("用户已被停用: phone={}, userId={}", phone, user.getUserId()); + throw new UserException("user.blocked", phone); + } + + return user; + } +} diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaProperties.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaProperties.java index 3e77490..12b7d2d 100644 --- a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaProperties.java +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/config/WxMaProperties.java @@ -39,4 +39,9 @@ public class WxMaProperties { */ private String msgDataFormat; + /** + * 默认客户端ID(小程序登录使用) + */ + private String defaultClientId = "miniapp-client"; + } diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/domain/bo/ReqWxLogin.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/domain/bo/ReqWxLogin.java index c9f188c..91ac9a2 100644 --- a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/domain/bo/ReqWxLogin.java +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/domain/bo/ReqWxLogin.java @@ -1,5 +1,6 @@ package com.intc.weixin.domain.bo; +import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotBlank; import lombok.Data; @@ -27,12 +28,12 @@ public class ReqWxLogin implements Serializable { * 登录时获取的jsCode(用于获取openId) */ @NotBlank(message = "jsCode不能为空") + @JsonProperty("js_code") private String jsCode; /** * 客户端ID */ - @NotBlank(message = "客户端ID不能为空") private String clientId; /**