1、新增小程序相关接口

2、调整OA
This commit is contained in:
2026-09-18 16:31:37 +08:00
parent 0fa0eae43c
commit 01993779a7
71 changed files with 6463 additions and 213 deletions
@@ -1,9 +1,13 @@
package org.springblade.thirdparty.oa.config;
import feign.Request;
import feign.RequestInterceptor;
import jakarta.annotation.Resource;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.TimeUnit;
/**
* @author bfhuange
* @since 2024/12/18
@@ -16,8 +20,23 @@ public class OAFeignClientConfig {
public RequestInterceptor requestInterceptor() {
return template -> {
// 空实现屏蔽 全局拦截器 BladeFeignRequestInterceptor
String authorization=oaProperties.getAuthorization();
template.header("Authorization",authorization);
template.header("Authorization", normalizeAuthorization(oaProperties.getAuthorization()));
};
}
@Bean
public Request.Options options() {
return new Request.Options(10, TimeUnit.SECONDS, 120, TimeUnit.SECONDS, true);
}
private String normalizeAuthorization(String authorization) {
if (StringUtil.isBlank(authorization)) {
return authorization;
}
if (StringUtil.startsWithIgnoreCase(authorization, "Basic ")
|| StringUtil.startsWithIgnoreCase(authorization, "Bearer ")) {
return authorization;
}
return "Basic " + authorization;
}
}
@@ -30,4 +30,8 @@ public class OAConvertConstant {
* 根公司父id
*/
public static final Long ROOT_PARENT_ID = 0L;
/**
* 同步人员时挂载的顶级组织名称
*/
public static final String ROOT_COMPANY_NAME = "桂物物流集团";
}
@@ -42,6 +42,6 @@ public interface IOAClient {
* @param param
* @return
*/
@PostMapping("${thirdParty.oa.queryPersonPageUrl:/api/hrm/resful/getHrmUserInfoWithPage}")
@PostMapping("${thirdParty.oa.queryPersonPageUrl:/gwzh/OA/OA_GET_USER_LIST}")
OAResponse<OAPersonResponse> queryPersonPage(@RequestBody OASearch<OAPersonSearch> param);
}
@@ -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>
@@ -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 {
}
@@ -0,0 +1,28 @@
package org.springblade.thirdparty.wechat.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 微信小程序配置(NacosthirdParty.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";
}
@@ -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";
}
@@ -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);
}
}
@@ -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;
}
@@ -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;
}
@@ -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);
}
@@ -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/appSecretNacos: 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 + ")");
}
}
}
@@ -0,0 +1 @@
org.springblade.thirdparty.wechat.config.ThirdPartyWechatAutoConfiguration
+1
View File
@@ -17,6 +17,7 @@
<module>blade-wps-api</module>
<module>blade-ocr-api</module>
<module>blade-track-api</module>
<module>blade-wechat-api</module>
</modules>
<packaging>pom</packaging>
<description>BladeX 第三方API集合</description>