From 65f1fa861d6838b46a5e866ff24a707e3f72146f Mon Sep 17 00:00:00 2001 From: xm <1350250847@qq.com> Date: Thu, 14 May 2026 01:33:58 +0800 Subject: [PATCH] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/controller/WxLoginController.java | 1700 ++++++++--------- src/main/resources/application-glt.yml | 3 +- 2 files changed, 852 insertions(+), 851 deletions(-) diff --git a/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java b/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java index 3c3942d..533bb03 100644 --- a/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java +++ b/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java @@ -1,851 +1,851 @@ -//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.List; -//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 getMpAccessToken() { -// return success("操作成功", getAccessToken()); -// } -// -// @Operation(summary = "获取微信openId") -// @Transactional(rollbackFor = {Exception.class}) -// @PostMapping("/getOpenId") -// public ApiResult 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 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().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 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 -// * ... -// */ -// 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 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 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 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 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 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 != null && Integer.valueOf(2).equals(website.getRunning())){ -// 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 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 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 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 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 createSampleWxConfig() { -// Map 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_userId_tenantId(优先)或 uid_userId(兼容旧格式) -// */ -// private Integer extractTenantIdFromScene(String scene) { -// try { -// System.out.println("解析scene参数: " + scene); -// -// if (scene != null && scene.startsWith("uid_")) { -// String content = scene.substring(4); // 去掉"uid_"前缀 -// -// // 优先解析 uid_userId_tenantId 格式 -// String[] parts = content.split("_"); -// if (parts.length >= 2) { -// try { -// Integer tenantId = Integer.parseInt(parts[1]); -// System.out.println("从scene直接解析到tenantId = " + tenantId); -// return tenantId; -// } catch (NumberFormatException e) { -// System.err.println("scene中tenantId格式异常: " + parts[1]); -// } -// } -// -// // 兼容旧格式 uid_userId:根据用户ID查询租户ID -// if (parts.length == 1) { -// Integer userId = Integer.parseInt(parts[0]); -// System.out.println("userId = " + userId); -// try { -// List users = userService.listByIdIgnoreTenant(userId); -// System.out.println("查询到用户数量 = " + (users != null ? users.size() : 0)); -// if (users != null && !users.isEmpty()) { -// System.out.println("从用户ID " + userId + " 获取到租户ID: " + users.get(0).getTenantId()); -// return users.get(0).getTenantId(); -// } else { -// System.err.println("未找到用户ID: " + userId); +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.List; +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 getMpAccessToken() { + return success("操作成功", getAccessToken()); + } + + @Operation(summary = "获取微信openId") + @Transactional(rollbackFor = {Exception.class}) + @PostMapping("/getOpenId") + public ApiResult 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 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().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 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("手机号码格式不正确"); // } -// } catch (Exception ex) { -// System.err.println("查询用户异常: " + ex.getMessage()); -// } -// } -// } -// -// // 如果无法解析,默认使用租户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()); -// } -// } -// -//} + return phoneNumber; + } + return null; + } + + /** + * 生成随机账号 + * + * @return username + */ + private String createUsername(String type) { + return type.concat(RandomUtil.randomString(12)); + } + + /** + * 获取接口调用凭据AccessToken + * ... + */ + 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 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 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 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 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 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 != null && Integer.valueOf(2).equals(website.getRunning())){ + 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 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 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 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 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 createSampleWxConfig() { + Map 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_userId_tenantId(优先)或 uid_userId(兼容旧格式) + */ + private Integer extractTenantIdFromScene(String scene) { + try { + System.out.println("解析scene参数: " + scene); + + if (scene != null && scene.startsWith("uid_")) { + String content = scene.substring(4); // 去掉"uid_"前缀 + + // 优先解析 uid_userId_tenantId 格式 + String[] parts = content.split("_"); + if (parts.length >= 2) { + try { + Integer tenantId = Integer.parseInt(parts[1]); + System.out.println("从scene直接解析到tenantId = " + tenantId); + return tenantId; + } catch (NumberFormatException e) { + System.err.println("scene中tenantId格式异常: " + parts[1]); + } + } + + // 兼容旧格式 uid_userId:根据用户ID查询租户ID + if (parts.length == 1) { + Integer userId = Integer.parseInt(parts[0]); + System.out.println("userId = " + userId); + try { + List users = userService.listByIdIgnoreTenant(userId); + System.out.println("查询到用户数量 = " + (users != null ? users.size() : 0)); + if (users != null && !users.isEmpty()) { + System.out.println("从用户ID " + userId + " 获取到租户ID: " + users.get(0).getTenantId()); + return users.get(0).getTenantId(); + } else { + System.err.println("未找到用户ID: " + userId); + } + } catch (Exception ex) { + System.err.println("查询用户异常: " + ex.getMessage()); + } + } + } + + // 如果无法解析,默认使用租户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()); + } + } + +} diff --git a/src/main/resources/application-glt.yml b/src/main/resources/application-glt.yml index 844d97e..12ba2fc 100644 --- a/src/main/resources/application-glt.yml +++ b/src/main/resources/application-glt.yml @@ -50,7 +50,8 @@ config: # 生产环境接口 server-url: https://glt-server.websoft.top/api # 业务模块接口 - api-url: https://glt-dev-api.websoft.top/api + api-url: https://glt-api.websoft.top/api +# api-url: https://glt-dev-api.websoft.top/api upload-path: /www/wwwroot/file.ws # 阿里云OSS云存储