|
@@ -5,7 +5,9 @@ import cn.dev33.satoken.stp.StpUtil;
|
|
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
|
import cn.hutool.core.bean.BeanUtil;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import jakarta.validation.constraints.NotNull;
|
|
import jakarta.validation.constraints.NotNull;
|
|
@@ -16,14 +18,19 @@ import org.apache.seata.spring.annotation.GlobalTransactional;
|
|
|
import org.dromara.auth.domain.SysUserWechat;
|
|
import org.dromara.auth.domain.SysUserWechat;
|
|
|
import org.dromara.auth.domain.bo.SysUserWechatBo;
|
|
import org.dromara.auth.domain.bo.SysUserWechatBo;
|
|
|
import org.dromara.auth.domain.vo.SysUserWechatVo;
|
|
import org.dromara.auth.domain.vo.SysUserWechatVo;
|
|
|
|
|
+import org.dromara.auth.mapper.SysUserWechatMapper;
|
|
|
import org.dromara.auth.service.MiniAuthService;
|
|
import org.dromara.auth.service.MiniAuthService;
|
|
|
|
|
+import org.dromara.auth.util.HttpClientUtil;
|
|
|
import org.dromara.common.core.domain.R;
|
|
import org.dromara.common.core.domain.R;
|
|
|
import org.dromara.common.core.utils.ServletUtils;
|
|
import org.dromara.common.core.utils.ServletUtils;
|
|
|
|
|
+import org.dromara.common.core.utils.StringUtils;
|
|
|
import org.dromara.common.satoken.utils.LoginHelper;
|
|
import org.dromara.common.satoken.utils.LoginHelper;
|
|
|
import org.dromara.common.tenant.helper.TenantHelper;
|
|
import org.dromara.common.tenant.helper.TenantHelper;
|
|
|
import org.dromara.resource.api.RemoteFileService;
|
|
import org.dromara.resource.api.RemoteFileService;
|
|
|
import org.dromara.resource.api.domain.RemoteFile;
|
|
import org.dromara.resource.api.domain.RemoteFile;
|
|
|
|
|
+import org.dromara.system.api.RemoteUserService;
|
|
|
import org.dromara.system.api.model.LoginUser;
|
|
import org.dromara.system.api.model.LoginUser;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.http.HttpEntity;
|
|
import org.springframework.http.HttpEntity;
|
|
|
import org.springframework.http.HttpHeaders;
|
|
import org.springframework.http.HttpHeaders;
|
|
@@ -33,6 +40,7 @@ import org.springframework.web.bind.annotation.*;
|
|
|
import org.springframework.web.client.RestTemplate;
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
|
|
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
import java.util.Objects;
|
|
|
|
|
|
|
@@ -47,11 +55,18 @@ import java.util.Objects;
|
|
|
@RestController
|
|
@RestController
|
|
|
@RequestMapping("/wx")
|
|
@RequestMapping("/wx")
|
|
|
public class MiniTokenController {
|
|
public class MiniTokenController {
|
|
|
|
|
+ public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
|
|
|
|
|
|
|
|
private final MiniAuthService miniAuthService;
|
|
private final MiniAuthService miniAuthService;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private SysUserWechatMapper wechatMapper;
|
|
|
@DubboReference
|
|
@DubboReference
|
|
|
private RemoteFileService remoteFileService;
|
|
private RemoteFileService remoteFileService;
|
|
|
|
|
|
|
|
|
|
+ @DubboReference
|
|
|
|
|
+ private RemoteUserService remoteUserService;
|
|
|
|
|
+
|
|
|
@Value("${tr.wechat.secret}")
|
|
@Value("${tr.wechat.secret}")
|
|
|
private String appSecret;
|
|
private String appSecret;
|
|
|
|
|
|
|
@@ -72,34 +87,150 @@ public class MiniTokenController {
|
|
|
|
|
|
|
|
// 在动态租户上下文中执行登录逻辑
|
|
// 在动态租户上下文中执行登录逻辑
|
|
|
return TenantHelper.dynamic(tenantId, () -> {
|
|
return TenantHelper.dynamic(tenantId, () -> {
|
|
|
- SysUserWechat wechat = miniAuthService.wxLogin(wechatBo);
|
|
|
|
|
- LoginUser loginUser = new LoginUser();
|
|
|
|
|
- loginUser.setUserId(wechat.getUserId());
|
|
|
|
|
- loginUser.setNickname(wechat.getNickname());
|
|
|
|
|
- loginUser.setUserType("app_user");
|
|
|
|
|
- loginUser.setOpenid(wechat.getOpenId());
|
|
|
|
|
- loginUser.setTenantId(tenantId); // 设置租户ID到登录用户
|
|
|
|
|
-
|
|
|
|
|
- // 配置登录参数
|
|
|
|
|
- SaLoginParameter model = new SaLoginParameter();
|
|
|
|
|
- model.setDeviceType("xcx"); // 小程序设备类型
|
|
|
|
|
- model.setTimeout(30 * 24 * 60 * 60); // 30天过期时间
|
|
|
|
|
- model.setActiveTimeout(7 * 24 * 60 * 60); // 7天活跃超时
|
|
|
|
|
- model.setExtra(LoginHelper.CLIENT_KEY, "miniprogram_client_id_2025"); // 使用系统认可的APP端客户端ID
|
|
|
|
|
-
|
|
|
|
|
- //生成token
|
|
|
|
|
- LoginHelper.login(loginUser, model);
|
|
|
|
|
- // 获取token信息
|
|
|
|
|
- String accessToken = StpUtil.getTokenValue();
|
|
|
|
|
- Long expireIn = StpUtil.getTokenTimeout();
|
|
|
|
|
- SysUserWechatVo wechatVo = BeanUtil.copyProperties(wechat, SysUserWechatVo.class);
|
|
|
|
|
- wechatVo.setOpenId(wechat.getOpenId());
|
|
|
|
|
- wechatVo.setAccessToken(accessToken);
|
|
|
|
|
- wechatVo.setExpireIn(expireIn);
|
|
|
|
|
- return R.ok(wechatVo);
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 调用 Service 层逻辑
|
|
|
|
|
+ // 注意:这里需要修改 Service,如果用户不存在,让它抛出特定异常
|
|
|
|
|
+ SysUserWechat wechat = miniAuthService.wxLogin(wechatBo);
|
|
|
|
|
+
|
|
|
|
|
+ // --- 登录成功逻辑 (用户已存在) ---
|
|
|
|
|
+ LoginUser loginUser = new LoginUser();
|
|
|
|
|
+ loginUser.setUserId(wechat.getUserId());
|
|
|
|
|
+ loginUser.setNickname(wechat.getNickname());
|
|
|
|
|
+ loginUser.setUserType("app_user");
|
|
|
|
|
+ loginUser.setOpenid(wechat.getOpenId());
|
|
|
|
|
+ loginUser.setTenantId(tenantId);
|
|
|
|
|
+
|
|
|
|
|
+ SaLoginParameter model = new SaLoginParameter();
|
|
|
|
|
+ model.setDeviceType("xcx");
|
|
|
|
|
+ model.setTimeout(30 * 24 * 60 * 60);
|
|
|
|
|
+ model.setActiveTimeout(7 * 24 * 60 * 60);
|
|
|
|
|
+ model.setExtra(LoginHelper.CLIENT_KEY, "miniprogram_client_id_2025");
|
|
|
|
|
+
|
|
|
|
|
+ LoginHelper.login(loginUser, model);
|
|
|
|
|
+ String accessToken = StpUtil.getTokenValue();
|
|
|
|
|
+ Long expireIn = StpUtil.getTokenTimeout();
|
|
|
|
|
+
|
|
|
|
|
+ SysUserWechatVo wechatVo = BeanUtil.copyProperties(wechat, SysUserWechatVo.class);
|
|
|
|
|
+ wechatVo.setOpenId(wechat.getOpenId());
|
|
|
|
|
+ wechatVo.setAccessToken(accessToken);
|
|
|
|
|
+ wechatVo.setExpireIn(expireIn);
|
|
|
|
|
+
|
|
|
|
|
+ return R.ok(wechatVo);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // --- 异常处理逻辑 ---
|
|
|
|
|
+ // 判断是否是“用户未注册”的特定异常
|
|
|
|
|
+ // 这里假设你在 Service 层抛出的异常信息包含 "USER_NOT_REGISTERED"
|
|
|
|
|
+ if (e.getMessage() != null && e.getMessage().contains("USER_NOT_REGISTERED")) {
|
|
|
|
|
+ log.info("检测到新用户,返回 AppID 引导注册");
|
|
|
|
|
+ SysUserWechatVo wechatVo = new SysUserWechatVo();
|
|
|
|
|
+ wechatVo.setOpenId(wechatBo.getCode());
|
|
|
|
|
+ wechatVo.setAppId(appId);
|
|
|
|
|
+ // 返回特定状态码 (例如 201) 和 AppID
|
|
|
|
|
+ return R.warn("用户未注册,请前往注册", wechatVo);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 其他异常正常抛出
|
|
|
|
|
+ log.error("登录异常", e);
|
|
|
|
|
+ return R.fail("登录失败: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 小程序登录手机号
|
|
|
|
|
+ * */
|
|
|
|
|
+ @SaIgnore
|
|
|
|
|
+ @PostMapping("/loginByPhone")
|
|
|
|
|
+ public R<SysUserWechatVo> loginByPhone(@RequestBody Map<String, String> data) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String code = data.get("code");
|
|
|
|
|
+ String openId = data.get("openId");
|
|
|
|
|
+ log.info("手机号快捷登录,获取临时code: {}", code);
|
|
|
|
|
+
|
|
|
|
|
+ // 获取 tenantId
|
|
|
|
|
+ String tenantId = ServletUtils.getHeader(Objects.requireNonNull(ServletUtils.getRequest()), "tenantId");
|
|
|
|
|
+
|
|
|
|
|
+ return TenantHelper.dynamic(tenantId, () -> {
|
|
|
|
|
+ // 1. 通过 code 换取手机号
|
|
|
|
|
+ String phoneNumber = getPhoneNumberByCode(code);
|
|
|
|
|
+ if (phoneNumber == null) {
|
|
|
|
|
+ return R.fail("获取手机号失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("获取到手机号: {}", phoneNumber);
|
|
|
|
|
+ SysUserWechat user = null;
|
|
|
|
|
+ LoginUser userInfoByPhonenumber = remoteUserService.getUserInfoByPhonenumber(phoneNumber, "000000");
|
|
|
|
|
+ if (ObjectUtil.isNotEmpty(userInfoByPhonenumber)) {
|
|
|
|
|
+ SysUserWechat sysUserWechat = miniAuthService.queryByPhoneNumber(phoneNumber);
|
|
|
|
|
+ if (ObjectUtil.isEmpty(sysUserWechat)) {
|
|
|
|
|
+ SysUserWechat userWechat = new SysUserWechat();
|
|
|
|
|
+ userWechat.setOpenId(openId);
|
|
|
|
|
+ userWechat.setStatus("0");
|
|
|
|
|
+ // 设置租户ID
|
|
|
|
|
+ userWechat.setTenantId(tenantId);
|
|
|
|
|
+
|
|
|
|
|
+ // --- 核心修复:处理昵称 ---
|
|
|
|
|
+ // 静默登录没传昵称,生成默认昵称
|
|
|
|
|
+ String nickName = "用户_" + openId.substring(openId.length() - 8);
|
|
|
|
|
+ userWechat.setUserId(userInfoByPhonenumber.getUserId());
|
|
|
|
|
+ userWechat.setPhoneNumber(phoneNumber);
|
|
|
|
|
+ // 保存微信关联表
|
|
|
|
|
+ userWechat.setNickname(nickName); // 保存默认昵称
|
|
|
|
|
+ // --- 处理头像 ---
|
|
|
|
|
+ userWechat.setAvatarUrl("/default/avatar.png"); // 默认头像
|
|
|
|
|
+ int insert = wechatMapper.insert(userWechat);
|
|
|
|
|
+ if (insert > 0) {
|
|
|
|
|
+ user = userWechat;
|
|
|
|
|
+ }else{
|
|
|
|
|
+ return R.fail("保存微信关联失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ user = sysUserWechat;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 根据手机号查询用户
|
|
|
|
|
+ // user = miniAuthService.queryByPhoneNumber(phoneNumber);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果业务逻辑是“查不到就注册”,请取消下面的注释
|
|
|
|
|
+ // if (user == null) {
|
|
|
|
|
+ // user = miniAuthService.registerByPhoneNumber(phoneNumber);
|
|
|
|
|
+ // }
|
|
|
|
|
+
|
|
|
|
|
+ if (user == null) {
|
|
|
|
|
+ return R.fail("该手机号未注册,请先注册");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 登录逻辑(与 /login 接口保持一致)
|
|
|
|
|
+ LoginUser loginUser = new LoginUser();
|
|
|
|
|
+ loginUser.setUserId(user.getUserId());
|
|
|
|
|
+ loginUser.setNickname(user.getNickname());
|
|
|
|
|
+ loginUser.setUserType("app_user");
|
|
|
|
|
+ loginUser.setOpenid(user.getOpenId());
|
|
|
|
|
+ loginUser.setTenantId(tenantId);
|
|
|
|
|
+
|
|
|
|
|
+ SaLoginParameter model = new SaLoginParameter();
|
|
|
|
|
+ model.setDeviceType("xcx");
|
|
|
|
|
+ model.setTimeout(30 * 24 * 60 * 60);
|
|
|
|
|
+ model.setActiveTimeout(7 * 24 * 60 * 60);
|
|
|
|
|
+ model.setExtra(LoginHelper.CLIENT_KEY, "miniprogram_client_id_2025");
|
|
|
|
|
+
|
|
|
|
|
+ LoginHelper.login(loginUser, model);
|
|
|
|
|
+ String accessToken = StpUtil.getTokenValue();
|
|
|
|
|
+ Long expireIn = StpUtil.getTokenTimeout();
|
|
|
|
|
+
|
|
|
|
|
+ SysUserWechatVo userVo = BeanUtil.copyProperties(user, SysUserWechatVo.class);
|
|
|
|
|
+ userVo.setAccessToken(accessToken);
|
|
|
|
|
+ userVo.setExpireIn(expireIn);
|
|
|
|
|
+
|
|
|
|
|
+ return R.ok(userVo);
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("手机号登录失败", e);
|
|
|
|
|
+ return R.fail("登录失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
|
|
|
@SaIgnore
|
|
@SaIgnore
|
|
|
@PostMapping("/bindPhone")
|
|
@PostMapping("/bindPhone")
|
|
@@ -187,9 +318,53 @@ public class MiniTokenController {
|
|
|
/*
|
|
/*
|
|
|
* 获取手机号
|
|
* 获取手机号
|
|
|
* */
|
|
* */
|
|
|
- @PostMapping("/getPhoneNumber")
|
|
|
|
|
- public Object getPhoneNumber(@RequestBody Map<String, String> data) {
|
|
|
|
|
- return R.ok(getPhoneNumberByCode(data.get("code")));
|
|
|
|
|
|
|
+ @PostMapping("/getOpenId")
|
|
|
|
|
+ public Object getOpenId(@RequestBody Map<String, String> data) {
|
|
|
|
|
+ return R.ok(getOpenid(data.get("code")));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String getOpenid(String code) {
|
|
|
|
|
+ // 1. 获取配置(注意:这里获取的是后端配置的 AppID,必须与前端小程序的 AppID 一致)
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 构建参数
|
|
|
|
|
+ Map<String, String> map = new HashMap<>();
|
|
|
|
|
+ map.put("appid", appId);
|
|
|
|
|
+ map.put("secret", appSecret);
|
|
|
|
|
+ map.put("js_code", code);
|
|
|
|
|
+ map.put("grant_type", "authorization_code");
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 3. 调用微信接口
|
|
|
|
|
+ String json = HttpClientUtil.doGet(WX_LOGIN, map);
|
|
|
|
|
+ log.info("微信接口返回: {}", json);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 解析结果
|
|
|
|
|
+ com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(json);
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 【关键】先检查有没有错误码
|
|
|
|
|
+ if (jsonObject.containsKey("errcode")) {
|
|
|
|
|
+ int errCode = jsonObject.getIntValue("errcode");
|
|
|
|
|
+ String errMsg = jsonObject.getString("errmsg");
|
|
|
|
|
+ log.error("微信登录失败: code={}, error={}", errCode, errMsg);
|
|
|
|
|
+ // 抛出异常或返回 null,让上层知道登录失败
|
|
|
|
|
+ throw new RuntimeException("微信登录失败: " + errMsg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 获取 OpenID
|
|
|
|
|
+ String openid = jsonObject.getString("openid");
|
|
|
|
|
+ if (StringUtils.isEmpty(openid)) {
|
|
|
|
|
+ throw new RuntimeException("微信返回openid为空");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 7. (可选) 获取 UnionID - 如果你需要打通公众号/App 账号体系
|
|
|
|
|
+ String unionId = jsonObject.getString("unionid");
|
|
|
|
|
+
|
|
|
|
|
+ return openid;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("调用微信接口异常", e);
|
|
|
|
|
+ throw new RuntimeException("网络连接微信服务失败", e);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private String getPhoneNumberByCode(String code) {
|
|
private String getPhoneNumberByCode(String code) {
|