From 2d424847c39c8a3947d75a170485061f8486b04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=A6=E9=B8=BF=E5=B1=95?= <18031053041@163.com> Date: Sat, 27 Jun 2026 14:06:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E5=BE=AE=E4=BF=A1=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/WechatMiniAppService.java | 148 ++++++++++++++++++ .../dto/wechat/WxPhoneNumberDTO.java | 14 ++ .../interfaces/facade/UserShipperAPI.java | 14 ++ .../src/main/resources/bootstrap.yml | 14 +- 4 files changed, 185 insertions(+), 5 deletions(-) create mode 100644 mhd-user-center/src/main/java/com/mhd/user/application/service/WechatMiniAppService.java create mode 100644 mhd-user-center/src/main/java/com/mhd/user/interfaces/dto/wechat/WxPhoneNumberDTO.java diff --git a/mhd-user-center/src/main/java/com/mhd/user/application/service/WechatMiniAppService.java b/mhd-user-center/src/main/java/com/mhd/user/application/service/WechatMiniAppService.java new file mode 100644 index 000000000..557d632b3 --- /dev/null +++ b/mhd-user-center/src/main/java/com/mhd/user/application/service/WechatMiniAppService.java @@ -0,0 +1,148 @@ +package com.mhd.user.application.service; + +import com.alibaba.fastjson.JSONObject; +import com.mhd.common.core.exception.ServiceException; +import com.mhd.common.core.web.domain.AjaxResult; +import com.mhd.user.interfaces.dto.wechat.WxPhoneNumberDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 微信小程序手机号快速验证 + * appId/appSecret 在 bootstrap.yml / Nacos 中配置,不同环境配置不同值即可 + */ +@Service +@Slf4j +public class WechatMiniAppService { + + private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; + private static final String GET_PHONE_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s"; + + @Value("${wechat.miniapp.appId:}") + private String appId; + + @Value("${wechat.miniapp.appSecret:}") + private String appSecret; + + /** access_token 缓存:{token, expireTime} */ + private volatile String cachedToken; + private volatile long tokenExpireTime; + + public AjaxResult wxGetPhoneNumber(WxPhoneNumberDTO dto, Long organizationId) { + if (dto == null || dto.getCode() == null || dto.getCode().isEmpty()) { + return AjaxResult.error("code 不能为空"); + } + if (appId.isEmpty() || appSecret.isEmpty()) { + return AjaxResult.error("未配置 wechat.miniapp.appId / wechat.miniapp.appSecret"); + } + + try { + String accessToken = getAccessToken(); + String body = "{\"code\":\"" + dto.getCode() + "\"}"; + String phoneJson = doPost(String.format(GET_PHONE_URL, accessToken), body); + JSONObject result = JSONObject.parseObject(phoneJson); + + int errcode = result.getIntValue("errcode"); + if (errcode != 0) { + String errmsg = result.getString("errmsg"); + log.error("微信手机号解密失败, errcode={}, errmsg={}", errcode, errmsg); + return AjaxResult.error("获取手机号失败:" + errmsg); + } + + JSONObject phoneInfo = result.getJSONObject("phone_info"); + if (phoneInfo == null) { + return AjaxResult.error("未获取到手机号信息"); + } + + JSONObject data = new JSONObject(); + data.put("phoneNumber", phoneInfo.getString("phoneNumber")); + data.put("purePhoneNumber", phoneInfo.getString("purePhoneNumber")); + data.put("countryCode", phoneInfo.getString("countryCode")); + return AjaxResult.success(data); + + } catch (ServiceException e) { + throw e; + } catch (Exception e) { + log.error("微信手机号验证异常", e); + return AjaxResult.error("微信手机号验证失败:" + e.getMessage()); + } + } + + private String getAccessToken() { + if (cachedToken != null && System.currentTimeMillis() < tokenExpireTime) { + return cachedToken; + } + synchronized (this) { + if (cachedToken != null && System.currentTimeMillis() < tokenExpireTime) { + return cachedToken; + } + String url = String.format(ACCESS_TOKEN_URL, appId, appSecret); + String response = doGet(url); + JSONObject result = JSONObject.parseObject(response); + if (result.containsKey("errcode") && result.getIntValue("errcode") != 0) { + throw new ServiceException("获取微信 access_token 失败:" + result.getString("errmsg")); + } + cachedToken = result.getString("access_token"); + int expiresIn = result.getIntValue("expires_in"); + tokenExpireTime = System.currentTimeMillis() + (expiresIn - 300) * 1000L; + return cachedToken; + } + } + + private String doGet(String urlStr) { + try { + URL url = new URL(urlStr); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(10000); + conn.setReadTimeout(10000); + return readResponse(conn); + } catch (Exception e) { + throw new ServiceException("微信接口请求失败:" + e.getMessage()); + } + } + + private String doPost(String urlStr, String body) { + try { + URL url = new URL(urlStr); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "application/json;charset=utf-8"); + conn.setConnectTimeout(10000); + conn.setReadTimeout(10000); + try (OutputStream os = conn.getOutputStream()) { + os.write(body.getBytes(StandardCharsets.UTF_8)); + os.flush(); + } + return readResponse(conn); + } catch (Exception e) { + throw new ServiceException("微信接口请求失败:" + e.getMessage()); + } + } + + private String readResponse(HttpURLConnection conn) throws Exception { + int code = conn.getResponseCode(); + BufferedReader reader = new BufferedReader(new InputStreamReader( + code >= 400 ? conn.getErrorStream() : conn.getInputStream(), StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + reader.close(); + if (code >= 400) { + throw new ServiceException("微信接口 HTTP " + code + ":" + sb); + } + return sb.toString(); + } +} diff --git a/mhd-user-center/src/main/java/com/mhd/user/interfaces/dto/wechat/WxPhoneNumberDTO.java b/mhd-user-center/src/main/java/com/mhd/user/interfaces/dto/wechat/WxPhoneNumberDTO.java new file mode 100644 index 000000000..1049ebef3 --- /dev/null +++ b/mhd-user-center/src/main/java/com/mhd/user/interfaces/dto/wechat/WxPhoneNumberDTO.java @@ -0,0 +1,14 @@ +package com.mhd.user.interfaces.dto.wechat; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 微信小程序手机号快速验证——请求参数 + */ +@Data +public class WxPhoneNumberDTO { + + @ApiModelProperty("wx.getPhoneNumber 返回的 code(注意:非 wx.login 的 code)") + private String code; +} diff --git a/mhd-user-center/src/main/java/com/mhd/user/interfaces/facade/UserShipperAPI.java b/mhd-user-center/src/main/java/com/mhd/user/interfaces/facade/UserShipperAPI.java index 82c801b75..f070f084b 100644 --- a/mhd-user-center/src/main/java/com/mhd/user/interfaces/facade/UserShipperAPI.java +++ b/mhd-user-center/src/main/java/com/mhd/user/interfaces/facade/UserShipperAPI.java @@ -5,7 +5,9 @@ import com.mhd.common.core.domain.entity.R; import com.mhd.common.core.utils.bean.BeanUtils; import com.mhd.common.log.annotation.Log; import com.mhd.common.log.enums.BusinessType; +import com.mhd.common.security.utils.SecurityUtils; import com.mhd.system.api.domain.UserShipperFeignDTO; +import com.mhd.system.api.model.LoginUser; import com.mhd.user.application.service.UserApplicationService; import com.mhd.user.application.service.UserShipperApplicationService; import com.mhd.user.domain.userAggregate.repository.todo.*; @@ -18,6 +20,7 @@ import com.mhd.common.core.web.page.TableDataInfo; import com.mhd.common.security.annotation.RepeatSubmit; import com.mhd.common.security.service.TokenService; import com.mhd.user.application.service.UserShipperMasterDataSyncService; +import com.mhd.user.application.service.WechatMiniAppService; import com.mhd.user.interfaces.dto.updateDTO.SettlementInfoUpdateDTO; import com.mhd.user.interfaces.vo.SettlementInfoQueryVo; import com.mhd.user.interfaces.vo.ShipperMasterDataSyncTimeVo; @@ -42,6 +45,8 @@ public class UserShipperAPI extends BaseController { @Resource private UserShipperMasterDataSyncService userShipperMasterDataSyncService; @Resource + private WechatMiniAppService wechatMiniAppService; + @Resource private UserApplicationService userApplicationService; @Resource private UserShipperAssember userShipperAssember; @@ -342,4 +347,13 @@ public class UserShipperAPI extends BaseController { } return AjaxResult.success(resultMap); } + + @ApiOperation("微信小程序手机号快速验证") + @PostMapping("/wxGetPhoneNumber") + public AjaxResult wxGetPhoneNumber(@RequestBody com.mhd.user.interfaces.dto.wechat.WxPhoneNumberDTO dto) { + LoginUser loginUser = SecurityUtils.getLoginUser(); + Long organizationId = (loginUser != null && loginUser.getUserPo() != null) + ? loginUser.getUserPo().getOrganizationId() : null; + return wechatMiniAppService.wxGetPhoneNumber(dto, organizationId); + } } \ No newline at end of file diff --git a/mhd-user-center/src/main/resources/bootstrap.yml b/mhd-user-center/src/main/resources/bootstrap.yml index 59db9fcf4..482da423b 100644 --- a/mhd-user-center/src/main/resources/bootstrap.yml +++ b/mhd-user-center/src/main/resources/bootstrap.yml @@ -18,16 +18,16 @@ spring: cloud: nacos: discovery: -# server-addr: 127.0.0.1:8848 + server-addr: 127.0.0.1:8848 # 线上测试环境配置 容器名+端口号 - server-addr: 10.33.0.129:6010 +# server-addr: 10.33.0.129:6010 # server-addr: 10.102.192.31:6848 # username: nacos # password: manhuoda@2023 config: -# server-addr: 127.0.0.1:8848 + server-addr: 127.0.0.1:8848 # 线上测试环境配置 容器名+端口号 - server-addr: 10.33.0.129:6010 +# server-addr: 10.33.0.129:6010 # server-addr: 10.102.192.31:6848 # username: nacos # password: manhuoda@2023 @@ -42,4 +42,8 @@ spring: max-request-size: 50MB mybatis-plus: configuration: - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl \ No newline at end of file + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl +wechat: + miniapp: + appId: wxc9dd2810a77e4e30 + appSecret: 9354ff1215a82785ef14f2355fbf2185