Files
mp-java/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java
赵忠林 6be4421ed9 feat(payment): 添加微信支付商家转账场景报备信息配置
- 在 application-cms.yml、application-dev.yml、application-prod.yml 和 application-yd.yml 中
  添加 wechatpay.transfer.scene-id 和 scene-report-infos-json 配置项
- 重构 CmsNavigation 实体类,将 modelName 字段位置调整到正确位置
- 修改 CmsNavigationMapper.xml 添加模型名称关联查询
- 更新 JSONUtil 工具类,注册 JavaTimeModule 支持 LocalDateTime 等 Java8 时间类型
- 扩展 ShopDealerUser 实体类,添加 dealerName 和 community 字段
- 在 ShopDealerUserController 中添加手机号排重逻辑
- 修改 ShopDealerUserMapper.xml 增加关键词搜索字段
- 移除 ShopDealerWithdrawController 中多余的操作日志注解
- 扩展 ShopGoods 实体类,添加 categoryName 字段并修改关联查询
- 更新 WxLoginController 构造函数注入 ObjectMapper
- 增强 WxTransferService 添加转账场景报备信息验证和日志记录
2026-01-29 20:49:18 +08:00

834 lines
32 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.gxwebsoft.common.system.controller;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxwebsoft.cms.entity.CmsWebsite;
import com.gxwebsoft.cms.service.CmsWebsiteService;
import com.gxwebsoft.common.core.config.ConfigProperties;
import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.core.security.JwtSubject;
import com.gxwebsoft.common.core.security.JwtUtil;
import com.gxwebsoft.common.core.utils.CommonUtil;
import com.gxwebsoft.common.core.utils.RedisUtil;
import com.gxwebsoft.common.core.utils.RequestUtil;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.system.entity.*;
import com.gxwebsoft.common.system.param.UserParam;
import com.gxwebsoft.common.system.result.LoginResult;
import com.gxwebsoft.common.system.service.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import okhttp3.*;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeUnit;
import static com.gxwebsoft.common.core.constants.PlatformConstants.MP_WEIXIN;
import static com.gxwebsoft.common.core.constants.RedisConstants.ACCESS_TOKEN_KEY;
import static com.gxwebsoft.common.core.constants.RedisConstants.MP_WX_KEY;
@RestController
@RequestMapping("/api/wx-login")
@Tag(name = "微信小程序登录API")
public class WxLoginController extends BaseController {
private final StringRedisTemplate redisTemplate;
private final OkHttpClient http = new OkHttpClient();
private final ObjectMapper om;
private volatile long tokenExpireEpoch = 0L; // 过期的 epoch 秒
@Resource
private SettingService settingService;
@Resource
private UserService userService;
@Resource
private ConfigProperties configProperties;
@Resource
private UserRoleService userRoleService;
@Resource
private LoginRecordService loginRecordService;
@Resource
private RoleService roleService;
@Resource
private RedisUtil redisUtil;
@Resource
private RequestUtil requestUtil;
@Resource
private ConfigProperties config;
@Resource
private UserRefereeService userRefereeService;
@Resource
private CmsWebsiteService cmsWebsiteService;
public WxLoginController(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) {
this.redisTemplate = redisTemplate;
this.om = objectMapper;
}
@Operation(summary = "获取微信AccessToken")
@Transactional(rollbackFor = {Exception.class})
@PostMapping("/getAccessToken")
public ApiResult<String> getMpAccessToken() {
return success("操作成功", getAccessToken());
}
@Operation(summary = "获取微信openId")
@Transactional(rollbackFor = {Exception.class})
@PostMapping("/getOpenId")
public ApiResult<LoginResult> getOpenId(@RequestBody UserParam userParam, HttpServletRequest request) {
// 1.获取openid
JSONObject result = getOpenIdByCode(userParam);
String openid = result.getString("openid");
String unionid = result.getString("unionid");
if (openid == null) {
return fail("获取openid失败", null);
}
// 2.通过openid查询用户是否已存在
User user = userService.getByOauthId(userParam);
// 3.存在则签发token并返回登录成功,不存在则注册新用户
if (user == null) {
user = addUser(userParam);
}
// 4.签发token
loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
configProperties.getTokenExpireTime(), configProperties.getTokenKey());
return success("登录成功", new LoginResult(access_token, user));
}
@Operation(summary = "微信授权手机号码并登录")
@Transactional(rollbackFor = {Exception.class})
@PostMapping("/loginByMpWxPhone")
public ApiResult<LoginResult> loginByMpWxPhone(@RequestBody UserParam userParam, HttpServletRequest request) {
// 获取手机号码
String phone = getPhoneByCode(userParam);
if (phone == null) {
String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString());
redisTemplate.delete(key);
throw new BusinessException("授权失败,请重试");
}
// 查询是否存在
User user = userService.getByPhone(phone);
// 不存在则注册
if (user == null) {
if ((userParam.getOpenid() == null || userParam.getOpenid().isEmpty()) && userParam.getAuthCode() != null) {
UserParam userParam2 = new UserParam();
userParam2.setCode(userParam.getAuthCode());
JSONObject result = getOpenIdByCode(userParam2);
String openid = result.getString("openid");
// String unionid = result.getString("unionid");
userParam.setOpenid(openid);
}
userParam.setPhone(phone);
user = addUser(userParam);
user.setRecommend(1);
} else {
// 存在则检查绑定上级
if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) {
UserReferee check = userRefereeService.check(user.getUserId(), userParam.getRefereeId());
if (check == null) {
UserReferee userReferee = new UserReferee();
userReferee.setDealerId(userParam.getRefereeId());
userReferee.setUserId(user.getUserId());
userRefereeService.save(userReferee);
}
}
}
// 签发token
String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
configProperties.getTokenExpireTime(), configProperties.getTokenKey());
loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_REGISTER, null, user.getTenantId(), request);
// 附加体育中心项目用户信息
// user.setBookingUser();
return success("登录成功", new LoginResult(access_token, user));
}
@Operation(summary = "微信授权手机号码并更新")
@Transactional(rollbackFor = {Exception.class})
@PostMapping("/updatePhoneByMpWx")
public ApiResult<?> updatePhoneByMpWx(@RequestBody UserParam userParam) {
// 获取微信授权手机号
String phone = getPhoneByCode(userParam);
// 查询当前用户
User user = userService.getById(userParam.getUserId());
if (user != null && phone != null) {
user.setPhone(phone);
userService.updateUser(user);
return success("更新成功", phone);
}
return fail("更新失败");
}
/**
* 新用户注册
*/
private User addUser(UserParam userParam) {
User addUser = new User();
// 注册用户
addUser.setStatus(0);
addUser.setUsername(createUsername("wx_"));
addUser.setNickname("微信用户");
addUser.setPlatform(MP_WEIXIN);
addUser.setGradeId(2);
if (userParam.getGradeId() != null) {
addUser.setGradeId(userParam.getGradeId());
}
if (userParam.getPhone() != null) {
addUser.setPhone(userParam.getPhone());
}
if (StrUtil.isNotBlank(userParam.getOpenid())) {
addUser.setOpenid(userParam.getOpenid());
}
if (StrUtil.isNotBlank(userParam.getUnionid())) {
addUser.setUnionid(userParam.getUnionid());
}
addUser.setPassword(userService.encodePassword(CommonUtil.randomUUID16()));
addUser.setTenantId(getTenantId());
addUser.setRecommend(1);
Role role = roleService.getOne(new QueryWrapper<Role>().eq("role_code", "user"), false);
addUser.setRoleId(role.getRoleId());
if (userService.saveUser(addUser)) {
// 添加用户角色
final UserRole userRole = new UserRole();
userRole.setUserId(addUser.getUserId());
userRole.setTenantId(addUser.getTenantId());
userRole.setRoleId(addUser.getRoleId());
userRoleService.save(userRole);
}
// 绑定关系
if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) {
UserReferee check = userRefereeService.check(addUser.getUserId(), userParam.getRefereeId());
if (check == null) {
UserReferee userReferee = new UserReferee();
userReferee.setDealerId(userParam.getRefereeId());
userReferee.setUserId(addUser.getUserId());
userRefereeService.save(userReferee);
}
}
return addUser;
}
// 获取openid
private JSONObject getOpenIdByCode(UserParam userParam) {
// 从缓存获取微信小程序配置信息
JSONObject setting = getWxConfigFromCache(getTenantId());
// 获取openId
String apiUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + setting.getString("appId") + "&secret=" + setting.getString("appSecret") + "&js_code=" + userParam.getCode() + "&grant_type=authorization_code";
// 执行get请求
String result = HttpUtil.get(apiUrl);
// 解析access_token
return JSON.parseObject(result);
}
/**
* 获取微信手机号码
*
* @param userParam 需要传微信凭证code
*/
private String getPhoneByCode(UserParam userParam) {
// 获取手机号码
String apiUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken();
HashMap<String, Object> paramMap = new HashMap<>();
if (StrUtil.isBlank(userParam.getCode())) {
throw new BusinessException("code不能为空");
}
paramMap.put("code", userParam.getCode());
// 执行post请求
String post = HttpUtil.post(apiUrl, JSON.toJSONString(paramMap));
JSONObject json = JSON.parseObject(post);
if (json.get("errcode").equals(0)) {
JSONObject phoneInfo = JSON.parseObject(json.getString("phone_info"));
// 微信用户的手机号码
final String phoneNumber = phoneInfo.getString("phoneNumber");
// 验证手机号码
// if (userParam.getNotVerifyPhone() == null && !Validator.isMobile(phoneNumber)) {
// String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString());
// redisTemplate.delete(key);
// throw new BusinessException("手机号码格式不正确");
// }
return phoneNumber;
}
return null;
}
/**
* 生成随机账号
*
* @return username
*/
private String createUsername(String type) {
return type.concat(RandomUtil.randomString(12));
}
/**
* 获取接口调用凭据AccessToken
* <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html">...</a>
*/
public String getAccessToken() {
Integer tenantId = getTenantId();
String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString());
// 从缓存获取微信小程序配置信息
JSONObject setting = getWxConfigFromCache(tenantId);
if (setting == null) {
throw new BusinessException("请先配置小程序");
}
// 从缓存获取access_token
String value = redisTemplate.opsForValue().get(key);
if (value != null) {
// 解析access_token
JSONObject response = JSON.parseObject(value);
String accessToken = response.getString("access_token");
if (accessToken != null) {
return accessToken;
}
}
// 微信获取凭证接口
String apiUrl = "https://api.weixin.qq.com/cgi-bin/token";
// 组装url参数
String url = apiUrl.concat("?grant_type=client_credential")
.concat("&appid=").concat(setting.getString("appId"))
.concat("&secret=").concat(setting.getString("appSecret"));
// 执行get请求
String result = HttpUtil.get(url);
// 解析access_token
JSONObject response = JSON.parseObject(result);
if (response.getString("access_token") != null) {
// 存入缓存
redisTemplate.opsForValue().set(key, result, 7000L, TimeUnit.SECONDS);
return response.getString("access_token");
}
throw new BusinessException("小程序配置不正确");
}
@Operation(summary = "获取微信openId并更新")
@PostMapping("/getWxOpenId")
public ApiResult<?> getWxOpenId(@RequestBody UserParam userParam) {
final User loginUser = getLoginUser();
if (loginUser == null) {
return fail("请先登录");
}
// 已存在直接返回
if (StrUtil.isNotBlank(loginUser.getOpenid())) {
return success(loginUser);
}
// 请求微信接口获取openid
String apiUrl = "https://api.weixin.qq.com/sns/jscode2session";
final HashMap<String, Object> map = new HashMap<>();
final JSONObject setting = getWxConfigFromCache(getTenantId());
final String appId = setting.getString("appId");
final String appSecret = setting.getString("appSecret");
map.put("appid", appId);
map.put("secret", appSecret);
map.put("js_code", userParam.getCode());
map.put("grant_type", "authorization_code");
final String response = HttpUtil.get(apiUrl, map);
final JSONObject jsonObject = JSONObject.parseObject(response);
String openid = jsonObject.getString("openid");
String sessionKey = jsonObject.getString("session_key");
String unionid = jsonObject.getString("unionid");
// 保存openID
if (loginUser.getOpenid() == null || StrUtil.isBlank(loginUser.getOpenid())) {
loginUser.setOpenid(openid);
loginUser.setUnionid(unionid);
requestUtil.updateUser(loginUser);
// userService.updateById(loginUser);
}
return success("获取成功", jsonObject);
}
@Operation(summary = "仅获取微信openId")
@PostMapping("/getWxOpenIdOnly")
public ApiResult<?> getWxOpenIdOnly(@RequestBody UserParam userParam) {
String apiUrl = "https://api.weixin.qq.com/sns/jscode2session";
final HashMap<String, Object> map = new HashMap<>();
final JSONObject setting = getWxConfigFromCache(getTenantId());
final String appId = setting.getString("appId");
final String appSecret = setting.getString("appSecret");
map.put("appid", appId);
map.put("secret", appSecret);
map.put("js_code", userParam.getCode());
map.put("grant_type", "authorization_code");
final String response = HttpUtil.get(apiUrl, map);
final JSONObject jsonObject = JSONObject.parseObject(response);
return success("获取成功", jsonObject);
}
@Operation(summary = "获取微信小程序码-用户ID")
@GetMapping("/getUserQRCode")
public ApiResult<?> getQRCode() {
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken();
final HashMap<String, Object> map = new HashMap<>();
map.put("path", "/package/user/qrcode?user_id=" + getLoginUserId());
// map.put("env_version","trial");
// 获取图片 Buffer
byte[] qrCode = HttpRequest.post(apiUrl)
.body(JSON.toJSONString(map))
.execute().bodyBytes();
// 保存的文件名称
final String fileName = CommonUtil.randomUUID8().concat(".png");
// 保存路径
String filePath = getUploadDir().concat("qrcode/") + fileName;
File file = FileUtil.writeBytes(qrCode, filePath);
if (file != null) {
return success(config.getFileServer().concat("/qrcode/").concat(fileName));
}
return fail("获取失败", null);
}
@Operation(summary = "获取微信小程序码-订单核销码")
@GetMapping("/getOrderQRCode/{orderNo}")
public ApiResult<?> getOrderQRCode(@PathVariable("orderNo") String orderNo) {
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken();
final HashMap<String, Object> map = new HashMap<>();
map.put("path", "/package/admin/order-scan?orderNo=".concat(orderNo));
map.put("env_version", "release");
// 获取图片 Buffer
byte[] qrCode = HttpRequest.post(apiUrl)
.body(JSON.toJSONString(map))
.execute().bodyBytes();
// 保存的文件名称
final String fileName = CommonUtil.randomUUID8().concat(".png");
// 保存路径
String filePath = getUploadDir().concat("qrcode/") + fileName;
File file = FileUtil.writeBytes(qrCode, filePath);
if (file != null) {
return success(config.getFileServer().concat("/qrcode/").concat(fileName));
}
return fail("获取失败", null);
}
@Operation(summary = "获取微信小程序码-订单核销码-数量极多的业务场景")
@GetMapping("/getOrderQRCodeUnlimited/{scene}")
public void getOrderQRCodeUnlimited(@PathVariable("scene") String scene, HttpServletResponse response) throws IOException {
try {
// 从scene参数中解析租户ID
System.out.println("scene = " + scene);
Integer tenantId = extractTenantIdFromScene(scene);
System.out.println("从scene参数中解析租户ID = " + tenantId);
if (tenantId == null) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("{\"error\":\"无法从scene参数中获取租户信息\"}");
return;
}
// 使用指定租户ID获取 access_token
String accessToken = getAccessTokenForTenant(tenantId);
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken;
final HashMap<String, Object> map = new HashMap<>();
map.put("scene", scene);
map.put("page", "pages/index/index");
map.put("env_version", "release");
// 判断应用运行状态
final CmsWebsite website = cmsWebsiteService.getByTenantId(tenantId);
if(website.getRunning().equals(2)){
map.put("check_path",false);
map.put("env_version","trial");
}
String jsonBody = JSON.toJSONString(map);
System.out.println("请求的 JSON body = " + jsonBody);
// 获取微信 API 响应
cn.hutool.http.HttpResponse httpResponse = HttpRequest.post(apiUrl)
.body(jsonBody)
.execute();
byte[] responseBytes = httpResponse.bodyBytes();
String contentType = httpResponse.header("Content-Type");
// 检查响应内容类型,判断是否为错误响应
if (contentType != null && contentType.contains("application/json")) {
// 微信返回了错误信息JSON格式
String errorResponse = new String(responseBytes, "UTF-8");
System.err.println("微信 API 错误响应: " + errorResponse);
// 返回错误信息给前端
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write(errorResponse);
return;
}
// 成功获取二维码图片
response.setContentType("image/png");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Content-Disposition", "inline; filename=qrcode.png");
// 输出图片
response.getOutputStream().write(responseBytes);
System.out.println("二维码生成成功,大小: " + responseBytes.length + " bytes");
} catch (Exception e) {
System.err.println("生成二维码失败: " + e.getMessage());
e.printStackTrace();
// 返回错误信息
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("{\"error\":\"生成二维码失败: " + e.getMessage() + "\"}");
}
}
@Operation(summary = "获取微信小程序码-用户ID")
@GetMapping("/getQRCodeText")
public byte[] getQRCodeText(String scene, String page, Integer width,
Boolean isHyaline, String envVersion) throws IOException {
HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/wxa/getwxacodeunlimit")
.newBuilder()
.addQueryParameter("access_token", getLocalAccessToken())
.build();
System.out.println("page = " + page);
// 构造请求 JSON
// 注意scene 仅支持可见字符,长度上限 32尽量 URL-safe字母数字下划线等
// page 必须是已发布小程序内的路径(不带开头斜杠也可)
var root = om.createObjectNode();
root.put("scene", scene);
if (page != null) root.put("page", page);
if (width != null) root.put("width", width); // 默认 430建议 280~1280
if (isHyaline != null) root.put("is_hyaline", isHyaline);
if (envVersion != null) root.put("env_version", envVersion); // release/trial/develop
okhttp3.RequestBody reqBody = okhttp3.RequestBody.create(
root.toString(), MediaType.parse("application/json; charset=utf-8"));
Request req = new Request.Builder().url(url).post(reqBody).build();
try (Response resp = http.newCall(req).execute()) {
if (!resp.isSuccessful()) {
throw new IOException("HTTP " + resp.code() + " calling getwxacodeunlimit");
}
MediaType ct = resp.body().contentType();
byte[] bytes = resp.body().bytes();
// 微信出错时返回 JSON需要识别一下
if (ct != null && ct.subtype() != null && ct.subtype().contains("json")) {
String err = new String(bytes);
throw new IOException("WeChat error: " + err);
}
return bytes; // 成功就是图片二进制PNG
}
}
/**
* 获取/刷新 access_token
*/
public String getLocalAccessToken() throws IOException {
long now = Instant.now().getEpochSecond();
String key = "AccessToken:Local:" + getTenantId();
// 从缓存获取access_token使用JSON格式
String value = redisUtil.get(key);
if (value != null && now < tokenExpireEpoch - 60) {
try {
// 尝试解析为JSON格式
JSONObject response = JSON.parseObject(value);
String accessToken = response.getString("access_token");
if (accessToken != null) {
System.out.println("从缓存获取到access_token(Local): " + accessToken.substring(0, Math.min(10, accessToken.length())) + "...");
return accessToken;
}
} catch (Exception e) {
// 如果解析失败可能是旧格式的纯字符串token
System.out.println("本地缓存token格式异常使用原值: " + e.getMessage());
return value;
}
}
// 从缓存获取微信小程序配置信息
Integer tenantId = getTenantId();
JSONObject setting = getWxConfigFromCache(tenantId);
if (setting == null) {
throw new IOException("请先配置小程序");
}
String appId = setting.getString("appId");
String appSecret = setting.getString("appSecret");
if (appId == null || appSecret == null) {
throw new IOException("小程序配置不完整,缺少 appId 或 appSecret");
}
HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/cgi-bin/token")
.newBuilder()
.addQueryParameter("grant_type", "client_credential")
.addQueryParameter("appid", appId)
.addQueryParameter("secret", appSecret)
.build();
Request req = new Request.Builder().url(url).get().build();
try (Response resp = http.newCall(req).execute()) {
String body = resp.body().string();
JsonNode json = om.readTree(body);
if (json.has("access_token")) {
String token = json.get("access_token").asText();
long expiresIn = json.get("expires_in").asInt(7200);
// 缓存完整的JSON响应与其他方法保持一致
redisUtil.set(key, body, expiresIn, TimeUnit.SECONDS);
tokenExpireEpoch = now + expiresIn;
System.out.println("获取新的access_token成功(Local)租户ID: " + tenantId);
return token;
} else {
throw new IOException("Get access_token failed: " + body);
}
}
}
/**
* 文件上传位置(服务器)
*/
private String getUploadDir() {
return config.getUploadPath();
}
@Operation(summary = "调试:检查微信小程序配置")
@GetMapping("/debug/checkWxConfig")
public ApiResult<?> debugCheckWxConfig() {
Integer tenantId = getTenantId();
Map<String, Object> result = new HashMap<>();
result.put("tenantId", tenantId);
try {
// 尝试从缓存获取配置
JSONObject setting = getWxConfigFromCache(tenantId);
result.put("hasConfig", true);
result.put("config", setting);
result.put("cacheKey", MP_WX_KEY + tenantId);
} catch (Exception e) {
result.put("hasConfig", false);
result.put("error", e.getMessage());
result.put("cacheKey", MP_WX_KEY + tenantId);
// 提供创建配置的建议
Map<String, Object> suggestion = new HashMap<>();
suggestion.put("message", "请在Redis中创建微信小程序配置");
suggestion.put("cacheKey", MP_WX_KEY + tenantId);
suggestion.put("tenantId", tenantId);
suggestion.put("sampleConfig", createSampleWxConfig());
result.put("suggestion", suggestion);
}
return success("配置检查完成", result);
}
@Operation(summary = "调试:创建示例微信小程序配置")
@PostMapping("/debug/createSampleWxConfig")
public ApiResult<?> debugCreateSampleWxConfig(@RequestBody Map<String, String> params) {
Integer tenantId = getTenantId();
String appId = params.get("appId");
String appSecret = params.get("appSecret");
if (appId == null || appSecret == null) {
return fail("请提供 appId 和 appSecret", null);
}
try {
// 直接在Redis中创建配置
String key = MP_WX_KEY + tenantId;
// 创建配置内容
Map<String, String> config = new HashMap<>();
config.put("appId", appId);
config.put("appSecret", appSecret);
config.put("tenantId", tenantId.toString());
config.put("settingKey", "mp-weixin");
config.put("settingId", "301");
// 保存到Redis缓存
redisUtil.set(key, JSON.toJSONString(config));
return success("微信小程序配置创建成功", config);
} catch (Exception e) {
return fail("创建配置失败: " + e.getMessage(), null);
}
}
private Map<String, String> createSampleWxConfig() {
Map<String, String> sample = new HashMap<>();
sample.put("appId", "wx_your_app_id_here");
sample.put("appSecret", "your_app_secret_here");
return sample;
}
@Operation(summary = "调试获取AccessToken")
@GetMapping("/debug/getAccessToken")
public ApiResult<?> debugGetAccessToken() {
try {
// 获取当前线程的租户ID
Integer tenantId = getTenantId();
if (tenantId == null) {
tenantId = 10550; // 默认租户
}
System.out.println("=== 开始调试获取AccessToken租户ID: " + tenantId + " ===");
// 手动调用获取AccessToken
String accessToken = getAccessTokenForTenant(tenantId);
String result = "获取AccessToken成功: " + (accessToken != null ? accessToken.substring(0, Math.min(10, accessToken.length())) + "..." : "null");
System.out.println("调试结果: " + result);
return success(result);
} catch (Exception e) {
System.err.println("调试获取AccessToken异常: " + e.getMessage());
e.printStackTrace();
return fail("获取AccessToken失败: " + e.getMessage());
}
}
/**
* 从Redis缓存中获取微信小程序配置
* @param tenantId 租户ID
* @return 微信配置信息
*/
private JSONObject getWxConfigFromCache(Integer tenantId) {
String key = MP_WX_KEY + tenantId;
String cacheValue = redisUtil.get(key);
if (StrUtil.isBlank(cacheValue)) {
throw new BusinessException("未找到微信小程序配置请检查缓存key: " + key);
}
try {
return JSON.parseObject(cacheValue);
} catch (Exception e) {
throw new BusinessException("微信小程序配置格式错误: " + e.getMessage());
}
}
/**
* 从scene参数中提取租户ID
* scene格式可能是: uid_33103 或其他包含用户ID的格式
*/
private Integer extractTenantIdFromScene(String scene) {
try {
System.out.println("解析scene参数: " + scene);
// 如果scene包含uid_前缀提取用户ID
if (scene != null && scene.startsWith("uid_")) {
String userIdStr = scene.substring(4); // 去掉"uid_"前缀
Integer userId = Integer.parseInt(userIdStr);
System.out.println("userId = " + userId);
// 根据用户ID查询用户信息获取租户ID
User user = userService.getByIdIgnoreTenant(userId);
System.out.println("user = " + user);
if (user != null) {
System.out.println("从用户ID " + userId + " 获取到租户ID: " + user.getTenantId());
return user.getTenantId();
} else {
System.err.println("未找到用户ID: " + userId);
}
}
// 如果无法解析默认使用租户10550
System.out.println("无法解析scene参数使用默认租户ID: 10550");
return 10550;
} catch (Exception e) {
System.err.println("解析scene参数异常: " + e.getMessage());
// 出现异常时默认使用租户10550
return 10550;
}
}
/**
* 为指定租户获取AccessToken
*/
private String getAccessTokenForTenant(Integer tenantId) {
try {
String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString());
// 从缓存获取access_token
String value = redisUtil.get(key);
if (value != null) {
try {
// 尝试解析为JSON格式与getAccessToken方法保持一致
JSONObject response = JSON.parseObject(value);
String accessToken = response.getString("access_token");
if (accessToken != null) {
System.out.println("从缓存获取到access_token: " + accessToken.substring(0, Math.min(10, accessToken.length())) + "...");
return accessToken;
}
} catch (Exception e) {
// 如果解析失败可能是旧格式的纯字符串token直接返回
System.out.println("缓存token格式异常使用原值: " + e.getMessage());
return value;
}
}
// 缓存中没有,重新获取
JSONObject wxConfig = getWxConfigFromCache(tenantId);
String appId = wxConfig.getString("appId");
String appSecret = wxConfig.getString("appSecret");
String apiUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
System.out.println("调用微信API获取token - 租户ID: " + tenantId + ", AppID: " + (appId != null ? appId.substring(0, Math.min(8, appId.length())) + "..." : "null"));
System.out.println("微信API请求URL: " + apiUrl.replaceAll("secret=[^&]*", "secret=***"));
String result = HttpUtil.get(apiUrl);
System.out.println("微信API响应: " + result);
JSONObject json = JSON.parseObject(result);
// 检查是否有错误
if (json.containsKey("errcode")) {
Integer errcode = json.getInteger("errcode");
String errmsg = json.getString("errmsg");
System.err.println("微信API错误 - errcode: " + errcode + ", errmsg: " + errmsg);
if (errcode == 40125) {
throw new RuntimeException("微信AppSecret配置错误请检查并更新正确的AppSecret");
} else if (errcode == 40013) {
throw new RuntimeException("微信AppID配置错误请检查并更新正确的AppID");
} else {
throw new RuntimeException("微信API调用失败: " + errmsg + " (errcode: " + errcode + ")");
}
}
if (json.containsKey("access_token")) {
String accessToken = json.getString("access_token");
Integer expiresIn = json.getInteger("expires_in");
// 缓存access_token存储完整JSON响应与getAccessToken方法保持一致
redisUtil.set(key, result, (long) (expiresIn - 300), TimeUnit.SECONDS);
System.out.println("获取新的access_token成功租户ID: " + tenantId);
return accessToken;
} else {
throw new RuntimeException("获取access_token失败: " + result);
}
} catch (Exception e) {
System.err.println("获取access_token异常租户ID: " + tenantId + ", 错误: " + e.getMessage());
throw new RuntimeException("获取access_token失败: " + e.getMessage());
}
}
}