1、新增小程序相关接口
2、调整OA
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-third-party-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>blade-wechat-api</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>微信小程序:code2session / 手机号 / openid</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-tool</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.springblade.thirdparty.wechat.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 微信小程序第三方能力自动配置
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan("org.springblade.thirdparty.wechat")
|
||||
@EnableConfigurationProperties(WechatMiniProperties.class)
|
||||
public class ThirdPartyWechatAutoConfiguration {
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package org.springblade.thirdparty.wechat.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 微信小程序配置(Nacos:thirdParty.wechat.mini)
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "third-party.wechat.mini")
|
||||
public class WechatMiniProperties {
|
||||
|
||||
/**
|
||||
* 小程序 AppId
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 小程序 AppSecret
|
||||
*/
|
||||
private String appSecret;
|
||||
|
||||
/**
|
||||
* 微信 API 根地址
|
||||
*/
|
||||
private String apiBase = "https://api.weixin.qq.com";
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package org.springblade.thirdparty.wechat.constant;
|
||||
|
||||
/**
|
||||
* 微信小程序常量
|
||||
*/
|
||||
public interface WechatMiniConstant {
|
||||
|
||||
/** blade_user_oauth.source */
|
||||
String SOURCE = "WECHAT_MINI";
|
||||
|
||||
/**
|
||||
* OAuth2 grant_type(对齐 BladeX OAuth2GranterConstant.WECHAT_APPLET)
|
||||
*/
|
||||
String GRANT_TYPE = "wechat_applet";
|
||||
|
||||
String JSCODE2SESSION_PATH = "/sns/jscode2session";
|
||||
String ACCESS_TOKEN_PATH = "/cgi-bin/token";
|
||||
String GET_PHONE_NUMBER_PATH = "/wxa/business/getuserphonenumber";
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.springblade.thirdparty.wechat.exception;
|
||||
|
||||
/**
|
||||
* 微信小程序调用异常
|
||||
*/
|
||||
public class WechatMiniException extends RuntimeException {
|
||||
|
||||
public WechatMiniException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public WechatMiniException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.springblade.thirdparty.wechat.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 微信手机号结果
|
||||
*/
|
||||
@Data
|
||||
public class WechatPhoneVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 不带区号的手机号 */
|
||||
private String phoneNumber;
|
||||
|
||||
/** 带区号手机号 */
|
||||
private String purePhoneNumber;
|
||||
|
||||
private String countryCode;
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.springblade.thirdparty.wechat.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* jscode2session 结果
|
||||
*/
|
||||
@Data
|
||||
public class WechatSessionVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String openid;
|
||||
private String sessionKey;
|
||||
private String unionid;
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.springblade.thirdparty.wechat.service;
|
||||
|
||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO;
|
||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatSessionVO;
|
||||
|
||||
/**
|
||||
* 微信小程序能力:openid / 手机号
|
||||
*/
|
||||
public interface IWechatMiniService {
|
||||
|
||||
/**
|
||||
* wx.login code → openid / session_key
|
||||
*/
|
||||
WechatSessionVO code2Session(String loginCode);
|
||||
|
||||
/**
|
||||
* getPhoneNumber 返回的 code → 手机号
|
||||
*/
|
||||
WechatPhoneVO getPhoneNumber(String phoneCode);
|
||||
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
package org.springblade.thirdparty.wechat.service.impl;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.thirdparty.wechat.config.WechatMiniProperties;
|
||||
import org.springblade.thirdparty.wechat.constant.WechatMiniConstant;
|
||||
import org.springblade.thirdparty.wechat.exception.WechatMiniException;
|
||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO;
|
||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatSessionVO;
|
||||
import org.springblade.thirdparty.wechat.service.IWechatMiniService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信小程序:code2session / getuserphonenumber
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatMiniServiceImpl implements IWechatMiniService {
|
||||
|
||||
/** access_token 提前 200 秒刷新 */
|
||||
private static final long TOKEN_REFRESH_AHEAD_MS = 200_000L;
|
||||
|
||||
private final WechatMiniProperties properties;
|
||||
|
||||
private volatile String cachedAccessToken;
|
||||
private volatile long accessTokenExpireAt;
|
||||
|
||||
@Override
|
||||
public WechatSessionVO code2Session(String loginCode) {
|
||||
assertConfigured();
|
||||
if (StringUtil.isBlank(loginCode)) {
|
||||
throw new WechatMiniException("微信登录 code 不能为空");
|
||||
}
|
||||
String url = properties.getApiBase() + WechatMiniConstant.JSCODE2SESSION_PATH
|
||||
+ "?appid=" + properties.getAppId()
|
||||
+ "&secret=" + properties.getAppSecret()
|
||||
+ "&js_code=" + loginCode
|
||||
+ "&grant_type=authorization_code";
|
||||
String body = HttpUtil.get(url, 8000);
|
||||
JSONObject json = parseJson(body, "code2session");
|
||||
assertWxOk(json, "获取 openid 失败");
|
||||
String openid = json.getStr("openid");
|
||||
if (StringUtil.isBlank(openid)) {
|
||||
throw new WechatMiniException("微信未返回 openid");
|
||||
}
|
||||
WechatSessionVO vo = new WechatSessionVO();
|
||||
vo.setOpenid(openid);
|
||||
vo.setSessionKey(json.getStr("session_key"));
|
||||
vo.setUnionid(json.getStr("unionid"));
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WechatPhoneVO getPhoneNumber(String phoneCode) {
|
||||
assertConfigured();
|
||||
if (StringUtil.isBlank(phoneCode)) {
|
||||
throw new WechatMiniException("微信手机号 code 不能为空");
|
||||
}
|
||||
String accessToken = getAccessToken();
|
||||
String url = properties.getApiBase() + WechatMiniConstant.GET_PHONE_NUMBER_PATH
|
||||
+ "?access_token=" + accessToken;
|
||||
Map<String, Object> payload = new HashMap<>(2);
|
||||
payload.put("code", phoneCode);
|
||||
String body = HttpRequest.post(url)
|
||||
.body(JSONUtil.toJsonStr(payload))
|
||||
.timeout(8000)
|
||||
.execute()
|
||||
.body();
|
||||
JSONObject json = parseJson(body, "getuserphonenumber");
|
||||
assertWxOk(json, "获取手机号失败");
|
||||
JSONObject phoneInfo = json.getJSONObject("phone_info");
|
||||
if (phoneInfo == null) {
|
||||
throw new WechatMiniException("微信未返回手机号信息");
|
||||
}
|
||||
WechatPhoneVO vo = new WechatPhoneVO();
|
||||
vo.setPhoneNumber(phoneInfo.getStr("phoneNumber"));
|
||||
vo.setPurePhoneNumber(phoneInfo.getStr("purePhoneNumber"));
|
||||
vo.setCountryCode(phoneInfo.getStr("countryCode"));
|
||||
if (StringUtil.isBlank(vo.getPurePhoneNumber()) && StringUtil.isBlank(vo.getPhoneNumber())) {
|
||||
throw new WechatMiniException("微信未返回有效手机号");
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
private String getAccessToken() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (StringUtil.isNotBlank(cachedAccessToken) && now < accessTokenExpireAt) {
|
||||
return cachedAccessToken;
|
||||
}
|
||||
synchronized (this) {
|
||||
now = System.currentTimeMillis();
|
||||
if (StringUtil.isNotBlank(cachedAccessToken) && now < accessTokenExpireAt) {
|
||||
return cachedAccessToken;
|
||||
}
|
||||
String url = properties.getApiBase() + WechatMiniConstant.ACCESS_TOKEN_PATH
|
||||
+ "?grant_type=client_credential"
|
||||
+ "&appid=" + properties.getAppId()
|
||||
+ "&secret=" + properties.getAppSecret();
|
||||
String body = HttpUtil.get(url, 8000);
|
||||
JSONObject json = parseJson(body, "getAccessToken");
|
||||
assertWxOk(json, "获取 access_token 失败");
|
||||
String token = json.getStr("access_token");
|
||||
Integer expiresIn = json.getInt("expires_in", 7200);
|
||||
if (StringUtil.isBlank(token)) {
|
||||
throw new WechatMiniException("微信未返回 access_token");
|
||||
}
|
||||
cachedAccessToken = token;
|
||||
accessTokenExpireAt = System.currentTimeMillis() + Math.max(60, expiresIn) * 1000L - TOKEN_REFRESH_AHEAD_MS;
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertConfigured() {
|
||||
if (StringUtil.isBlank(properties.getAppId()) || StringUtil.isBlank(properties.getAppSecret())) {
|
||||
throw new WechatMiniException("未配置微信小程序 appId/appSecret(Nacos: thirdParty.wechat.mini)");
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject parseJson(String body, String action) {
|
||||
if (StringUtil.isBlank(body)) {
|
||||
throw new WechatMiniException("微信接口无响应: " + action);
|
||||
}
|
||||
try {
|
||||
return JSONUtil.parseObj(body);
|
||||
} catch (Exception e) {
|
||||
log.error("解析微信响应失败 action={} body={}", action, body, e);
|
||||
throw new WechatMiniException("解析微信响应失败: " + action, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertWxOk(JSONObject json, String fallbackMsg) {
|
||||
Integer errcode = json.getInt("errcode");
|
||||
if (errcode != null && errcode != 0) {
|
||||
String errmsg = json.getStr("errmsg", fallbackMsg);
|
||||
throw new WechatMiniException(fallbackMsg + ":" + errmsg + "(" + errcode + ")");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.springblade.thirdparty.wechat.config.ThirdPartyWechatAutoConfiguration
|
||||
Reference in New Issue
Block a user