Merge branch 'test' into test-dsc

* test:
  把-server项目改为jar包,新增 server-app项目作为启动器 http://172.16.46.63:31560/index.php?m=task&f=view&taskID=699
  [+]增加国密SM4加解密工具包
  fix(user-dept): 修改用户来源筛选条件
  fix(databus): 修复部门数据查询中缺少数据源过滤条件
  fix(databus): 修改用户同步的数据源过滤条件
  fix(databus): 修改用户同步的数据源过滤条件
  [+]增加国密SM4接口加解密
  [#]修改部门推送消息逻辑

# Conflicts:
#	zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/CryptoSignatureUtils.java
#	zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/sms/dto/send/SmsSendSingleToUserReqDTO.java
#	zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusDeptProviderApiImpl.java
#	zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/SmsCallbackController.java
#	zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/SmsChannelEnum.java
This commit is contained in:
ranke
2026-01-12 18:49:36 +08:00
130 changed files with 7899 additions and 147 deletions

View File

@@ -282,13 +282,13 @@
</properties> </properties>
</profile> </profile>
<profile> <profile>
<id>chenbowen</id> <id>haodongqiang</id>
<properties> <properties>
<!-- <env.name>local</env.name>--> <!-- <env.name>local</env.name>-->
<env.name>dev</env.name> <env.name>dev</env.name>
<!-- <config.server-addr>localhost:8848</config.server-addr>--> <!-- <config.server-addr>localhost:8848</config.server-addr>-->
<config.server-addr>172.16.46.63:30848</config.server-addr> <config.server-addr>172.16.46.63:30848</config.server-addr>
<config.namespace>chenbowen</config.namespace> <config.namespace>haodongqiang</config.namespace>
<config.group>DEFAULT_GROUP</config.group> <config.group>DEFAULT_GROUP</config.group>
<config.username>nacos</config.username> <config.username>nacos</config.username>
<config.password>P@ssword25</config.password> <config.password>P@ssword25</config.password>

View File

@@ -0,0 +1,9 @@
-- 短信渠道新增鸿联九五支持达梦8
-- 1) system_sms_channel 表新增 epid 字段
-- 2) system_sms_channel_code 字典新增 HL95 选项
ALTER TABLE system_sms_channel ADD COLUMN epid VARCHAR(64);
COMMENT ON COLUMN system_sms_channel.epid IS '企业编号(epid)';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
VALUES (1593, 5, '中铝e办', 'ZLE', 'system_sms_channel_code', 0, '', '', '', '1', '2026-01-09 00:00:00', '1', '2026-01-09 00:00:00', '0');

View File

@@ -163,6 +163,33 @@
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.14</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -15,7 +15,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
@Schema(description = "分页结果") @Schema(description = "分页结果")
@Data @Data //TODO 分页结果参考这个
public final class PageResult<T> implements Serializable { public final class PageResult<T> implements Serializable {
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)

View File

@@ -0,0 +1,382 @@
package com.zt.plat.framework.common.util.http;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
/**
* HttpClient工具类
*
* @author luzemin
*/
@Slf4j
public class HttpClientUtils {
/**
* 请求配置对象
*/
private static final RequestConfig REQUEST_CONFIG;
static {
/* 设置请求和传输超时时间 */
REQUEST_CONFIG = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
}
/**
* post请求传输json参数
*
* @param url url地址
* @param jsonParam 参数
* @return JSONObject 请求结果对象
*/
public static JSONObject httpPost(String url, JSONObject jsonParam) {
/* 请求返回结果 */
JSONObject jsonResult = null;
/* 构建连接对象 */
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(REQUEST_CONFIG);
try {
/* 构建请求体 */
if (StringUtils.isNotBlank(jsonParam.toJSONString())) {
StringEntity entity = new StringEntity(jsonParam.toJSONString(), StandardCharsets.UTF_8);
entity.setContentEncoding(StandardCharsets.UTF_8.name());
entity.setContentType(ContentType.APPLICATION_JSON.getMimeType());
httpPost.setEntity(entity);
}
/* 提交请求,构建响应对象 */
CloseableHttpResponse response = httpClient.execute(httpPost);
/* 处理请求结果 */
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity responseEntity = response.getEntity();
String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
jsonResult = JSONObject.parseObject(result);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
httpPost.releaseConnection();
}
return jsonResult;
}
/**
* post请求传输String参数 例如name=Jack&sex=1&type=2
* Content-type:application/x-www-form-urlencoded
*
* @param url url地址
* @param strParam 参数
* @return JSONObject 请求结果对象
*/
public static JSONObject httpPost(String url, String strParam) {
/* 请求返回结果 */
JSONObject jsonResult = null;
/* 构建连接对象 */
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(REQUEST_CONFIG);
try {
/* 构建请求体 */
if (StringUtils.isNotBlank(strParam)) {
StringEntity entity = new StringEntity(strParam, StandardCharsets.UTF_8);
entity.setContentEncoding(StandardCharsets.UTF_8.name());
entity.setContentType(ContentType.APPLICATION_JSON.getMimeType());
httpPost.setEntity(entity);
}
/* 提交请求,构建响应对象 */
CloseableHttpResponse response = httpClient.execute(httpPost);
/* 处理请求结果 */
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity responseEntity = response.getEntity();
String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
jsonResult = JSONObject.parseObject(result);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
httpPost.releaseConnection();
}
return jsonResult;
}
/**
* post请求传输String参数 例如name=Jack&sex=1&type=2
* Content-type:application/x-www-form-urlencoded
*
* @param url url地址
* @param strParam 参数
* @param token 身份认证令牌
* @return JSONObject 请求结果对象
*/
public static JSONObject httpPostByToken(String url, String strParam, String token) {
/* 请求返回结果 */
JSONObject jsonResult = null;
/* 构建连接对象 */
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(REQUEST_CONFIG);
try {
/* 构建请求体 */
if (StringUtils.isNotBlank(strParam)) {
StringEntity entity = new StringEntity(strParam, StandardCharsets.UTF_8);
entity.setContentEncoding(StandardCharsets.UTF_8.name());
entity.setContentType(ContentType.APPLICATION_JSON.getMimeType());
httpPost.setEntity(entity);
}
if (StringUtils.isNotBlank(token)) {
httpPost.setHeader("token", token);
}
/* 提交请求,构建响应对象 */
CloseableHttpResponse response = httpClient.execute(httpPost);
/* 处理请求结果 */
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity responseEntity = response.getEntity();
String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
jsonResult = JSONObject.parseObject(result);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
httpPost.releaseConnection();
}
return jsonResult;
}
/**
* post请求传输String参数 例如name=Jack&sex=1&type=2
* Content-type:application/x-www-form-urlencoded
*
* @param url url地址
* @param strParam 参数
* @return String 请求结果对象
*/
public static String httpPostStr(String url, String strParam) {
/* 请求返回结果 */
String jsonResult = null;
/* 构建连接对象 */
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(REQUEST_CONFIG);
try {
/* 构建请求体 */
if (StringUtils.isNotBlank(strParam)) {
StringEntity entity = new StringEntity(strParam, StandardCharsets.UTF_8);
entity.setContentEncoding(StandardCharsets.UTF_8.name());
entity.setContentType(ContentType.APPLICATION_FORM_URLENCODED.getMimeType());
httpPost.setEntity(entity);
}
/* 提交请求,构建响应对象 */
CloseableHttpResponse response = httpClient.execute(httpPost);
/* 处理请求结果 */
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity responseEntity = response.getEntity();
jsonResult = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
httpPost.releaseConnection();
}
return jsonResult;
}
/**
* post请求传输String参数 例如name=Jack&sex=1&type=2
* Content-type:application/x-www-form-urlencoded
*
* @param url url地址
* @param strParam 参数
* @param token 身份认证字符串
* @return String 请求结果对象
*/
public static String httpPostStrByToken(String url, String strParam, String token) {
/* 请求返回结果 */
String jsonResult = null;
/* 构建连接对象 */
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(REQUEST_CONFIG);
try {
/* 构建请求体 */
if (StringUtils.isNotBlank(strParam)) {
StringEntity entity = new StringEntity(strParam, StandardCharsets.UTF_8);
entity.setContentEncoding(StandardCharsets.UTF_8.name());
entity.setContentType(ContentType.APPLICATION_JSON.getMimeType());
httpPost.setEntity(entity);
}
if (StringUtils.isNotBlank(token)) {
httpPost.setHeader("token", token);
}
/* 提交请求,构建响应对象 */
CloseableHttpResponse response = httpClient.execute(httpPost);
/* 处理请求结果 */
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity responseEntity = response.getEntity();
jsonResult = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
httpPost.releaseConnection();
}
return jsonResult;
}
/**
* 根据指定的url地址上传文件
*
* @param mediaName 服务器定义的读取文件流的name
* @param url 文件上传url
* @param file 文件对象
* @return JSONObject 文件上传结果
*/
public static JSONObject httpFileUpload(String mediaName, String url, MultipartFile file) {
/* 请求返回结果 */
JSONObject jsonResult = null;
/* 构建连接对象 */
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(REQUEST_CONFIG);
/* 构建请求头 */
String boundaryStr = UUID.randomUUID().toString();
httpPost.setHeader(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
httpPost.setHeader(HttpHeaders.CONNECTION, "Keep-Alive");
httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.MULTIPART_FORM_DATA.getMimeType() + ";boundary=" + boundaryStr);
try {
/* 构建文件对象 */
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setBoundary(boundaryStr)
.setContentType(ContentType.APPLICATION_OCTET_STREAM)
.setCharset(StandardCharsets.UTF_8)
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntityBuilder.addBinaryBody(mediaName, file.getInputStream(), ContentType.APPLICATION_OCTET_STREAM, file.getOriginalFilename());
HttpEntity entity = multipartEntityBuilder.build();
httpPost.setEntity(entity);
/* 提交请求,构建响应对象 */
CloseableHttpResponse response = httpClient.execute(httpPost);
/* 处理请求结果 */
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity responseEntity = response.getEntity();
String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
jsonResult = JSONObject.parseObject(result);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
httpPost.releaseConnection();
}
return jsonResult;
}
/**
* 发送get请求
*
* @param url 路径
* @return JSONObject 请求结果对象
*/
public static JSONObject httpGet(String url) {
/* 请求返回结果 */
JSONObject jsonResult = null;
/* 构建连接对象 */
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(REQUEST_CONFIG);
try {
/* 提交请求,构建响应对象 */
CloseableHttpResponse response = httpClient.execute(httpGet);
/* 处理请求结果 */
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity responseEntity = response.getEntity();
String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
jsonResult = JSONObject.parseObject(result);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
httpGet.releaseConnection();
}
return jsonResult;
}
/**
* 发送get请求
*
* @param url 路径
* @return JSONObject 请求结果对象
*/
public static String httpGetStr(String url) {
/* 请求返回结果 */
String jsonResult = null;
/* 构建连接对象 */
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(REQUEST_CONFIG);
try {
/* 提交请求,构建响应对象 */
CloseableHttpResponse response = httpClient.execute(httpGet);
/* 处理请求结果 */
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity responseEntity = response.getEntity();
jsonResult = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
httpGet.releaseConnection();
}
return jsonResult;
}
}

View File

@@ -1,9 +1,10 @@
package com.zt.plat.framework.common.util.security; package com.zt.plat.framework.common.util.security;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SM4;
import com.zt.plat.framework.common.util.json.JsonUtils; import com.zt.plat.framework.common.util.json.JsonUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.KeyGenerator; import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@@ -11,6 +12,7 @@ import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security;
import java.util.*; import java.util.*;
/** /**
@@ -27,6 +29,15 @@ public final class CryptoSignatureUtils {
private static final String AES_TRANSFORMATION = "AES/ECB/PKCS5Padding"; private static final String AES_TRANSFORMATION = "AES/ECB/PKCS5Padding";
public static final String SIGNATURE_FIELD = "signature"; public static final String SIGNATURE_FIELD = "signature";
private static final String CHARSET = "UTF-8";
//@Value("${sa.encrypt.sm4.key}")
private static String SM4_KEY = "1234567890123456";
static {
Security.addProvider(new BouncyCastleProvider());
}
private CryptoSignatureUtils() { private CryptoSignatureUtils() {
} }
@@ -220,4 +231,91 @@ public final class CryptoSignatureUtils {
} }
return sb.toString(); return sb.toString();
} }
//-------------------------------- 国密方式2 -------------------------------------------------------
/**
* 加密
*/
public static String enCode(String data) {
try {
// 第一步: SM4 加密
SM4 sm4 = new SM4(hexToBytes(stringToHex(SM4_KEY)));
String encryptHex = sm4.encryptHex(data);
// 第二步: Base64 编码
return new String(Base64.getEncoder().encode(encryptHex.getBytes(CHARSET)), CHARSET);
} catch (Exception e) {
log.error("国密加密失败{}",e.getMessage(),e);
return "";
}
}
/**
* 解密
*/
public static String deCode(String data) {
try {
// 第一步: Base64 解码
byte[] base64Decode = Base64.getDecoder().decode(data);
// 第二步: SM4 解密
SM4 sm4 = new SM4(hexToBytes(stringToHex(SM4_KEY)));
return sm4.decryptStr(new String(base64Decode));
} catch (Exception e) {
log.error("国密解密失败{}",e.getMessage(),e);
return "";
}
}
public static String stringToHex(String input) {
char[] chars = input.toCharArray();
StringBuilder hex = new StringBuilder();
for (char c : chars) {
hex.append(Integer.toHexString((int) c));
}
return hex.toString();
}
/**
* 16 进制串转字节数组
*
* @param hex 16进制字符串
* @return byte数组
*/
public static byte[] hexToBytes(String hex) {
int length = hex.length();
byte[] result;
if (length % 2 == 1) {
length++;
result = new byte[(length / 2)];
hex = "0" + hex;
} else {
result = new byte[(length / 2)];
}
int j = 0;
for (int i = 0; i < length; i += 2) {
result[j] = hexToByte(hex.substring(i, i + 2));
j++;
}
return result;
}
/**
* 16 进制字符转字节
*
* @param hex 16进制字符 0x00到0xFF
* @return byte
*/
private static byte hexToByte(String hex) {
return (byte) Integer.parseInt(hex, 16);
}
} }

View File

@@ -0,0 +1,195 @@
package com.zt.plat.framework.common.util.security;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 3DES加密解密工具类
* 注意如果websphere下报错Could not find class 'com.sun.crypto.provider.SunJCE'
* 解决方法如下: 下载sunjce_provider.jar放到jdk目录\jre\lib\ext里即可解决
*
* @author apple
*/
public class DESUtil {
/**
* 默认的密钥
*/
private static final String strDefaultKey = "cymco-20160329000000000000000ABCGHDYFYSSPPOEWWWDDDSSXX-cymco";
private Cipher encryptCipher = null;
private Cipher decryptCipher = null;
/**
* 将byte数组转换为表示16进制值的字符串byte[]{8,18}转换为0813 和public static byte[]
* hexStr2ByteArr(String strIn) 互为可逆的转换过程
* @param arrB 需要转换的byte数组
* @return 转换后的字符串
* @throws Exception 本方法不处理任何异常,所有异常全部抛出
*/
public static String byteArr2HexStr(byte[] arrB) throws Exception {
int iLen = arrB.length;
// 每个byte用两个字符才能表示所以字符串的长度是数组长度的两倍
StringBuffer sb = new StringBuffer(iLen * 2);
for (int i = 0; i < iLen; i++) {
int intTmp = arrB[i];
// 把负数转换为正数
while (intTmp < 0) {
intTmp = intTmp + 256;
}
// 小于0F的数需要在前面补0
if (intTmp < 16) {
sb.append("0");
}
sb.append(Integer.toString(intTmp, 16));
}
return sb.toString();
}
/**
* 将表示16进制值的字符串转换为byte数组 和public static String byteArr2HexStr(byte[] arrB)
* 互为可逆的转换过程
* @param strIn 需要转换的字符串
* @return 转换后的byte数组
* @throws Exception 本方法不处理任何异常,所有异常全部抛出
*/
public static byte[] hexStr2ByteArr(String strIn) throws Exception {
byte[] arrB = strIn.getBytes(StandardCharsets.UTF_8);
int iLen = arrB.length;
// 两个字符表示一个字节所以字节数组长度是字符串长度除以2
byte[] arrOut = new byte[iLen / 2];
for (int i = 0; i < iLen; i = i + 2) {
String strTmp = new String(arrB, i, 2);
arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16);
}
return arrOut;
}
/**
* 默认构造方法,使用默认密钥
* @throws Exception
*/
public DESUtil() throws Exception {
this(strDefaultKey);
}
/**
* 指定密钥构造方法
* @param strKey 指定的密钥
* @throws Exception
*/
public DESUtil(String strKey) throws Exception {
//Security.addProvider(new com.sun.crypto.provider.SunJCE());
Key key = getKey(strKey.getBytes());
encryptCipher = Cipher.getInstance("DES");
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
decryptCipher = Cipher.getInstance("DES");
decryptCipher.init(Cipher.DECRYPT_MODE, key);
}
/**
* 加密字节数组
* @param arrB 需加密的字节数组
* @return 加密后的字节数组
* @throws Exception
*/
public byte[] encrypt(byte[] arrB) throws Exception {
return encryptCipher.doFinal(arrB);
}
/**
* 加密字符串
* @param strIn 需加密的字符串
* @return 加密后的字符串
* @throws Exception
*/
public String encrypt(String strIn) throws Exception {
return byteArr2HexStr(encrypt(strIn.getBytes()));
}
/**
* 解密字节数组
* @param arrB 需解密的字节数组
* @return 解密后的字节数组
* @throws Exception
*/
public byte[] decrypt(byte[] arrB) throws Exception {
return decryptCipher.doFinal(arrB);
}
/**
* 解密字符串
* @param strIn 需解密的字符串
* @return 解密后的字符串
* @throws Exception
*/
public String decrypt(String strIn) throws Exception {
return new String(decrypt(hexStr2ByteArr(strIn)));
}
/**
* 从指定字符串生成密钥密钥所需的字节数组长度为8位 不足8位时后面补0超出8位只取前8位
* @param arrBTmp 构成该字符串的字节数组
* @return 生成的密钥
* @throws Exception
*/
private Key getKey(byte[] arrBTmp) throws Exception {
// 创建一个空的8位字节数组默认值为0
byte[] arrB = new byte[8];
// 将原始字节数组转换为8位
for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) {
arrB[i] = arrBTmp[i];
}
// 生成密钥
Key key = new javax.crypto.spec.SecretKeySpec(arrB, "DES");
return key;
}
public static String SHA1(String decript) throws NoSuchAlgorithmException {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
public static void main(String [] args){
DESUtil des;
try {
des = new DESUtil();
String en= des.encrypt("fls123,12121");
System.out.println(en);
String de = des.decrypt(en);
System.out.println(de);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,210 @@
package com.zt.plat.framework.common.util.validation;
import java.util.regex.Pattern;
/**
* 正则校验工具
*
* @author luzemin
**/
public class MatcherSolveUtils {
/**
* 正则校验正数、负数、和小数
*/
private static final Pattern IS_NUM = Pattern.compile("^(\\-|\\+)?\\d+(\\.\\d+)?$");
/**
* 正则校验大于0的数字
*/
private static final Pattern IS_GT_0_NUM = Pattern.compile("(^[0-9]+\\.\\d+$)|(^[0-9]+$)");
/**
* 正则校验大于等于0的数字
*/
private static final Pattern IS_GE_EQ_0_NUM = Pattern.compile("(^[0-9]+\\.\\d+$)|(^[1-9]+\\d*$)");
/**
* 正则校验字母或者数字组成的字符串
*/
private static final Pattern EN_NUM = Pattern.compile("^[A-Za-z0-9]+$");
/**
* 中文、英文、数字但不包括下划线等符号
*/
private static final Pattern EN_CN_NUM = Pattern.compile("^[\\u4E00-\\u9FA5A-Za-z0-9]+$");
/**
* Email地址
*/
private static final Pattern EMAIL = Pattern.compile("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$");
/**
* 域名
*/
private static final Pattern WWW = Pattern.compile("[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\\.?");
/**
* InternetURL
*/
private static final Pattern URL = Pattern.compile("(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]");
/**
* 手机号码
*/
private static final Pattern MOBILE_NUM = Pattern.compile("^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$");
/**
* 车牌号
*/
private static final Pattern TRUCK_NO = Pattern.compile("^([京津晋冀蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新][ABCDEFGHJKLMNPQRSTUVWXY][1-9DF][1-9ABCDEFGHJKLMNPQRSTUVWXYZ]\\d{3}[1-9DF]|[京津晋冀蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新][ABCDEFGHJKLMNPQRSTUVWXY][\\dABCDEFGHJKLNMxPQRSTUVWXYZ]{5})$");
/**
* 电话号码正则表达式支持手机号码3-4位区号7-8位直播号码14位分机号
*/
private static final Pattern PHONE_NUM = Pattern.compile("((\\d{11})|^((\\d{7,8})|(\\d{4}|\\d{3})-(\\d{7,8})|(\\d{4}|\\d{3})-(\\d{7,8})-(\\d{4}|\\d{3}|\\d{2}|\\d{1})|(\\d{7,8})-(\\d{4}|\\d{3}|\\d{2}|\\d{1}))$)");
/**
* 身份证
*/
private static final Pattern ID_CARD = Pattern.compile("(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)");
/**
* 帐号是否合法(字母开头允许5-16字节允许字母数字下划线)
*/
private static final Pattern ACCOUNT = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]{4,15}$");
/**
* 密码(以字母开头长度在6~18之间只能包含字母、数字和下划线)
*/
private static final Pattern PASSWORD = Pattern.compile("^[a-zA-Z]\\w{5,17}$");
/**
* 强密码(必须包含大小写字母和数字的组合可以使用特殊字符长度在8-18之间)
*/
private static final Pattern PASSWORD_STRONG = Pattern.compile("^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,18}$");
/**
* 正则校验正数、负数、和小数
*
* @param str 待校验字符串
*/
public static boolean checkIsNum(String str) {
return IS_NUM.matcher(str).matches();
}
/**
* 正则校验大于0的数字
*
* @param str 待校验字符串t
*/
public static boolean checkIsGt0Num(String str) {
return IS_GT_0_NUM.matcher(str).matches();
}
/**
* 正则校验大于等于0的数字
*
* @param str 待校验字符串
*/
public static boolean checkIsGtEq0Num(String str) {
return IS_GE_EQ_0_NUM.matcher(str).matches();
}
/**
* 正则校验字母或者数字组成的字符串
*
* @param str 待校验字符串
*/
public static boolean checkEnNum(String str) {
return EN_NUM.matcher(str).matches();
}
/**
* 中文、英文、数字但不包括下划线等符号
*
* @param str 待校验字符串
*/
public static boolean checkEnCnNum(String str) {
return EN_CN_NUM.matcher(str).matches();
}
/**
* Email地址
*
* @param str 待校验字符串
*/
public static boolean checkEmail(String str) {
return EMAIL.matcher(str).matches();
}
/**
* 域名
*
* @param str 待校验字符串
*/
public static boolean checkWww(String str) {
return WWW.matcher(str).matches();
}
/**
* InternetURL
*
* @param str 待校验字符串
*/
public static boolean checkInternetURL(String str) {
return URL.matcher(str).matches();
}
/**
* 手机号码
*
* @param str 待校验字符串
*/
public static boolean checkMobileNum(String str) {
return MOBILE_NUM.matcher(str).matches();
}
/**
* 车牌号
*
* @param str 待校验字符串
* @return 校验通过-true反之-false
*/
public static boolean checkTruckNo(String str) {
return TRUCK_NO.matcher(str).matches();
}
/**
* 电话号码正则表达式支持手机号码3-4位区号7-8位直播号码14位分机号
*
* @param str 待校验字符串
*/
public static boolean checkPhoneNum(String str) {
return PHONE_NUM.matcher(str).matches();
}
/**
* 身份证
*
* @param str 待校验字符串
*/
public static boolean checkIdCard(String str) {
return ID_CARD.matcher(str).matches();
}
/**
* 帐号是否合法(字母开头允许5-16字节允许字母数字下划线)
*
* @param str 待校验字符串
*/
public static boolean checkAccount(String str) {
return ACCOUNT.matcher(str).matches();
}
/**
* 密码(以字母开头长度在6~18之间只能包含字母、数字和下划线)
*
* @param str 待校验字符串
*/
public static boolean checkPassword(String str) {
return PASSWORD.matcher(str).matches();
}
/**
* 强密码(必须包含大小写字母和数字的组合可以使用特殊字符长度在8-18之间)
*
* @param str 待校验字符串
*/
public static boolean checkPasswordStrong(String str) {
return PASSWORD_STRONG.matcher(str).matches();
}
}

View File

@@ -11,7 +11,7 @@ import static com.zt.plat.framework.web.core.util.WebFrameworkUtils.HEADER_TENAN
* *
* Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中 * Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
* *
* @author ZT * @author ZT TODO
*/ */
public class TenantRocketMQSendMessageHook implements SendMessageHook { public class TenantRocketMQSendMessageHook implements SendMessageHook {

View File

@@ -54,7 +54,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
PageSumSupport.tryAttachSummary(this, queryWrapper, pageResult); PageSumSupport.tryAttachSummary(this, queryWrapper, pageResult);
return pageResult; return pageResult;
} }
//TODO 分页结果参考这个 ===============================
// MyBatis Plus 查询 // MyBatis Plus 查询
IPage<T> mpPage = MyBatisUtils.buildPage(pageParam, sortingFields); IPage<T> mpPage = MyBatisUtils.buildPage(pageParam, sortingFields);
selectPage(mpPage, queryWrapper); selectPage(mpPage, queryWrapper);

View File

@@ -10,6 +10,7 @@
<modules> <modules>
<module>zt-module-databus-api</module> <module>zt-module-databus-api</module>
<module>zt-module-databus-server</module> <module>zt-module-databus-server</module>
<module>zt-module-databus-server-app</module>
</modules> </modules>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,48 @@
<?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>
<artifactId>zt-module-databus</artifactId>
<groupId>com.zt.plat</groupId>
<version>${revision}</version>
</parent>
<artifactId>zt-module-databus-server-app</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
Databus 模块启动器。
</description>
<dependencies>
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-module-databus-server</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -192,24 +192,4 @@
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project> </project>

View File

@@ -11,6 +11,7 @@
<modules> <modules>
<module>zt-module-infra-api</module> <module>zt-module-infra-api</module>
<module>zt-module-infra-server</module> <module>zt-module-infra-server</module>
<module>zt-module-infra-server-app</module>
</modules> </modules>
<artifactId>zt-module-infra</artifactId> <artifactId>zt-module-infra</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>

View File

@@ -3,10 +3,10 @@
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
## 创建目录,并使用它作为工作目录 ## 创建目录,并使用它作为工作目录
RUN mkdir -p /zt-module-infra-server RUN mkdir -p /zt-module-infra-server-app
WORKDIR /zt-module-infra-server WORKDIR /zt-module-infra-server-app
## 将后端项目的 Jar 文件,复制到镜像中 ## 将后端项目的 Jar 文件,复制到镜像中
COPY ./target/zt-module-infra-server.jar app.jar COPY ../zt-module-infra-server/target/zt-module-infra-server-app.jar app.jar
## 设置 TZ 时区 ## 设置 TZ 时区
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖

View File

@@ -0,0 +1,48 @@
<?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>
<artifactId>zt-module-infra</artifactId>
<groupId>com.zt.plat</groupId>
<version>${revision}</version>
</parent>
<artifactId>zt-module-infra-server-app</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
infra 模块启动器。
</description>
<dependencies>
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-module-infra-server</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -149,24 +149,5 @@
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project> </project>

View File

@@ -11,6 +11,7 @@
<modules> <modules>
<module>zt-module-report-api</module> <module>zt-module-report-api</module>
<module>zt-module-report-server</module> <module>zt-module-report-server</module>
<module>zt-module-report-server-app</module>
</modules> </modules>
<artifactId>zt-module-report</artifactId> <artifactId>zt-module-report</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>

View File

@@ -3,10 +3,10 @@
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
## 创建目录,并使用它作为工作目录 ## 创建目录,并使用它作为工作目录
RUN mkdir -p /zt-module-report-server RUN mkdir -p /zt-module-report-server-app
WORKDIR /zt-module-report-server WORKDIR /zt-module-report-server-app
## 将后端项目的 Jar 文件,复制到镜像中 ## 将后端项目的 Jar 文件,复制到镜像中
COPY ./target/zt-module-report-server.jar app.jar COPY ../zt-module-report-server/target/zt-module-report-server-app.jar app.jar
## 设置 TZ 时区 ## 设置 TZ 时区
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖

View File

@@ -0,0 +1,48 @@
<?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>
<artifactId>zt-module-report</artifactId>
<groupId>com.zt.plat</groupId>
<version>${revision}</version>
</parent>
<artifactId>zt-module-report-server-app</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
report 模块启动器。
</description>
<dependencies>
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-module-report-server</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -111,23 +111,4 @@
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project> </project>

View File

@@ -11,6 +11,7 @@
<modules> <modules>
<module>zt-module-system-api</module> <module>zt-module-system-api</module>
<module>zt-module-system-server</module> <module>zt-module-system-server</module>
<module>zt-module-system-server-app</module>
</modules> </modules>
<artifactId>zt-module-system</artifactId> <artifactId>zt-module-system</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>

View File

@@ -0,0 +1,58 @@
package com.zt.plat.module.system.api.dept.dto;
import com.zt.plat.framework.common.enums.CommonStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 公司部门推送消息 Response DTO
*
* @author ZT
*/
@Schema(description = "RPC 服务 - 部门推送消息 Response DTO")
@Data
public class DeptMsgRespDTO {
/**
* 主键编号
*/
private Long id;
/**
* 本系统部门 ID
*/
private Long deptId;
/**
* 外部系统标识
*/
private String systemCode;
/**
* 外部系统组织编码
*/
private String externalDeptCode;
/**
* 外部系统组织名称
*/
private String externalDeptName;
/**
* 映射状态
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 是否发送消息
*/
private Integer isSendMsg;
}

View File

@@ -2,23 +2,27 @@ package com.zt.plat.module.system.api.esp;
import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.system.api.dept.dto.*; import com.zt.plat.module.system.api.dept.dto.*;
import com.zt.plat.module.system.api.esp.dto.EspDto;
import com.zt.plat.module.system.enums.ApiConstants; import com.zt.plat.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
@FeignClient(name = ApiConstants.NAME) @FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 部门") @Tag(name = "RPC 服务 - 部门推送消息")
public interface EspApi { public interface EspApi {
String PREFIX = ApiConstants.PREFIX + "/dept"; String PREFIX = ApiConstants.PREFIX + "/dept-esp";
@PostMapping(PREFIX + "/create")
@Operation(summary = "新增部门")
CommonResult<Long> createDept(@RequestBody DeptSaveReqDTO createReqVO);
@PostMapping(PREFIX + "/pushMsg") @PostMapping(PREFIX + "/pushMsg")
@Operation(summary = "推送消息") @Operation(summary = "查询部门消息")
CommonResult<List<EspDto>> pushMsg(@RequestBody DeptSaveReqDTO syncReqDTO); CommonResult<List<DeptMsgRespDTO>> selectDepMsg(@RequestBody DeptSaveReqDTO syncReqDTO);
} }

View File

@@ -0,0 +1,72 @@
package com.zt.plat.module.system.api.msg;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.system.api.sms.dto.log.SmsLogRespDTO;
import com.zt.plat.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import com.zt.plat.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 消息发送")
public interface MsgSendApi {
String PREFIX = ApiConstants.PREFIX + "/msg/send";
@PostMapping(PREFIX + "/sendTextMsg")
@Operation(summary = "发送企业微信文本消息", description = "在 mobile 为空时,使用 userId 加载对应 Admin 的手机号")
CommonResult<Long> sendTextMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO);
@PostMapping(PREFIX + "/sendImageMsg")
@Operation(summary = "发送企业微信图片消息", description = "在 mobile 为空时,使用 userId 加载对应 Member 的手机号")
CommonResult<Long> sendImageMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO);
@GetMapping(PREFIX + "/sendVoiceMsg")
@Operation(summary = "发送企业微信语音消息")
CommonResult<SmsLogRespDTO> getSmsLog(@RequestParam("id") Long id);
@PostMapping(PREFIX + "/sendVideoMsg")
@Operation(summary = "发送企业微信视频消息", description = "在 mobile 为空时,使用 userId 加载对应 Admin 的手机号")
CommonResult<Long> sendVideoMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO);
@PostMapping(PREFIX + "/sendFileMsg")
@Operation(summary = "发送企业微信文件消息", description = "在 mobile 为空时,使用 userId 加载对应 Member 的手机号")
CommonResult<Long> sendFileMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO);
@GetMapping(PREFIX + "/sendTextCardMsg")
@Operation(summary = "发送企业微信文本卡片消息")
CommonResult<SmsLogRespDTO> sendTextCardMsg(@RequestParam("id") Long id);
@PostMapping(PREFIX + "/sendTextCardMsgPich01")
@Operation(summary = "发送企业微信文本卡片消息 -物资存货智能管理 预警信息", description = "在 mobile 为空时,使用 userId 加载对应 Admin 的手机号")
CommonResult<Long> sendTextCardMsgPich01(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO);
@PostMapping(PREFIX + "/sendNewsMsg")
@Operation(summary = "发送企业微信图文消息", description = "在 mobile 为空时,使用 userId 加载对应 Member 的手机号")
CommonResult<Long> sendNewsMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO);
@GetMapping(PREFIX + "/sendMpNewsMsg")
@Operation(summary = "发送企业微信图文消息mpnews")
CommonResult<SmsLogRespDTO> sendMpNewsMsg(@RequestParam("id") Long id);
@PostMapping(PREFIX + "/sendMarkdownMsg")
@Operation(summary = "发送企业微信小程序通知消息", description = "在 mobile 为空时,使用 userId 加载对应 Admin 的手机号")
CommonResult<Long> sendMarkdownMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO);
@PostMapping(PREFIX + "/sendMiniProgramNoticeMsg")
@Operation(summary = "发送企业微信图片消息", description = "在 mobile 为空时,使用 userId 加载对应 Member 的手机号")
CommonResult<Long> sendMiniProgramNoticeMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO);
@GetMapping(PREFIX + "/sendInteractiveTaskCardMsg")
@Operation(summary = "发送企业微信任务卡片消息")
CommonResult<SmsLogRespDTO> sendInteractiveTaskCardMsg(@RequestParam("id") Long id);
}

View File

@@ -0,0 +1,44 @@
package com.zt.plat.module.system.api.sms.dto.code;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
public class User implements Serializable {
/**
* 返回码
*/
private Integer errcode;
/**
* 对返回码的文本描述内容
*/
private String errmsg;
/**
* 成员UserID
*/
private String UserId;
/**
* 手机设备号(由中铝集团在安装时随机生成,删除重装会改变,升级不受影响)
*/
private String DeviceId;
/**
* 成员身份信息2超级管理员, 4:分级管理员5普通成员
*/
private Integer usertype;
/**
* 判断受否授权成功
*
* @return true-授权成功、false-授权失败
*/
public boolean isAuthorized() {
return this.getErrcode() == 0;
}
}

View File

@@ -4,7 +4,6 @@ import com.zt.plat.framework.common.validation.Mobile;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import lombok.Data; import lombok.Data;
import java.util.Map; import java.util.Map;
@Schema(description = "RPC 服务 - 短信发送给 Admin 或者 Member 用户 Request DTO") @Schema(description = "RPC 服务 - 短信发送给 Admin 或者 Member 用户 Request DTO")

View File

@@ -11,7 +11,6 @@ public class ApiConstants {
/** /**
* 服务名 * 服务名
*
* 注意,需要保证和 spring.application.name 保持一致 * 注意,需要保证和 spring.application.name 保持一致
*/ */
public static final String NAME = "system-server"; public static final String NAME = "system-server";

View File

@@ -108,6 +108,9 @@ public interface ErrorCodeConstants {
ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1_002_011_001, "短信渠道不处于开启状态,不允许选择"); ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1_002_011_001, "短信渠道不处于开启状态,不允许选择");
ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1_002_011_002, "无法删除,该短信渠道还有短信模板"); ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1_002_011_002, "无法删除,该短信渠道还有短信模板");
ErrorCode SMS_CHANNEL_BALANCE_UNSUPPORTED = new ErrorCode(1_002_011_003, "该短信渠道不支持余额查询"); ErrorCode SMS_CHANNEL_BALANCE_UNSUPPORTED = new ErrorCode(1_002_011_003, "该短信渠道不支持余额查询");
ErrorCode MSG_CHANNEL_NOT_EXISTS = new ErrorCode(1_002_011_004, "消息渠道不存在");
// ========== 短信模板 1-002-012-000 ========== // ========== 短信模板 1-002-012-000 ==========
ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_012_000, "短信模板不存在"); ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_012_000, "短信模板不存在");
@@ -122,6 +125,7 @@ public interface ErrorCodeConstants {
ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_013_001, "模板参数({})缺失"); ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_013_001, "模板参数({})缺失");
ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_013_002, "短信模板不存在"); ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_013_002, "短信模板不存在");
ErrorCode SMS_CALLBACK_SIGN_INVALID = new ErrorCode(1_002_013_100, "短信回调签名校验失败"); ErrorCode SMS_CALLBACK_SIGN_INVALID = new ErrorCode(1_002_013_100, "短信回调签名校验失败");
ErrorCode MSG_CALLBACK_SIGN_INVALID = new ErrorCode(1_002_013_101, "消息回调签名校验失败");
// ========== 短信验证码 1-002-014-000 ========== // ========== 短信验证码 1-002-014-000 ==========
ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在"); ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在");
@@ -206,6 +210,8 @@ public interface ErrorCodeConstants {
// ========== 用户与部门关系 1-002-029-000 ========== // ========== 用户与部门关系 1-002-029-000 ==========
ErrorCode USER_DEPT_NOT_EXISTS = new ErrorCode(1_002_029_000, "用户与部门关系不存在"); ErrorCode USER_DEPT_NOT_EXISTS = new ErrorCode(1_002_029_000, "用户与部门关系不存在");
ErrorCode USER_DEPT_SAVE_EXISTS = new ErrorCode(1_002_029_001, "插入用户部门失败");
// ========== 系统序列号分段明细 1-002-030-000 ========== // ========== 系统序列号分段明细 1-002-030-000 ==========
ErrorCode SEQUENCE_DETAIL_NOT_EXISTS = new ErrorCode(1_002_030_000, "系统序列号分段明细不存在"); ErrorCode SEQUENCE_DETAIL_NOT_EXISTS = new ErrorCode(1_002_030_000, "系统序列号分段明细不存在");

View File

@@ -3,10 +3,10 @@
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
## 创建目录,并使用它作为工作目录 ## 创建目录,并使用它作为工作目录
RUN mkdir -p /zt-module-system-server RUN mkdir -p /zt-module-system-server-app
WORKDIR /zt-module-system-server WORKDIR /zt-module-system-server-app
## 将后端项目的 Jar 文件,复制到镜像中 ## 将后端项目的 Jar 文件,复制到镜像中
COPY ./target/zt-module-system-server.jar app.jar COPY ../zt-module-system-server/target/zt-module-system-server-app.jar app.jar
## 设置 TZ 时区 ## 设置 TZ 时区
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖

View File

@@ -0,0 +1,48 @@
<?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>
<artifactId>zt-module-system</artifactId>
<groupId>com.zt.plat</groupId>
<version>${revision}</version>
</parent>
<artifactId>zt-module-system-server-app</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
system 模块启动器。
</description>
<dependencies>
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-module-system-server</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -200,26 +200,14 @@
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.13.1</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project> </project>

View File

@@ -0,0 +1,224 @@
package com.zt.plat.module.system.api.databus;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.databus.api.data.DatabusDeptData;
import com.zt.plat.module.databus.api.dto.CursorPageReqDTO;
import com.zt.plat.module.databus.api.dto.CursorPageResult;
import com.zt.plat.module.databus.api.provider.DatabusDeptProviderApi;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO;
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* Databus 部门数据提供者 API 实现
*
* @author ZT
*/
@Slf4j
@RestController
@Validated
public class DatabusDeptProviderApiImpl implements DatabusDeptProviderApi {
@Resource
private DeptMapper deptMapper;
@Resource
private AdminUserMapper userMapper;
@Override
public CommonResult<CursorPageResult<DatabusDeptData>> getPageByCursor(CursorPageReqDTO reqDTO) {
// 构建游标查询条件
LambdaQueryWrapper<DeptDO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DeptDO::getDeptSource, 3);
// 游标条件create_time > cursorTime OR (create_time = cursorTime AND id > cursorId)
if (!reqDTO.isFirstPage()) {
queryWrapper.and(w -> w
.gt(DeptDO::getCreateTime, reqDTO.getCursorTime())
.or(o -> o
.eq(DeptDO::getCreateTime, reqDTO.getCursorTime())
.gt(DeptDO::getId, reqDTO.getCursorId())
)
);
}
// 租户过滤(如果指定)
if (reqDTO.getTenantId() != null) {
queryWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId());
}
// 按 create_time, id 升序排列,确保顺序稳定
queryWrapper.orderByAsc(DeptDO::getCreateTime)
.orderByAsc(DeptDO::getId);
// 多查一条判断是否有更多数据
int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100;
queryWrapper.last("LIMIT " + (limit + 1));
List<DeptDO> deptList = deptMapper.selectList(queryWrapper);
// 判断是否有更多
boolean hasMore = deptList.size() > limit;
if (hasMore) {
deptList = deptList.subList(0, limit);
}
if (CollUtil.isEmpty(deptList)) {
return success(CursorPageResult.empty());
}
// 收集负责人用户ID
Set<Long> leaderUserIds = deptList.stream()
.map(DeptDO::getLeaderUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (CollUtil.isNotEmpty(leaderUserIds)) {
List<AdminUserDO> users = userMapper.selectBatchIds(leaderUserIds);
userNameMap = users.stream()
.collect(Collectors.toMap(AdminUserDO::getId, AdminUserDO::getNickname, (v1, v2) -> v1));
}
// 转换为同步数据
Map<Long, String> finalUserNameMap = userNameMap;
List<DatabusDeptData> dataList = deptList.stream()
.map(dept -> convertToDatabusDeptData(dept, finalUserNameMap))
.collect(Collectors.toList());
// 获取最后一条数据的游标
DeptDO lastDept = deptList.get(deptList.size() - 1);
// 首次查询时返回总数
Long total = null;
if (reqDTO.isFirstPage()) {
LambdaQueryWrapper<DeptDO> countWrapper = new LambdaQueryWrapper<>();
countWrapper.eq(DeptDO::getDeptSource, 3);
if (reqDTO.getTenantId() != null) {
countWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId());
}
total = deptMapper.selectCount(countWrapper);
}
return success(CursorPageResult.of(
dataList,
lastDept.getCreateTime(),
lastDept.getId(),
hasMore,
total
));
}
@Override
public CommonResult<DatabusDeptData> getById(Long id) {
DeptDO dept = deptMapper.selectById(id);
if (dept == null) {
return success(null);
}
// 查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (dept.getLeaderUserId() != null) {
AdminUserDO user = userMapper.selectById(dept.getLeaderUserId());
if (user != null) {
userNameMap.put(user.getId(), user.getNickname());
}
}
return success(convertToDatabusDeptData(dept, userNameMap));
}
@Override
public CommonResult<List<DatabusDeptData>> getListByIds(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return success(Collections.emptyList());
}
List<DeptDO> deptList = deptMapper.selectBatchIds(ids);
if (CollUtil.isEmpty(deptList)) {
return success(Collections.emptyList());
}
// 收集负责人用户ID
Set<Long> leaderUserIds = deptList.stream()
.map(DeptDO::getLeaderUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (CollUtil.isNotEmpty(leaderUserIds)) {
List<AdminUserDO> users = userMapper.selectBatchIds(leaderUserIds);
userNameMap = users.stream()
.collect(Collectors.toMap(AdminUserDO::getId, AdminUserDO::getNickname, (v1, v2) -> v1));
}
Map<Long, String> finalUserNameMap = userNameMap;
List<DatabusDeptData> dataList = deptList.stream()
.map(dept -> convertToDatabusDeptData(dept, finalUserNameMap))
.collect(Collectors.toList());
return success(dataList);
}
@Override
public CommonResult<Long> count(Long tenantId) {
LambdaQueryWrapper<DeptDO> queryWrapper = new LambdaQueryWrapper<>();
<<<<<<< HEAD
// ⚠️ 只统计 userSource = 3 的用户
=======
>>>>>>> test
queryWrapper.eq(DeptDO::getDeptSource, 3);
if (tenantId != null) {
queryWrapper.eq(DeptDO::getTenantId, tenantId);
}
return success(deptMapper.selectCount(queryWrapper));
}
/**
* 将 DeptDO 转换为 DatabusDeptData
*/
private DatabusDeptData convertToDatabusDeptData(DeptDO dept, Map<Long, String> userNameMap) {
// 根据 isCompany 反推 deptType
Integer deptType = null;
if (Boolean.TRUE.equals(dept.getIsCompany())) {
deptType = 28; // 公司
} else if (Boolean.FALSE.equals(dept.getIsCompany())) {
deptType = 26; // 部门
}
return DatabusDeptData.builder()
.id(dept.getId())
.code(dept.getCode())
.name(dept.getName())
.shortName(dept.getShortName())
.parentId(dept.getParentId())
.sort(dept.getSort())
.status(dept.getStatus())
.deptType(deptType)
.isGroup(dept.getIsGroup())
.isCompany(dept.getIsCompany())
.deptSource(dept.getDeptSource())
.leaderUserId(dept.getLeaderUserId())
.leaderUserName(dept.getLeaderUserId() != null ? userNameMap.get(dept.getLeaderUserId()) : null)
.phone(dept.getPhone())
.email(dept.getEmail())
.tenantId(dept.getTenantId())
.createTime(dept.getCreateTime())
.updateTime(dept.getUpdateTime())
.build();
}
}

View File

@@ -0,0 +1,218 @@
package com.zt.plat.module.system.api.databus;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.databus.api.data.DatabusDeptData;
import com.zt.plat.module.databus.api.dto.CursorPageReqDTO;
import com.zt.plat.module.databus.api.dto.CursorPageResult;
import com.zt.plat.module.databus.api.provider.DatabusDeptProviderApi;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO;
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* Databus 部门数据提供者 API 实现
*
* @author ZT
*/
@Slf4j
@RestController
@Validated
public class DatabusDeptProviderApiImpl implements DatabusDeptProviderApi {
@Resource
private DeptMapper deptMapper;
@Resource
private AdminUserMapper userMapper;
@Override
public CommonResult<CursorPageResult<DatabusDeptData>> getPageByCursor(CursorPageReqDTO reqDTO) {
// 构建游标查询条件
LambdaQueryWrapper<DeptDO> queryWrapper = new LambdaQueryWrapper<>();
// 游标条件create_time > cursorTime OR (create_time = cursorTime AND id > cursorId)
if (!reqDTO.isFirstPage()) {
queryWrapper.and(w -> w
.gt(DeptDO::getCreateTime, reqDTO.getCursorTime())
.or(o -> o
.eq(DeptDO::getCreateTime, reqDTO.getCursorTime())
.gt(DeptDO::getId, reqDTO.getCursorId())
)
);
}
// 租户过滤(如果指定)
if (reqDTO.getTenantId() != null) {
queryWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId());
}
// 按 create_time, id 升序排列,确保顺序稳定
queryWrapper.orderByAsc(DeptDO::getCreateTime)
.orderByAsc(DeptDO::getId);
// 多查一条判断是否有更多数据
int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100;
queryWrapper.last("LIMIT " + (limit + 1));
List<DeptDO> deptList = deptMapper.selectList(queryWrapper);
// 判断是否有更多
boolean hasMore = deptList.size() > limit;
if (hasMore) {
deptList = deptList.subList(0, limit);
}
if (CollUtil.isEmpty(deptList)) {
return success(CursorPageResult.empty());
}
// 收集负责人用户ID
Set<Long> leaderUserIds = deptList.stream()
.map(DeptDO::getLeaderUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (CollUtil.isNotEmpty(leaderUserIds)) {
List<AdminUserDO> users = userMapper.selectBatchIds(leaderUserIds);
userNameMap = users.stream()
.collect(Collectors.toMap(AdminUserDO::getId, AdminUserDO::getNickname, (v1, v2) -> v1));
}
// 转换为同步数据
Map<Long, String> finalUserNameMap = userNameMap;
List<DatabusDeptData> dataList = deptList.stream()
.map(dept -> convertToDatabusDeptData(dept, finalUserNameMap))
.collect(Collectors.toList());
// 获取最后一条数据的游标
DeptDO lastDept = deptList.get(deptList.size() - 1);
// 首次查询时返回总数
Long total = null;
if (reqDTO.isFirstPage()) {
LambdaQueryWrapper<DeptDO> countWrapper = new LambdaQueryWrapper<>();
if (reqDTO.getTenantId() != null) {
countWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId());
}
total = deptMapper.selectCount(countWrapper);
}
return success(CursorPageResult.of(
dataList,
lastDept.getCreateTime(),
lastDept.getId(),
hasMore,
total
));
}
@Override
public CommonResult<DatabusDeptData> getById(Long id) {
DeptDO dept = deptMapper.selectById(id);
if (dept == null) {
return success(null);
}
// 查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (dept.getLeaderUserId() != null) {
AdminUserDO user = userMapper.selectById(dept.getLeaderUserId());
if (user != null) {
userNameMap.put(user.getId(), user.getNickname());
}
}
return success(convertToDatabusDeptData(dept, userNameMap));
}
@Override
public CommonResult<List<DatabusDeptData>> getListByIds(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return success(Collections.emptyList());
}
List<DeptDO> deptList = deptMapper.selectBatchIds(ids);
if (CollUtil.isEmpty(deptList)) {
return success(Collections.emptyList());
}
// 收集负责人用户ID
Set<Long> leaderUserIds = deptList.stream()
.map(DeptDO::getLeaderUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (CollUtil.isNotEmpty(leaderUserIds)) {
List<AdminUserDO> users = userMapper.selectBatchIds(leaderUserIds);
userNameMap = users.stream()
.collect(Collectors.toMap(AdminUserDO::getId, AdminUserDO::getNickname, (v1, v2) -> v1));
}
Map<Long, String> finalUserNameMap = userNameMap;
List<DatabusDeptData> dataList = deptList.stream()
.map(dept -> convertToDatabusDeptData(dept, finalUserNameMap))
.collect(Collectors.toList());
return success(dataList);
}
@Override
public CommonResult<Long> count(Long tenantId) {
LambdaQueryWrapper<DeptDO> queryWrapper = new LambdaQueryWrapper<>();
if (tenantId != null) {
queryWrapper.eq(DeptDO::getTenantId, tenantId);
}
return success(deptMapper.selectCount(queryWrapper));
}
/**
* 将 DeptDO 转换为 DatabusDeptData
*/
private DatabusDeptData convertToDatabusDeptData(DeptDO dept, Map<Long, String> userNameMap) {
// 根据 isCompany 反推 deptType
Integer deptType = null;
if (Boolean.TRUE.equals(dept.getIsCompany())) {
deptType = 28; // 公司
} else if (Boolean.FALSE.equals(dept.getIsCompany())) {
deptType = 26; // 部门
}
return DatabusDeptData.builder()
.id(dept.getId())
.code(dept.getCode())
.name(dept.getName())
.shortName(dept.getShortName())
.parentId(dept.getParentId())
.sort(dept.getSort())
.status(dept.getStatus())
.deptType(deptType)
.isGroup(dept.getIsGroup())
.isCompany(dept.getIsCompany())
.deptSource(dept.getDeptSource())
.leaderUserId(dept.getLeaderUserId())
.leaderUserName(dept.getLeaderUserId() != null ? userNameMap.get(dept.getLeaderUserId()) : null)
.phone(dept.getPhone())
.email(dept.getEmail())
.tenantId(dept.getTenantId())
.createTime(dept.getCreateTime())
.updateTime(dept.getUpdateTime())
.build();
}
}

View File

@@ -0,0 +1,221 @@
package com.zt.plat.module.system.api.databus;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.databus.api.data.DatabusDeptData;
import com.zt.plat.module.databus.api.dto.CursorPageReqDTO;
import com.zt.plat.module.databus.api.dto.CursorPageResult;
import com.zt.plat.module.databus.api.provider.DatabusDeptProviderApi;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO;
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* Databus 部门数据提供者 API 实现
*
* @author ZT
*/
@Slf4j
@RestController
@Validated
public class DatabusDeptProviderApiImpl implements DatabusDeptProviderApi {
@Resource
private DeptMapper deptMapper;
@Resource
private AdminUserMapper userMapper;
@Override
public CommonResult<CursorPageResult<DatabusDeptData>> getPageByCursor(CursorPageReqDTO reqDTO) {
// 构建游标查询条件
LambdaQueryWrapper<DeptDO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DeptDO::getDeptSource, 3);
// 游标条件create_time > cursorTime OR (create_time = cursorTime AND id > cursorId)
if (!reqDTO.isFirstPage()) {
queryWrapper.and(w -> w
.gt(DeptDO::getCreateTime, reqDTO.getCursorTime())
.or(o -> o
.eq(DeptDO::getCreateTime, reqDTO.getCursorTime())
.gt(DeptDO::getId, reqDTO.getCursorId())
)
);
}
// 租户过滤(如果指定)
if (reqDTO.getTenantId() != null) {
queryWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId());
}
// 按 create_time, id 升序排列,确保顺序稳定
queryWrapper.orderByAsc(DeptDO::getCreateTime)
.orderByAsc(DeptDO::getId);
// 多查一条判断是否有更多数据
int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100;
queryWrapper.last("LIMIT " + (limit + 1));
List<DeptDO> deptList = deptMapper.selectList(queryWrapper);
// 判断是否有更多
boolean hasMore = deptList.size() > limit;
if (hasMore) {
deptList = deptList.subList(0, limit);
}
if (CollUtil.isEmpty(deptList)) {
return success(CursorPageResult.empty());
}
// 收集负责人用户ID
Set<Long> leaderUserIds = deptList.stream()
.map(DeptDO::getLeaderUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (CollUtil.isNotEmpty(leaderUserIds)) {
List<AdminUserDO> users = userMapper.selectBatchIds(leaderUserIds);
userNameMap = users.stream()
.collect(Collectors.toMap(AdminUserDO::getId, AdminUserDO::getNickname, (v1, v2) -> v1));
}
// 转换为同步数据
Map<Long, String> finalUserNameMap = userNameMap;
List<DatabusDeptData> dataList = deptList.stream()
.map(dept -> convertToDatabusDeptData(dept, finalUserNameMap))
.collect(Collectors.toList());
// 获取最后一条数据的游标
DeptDO lastDept = deptList.get(deptList.size() - 1);
// 首次查询时返回总数
Long total = null;
if (reqDTO.isFirstPage()) {
LambdaQueryWrapper<DeptDO> countWrapper = new LambdaQueryWrapper<>();
countWrapper.eq(DeptDO::getDeptSource, 3);
if (reqDTO.getTenantId() != null) {
countWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId());
}
total = deptMapper.selectCount(countWrapper);
}
return success(CursorPageResult.of(
dataList,
lastDept.getCreateTime(),
lastDept.getId(),
hasMore,
total
));
}
@Override
public CommonResult<DatabusDeptData> getById(Long id) {
DeptDO dept = deptMapper.selectById(id);
if (dept == null) {
return success(null);
}
// 查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (dept.getLeaderUserId() != null) {
AdminUserDO user = userMapper.selectById(dept.getLeaderUserId());
if (user != null) {
userNameMap.put(user.getId(), user.getNickname());
}
}
return success(convertToDatabusDeptData(dept, userNameMap));
}
@Override
public CommonResult<List<DatabusDeptData>> getListByIds(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return success(Collections.emptyList());
}
List<DeptDO> deptList = deptMapper.selectBatchIds(ids);
if (CollUtil.isEmpty(deptList)) {
return success(Collections.emptyList());
}
// 收集负责人用户ID
Set<Long> leaderUserIds = deptList.stream()
.map(DeptDO::getLeaderUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (CollUtil.isNotEmpty(leaderUserIds)) {
List<AdminUserDO> users = userMapper.selectBatchIds(leaderUserIds);
userNameMap = users.stream()
.collect(Collectors.toMap(AdminUserDO::getId, AdminUserDO::getNickname, (v1, v2) -> v1));
}
Map<Long, String> finalUserNameMap = userNameMap;
List<DatabusDeptData> dataList = deptList.stream()
.map(dept -> convertToDatabusDeptData(dept, finalUserNameMap))
.collect(Collectors.toList());
return success(dataList);
}
@Override
public CommonResult<Long> count(Long tenantId) {
LambdaQueryWrapper<DeptDO> queryWrapper = new LambdaQueryWrapper<>();
// ⚠️ 只统计 userSource = 3 的用户
queryWrapper.eq(DeptDO::getDeptSource, 3);
if (tenantId != null) {
queryWrapper.eq(DeptDO::getTenantId, tenantId);
}
return success(deptMapper.selectCount(queryWrapper));
}
/**
* 将 DeptDO 转换为 DatabusDeptData
*/
private DatabusDeptData convertToDatabusDeptData(DeptDO dept, Map<Long, String> userNameMap) {
// 根据 isCompany 反推 deptType
Integer deptType = null;
if (Boolean.TRUE.equals(dept.getIsCompany())) {
deptType = 28; // 公司
} else if (Boolean.FALSE.equals(dept.getIsCompany())) {
deptType = 26; // 部门
}
return DatabusDeptData.builder()
.id(dept.getId())
.code(dept.getCode())
.name(dept.getName())
.shortName(dept.getShortName())
.parentId(dept.getParentId())
.sort(dept.getSort())
.status(dept.getStatus())
.deptType(deptType)
.isGroup(dept.getIsGroup())
.isCompany(dept.getIsCompany())
.deptSource(dept.getDeptSource())
.leaderUserId(dept.getLeaderUserId())
.leaderUserName(dept.getLeaderUserId() != null ? userNameMap.get(dept.getLeaderUserId()) : null)
.phone(dept.getPhone())
.email(dept.getEmail())
.tenantId(dept.getTenantId())
.createTime(dept.getCreateTime())
.updateTime(dept.getUpdateTime())
.build();
}
}

View File

@@ -0,0 +1,220 @@
package com.zt.plat.module.system.api.databus;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.databus.api.data.DatabusDeptData;
import com.zt.plat.module.databus.api.dto.CursorPageReqDTO;
import com.zt.plat.module.databus.api.dto.CursorPageResult;
import com.zt.plat.module.databus.api.provider.DatabusDeptProviderApi;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO;
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* Databus 部门数据提供者 API 实现
*
* @author ZT
*/
@Slf4j
@RestController
@Validated
public class DatabusDeptProviderApiImpl implements DatabusDeptProviderApi {
@Resource
private DeptMapper deptMapper;
@Resource
private AdminUserMapper userMapper;
@Override
public CommonResult<CursorPageResult<DatabusDeptData>> getPageByCursor(CursorPageReqDTO reqDTO) {
// 构建游标查询条件
LambdaQueryWrapper<DeptDO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DeptDO::getDeptSource, 3);
// 游标条件create_time > cursorTime OR (create_time = cursorTime AND id > cursorId)
if (!reqDTO.isFirstPage()) {
queryWrapper.and(w -> w
.gt(DeptDO::getCreateTime, reqDTO.getCursorTime())
.or(o -> o
.eq(DeptDO::getCreateTime, reqDTO.getCursorTime())
.gt(DeptDO::getId, reqDTO.getCursorId())
)
);
}
// 租户过滤(如果指定)
if (reqDTO.getTenantId() != null) {
queryWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId());
}
// 按 create_time, id 升序排列,确保顺序稳定
queryWrapper.orderByAsc(DeptDO::getCreateTime)
.orderByAsc(DeptDO::getId);
// 多查一条判断是否有更多数据
int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100;
queryWrapper.last("LIMIT " + (limit + 1));
List<DeptDO> deptList = deptMapper.selectList(queryWrapper);
// 判断是否有更多
boolean hasMore = deptList.size() > limit;
if (hasMore) {
deptList = deptList.subList(0, limit);
}
if (CollUtil.isEmpty(deptList)) {
return success(CursorPageResult.empty());
}
// 收集负责人用户ID
Set<Long> leaderUserIds = deptList.stream()
.map(DeptDO::getLeaderUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (CollUtil.isNotEmpty(leaderUserIds)) {
List<AdminUserDO> users = userMapper.selectBatchIds(leaderUserIds);
userNameMap = users.stream()
.collect(Collectors.toMap(AdminUserDO::getId, AdminUserDO::getNickname, (v1, v2) -> v1));
}
// 转换为同步数据
Map<Long, String> finalUserNameMap = userNameMap;
List<DatabusDeptData> dataList = deptList.stream()
.map(dept -> convertToDatabusDeptData(dept, finalUserNameMap))
.collect(Collectors.toList());
// 获取最后一条数据的游标
DeptDO lastDept = deptList.get(deptList.size() - 1);
// 首次查询时返回总数
Long total = null;
if (reqDTO.isFirstPage()) {
LambdaQueryWrapper<DeptDO> countWrapper = new LambdaQueryWrapper<>();
countWrapper.eq(DeptDO::getDeptSource, 3);
if (reqDTO.getTenantId() != null) {
countWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId());
}
total = deptMapper.selectCount(countWrapper);
}
return success(CursorPageResult.of(
dataList,
lastDept.getCreateTime(),
lastDept.getId(),
hasMore,
total
));
}
@Override
public CommonResult<DatabusDeptData> getById(Long id) {
DeptDO dept = deptMapper.selectById(id);
if (dept == null) {
return success(null);
}
// 查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (dept.getLeaderUserId() != null) {
AdminUserDO user = userMapper.selectById(dept.getLeaderUserId());
if (user != null) {
userNameMap.put(user.getId(), user.getNickname());
}
}
return success(convertToDatabusDeptData(dept, userNameMap));
}
@Override
public CommonResult<List<DatabusDeptData>> getListByIds(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return success(Collections.emptyList());
}
List<DeptDO> deptList = deptMapper.selectBatchIds(ids);
if (CollUtil.isEmpty(deptList)) {
return success(Collections.emptyList());
}
// 收集负责人用户ID
Set<Long> leaderUserIds = deptList.stream()
.map(DeptDO::getLeaderUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询负责人用户名
Map<Long, String> userNameMap = new HashMap<>();
if (CollUtil.isNotEmpty(leaderUserIds)) {
List<AdminUserDO> users = userMapper.selectBatchIds(leaderUserIds);
userNameMap = users.stream()
.collect(Collectors.toMap(AdminUserDO::getId, AdminUserDO::getNickname, (v1, v2) -> v1));
}
Map<Long, String> finalUserNameMap = userNameMap;
List<DatabusDeptData> dataList = deptList.stream()
.map(dept -> convertToDatabusDeptData(dept, finalUserNameMap))
.collect(Collectors.toList());
return success(dataList);
}
@Override
public CommonResult<Long> count(Long tenantId) {
LambdaQueryWrapper<DeptDO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DeptDO::getDeptSource, 3);
if (tenantId != null) {
queryWrapper.eq(DeptDO::getTenantId, tenantId);
}
return success(deptMapper.selectCount(queryWrapper));
}
/**
* 将 DeptDO 转换为 DatabusDeptData
*/
private DatabusDeptData convertToDatabusDeptData(DeptDO dept, Map<Long, String> userNameMap) {
// 根据 isCompany 反推 deptType
Integer deptType = null;
if (Boolean.TRUE.equals(dept.getIsCompany())) {
deptType = 28; // 公司
} else if (Boolean.FALSE.equals(dept.getIsCompany())) {
deptType = 26; // 部门
}
return DatabusDeptData.builder()
.id(dept.getId())
.code(dept.getCode())
.name(dept.getName())
.shortName(dept.getShortName())
.parentId(dept.getParentId())
.sort(dept.getSort())
.status(dept.getStatus())
.deptType(deptType)
.isGroup(dept.getIsGroup())
.isCompany(dept.getIsCompany())
.deptSource(dept.getDeptSource())
.leaderUserId(dept.getLeaderUserId())
.leaderUserName(dept.getLeaderUserId() != null ? userNameMap.get(dept.getLeaderUserId()) : null)
.phone(dept.getPhone())
.email(dept.getEmail())
.tenantId(dept.getTenantId())
.createTime(dept.getCreateTime())
.updateTime(dept.getUpdateTime())
.build();
}
}

View File

@@ -2,15 +2,17 @@ package com.zt.plat.module.system.api.esp;
import com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants; import com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.util.object.ObjectUtils; import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.api.dept.dto.DeptMsgRespDTO;
import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO; import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO;
import com.zt.plat.module.system.api.esp.dto.EspDto; import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
import com.zt.plat.module.system.service.dept.IEspService; import com.zt.plat.module.system.service.dept.IEspService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@RestController @RestController
@Validated @Validated
@@ -18,16 +20,26 @@ public class EspApiImpl implements EspApi {
@Resource @Resource
private IEspService deptService; private IEspService espService;
@Override @Override
public CommonResult<List<EspDto>> pushMsg(DeptSaveReqDTO syncReqDTO) public CommonResult<Long> createDept(DeptSaveReqDTO createReqVO) {
DeptSaveReqVO reqVO = BeanUtils.toBean(createReqVO, DeptSaveReqVO.class);
Long deptId = espService.createDept(reqVO);
return success(deptId);
}
@Override
public CommonResult<List<DeptMsgRespDTO>> selectDepMsg(DeptSaveReqDTO syncReqDTO)
{ {
if(Objects.isNull(syncReqDTO) || null == syncReqDTO.getId()) if(Objects.isNull(syncReqDTO) || null == syncReqDTO.getId())
{ {
return CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), return CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
"ID不能为空"); "ID不能为空");
} }
return CommonResult.success(deptService.pushMsg(syncReqDTO)); return espService.selectDepMsg(syncReqDTO);
} }
} }

View File

@@ -0,0 +1,84 @@
package com.zt.plat.module.system.api.msg;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.api.sms.dto.log.SmsLogRespDTO;
import com.zt.plat.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import com.zt.plat.module.system.service.sms.SmsLogService;
import com.zt.plat.module.system.service.sms.SmsSendService;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@RestController
@Validated
public class MsgSendApiImpl implements MsgSendApi {
@Resource
private SmsSendService smsSendService;
@Resource
private SmsLogService smsLogService;
@Override
public CommonResult<Long> sendTextMsg(SmsSendSingleToUserReqDTO reqDTO) {
return null;
}
@Override
public CommonResult<Long> sendImageMsg(SmsSendSingleToUserReqDTO reqDTO) {
return null;
}
@Override
public CommonResult<SmsLogRespDTO> getSmsLog(Long id) {
return success(BeanUtils.toBean(smsLogService.getSmsLog(id), SmsLogRespDTO.class));
}
@Override
public CommonResult<Long> sendVideoMsg(SmsSendSingleToUserReqDTO reqDTO) {
return null;
}
@Override
public CommonResult<Long> sendFileMsg(SmsSendSingleToUserReqDTO reqDTO) {
return null;
}
@Override
public CommonResult<SmsLogRespDTO> sendTextCardMsg(Long id) {
return null;
}
@Override
public CommonResult<Long> sendTextCardMsgPich01(SmsSendSingleToUserReqDTO reqDTO) {
return null;
}
@Override
public CommonResult<Long> sendNewsMsg(SmsSendSingleToUserReqDTO reqDTO) {
return null;
}
@Override
public CommonResult<SmsLogRespDTO> sendMpNewsMsg(Long id) {
return null;
}
@Override
public CommonResult<Long> sendMarkdownMsg(SmsSendSingleToUserReqDTO reqDTO) {
return null;
}
@Override
public CommonResult<Long> sendMiniProgramNoticeMsg(SmsSendSingleToUserReqDTO reqDTO) {
return null;
}
@Override
public CommonResult<SmsLogRespDTO> sendInteractiveTaskCardMsg(Long id) {
return null;
}
}

View File

@@ -0,0 +1,18 @@
package com.zt.plat.module.system.controller.admin.sms;
import com.zt.plat.module.system.service.sms.SmsSendService;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "管理后台 - 消息回调")
@RestController
@RequestMapping("/system/sms/callback")
public class MsgCallBackController {
@Resource
private SmsSendService smsSendService;
}

View File

@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import static com.zt.plat.framework.common.pojo.CommonResult.success; import static com.zt.plat.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 短信回调") @Tag(name = "管理后台 - 短信回调")
@@ -73,4 +74,13 @@ public class SmsCallbackController {
return success(true); return success(true);
} }
@PostMapping("/zle")
@PermitAll
@TenantIgnore
@Operation(summary = "中铝e办短信的回调")
public CommonResult<Boolean> receiveZleSmsStatus(@RequestBody String requestBody) throws Throwable {
smsSendService.receiveSmsStatus(SmsChannelEnum.ZLE.getCode(), requestBody);
return success(true);
}
} }

View File

@@ -6,11 +6,10 @@ import com.zt.plat.framework.common.pojo.PageParam;
import com.zt.plat.framework.common.pojo.PageResult; import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.framework.excel.core.util.ExcelUtils; import com.zt.plat.framework.excel.core.util.ExcelUtils;
import com.zt.plat.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; import com.zt.plat.module.system.controller.admin.sms.vo.msg.TextMessage;
import com.zt.plat.module.system.controller.admin.sms.vo.template.SmsTemplateRespVO; import com.zt.plat.module.system.controller.admin.sms.vo.template.*;
import com.zt.plat.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO;
import com.zt.plat.module.system.controller.admin.sms.vo.template.SmsTemplateSendReqVO;
import com.zt.plat.module.system.dal.dataobject.sms.SmsTemplateDO; import com.zt.plat.module.system.dal.dataobject.sms.SmsTemplateDO;
import com.zt.plat.module.system.service.msg.ISendMsgService;
import com.zt.plat.module.system.service.sms.SmsSendService; import com.zt.plat.module.system.service.sms.SmsSendService;
import com.zt.plat.module.system.service.sms.SmsTemplateService; import com.zt.plat.module.system.service.sms.SmsTemplateService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -19,13 +18,13 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import static com.zt.plat.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static com.zt.plat.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
import static com.zt.plat.framework.common.pojo.CommonResult.success; import static com.zt.plat.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 短信模板") @Tag(name = "管理后台 - 短信模板")
@@ -37,6 +36,8 @@ public class SmsTemplateController {
private SmsTemplateService smsTemplateService; private SmsTemplateService smsTemplateService;
@Resource @Resource
private SmsSendService smsSendService; private SmsSendService smsSendService;
@Resource
private ISendMsgService sendMsgService;
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建短信模板") @Operation(summary = "创建短信模板")
@@ -100,4 +101,18 @@ public class SmsTemplateController {
sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams())); sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));
} }
@PostMapping("/send-msg")
@Operation(summary = "发送消息")
@PreAuthorize("@ss.hasPermission('system:sms-template:send-msg')")
public CommonResult<Object> sendMsg(@Valid @RequestBody MsgTemplateSendReqVO sendReqVO,TextMessage textMessage) throws Exception{
String msgtype = textMessage.getMsgtype();
if (StringUtils.isBlank(msgtype)){
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数缺失:%s",msgtype));
}
//发送消息到MQ
CommonResult<Object> objectCommonResult = sendMsgService.sendTextMsg(sendReqVO,textMessage);
return success(objectCommonResult);
}
} }

View File

@@ -0,0 +1,48 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 企业微信消息发送对象父类
*
* @author dongqiang.hao
*/
@Data
@Accessors(chain = true)
public class BaseMessage implements Serializable {
/**
* 指定接收消息的成员成员ID列表多个接收者用|分隔最多支持1000个。特殊情况指定为”@all”则向该企业应用的全部成员发送
*/
private String touser;
/**
* 指定接收消息的部门部门ID列表多个接收者用|分隔最多支持100个。当touser为”@all”时忽略本参数
*/
private String toparty;
/**
* 指定接收消息的标签标签ID列表多个接收者用|分隔最多支持100个。当touser为”@all”时忽略本参数
*/
private String totag;
/**
* 企业应用的id整型。企业内部开发可在应用的设置页面查看第三方服务商可通过接口 获取企业授权信息 获取该参数值
*/
private String agentid;
/**
* 消息类型
*/
private String msgtype;
/**
* 发送消息的自建应用类型
*/
private String appType;
/**
* 表示是否开启重复消息检查0表示否1表示是默认0
*/
private Integer enable_duplicate_check = 0;
/**
* 表示是否重复消息检查的时间间隔默认1800s最大不超过4小时
*/
private Integer duplicate_check_interval;
}

View File

@@ -0,0 +1,34 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import lombok.Data;
import java.io.Serializable;
/**
* 任务卡片消息按钮对象
*
* @author luzemin
*/
@Data
public class Btn implements Serializable {
/**
* 按钮key值用户点击后会产生任务卡片回调事件回调事件会带上该key值只能由数字、字母和“_-@”组成最长支持128字节
*/
private String key;
/**
* 按钮名称最长支持18个字节超过则截断
*/
private String name;
/**
* 按钮字体颜色可选“red”或者“blue”,默认为“blue”
*/
private String color;
/**
* 按钮字体是否加粗默认false
*/
private Boolean is_bold;
}

View File

@@ -0,0 +1,22 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import lombok.Data;
import java.io.Serializable;
/**
* 小程序通知消息内容元素对象
*
* @author luzemin
*/
@Data
public class ContentItem implements Serializable {
/**
* 元素对象键值,长度10个汉字以内
*/
private String key;
/**
* 元素对象值,长度30个汉字以内支持id转译
*/
private String value;
}

View File

@@ -0,0 +1,30 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信文件消息发送对象
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class FileMessage extends BaseMessage {
/**
* 消息类型此时固定为file
*/
private final String msgtype = WxMsgTypeConstant.FILE.getCode();
/**
* 企业微信文件消息体对象
*/
private Media file = new Media();
/**
* 表示是否是保密消息0表示可对外分享1表示不能分享且内容显示水印默认为0
*/
private Integer safe = 0;
}

View File

@@ -0,0 +1,31 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信图片消息发送对象
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class ImageMessage extends BaseMessage {
/**
* 消息类型此时固定为image
*/
private final String msgtype = WxMsgTypeConstant.IMAGE.getCode();
/**
* 企业微信图片消息体对象
*/
private Media image = new Media();
/**
* 表示是否是保密消息0表示可对外分享1表示不能分享且内容显示水印默认为0
*/
private Integer safe = 0;
}

View File

@@ -0,0 +1,40 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 企业微信任务卡片消息体对象
*
* @author luzemin
*/
@Data
public class InteractiveTaskCard implements Serializable {
/**
* 标题不超过128个字节超过会自动截断支持id转译
*/
private String title;
/**
* 描述不超过512个字节超过会自动截断支持id转译
*/
private String description;
/**
* 点击后跳转的链接。最长2048字节请确保包含了协议头(http/https)
*/
private String url;
/**
* 任务id同一个应用发送的任务卡片消息的任务id不能重复只能由数字、字母和“_-@”组成最长支持128字节
*/
private String task_id;
/**
* 按钮列表按钮个数为1~2个。
*/
private List<Btn> btn = new ArrayList<>();
}

View File

@@ -0,0 +1,29 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信任务卡片消息发送对象
* 仅企业微信3.1.6及以上版本支持
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class InteractiveTaskCardMessage extends BaseMessage {
/**
* 消息类型此时固定为interactive_taskcard
*/
private final String msgtype = WxMsgTypeConstant.INTERACTIVE_TASKCARD.getCode();
/**
* 任务卡片消息体对象,
*/
private InteractiveTaskCard interactive_taskcard = new InteractiveTaskCard();
/**
* 表示是否开启id转译0表示否1表示是默认0。仅第三方应用需要用到企业自建应用可以忽略。
*/
private Integer enable_id_trans = 0;
}

View File

@@ -0,0 +1,17 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import lombok.Data;
import java.io.Serializable;
/**
* 企业微信markdown内容消息体对象
*
* @author luzemin
*/
@Data
public class Markdown implements Serializable {
/**
* markdown内容最长不超过2048个字节必须是utf8编码
*/
private String content;
}

View File

@@ -0,0 +1,25 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信markdown消息发送对象
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class MarkdownMessage extends BaseMessage {
/**
* 消息类型此时固定为markdown
*/
private final String msgtype = WxMsgTypeConstant.MARKDOWN.getCode();
/**
* markdown消息内容对象
*/
private Markdown markdown = new Markdown();
}

View File

@@ -0,0 +1,28 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import lombok.Data;
import java.io.Serializable;
/**
* 企业微信媒体消息体对象
*
* @author luzemin
*/
@Data
public class Media implements Serializable {
/**
* 文件上传时企业微信服务器读取文件流的name
*/
public static final String MEDIA_NAME = "media";
/**
* 图片、语音、视频媒体文件id可以调用上传临时素材接口获取
*/
private String media_id;
/**
* 视频消息的标题不超过128个字节超过会自动截断视频消息
*/
private String title;
/**
* 视频消息的描述不超过512个字节超过会自动截断视频消息
*/
private String description;
}

View File

@@ -0,0 +1,44 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 企业微信图文消息体对象
*
* @author luzemin
*/
@Data
public class MiniProgramNotice implements Serializable {
/**
* 小程序appid必须是与当前应用关联的小程序
*/
private String appid;
/**
* 点击消息卡片后的小程序页面,仅限本小程序内的页面。该字段不填则消息点击后不跳转。
*/
private String page;
/**
* 消息标题长度限制4-12个汉字支持id转译
*/
private String title;
/**
* 消息描述长度限制4-12个汉字支持id转译
*/
private String description;
/**
* 是否放大第一个content_item
*/
private String emphasis_first_item;
/**
* 消息内容键值对最多允许10个item
*/
private List<ContentItem> content_item = new ArrayList<>();
}

View File

@@ -0,0 +1,28 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信小程序通知消息发送对象
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class MiniProgramNoticeMessage extends BaseMessage {
/**
* 消息类型此时固定为miniprogram_notice
*/
private final String msgtype = WxMsgTypeConstant.MINIPROGRAM_NOTICE.getCode();
/**
* 小程序通知消息内容对象,
*/
private MiniProgramNotice miniprogram_notice = new MiniProgramNotice();
/**
* 表示是否开启id转译0表示否1表示是默认0。仅第三方应用需要用到企业自建应用可以忽略。
*/
private Integer enable_id_trans = 0;
}

View File

@@ -0,0 +1,36 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.client.dto.msg.MpNews;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信图文消息发送对象(文件存储在企业微信)
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class MpNewsMessage extends BaseMessage {
/**
* 消息类型此时固定为mpnews
*/
private final String msgtype = WxMsgTypeConstant.MPNEWS.getCode();
/**
* 企业微信图文消息体对象
*/
private MpNews mpnews = new MpNews();
/**
* 表示是否是保密消息0表示可对外分享1表示不能分享且内容显示水印默认为0
*/
private Integer safe = 0;
/**
* 表示是否开启id转译0表示否1表示是默认0。仅第三方应用需要用到企业自建应用可以忽略。
*/
private Integer enable_id_trans = 0;
}

View File

@@ -0,0 +1,31 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.client.dto.msg.News;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信图文消息发送对象
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class NewsMessage extends BaseMessage {
/**
* 消息类型此时固定为news
*/
private final String msgtype = WxMsgTypeConstant.NEWS.getCode();
/**
* 企业微信图文消息体对象
*/
private News news = new News();
/**
* 表示是否开启id转译0表示否1表示是默认0。仅第三方应用需要用到企业自建应用可以忽略。
*/
private Integer enable_id_trans = 0;
}

View File

@@ -0,0 +1,43 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.util.StringSolveUtils;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* 企业微信文本消息体对象
*
* @author luzemin
*/
@Data
public class Text implements Serializable {
/**
* 点击事件消息模板
*/
public static final String URL_CONTENT_TEMPLATE = "<a href=\"${url}\">${content}</a>";
/**
* 消息内容最长不超过2048个字节超过将截断支持id转译
*/
private String content;
/**
* 消息点击url地址(自定义属性)
*/
private String url;
/**
* 构建点击事件消息模板
*/
public void buildUrlContent() {
if (StringUtils.isNotBlank(this.getUrl())) {
Map<String, Object> substituteMap = new HashMap<>(2);
substituteMap.put("url", this.getUrl());
substituteMap.put("content", this.getContent());
this.setContent(StringSolveUtils.placeholderReplace(URL_CONTENT_TEMPLATE, substituteMap));
}
}
}

View File

@@ -0,0 +1,29 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.client.dto.msg.TextCard;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信文本卡片消息发送对象
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class TextCardMessage extends BaseMessage {
/**
* 消息类型此时固定为textcard
*/
private final String msgtype = WxMsgTypeConstant.TEXTCARD.getCode();
/**
* 消息内容对象
*/
private TextCard textcard = new TextCard();
/**
* 表示是否开启id转译0表示否1表示是默认0。仅第三方应用需要用到企业自建应用可以忽略。
*/
private Integer enable_id_trans = 0;
}

View File

@@ -0,0 +1,33 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信文本消息发送对象
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class TextMessage extends BaseMessage {
/**
* 消息类型此时固定为text
*/
private final String msgtype = WxMsgTypeConstant.TEXT.getCode();
/**
* 消息内容对象其中text参数的content字段可以支持换行、以及A标签即可打开自定义的网页可参考以上示例代码(注意:换行符请用转义过的\n)
*/
private Text text = new Text();
/**
* 表示是否是保密消息0表示可对外分享1表示不能分享且内容显示水印默认为0
*/
private Integer safe = 0;
/**
* 表示是否开启id转译0表示否1表示是默认0。仅第三方应用需要用到企业自建应用可以忽略。
*/
private Integer enable_id_trans = 0;
}

View File

@@ -0,0 +1,30 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信视频消息发送对象
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class VideoMessage extends BaseMessage {
/**
* 消息类型此时固定为video
*/
private final String msgtype = WxMsgTypeConstant.VIDEO.getCode();
/**
* 企业微信视频消息体对象
*/
private Media video = new Media();
/**
* 表示是否是保密消息0表示可对外分享1表示不能分享且内容显示水印默认为0
*/
private Integer safe = 0;
}

View File

@@ -0,0 +1,25 @@
package com.zt.plat.module.system.controller.admin.sms.vo.msg;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 企业微信语音消息发送对象
*
* @author luzemin
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class VoiceMessage extends BaseMessage {
/**
* 消息类型此时固定为voice
*/
private final String msgtype = WxMsgTypeConstant.VOICE.getCode();
/**
* 企业微信语音消息体对象
*/
private Media voice = new Media();
}

View File

@@ -0,0 +1,31 @@
package com.zt.plat.module.system.controller.admin.sms.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import java.util.Map;
@Schema(description = "管理后台 - 短信模板的发送 Request VO")
@Data
public class MsgTemplateSendReqVO {
/* @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
@NotNull(message = "手机号不能为空")
private String mobile;*/
@Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01")
@NotNull(message = "模板编码不能为空")
private String templateCode;
@Schema(description = "模板参数")
private Map<String, Object> templateParams;
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01")
@NotNull(message = "用户ID不能为空")
private Long userId;
@Schema(description = "用户属性", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01")
@NotNull(message = "用户属性不能为空")
private Integer userType;
}

View File

@@ -74,14 +74,15 @@ public class DeptDO extends TenantBaseDO {
* 枚举 {@link CommonStatusEnum} * 枚举 {@link CommonStatusEnum}
*/ */
private Integer status; private Integer status;
/**
* 是否公司
*/
private Boolean isCompany;
/** /**
* 是否集团 * 是否集团
*/ */
private Boolean isGroup; private Boolean isGroup;
/**
* 是否公司
*/
private Boolean isCompany;
/** /**
* 部门来源类型 * 部门来源类型

View File

@@ -0,0 +1,69 @@
package com.zt.plat.module.system.dal.dataobject.msg;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.common.enums.CommonStatusEnum;
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
import com.zt.plat.module.system.framework.sms.core.enums.SmsChannelEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* 消息渠道 DO
*
* @author zzf
* @since 2021-01-25
*/
@TableName(value = "system_sms_channel", autoResultMap = true)
@KeySequence("system_sms_channel_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@TenantIgnore
public class MsgChannelDO extends BaseDO {
/**
* 渠道编号
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 短信签名
*/
private String signature;
/**
* 企业编号epid
*/
private String epid;
/**
* 渠道编码
* 枚举 {@link SmsChannelEnum}
*/
private String code;
/**
* 启用状态
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 短信 API 的账号
*/
private String apiKey;
/**
* 短信 API 的密钥
*/
private String apiSecret;
/**
* 短信发送回调 URL
*/
private String callbackUrl;
}

View File

@@ -0,0 +1,87 @@
package com.zt.plat.module.system.dal.dataobject.msg;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.zt.plat.framework.common.enums.CommonStatusEnum;
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
import com.zt.plat.module.system.dal.dataobject.sms.SmsChannelDO;
import com.zt.plat.module.system.enums.sms.SmsTemplateTypeEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
/**
* 消息模板 DO
* @author zzf
* @since 2021-01-25
*/
@TableName(value = "system_sms_template", autoResultMap = true)
@KeySequence("system_sms_template_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@TenantIgnore
public class MsgTemplateDO extends BaseDO {
/**
* 自增编号
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
// ========= 模板相关字段 =========
/**
* 短信类型
* 枚举 {@link SmsTemplateTypeEnum}
*/
private Integer type;
/**
* 启用状态
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 模板编码,保证唯一
*/
private String code;
/**
* 模板名称
*/
private String name;
/**
* 模板内容
* 内容的参数,使用 {} 包括,例如说 {name}
*/
private String content;
/**
* 参数数组(自动根据内容生成)
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> params;
/**
* 备注
*/
private String remark;
/**
* 短信 API 的模板编号
*/
private String apiTemplateId;
// ========= 渠道相关字段 =========
/**
* 短信渠道编号
* 关联 {@link SmsChannelDO#getId()}
*/
private Long channelId;
/**
* 短信渠道编码
* 冗余 {@link SmsChannelDO#getCode()}
*/
private String channelCode;
}

View File

@@ -0,0 +1,56 @@
package com.zt.plat.module.system.dal.dataobject.msg;
import com.baomidou.mybatisplus.annotation.*;
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
/**
* Activemq消息队列
*
* @author Dy
* @since 2021-07-22
*/
@TableName(value = "sys_active_mq", autoResultMap = true)
@KeySequence("sys_active_mq_seq")
@Data
@ToString(callSuper = true)
@TenantIgnore
public class SysActiveMq implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "ID", type = IdType.ASSIGN_ID)
private String id;
@TableField("TITLE")
private String title;
@TableField("JMS_TYPE")
private String jmsType;
@TableField("CONTENT_TYPE")
private String contentType;
@TableField("DATETIME")
private String datetime;
@TableField("CONSUME_DATETIME")
private String consumeDatetime;
@TableField("PUBLISHER")
private String publisher;
@TableField("LISTENER_CLASS_NAME")
private String listenerClassName;
@TableField("CONSUME_FLAG")
private String consumeFlag;
@TableField("CONSUME_MESSAGE")
private String consumeMessage;
@TableField("TEXT_CONTENT")
private String textContent;
}

View File

@@ -0,0 +1,56 @@
package com.zt.plat.module.system.dal.dataobject.msg;
import com.baomidou.mybatisplus.annotation.*;
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
/**
* Activemq消息队列
*
* @author Dy
* @since 2021-07-22
*/
@TableName(value = "sys_active_mq_log", autoResultMap = true)
@KeySequence("sys_active_mq_log_seq")
@Data
@ToString(callSuper = true)
@TenantIgnore
public class SysActiveMqLog implements Serializable {
@TableId(value = "ID", type = IdType.ASSIGN_ID)
private String id;
@TableField("TITLE")
private String title;
@TableField("JMS_TYPE")
private String jmsType;
@TableField("CONTENT_TYPE")
private String contentType;
@TableField("DATETIME")
private String datetime;
@TableField("CONSUME_DATETIME")
private String consumeDatetime;
@TableField("PUBLISHER")
private String publisher;
@TableField("LISTENER_CLASS_NAME")
private String listenerClassName;
@TableField("CONSUME_FLAG")
private String consumeFlag;
@TableField("CONSUME_MESSAGE")
private String consumeMessage;
@TableField("TEXT_CONTENT")
private String textContent;
}

View File

@@ -0,0 +1,89 @@
package com.zt.plat.module.system.dal.dataobject.msg;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
/**
* 企业微信审核配置表
* @author Dy
* @since 2021-08-26
*/
@TableName(value = "sys_wx_audit_config", autoResultMap = true)
@KeySequence("sys_wx_audit_config_seq")
@Data
@ToString(callSuper = true)
@TenantIgnore
public class SysWxAuditConfig implements Serializable {
/**
* 状态-生效
*/
public static final String STATE_ACTIVE = "active";
/**
* 状态-未生效
*/
public static final String STATE_INACTIVE = "inactive";
private static final long serialVersionUID = 1L;
@TableField("APP_TYPE")
private String appType;
@TableField("CORP_ID")
private String corpId;
@TableField("CORP_SECRET")
private String corpSecret;
@TableField("AGENT_ID")
private String agentId;
@TableField("STATE")
private String state;
@TableField("SORT_INDEX")
private Integer sortIndex;
@TableField("EXT1")
private String ext1;
@TableField("EXT2")
private String ext2;
@TableField("EXT3")
private String ext3;
@TableField("CREATE_USER")
private String createUser;
@TableField("CREATE_USER_NAME")
private String createUserName;
@TableField("ID")
private Integer id;
@TableField("WX_CONTEXT_PATH")
private String wxContextPath;
@TableField("HTTP_CONTEXT_PATH")
private String httpContextPath;
@TableField("UPDATE_TIME")
private String updateTime;
@TableField("CREATE_TIME")
private String createTime;
@TableField("UPDATE_USER")
private String updateUser;
@TableField("UPDATE_USER_NAME")
private String updateUserName;
}

View File

@@ -0,0 +1,12 @@
package com.zt.plat.module.system.dal.mysql.msg;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.module.system.dal.dataobject.msg.SysActiveMqLog;
import com.zt.plat.module.system.dal.dataobject.sms.SmsLogDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysActiveMqDao extends BaseMapperX<SysActiveMqLog> {
}

View File

@@ -0,0 +1,28 @@
package com.zt.plat.module.system.framework.sms.core.client.dto.msg;
import lombok.Data;
import java.io.Serializable;
/**
* 企业微信图文消息体对象
*
* @author ZT
*/
@Data
public class Article implements Serializable {
/**
* 标题不超过128个字节超过会自动截断支持id转译
*/
private String title;
/**
* 描述不超过512个字节超过会自动截断支持id转译
*/
private String description;
/**
* 图文消息的图片链接支持JPG、PNG格式较好的效果为大图 1068*455小图150*150。
*/
private String url;
}

View File

@@ -0,0 +1,44 @@
package com.zt.plat.module.system.framework.sms.core.client.dto.msg;
import lombok.Data;
import java.io.Serializable;
/**
* 企业微信图文消息体对象(文件存储在企业微信)
*
* @author luzemin
*/
@Data
public class MpArticle implements Serializable {
/**
* 标题不超过128个字节超过会自动截断支持id转译
*/
private String title;
/**
* 图文消息缩略图的media_id, 可以通过素材管理接口获得。此处thumb_media_id即上传接口返回的media_id
*/
private String thumb_media_id;
/**
* 图文消息的作者不超过64个字节
*/
private String author;
/**
* 图文消息点击“阅读原文”之后的页面链接
*/
private String content_source_url;
/**
* 图文消息的内容支持html标签不超过666 K个字节支持id转译
*/
private String content;
/**
* 图文消息的描述不超过512个字节超过会自动截断支持id转译
*/
private String digest;
}

View File

@@ -0,0 +1,19 @@
package com.zt.plat.module.system.framework.sms.core.client.dto.msg;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 企业微信图文消息体对象(文件存储在企业微信)
*
* @author luzemin
*/
@Data
public class MpNews implements Serializable {
/**
* 图文消息一个图文消息支持1到8条图文
*/
private List<MpArticle> articles = new ArrayList<>();
}

View File

@@ -0,0 +1,19 @@
package com.zt.plat.module.system.framework.sms.core.client.dto.msg;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 企业微信图文消息体对象
*
* @author luzemin
*/
@Data
public class News implements Serializable {
/**
* 图文消息一个图文消息支持1到8条图文
*/
private List<Article> articles = new ArrayList<>();
}

View File

@@ -0,0 +1,37 @@
package com.zt.plat.module.system.framework.sms.core.client.dto.msg;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 企业微信文本卡片消息体对象
*
* @author luzemin
*/
@Data
@Accessors(chain = true)
public class TextCard implements Serializable {
/**
* 标题不超过128个字节超过会自动截断支持id转译
*/
private String title;
/**
* 描述不超过512个字节超过会自动截断支持id转译
*/
private String description;
/**
* 消息点击点击后跳转的链接。最长2048字节请确保包含了协议头(http/https)
*/
private String url;
/**
* 按钮文字。 默认为“详情”, 不超过4个文字超过自动截断。
*/
private String btntxt;
}

View File

@@ -7,7 +7,6 @@ import com.zt.plat.module.system.framework.sms.core.property.SmsChannelPropertie
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@@ -83,6 +82,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
case QINIU: return new QiniuSmsClient(properties); case QINIU: return new QiniuSmsClient(properties);
// case CMCC_MAS: return new CmccMasSmsClient(properties); // case CMCC_MAS: return new CmccMasSmsClient(properties);
case HL95: return new Hl95SmsClient(properties); case HL95: return new Hl95SmsClient(properties);
case ZLE: return new ZleSmsClient(properties);
} }
// 创建失败,错误日志 + 抛出异常 // 创建失败,错误日志 + 抛出异常
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties); log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);

View File

@@ -0,0 +1,171 @@
package com.zt.plat.module.system.framework.sms.core.client.impl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zt.plat.framework.common.core.KeyValue;
import com.zt.plat.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
import com.zt.plat.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
import com.zt.plat.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
import com.zt.plat.module.system.framework.sms.core.client.impl.extra.SmsBalanceClient;
import com.zt.plat.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import com.zt.plat.module.system.framework.sms.core.property.SmsChannelProperties;
import lombok.extern.slf4j.Slf4j;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class ZleSmsClient extends AbstractSmsClient implements SmsBalanceClient {
private static final String SEND_URL = "https://api.sms.95ytx.com:9091/mxt/send";
private static final String BALANCE_URL = "https://api.sms.95ytx.com:9091/mxt/getfee";
private static final DateTimeFormatter STATUS_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
public ZleSmsClient(SmsChannelProperties properties) {
super(properties);
Assert.notEmpty(properties.getApiKey(), "用户名(apiKey) 不能为空");
Assert.notEmpty(properties.getApiSecret(), "密码(apiSecret) 不能为空");
}
@Override
public SmsSendRespDTO sendSms(Long logId, String mobile, String content, String apiTemplateId,
List<KeyValue<String, Object>> templateParams) {
Assert.notEmpty(properties.getEpid(), "中铝e办需要配置 epid");
Assert.notEmpty(properties.getSignature(), "短信签名不能为空");
String finalContent = appendSignatureIfMissing(content, properties.getSignature());
String linkId = buildLinkId(logId);
Map<String, Object> form = new HashMap<>();
form.put("username", properties.getApiKey());
form.put("password", properties.getApiSecret());
form.put("epid", properties.getEpid());
form.put("phone", mobile);
form.put("message", finalContent);
form.put("linkid", linkId);
// subcode 可为空
String resp;
try (HttpResponse response = HttpRequest.post(SEND_URL)
.form(form)
.charset(StandardCharsets.UTF_8)
.execute()) {
resp = StrUtil.trim(response.body());
}
boolean success = StrUtil.equals(resp, "00");
return new SmsSendRespDTO()
.setSuccess(success)
.setApiCode(resp)
.setApiMsg(resp)
.setApiRequestId(linkId)
.setSerialNo(linkId);
}
/**
* 解析短信状态
* @param text 结果
* @return List<SmsReceiveRespDTO>
*/
@Override
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
JSONObject obj = JSONUtil.parseObj(text, false);
String reportCode = obj.getStr("FReportCode");
String linkId = obj.getStr("FLinkID");
LocalDateTime deliverTime = parseDeliverTime(obj.getStr("FDeliverTime"));
String mobile = obj.getStr("FDestAddr");
boolean success = StrUtil.equalsIgnoreCase(reportCode, "DELIVRD") || StrUtil.equals(reportCode, "0");
Long logId = parseLongSafely(linkId);
SmsReceiveRespDTO dto = new SmsReceiveRespDTO()
.setSuccess(success)
.setErrorCode(reportCode)
.setErrorMsg(reportCode)
.setMobile(mobile)
.setReceiveTime(deliverTime)
.setSerialNo(linkId)
.setLogId(logId);
return Collections.singletonList(dto);
}
@Override
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) {
// 中铝e办无模板审核接口直接返回可用
return new SmsTemplateRespDTO()
.setId(apiTemplateId)
.setContent(apiTemplateId)
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus());
}
/**
* 查询余额
* @return Integer
*/
@Override
public Integer queryBalance() {
Assert.notEmpty(properties.getEpid(), "中铝e办需要配置 epid");
Map<String, Object> form = MapUtil.<String, Object>builder()
.put("username", properties.getApiKey())
.put("password", properties.getApiSecret())
.put("epid", properties.getEpid())
.build();
String resp;
try (HttpResponse response = HttpRequest.get(BALANCE_URL)
.form(form)
.charset(StandardCharsets.UTF_8)
.execute()) {
if (response.getStatus() != HttpURLConnection.HTTP_OK) {
throw new IllegalStateException("余额查询失败HTTP 状态码:" + response.getStatus());
}
resp = StrUtil.trim(response.body());
}
if (!StrUtil.isNumeric(resp)) {
throw new IllegalStateException("余额查询失败,返回值:" + resp);
}
return Integer.valueOf(resp);
}
private static String appendSignatureIfMissing(String content, String signature) {
if (StrUtil.isBlank(signature)) {
return content;
}
String wrapped = StrUtil.startWithAny(signature, "", "[") ? signature : "" + signature + "";
return StrUtil.startWith(content, wrapped) ? content : wrapped + content;
}
private static String buildLinkId(Long logId) {
String raw = String.valueOf(logId);
return raw.length() > 20 ? raw.substring(raw.length() - 20) : raw;
}
private static LocalDateTime parseDeliverTime(String timeText) {
if (StrUtil.isBlank(timeText)) {
return null;
}
try {
return LocalDateTime.parse(timeText, STATUS_TIME_FORMATTER);
} catch (Exception ex) {
log.warn("[parseDeliverTime][无法解析时间:{}]", timeText, ex);
return null;
}
}
private static Long parseLongSafely(String text) {
try {
return Long.parseLong(text);
} catch (Exception ignore) {
return null;
}
}
}

View File

@@ -0,0 +1,155 @@
package com.zt.plat.module.system.framework.sms.core.enums;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 审核类型静态枚举类
*
* @author luzemin
*/
@Component
public class AuditConstants {
/**
* 中铝E办驰宏数字化应用企业微信
*/
public static final String CHINALCO_ZLEB_CHSZH = "chinalco_zleb_chszh";
/**
* 中铜阳光采购应用(企业微信)
*/
public static final String CHINALCO_SUNEPS = "chinalco_suneps";
/**
* token身份认证令牌
*/
public static final String TOKEN = "0123456789abcdefghijklmnopqrstuvwxyz";
/**
* 企业微信审核页面加载配置代码集ID
*/
public static final String AUDIT_CODE_SET = "qywx.audit_dispatcher";
/**
* 阳光采购企业微信审核回调地址集合映射
*/
public static final Map<String, String> REDIRECT_URL_MAPPING = new HashMap<>(1);
/**
* 企业微信审核回调地址-需要微信认证,使用时需要在前面添加微信请求uri
*/
public static final String REDIRECT_URL_AUTH = "/qywx/${appType}/index?appType=${appType}&approveType=${approveType}&processInstanceId=${processInstanceId}&taskId=${taskId}&companyCode=${companyCode}&businessId=${businessId}";
/**
* 企业微信审核回调地址-不需要微信认证,使用时需要在前面添加微信请求uri
*/
public static final String REDIRECT_URL = "/${appType}/index?appType=${appType}&approveType=${approveType}&processInstanceId=${processInstanceId}&taskId=${taskId}&companyCode=${companyCode}&businessId=${businessId}";
static {
REDIRECT_URL_MAPPING.put("noAuth", REDIRECT_URL);
REDIRECT_URL_MAPPING.put("auth", REDIRECT_URL_AUTH);
}
/**
* 审批类型
*/
public enum SunepsAuditType {
/**
* 询价单审批
*/
REQUEST_AUDIT("requestAudit", "询价单审批"),
/**
* 会审会签审批
*/
RESULT_AUDIT("resultAudit", "会审会签审批"),
/**
* 流标审批
*/
FLOW_AUDIT("flowAudit", "流标审批"),
/**
* 谈判方案审批
*/
NEGO_PLAN("negoPlan", "谈判方案审批"),
/**
* 终止审批
*/
TERM_AUDIT("termAudit", "终止审批"),
/**
* 评标报告审批
*/
REQUEST_EVA_REPORT("requestEvaReport", "评标报告审批"),
/**
* 供应商注册
*/
SUPPLIER_REGISTER("supplierRegister", "供应商注册"),
/**
* 供应商自荐
*/
SUPPLIER_SELF("supplierSelf", "供应商自荐"),
/**
* 供应商基本信息变更
*/
SUPPLIER_BASE_INFO("supplierBaseInfo", "供应商基本信息变更"),
/**
* 供应商启用
*/
SUPPLIER_ENABLE("supplierEnable", "供应商启用"),
/**
* 供应商可供大类变更
*/
SUPPLIER_MATE_TYPE("supplierMateType", "供应商可供大类变更"),
/**
* 合格供应商变更
*/
SUPPLIER_CHANGE("supplierChange", "合格供应商变更"),
/**
* 特种供应商认证审批
*/
T_SUPP_AUDIT("TSuppAudit", "特种供应商认证审批"),
/**
* 审核消息卡片描述模型
*/
AUDIT_DESCRIPTION_FORMAT("<div class=\"gray\">${subTitle}</div> <div class=\"normal\">${description}</div> <div class=\"normal\">当前节点:${taskName}</div> <div class=\"highlight\">点击查看详细情况</div>", "审核消息卡片描述模型"),
/**
* 审核回调url,使用时需要在前面添加微信请求uri
*/
REDIRECT_URL("/qywx/${appType}/index?appType=${appType}&approveType=${approveType}&processInstanceId=${processInstanceId}&taskId=${taskId}&companyCode=${companyCode}&businessId=${businessId}", "审核回调url");
/**
* 消息全局键key-key
*/
public static final String CODE_KEY = "code";
/**
* 消息全局值value-key
*/
public static final String MSG_KEY = "msg";
/**
* 审核类型代码
*/
private final String code;
/**
* 审核类型说明
*/
private final String msg;
SunepsAuditType(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
}

View File

@@ -0,0 +1,55 @@
package com.zt.plat.module.system.framework.sms.core.enums;
/**
* 消息ActiveMQ静态变量枚举
*
* @author luzemin
*/
public enum JmsConstant {
/**
* 测试点对点消息队列,使用时需要添加环境前缀JmsProperties jmsProperties===>jmsProperties.getQueuePrefix()
*/
QUEUE_TEST("queue.test", "测试点对点消息队列"),
/**
* 测试主题消息队列,使用时需要添加环境前缀JmsProperties jmsProperties===>jmsProperties.getTopicPrefix()
*/
TOPIC_TEST("topic.test", "测试主题消息队列"),
/**
* 消息类型-队列
*/
JMS_TYPE_QUEUE("queue", "消息类型-队列"),
/**
* 消息类型-主题
*/
JMS_TYPE_TOPIC("topic", "消息类型-主题"),
/**
* 消息消费结果-成功
*/
CONSUME_SUCCESS("success", "消息消费结果-成功"),
/**
* 消息消费结果-失败
*/
CONSUME_FAILURE("failure", "消息消费结果-失败");
/**
* 值
*/
private final String code;
/**
* 说明
*/
private final String msg;
JmsConstant(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
}

View File

@@ -21,6 +21,7 @@ public enum SmsChannelEnum {
QINIU("QINIU", "七牛云"), QINIU("QINIU", "七牛云"),
HL95("HL95", "鸿联九五"), HL95("HL95", "鸿联九五"),
// CMCC_MAS("CMCC_MAS", "中国移动云MAS"), // CMCC_MAS("CMCC_MAS", "中国移动云MAS"),
ZLE("ZLE", "中铝e办"),
; ;
/** /**

View File

@@ -0,0 +1,94 @@
package com.zt.plat.module.system.framework.sms.core.enums;
/**
* 企业微信消息类型枚举类
*
* @author luzemin
*/
public enum WxMsgTypeConstant {
/**
* 文本消息:其中text参数的content字段可以支持换行、以及A标签即可打开自定义的网页可参考以上示例代码(注意:换行符请用转义过的\n)
*/
TEXT("text", "文本消息"),
/**
* 图片消息
*/
IMAGE("image", "图片消息"),
/**
* 语音消息
*/
VOICE("voice", "语音消息"),
/**
* 视频消息
*/
VIDEO("video", "视频消息"),
/**
* 文件消息
*/
FILE("file", "文件消息"),
/**
* 卡片消息的展现形式非常灵活支持使用br标签或者空格来进行换行处理也支持使用div标签来使用不同的字体颜色目前内置了3种文字颜色灰色(gray)、高亮(highlight)、默认黑色(normal)将其作为div标签的class属性即可具体用法请参考上面的示例。
*/
TEXTCARD("textcard", "文本卡片消息"),
/**
* 图文消息
*/
NEWS("news", "图文消息"),
/**
* mpnews类型的图文消息跟普通的图文消息一致唯一的差异是图文内容存储在企业微信。多次发送mpnews会被认为是不同的图文阅读、点赞的统计会被分开计算。
*/
MPNEWS("mpnews", "图文消息mpnews"),
/**
* 目前仅支持markdown语法的子集,微工作台原企业号不支持展示markdown消息
*/
MARKDOWN("markdown", "markdown消息"),
/**
* 小程序通知消息
* 小程序通知消息只允许绑定了小程序的应用发送,之前,消息会通过统一的会话【小程序通知】发送给用户。
* 从2019年6月28日起用户收到的小程序通知会出现在各个独立的应用中。
* 不支持@all全员发送
*/
MINIPROGRAM_NOTICE("miniprogram_notice", "小程序通知消息"),
/**
* 任务卡片消息
* 仅企业微信3.1.6及以上版本支持
* 任务卡片消息的展现支持简单的markdown语法详情请见附录支持的markdown语法 。
* 要发送该类型的消息应用必须配置好回调URL详见配置应用回调用户点击任务卡片的按钮后企业微信会回调任务卡片事件到该URL配置的URL按任务卡片更新消息协议返回数据即可。
* 开发者可以通过更新任务卡片消息状态接口更新卡片状态。
*/
INTERACTIVE_TASKCARD("interactive_taskcard", "任务卡片消息");
/**
* 类型key
*/
private final String code;
/**
* 类型说明
*/
private final String msg;
/**
* 构造器
*
* @param code 类型
* @param msg 说明
*/
WxMsgTypeConstant(String code, String msg) {
this.code = code;
this.msg = msg;
}
/**
* 获取类型
*/
public String getCode() {
return code;
}
/**
* 获取说明
*/
public String getMsg() {
return msg;
}
}

View File

@@ -0,0 +1,176 @@
package com.zt.plat.module.system.mq.consumer.msg;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zt.plat.module.system.controller.admin.sms.vo.msg.TextCardMessage;
import com.zt.plat.module.system.framework.sms.core.client.dto.msg.TextCard;
import com.zt.plat.module.system.framework.sms.core.enums.AuditConstants;
import com.zt.plat.module.system.framework.sms.core.enums.JmsConstant;
import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant;
import com.zt.plat.module.system.mq.message.sms.SmsSendMessage;
import com.zt.plat.module.system.service.msg.ISendWxMsgService;
import com.zt.plat.module.system.service.msg.ISysRocketMqService;
import com.zt.plat.module.system.util.StringSolveUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
/**
* 中铝 e 办的消费者
*
* @author zzf
*/
@Component
@RocketMQMessageListener(
topic = SmsSendMessage.TOPIC,
consumerGroup = SmsSendMessage.TOPIC + "_CONSUMER"
)
@Slf4j
public class MsgSendConsumer implements RocketMQListener<MessageExt> {
@Resource
private ISysRocketMqService sysActiveMqService;
@Resource
private ISendWxMsgService sendWxMsgService;
@Override
public void onMessage(MessageExt msg) {
log.info("中铝e办[onMessage][消息内容({})]", msg.toString());
String msgId = msg.getMsgId();
int reconsumeTimes = msg.getReconsumeTimes();
String consumeMsg;
try {
if (msg.getBody() == null || msg.getBody().length == 0){
log.error("[onMessage][消息体为空 msgId={}]", msgId);
return;
}
String json = new String(msg.getBody(), StandardCharsets.UTF_8);
JSONObject jsonObject = JSON.parseObject(json);
String toUser = jsonObject.getString("touser"); // 企业微信接收者
String userName = jsonObject.getString("userName"); // 用户名
String userEname = jsonObject.getString("userEname"); // 英文名
String userCname = jsonObject.getString("userCname"); // 中文名
String desc = jsonObject.getString("description");
// 校验接受用户
if (StringUtils.isBlank(toUser)) {
consumeMsg = "企业微信消息发送失败,用户【" + userEname + "-" + userCname + "】未绑定企业微信账号!";
//TODO 添加文本消息
sysActiveMqService.saveByTextMessage(this.getClass().getName(), msg, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_FAILURE.getCode(), consumeMsg);
} else {
String msgtype = jsonObject.getString("msgtype");
// 文本卡片消息
if (Objects.equals(msgtype, WxMsgTypeConstant.TEXTCARD.getCode())) {
/* 构建审批回调url */
String appType = jsonObject.getString("appType");
String approveType = jsonObject.getString("approveType");
String companyCode = jsonObject.getString("userCompanyCode");
String taskId = jsonObject.getString("taskId");
String processInstanceId = jsonObject.getString("processInstanceId");
String businessId = jsonObject.getString("businessId");
Map<String, Object> substituteMap = new HashMap<>(5);
substituteMap.put("appType", appType.toLowerCase(Locale.ROOT));
substituteMap.put("approveType", approveType);
substituteMap.put("companyCode", companyCode);
substituteMap.put("taskId", taskId);
substituteMap.put("processInstanceId", processInstanceId);
substituteMap.put("businessId", businessId);
String redirectUrl = StringSolveUtils.placeholderReplace(AuditConstants.SunepsAuditType.REDIRECT_URL.getCode(), substituteMap);
/* 构建文本卡片消息 */
String title = jsonObject.getString("title");
String taskName = jsonObject.getString("taskName");
String datetime = jsonObject.getString("datetime");
String btnTxt = jsonObject.getString("btntxt");
substituteMap.put("subTitle", datetime + "——" + userName);
substituteMap.put("description", desc);
substituteMap.put("taskName", taskName);
String description = StringSolveUtils.placeholderReplace(AuditConstants.SunepsAuditType.AUDIT_DESCRIPTION_FORMAT.getCode(), substituteMap);
TextCardMessage textCardMessage = new TextCardMessage();
TextCard textCard = new TextCard();
textCard.setTitle(title)
.setDescription(description)
.setUrl(redirectUrl);
if (StringUtils.isNotBlank(btnTxt)) {
textCard.setBtntxt(btnTxt);
}
textCardMessage.setTextcard(textCard).setAppType(appType).setTouser(toUser);
//TODO 发送企业微信文本卡片消息
sendWxMsgService.sendTextCardMsg(textCardMessage);
consumeMsg = "企业微信消息发送成功,接收用户【" + userEname + "-" + msgtype + "】!";
//TODO 保存文本卡片消息队列消费的消息信息
sysActiveMqService.saveByTextMessage(this.getClass().getName(), msg, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_SUCCESS.getCode(), consumeMsg);
}
// 文本消息
if (Objects.equals(msgtype, WxMsgTypeConstant.FILE.getCode())) {
/* 构建审批回调url */
String appType = jsonObject.getString("appType");
String approveType = jsonObject.getString("approveType");
String companyCode = jsonObject.getString("userCompanyCode");
String taskId = jsonObject.getString("taskId");
String processInstanceId = jsonObject.getString("processInstanceId");
String businessId = jsonObject.getString("businessId");
Map<String, Object> substituteMap = new HashMap<>(5);
substituteMap.put("appType", appType.toLowerCase(Locale.ROOT));
substituteMap.put("approveType", approveType);
substituteMap.put("companyCode", companyCode);
substituteMap.put("taskId", taskId);
substituteMap.put("processInstanceId", processInstanceId);
substituteMap.put("businessId", businessId);
String redirectUrl = StringSolveUtils.placeholderReplace(AuditConstants.SunepsAuditType.REDIRECT_URL.getCode(), substituteMap);
/* 构建文本消息 */
String title = jsonObject.getString("title");
String taskName = jsonObject.getString("taskName");
String datetime = jsonObject.getString("datetime");
String btnTxt = jsonObject.getString("btntxt");
substituteMap.put("subTitle", datetime + "——" + userName);
substituteMap.put("description", desc);
substituteMap.put("taskName", taskName);
String description = StringSolveUtils.placeholderReplace(AuditConstants.SunepsAuditType.AUDIT_DESCRIPTION_FORMAT.getCode(), substituteMap);
TextCardMessage textCardMessage = new TextCardMessage();
TextCard textCard = new TextCard();
textCard.setTitle(title)
.setDescription(description)
.setUrl(redirectUrl);
if (StringUtils.isNotBlank(btnTxt)) {
textCard.setBtntxt(btnTxt);
}
textCardMessage.setTextcard(textCard).setAppType(appType).setTouser(toUser);
//TODO 送企业微信文本卡片消息
sendWxMsgService.sendTextCardMsg(textCardMessage);
consumeMsg = "企业微信消息发送成功,接收用户【" + userEname + "-" + msgtype + "】!";
//TODO 存文本卡片消息队列消费的消息信息
sysActiveMqService.saveByTextMessage(this.getClass().getName(), msg, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_SUCCESS.getCode(), consumeMsg);
}
}
} catch (Exception e) {
log.error("消息发送失败!{}", e.getMessage(), e);
try {
// 重试时不重复保存队列消息
if (reconsumeTimes>0) {
sysActiveMqService.saveByTextMessage(this.getClass().getName(), msg, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_FAILURE.getCode(), e.getMessage());
}
} catch (Exception jmsException) {
log.error("消息保存失败!{}", jmsException.getMessage(), jmsException);
}
}
}
}

View File

@@ -0,0 +1,43 @@
package com.zt.plat.module.system.mq.message.msg;
import com.zt.plat.framework.common.core.KeyValue;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 消息发送消息
*
* @author ZT
*/
@Data
public class MsgSendMessage {
public static final String TOPIC = "SMS_SEND_TOPIC";
/**
* 消息日志编号
*/
@NotNull(message = "短信日志编号不能为空")
private Long logId;
/**
* 消息内容(已按模板格式化后的文本)
*/
private String content;
/**
* 消息渠道编号
*/
@NotNull(message = "短信渠道编号不能为空")
private Long channelId;
/**
* 消息 API 的模板编号
*/
@NotNull(message = "消息 API 的模板编号不能为空")
private String apiTemplateId;
/**
* 短信模板参数
*/
private List<KeyValue<String, Object>> templateParams;
}

Some files were not shown because too many files have changed in this diff Show More