- 在 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 添加转账场景报备信息验证和日志记录
834 lines
32 KiB
Java
834 lines
32 KiB
Java
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());
|
||
}
|
||
}
|
||
|
||
}
|