新增 txw-common 公共模块

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
liulujian 2026-04-03 13:44:41 +08:00
parent 04d32e93df
commit f96d815796
18 changed files with 794 additions and 0 deletions

38
txw-common/.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

1
txw-common/README.md Normal file
View File

@ -0,0 +1 @@
test

78
txw-common/pom.xml Normal file
View File

@ -0,0 +1,78 @@
<?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>com.css.ggzc</groupId>
<artifactId>ggzc-framework-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>txw-common</artifactId>
<groupId>com.css.txw</groupId>
<version>1.0.0-SNAPSHOT</version>
<name>${project.artifactId}</name>
<description>txw-common</description>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
</plugins>
</build>
<distributionManagement>
<repository>
<!--必须与 settings.xml 的 id 一致-->
<id>codingcorp-qyd_repo-mvn_public</id>
<name>mvn_public</name>
<url>http://codingcorp-maven.pkg.codingstd.xc01.cloud.sat.tax/repository/qyd_repo/mvn_public/</url>
</repository>
</distributionManagement>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.css.ggzc</groupId>
<artifactId>ggzc-framework-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Web 相关 -->
<dependency>
<groupId>com.css.ggzc</groupId>
<artifactId>ggzc-framework-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.css.ggzc</groupId>
<artifactId>ggzc-framework-starter-common</artifactId>
</dependency>
<dependency>
<groupId>com.css.ggzc</groupId>
<artifactId>ggzc-framework-starter-cache</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>com.css.ggzc</groupId>
<artifactId>ggzc-framework-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>com.css.ggzc</groupId>
<artifactId>ggzc-framework-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
</dependencies>
</project>

61
txw-common/settings.xml Normal file
View File

@ -0,0 +1,61 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!--<localRepository>[本地maven库目录]</localRepository>-->
<!-- omitted xml -->
<!-- 请妥善保管好您的配置,不要随意分享给他人 -->
<servers>
<server>
<id>codingcorp-qyd_repo-mvn_public</id>
<username>coding-user</username>
<password>coding-pwd</password>
</server>
</servers>
<!-- omitted xml -->
<profiles>
<profile>
<id>Repository Proxy</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<!--必须与 settings.xml 的 id 一致-->
<id>codingcorp-qyd_repo-mvn_public</id>
<name>mvn_public</name>
<url>http://codingcorp-maven.pkg.codingstd.xc01.cloud.sat.tax/repository/qyd_repo/mvn_public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>codingcorp-qyd_repo-mvn_public</id>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<url>http://codingcorp-maven.pkg.codingstd.xc01.cloud.sat.tax/repository/qyd_repo/mvn_public/</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<mirrors>
<mirror>
<id>codingcorp-qyd_repo-mvn_public</id>
<!-- 此配置避免了本仓库制品的拉取流量被切换到腾讯云镜像源,保证您在使用镜像加速的同时可以从本仓库拉取制品 -->
<mirrorOf>central</mirrorOf>
<name>mvn_public</name>
<url>http://codingcorp-maven.pkg.codingstd.xc01.cloud.sat.tax/repository/qyd_repo/mvn_public/</url>
</mirror>
</mirrors>
</settings>

View File

@ -0,0 +1,30 @@
package com.css.txw.common.configuration;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* @Description: 短信配置
* @Autor wujy
* @Date 2025/11/21
*/
@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "css.txw.sms")
public class SMSConfig {
private String host;
//app请求应用的唯一标识例如OA
private String app;
//key双方系统约定的密钥
private String key;
//短信内容模版
private String template;
}

View File

@ -0,0 +1,11 @@
package com.css.txw.common.configuration;
/**
* @Description: 短信服务相关
* @Autor wujy
* @Date 2025/11/21
*/
public class SMSConstant {
public static final String SEND_SM_URI = "/nbsgd/messageApi/smsmessage/send";
}

View File

@ -0,0 +1,10 @@
package com.css.txw.common.configuration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@AutoConfiguration
@ComponentScan("com.css.txw.common")
public class TxwCommonConfiguration {
}

View File

@ -0,0 +1,87 @@
package com.css.txw.common.net;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.Map;
@Slf4j
@Service
public class HttpHutoolImpl implements IHttpService {
public final static String CONTENT_TYPE_JSON = "application/json";
public final static String CONTENT_TYPE_FORM = "multipart/form-data;";
public final static String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36";
public final static String ACCEPT = "application/json, text/plain, */*";
public HttpResponse get(String url) {
HttpRequest request = HttpUtil.createGet(url);
return this.executeReq(request);
}
@Override
public HttpResponse get(String url, Map<String, Object> params) {
HttpRequest request = HttpUtil.createGet(url);
request.form(params);
return this.executeReq(request);
}
@Override
public HttpResponse postForm(String url, Map<String, Object> form) {
HttpRequest request = HttpUtil.createPost(url);
request.form(form);
return this.executeReq(request, CONTENT_TYPE_FORM);
}
@Override
public HttpResponse postForm(String url, Map<String, Object> form, Map<String, File> files) {
HttpRequest request = HttpUtil.createPost(url);
request.form(form);
if (null != files) {
for (String key : files.keySet()) {
request.form(key, files.get(key), files.get(key).getName());
}
request.timeout(1000 * 60);
}
return this.executeReq(request, CONTENT_TYPE_FORM);
}
@Override
public <T> T postJson(String url, String body, Class<T> resType) {
HttpRequest request = HttpUtil.createPost(url);
if (StringUtils.isNotBlank(body)) {
request.body(body);
}
try {
HttpResponse response = this.executeReq(request);
if (response.isOk()) {
String resBody = response.body();
log.info("Hutool http response body: {}", resBody);
return new Gson().fromJson(resBody, resType);
} else {
log.info("请求失败,状态码:{}", response.getStatus());
}
} catch (Exception e) {
log.error("error:", e);
}
return null;
}
private HttpResponse executeReq(HttpRequest request) {
return this.executeReq(request, CONTENT_TYPE_JSON);
}
private HttpResponse executeReq(HttpRequest request,String contentType) {
request.contentType(contentType).header(Header.USER_AGENT, USER_AGENT).header(Header.ACCEPT, ACCEPT);
return request.execute();
}
}

View File

@ -0,0 +1,17 @@
package com.css.txw.common.net;
import cn.hutool.http.HttpResponse;
import java.io.File;
import java.util.Map;
public interface IHttpService {
HttpResponse get(String url, Map<String, Object> params);
HttpResponse postForm(String url, Map<String, Object> form);
HttpResponse postForm(String url, Map<String, Object> form, Map<String, File> files);
<T> T postJson(String url, String body, Class<T> resType);
}

View File

@ -0,0 +1,10 @@
package com.css.txw.common.pojo;
import lombok.Data;
@Data
public class ExcelVerifyInfo {
private String errorMsg;
private Integer rowNum;
}

View File

@ -0,0 +1,17 @@
package com.css.txw.common.pojo.dto.sms;
import lombok.Data;
/**
* @Description: 发送短信验证码根据配置模版格式发送验证码
* @Autor wujy
* @Date 2025/11/21
*/
@Data
public class SMCaptchaDTO {
//接收手机号码 "1311111111,1352222222"
private String receiveMobile;
//验证码
private String captcha;
}

View File

@ -0,0 +1,19 @@
package com.css.txw.common.pojo.dto.sms;
import lombok.Data;
/**
* @Description: 短信发送报文
* @Autor wujy
* @Date 2025/11/21
*/
@Data
public class SMSReqDTO {
//接收手机号码 "1311111111,1352222222"
private String receiveMobile;
//短信内容
private String content;
//发送时间, 自定义发送时间空或小于当前时间立即发送
private String sendTime;
}

View File

@ -0,0 +1,17 @@
package com.css.txw.common.pojo.dto.sms;
import lombok.Data;
/**
* @Description: 短信服务响应报文
* @Autor wujy
* @Date 2025/11/21
*/
@Data
public class SMSResDTO<T> {
private String code;//返回代码值
private String type;//枚举型包括successerror
private String token;//请求服务时传递的唯一标识UUID
private String message;//返回消息
private T data;//返回数据内容
}

View File

@ -0,0 +1,9 @@
package com.css.txw.common.service;
import com.css.txw.common.pojo.dto.sms.SMCaptchaDTO;
import com.css.txw.common.pojo.dto.sms.SMSResDTO;
public interface ISMService {
SMSResDTO sendCaptcha(String phone, String code);
}

View File

@ -0,0 +1,62 @@
package com.css.txw.common.service.impl;
import cn.hutool.crypto.digest.DigestUtil;
import com.css.ggzc.framework.common.util.gy.GyUtils;
import com.css.txw.common.configuration.SMSConfig;
import com.css.txw.common.configuration.SMSConstant;
import com.css.txw.common.net.IHttpService;
import com.css.txw.common.pojo.dto.sms.SMSReqDTO;
import com.css.txw.common.pojo.dto.sms.SMSResDTO;
import com.css.txw.common.service.ISMService;
import com.css.txw.common.util.TxwHttpUtils;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
/**
* @Description: 短信服务
* @Autor wujy
* @Date 2025/11/21
*/
@Slf4j
@Service
public class SMServiceImpl implements ISMService {
@Resource
private SMSConfig config;
@Resource
private IHttpService httpService;
//发送短信验证码
@Override
public SMSResDTO sendCaptcha(String phone, String code) {
//时间戳1611472667681
long timestamp = new Date().getTime();
String app = config.getApp();
//请求唯一标识使用UUID
String token = GyUtils.getUuid();
//校验码使用timestamp+app+token+key进行MD5加密后的字符再进行性一次加密最后形成校验码
String vcode = DigestUtil.md5Hex(DigestUtil.md5Hex(timestamp + app + token + config.getKey()));
String url = config.getHost() + SMSConstant.SEND_SM_URI + "?timestamp=" + timestamp + "&token=" + token +
"&app=" + app + "&vcode=" + vcode;
//构建报文
String content = String.format(config.getTemplate(), code);
SMSReqDTO smReqDTO = new SMSReqDTO();
smReqDTO.setReceiveMobile(phone);
smReqDTO.setContent(content);
//发送
SMSResDTO res = httpService.postJson(url, new Gson().toJson(smReqDTO), SMSResDTO.class);
log.info("短信发送 Response {}, code:{} message:{}", res.getType(),res.getCode(), res.getMessage());
return res;
}
// 发送其他短信
public String sendMsg(SMSReqDTO reqDTO) {
// to do something
return null;
}
}

View File

@ -0,0 +1,289 @@
package com.css.txw.common.util;
import com.css.ggzc.framework.cache.utils.XtcsUtils;
import com.css.ggzc.framework.common.util.gy.GyUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.net.Proxy;
import java.util.Map;
/**
* 基于Spring Cloud Alibaba的HTTP工具类
* 支持GETPOSTPUTDELETE请求兼容HTTP/HTTPS协议
* 支持Nacos服务发现和负载均衡
* 版本Spring Cloud Alibaba 2021.0.6.0
*/
@Slf4j
@Component
public class TxwHttpUtils {
@Resource
private RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();
@PostConstruct
public void init() {
log.info("SpringCloudAlibabaHttpUtil initialized successfully with Spring Cloud Alibaba 2021.0.6.0");
}
// ========== GET请求方法 ==========
/**
* GET请求 - 直接URL调用
*/
public <T> T get(String url, Class<T> responseType) {
return get(url, null, responseType, null);
}
public <T> T get(String url, Map<String, Object> params, Class<T> responseType) {
return get(url, params, responseType, null);
}
public <T> T get(String url, Map<String, Object> params, Class<T> responseType,
Map<String, String> headers) {
try {
HttpEntity<?> entity = createHttpEntity(null, headers);
ResponseEntity<T> response = restTemplate.exchange(
buildUrlWithParams(url, params),
HttpMethod.GET,
entity,
responseType
);
return handleResponse(response);
} catch (Exception e) {
log.error("GET请求失败, url: {}", url, e);
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
}
}
// ========== POST请求方法 ==========
/**
* POST请求 - 直接URL调用
*/
public <T> T post(String url, Object requestBody, Class<T> responseType) {
return post(url, requestBody, responseType, null);
}
public <T> T post(String url, Object requestBody, Class<T> responseType,
Map<String, String> headers) {
try {
HttpEntity<?> entity = createHttpEntity(requestBody, headers);
ResponseEntity<T> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
responseType
);
return handleResponse(response);
} catch (Exception e) {
log.error("POST请求失败, url: {}", url, e);
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
}
}
// ========== PUT请求方法 ==========
/**
* PUT请求 - 直接URL调用
*/
public <T> T put(String url, Object requestBody, Class<T> responseType) {
return put(url, requestBody, responseType, null);
}
public <T> T put(String url, Object requestBody, Class<T> responseType,
Map<String, String> headers) {
try {
HttpEntity<?> entity = createHttpEntity(requestBody, headers);
ResponseEntity<T> response = restTemplate.exchange(
url,
HttpMethod.PUT,
entity,
responseType
);
return handleResponse(response);
} catch (Exception e) {
log.error("PUT请求失败, url: {}", url, e);
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
}
}
// ========== DELETE请求方法 ==========
/**
* DELETE请求 - 直接URL调用
*/
public <T> T delete(String url, Class<T> responseType) {
return delete(url, null, responseType, null);
}
public <T> T delete(String url, Map<String, Object> params, Class<T> responseType) {
return delete(url, params, responseType, null);
}
public <T> T delete(String url, Map<String, Object> params, Class<T> responseType,
Map<String, String> headers) {
try {
HttpEntity<?> entity = createHttpEntity(null, headers);
ResponseEntity<T> response = restTemplate.exchange(
buildUrlWithParams(url, params),
HttpMethod.DELETE,
entity,
responseType
);
return handleResponse(response);
} catch (Exception e) {
log.error("DELETE请求失败, url: {}", url, e);
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
}
}
// ========== 通用请求方法 ==========
/**
* 通用请求方法
*/
public <T> T exchange(String url, HttpMethod method, Object requestBody,
Map<String, String> headers, Class<T> responseType) {
try {
ResponseEntity<T> response;
RestTemplate restTemplateWithProxy = createRestTemplateWithProxy();
HttpEntity<?> entity = createHttpEntity(requestBody, headers);
if (GyUtils.isNotNull(restTemplateWithProxy)) {
response = restTemplateWithProxy.exchange(url, method, entity, responseType);
} else {
response = restTemplate.exchange(url, method, entity, responseType);
}
return handleResponse(response);
} catch (Exception e) {
log.error("{}请求失败, url: {}", method.name(), url, e);
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
}
}
// ========== 辅助方法 ==========
/**
* 构建带参数的URL
*/
private String buildUrlWithParams(String url, Map<String, Object> params) {
if (params == null || params.isEmpty()) {
return url;
}
StringBuilder urlBuilder = new StringBuilder(url);
boolean hasQuery = url.contains("?");
for (Map.Entry<String, Object> entry : params.entrySet()) {
if (entry.getValue() == null) continue;
if (!hasQuery) {
urlBuilder.append("?");
hasQuery = true;
} else {
urlBuilder.append("&");
}
urlBuilder.append(entry.getKey())
.append("=")
.append(encodeParam(entry.getValue().toString()));
}
return urlBuilder.toString();
}
/**
* 参数编码
*/
private String encodeParam(String param) {
try {
return java.net.URLEncoder.encode(param, "UTF-8");
} catch (Exception e) {
log.warn("参数编码失败: {}", param, e);
return param;
}
}
/**
* 创建HTTP实体
*/
private HttpEntity<?> createHttpEntity(Object requestBody, Map<String, String> headers) {
HttpHeaders httpHeaders = new HttpHeaders();
// 设置默认头
if (requestBody instanceof MultiValueMap) {
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
} else if (requestBody != null) {
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
}
// 添加自定义头
if (headers != null) {
headers.forEach(httpHeaders::add);
}
return new HttpEntity<>(requestBody, httpHeaders);
}
/**
* 处理响应
*/
private <T> T handleResponse(ResponseEntity<T> response) {
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody();
} else {
String errorMsg = String.format("HTTP请求失败, 状态码: %d, 响应: %s",
response.getStatusCodeValue(), response.getBody());
log.error(errorMsg);
throw new RuntimeException(errorMsg);
}
}
/**
* 将对象转换为MultiValueMap用于表单提交
*/
public MultiValueMap<String, String> convertToFormData(Object obj) {
try {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
Map<String, Object> map = objectMapper.convertValue(obj, new TypeReference<Map<String, Object>>() {});
map.forEach((key, value) -> {
if (value != null) {
formData.add(key, value.toString());
}
});
return formData;
} catch (Exception e) {
log.error("对象转换表单数据失败", e);
throw new RuntimeException("对象转换失败", e);
}
}
public RestTemplate createRestTemplateWithProxy() {
// 设置代理
Proxy proxy = XtcsUtils.getSystemHttpProxy();
if (GyUtils.isNotNull(proxy)) {
SimpleClientHttpRequestFactory requestFactory =
new SimpleClientHttpRequestFactory();
// 设置超时时间
requestFactory.setConnectTimeout(5000);
requestFactory.setReadTimeout(10000);
requestFactory.setProxy(proxy);
return new RestTemplate(requestFactory);
}
return null;
}
}

View File

@ -0,0 +1 @@
com.css.txw.common.configuration.TxwCommonConfiguration

View File

@ -0,0 +1,37 @@
{
"groups": [
{
"name": "css.txw.sms",
"type": "com.css.txw.common.configuration.SMSConfig",
"sourceType": "com.css.txw.common.configuration.SMSConfig"
}
],
"properties": [
{
"name": "css.txw.sms.host",
"type": "java.lang.String",
"description": "short message service host",
"sourceType": "com.css.txw.common.configuration.SMSConfig"
},
{
"name": "css.txw.sms.app",
"type": "java.lang.String",
"description": "app name",
"sourceType": "com.css.txw.common.configuration.SMSConfig"
},
{
"name": "css.txw.sms.key",
"type": "java.lang.String",
"description": "app key",
"sourceType": "com.css.txw.common.configuration.SMSConfig"
},
{
"name": "css.txw.sms.template",
"type": "java.lang.String",
"description": "short message content template",
"sourceType": "com.css.txw.common.configuration.SMSConfig"
}
],
"hints": [
]
}