Compare commits

...

10 Commits

Author SHA1 Message Date
0e366da5e8 sms 2025-07-14 13:09:57 +08:00
33f441cfa5 邮件 2025-06-27 10:04:09 +08:00
a21c343c5c Merge remote-tracking branch 'refs/remotes/origin/master' into dev
# Conflicts:
#	src/main/resources/application-dev.yml
2025-06-25 16:36:23 +08:00
176dbc5ffb 配置文件 2025-06-25 16:29:09 +08:00
7c00fdf7a4 合规风险 2025-06-25 13:28:21 +08:00
77812cedca 短信 2025-06-25 11:19:42 +08:00
2cc6f87a43 短信 2025-06-25 10:59:18 +08:00
983c4886ad 短信 2025-06-25 10:15:43 +08:00
d4a0d179ea 合规风险 2025-06-25 10:15:19 +08:00
cfea56bfa1 合并分支,修改内容 2025-06-24 16:07:21 +08:00
22 changed files with 966 additions and 46 deletions

View File

@ -21,6 +21,12 @@
<groupId>com.chinaunicom.mall.ebtp</groupId>
<artifactId>uboot-core</artifactId>
<version>2.4.1-zyhy-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<groupId>org.apache.shardingsphere</groupId>
</exclusion>
</exclusions>
</dependency>

View File

@ -16,7 +16,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
@SpringBootApplication
@EnableFeignClients
@MapperScan({"com.chinaunicom.mall.ebtp.extend.**.dao"})
@ComponentScan("com.chinaunicom.mall.ebtp.*")

View File

@ -1,7 +1,7 @@
package com.chinaunicom.mall.ebtp.extend.mail.controller;
import com.chinaunicom.mall.ebtp.common.base.entity.BaseResponse;
import com.chinaunicom.mall.ebtp.extend.mail.entity.MailRequest;
import com.chinaunicom.mall.ebtp.extend.mail.entity.MailRequestDTO;
import com.chinaunicom.mall.ebtp.extend.mail.service.IMailService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -22,7 +22,7 @@ public class MailController {
@PostMapping("/simple")
@ApiOperation(value = "发送简单文本邮件", notes = "发送纯文本格式的邮件")
public BaseResponse<String> sendSimpleMail(@RequestBody MailRequest request) {
public BaseResponse<String> sendSimpleMail(@RequestBody MailRequestDTO request) {
try {
mailService.sendSimpleMail(request.getTo(), request.getSubject(), request.getContent());
return BaseResponse.success("邮件发送成功");
@ -33,7 +33,7 @@ public class MailController {
@PostMapping("/html")
@ApiOperation(value = "发送HTML格式邮件", notes = "发送HTML格式的邮件支持富文本内容")
public BaseResponse<String> sendHtmlMail(@RequestBody MailRequest request) {
public BaseResponse<String> sendHtmlMail(@RequestBody MailRequestDTO request) {
try {
mailService.sendHtmlMail(request.getTo(), request.getSubject(), request.getContent());
return BaseResponse.success("HTML邮件发送成功");

View File

@ -6,7 +6,7 @@ import lombok.Data;
@Data
@ApiModel("邮件请求参数")
public class MailRequest {
public class MailRequestDTO {
@ApiModelProperty(value = "收件人邮箱", required = true, example = "recipient@example.com")
private String to;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,111 @@
package com.chinaunicom.mall.ebtp.extend.rm.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chinaunicom.mall.ebtp.common.base.entity.BaseResponse;
import com.chinaunicom.mall.ebtp.extend.rm.client.RmClient;
import com.chinaunicom.mall.ebtp.extend.rm.entity.RmBaseResponseDTO;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RestController
@Api(tags = "合规风险系统接口")
@RequestMapping("/rm/api")
@Slf4j
public class RmController {
@Value("${spring.rm.username}")
private String username;
@Value("${spring.rm.password}")
private String password;
@Resource
private RmClient rmClient;
/**
* 合规风险系统精确查询
* @param search 查询条件
* @return 合规风险系统响应数据
*/
@GetMapping("/queryRiskInfo")
public BaseResponse<List<RmBaseResponseDTO.DataItem>> queryRiskInfo(@RequestParam("search") String search){
// public BaseResponse<IPage<RmBaseResponseDTO.DataItem>> queryRiskInfo(@RequestParam("search") String search){
RmBaseResponseDTO rmBaseResponseDTO = rmClient.queryRiskInfo(search, username, password);
// IPage<RmBaseResponseDTO.DataItem> page = new Page<>(1, 10);
// page.setRecords(rmBaseResponseDTO.getData());
// page.setTotal(rmBaseResponseDTO.getDatasize());
return BaseResponse.success(rmBaseResponseDTO.getData());
}
/**
* 合规风险系统精确查询详细
* @param search 查询条件
* @return 合规风险系统响应数据
*/
@GetMapping("/queryRiskInfoDetails")
public BaseResponse<List<RmBaseResponseDTO.DataItem>> queryRiskInfoDetails(@RequestParam("search") String search){
// public BaseResponse<IPage<RmBaseResponseDTO.DataItem>> queryRiskInfoDetails(@RequestParam("search") String search){
RmBaseResponseDTO rmBaseResponseDTO = rmClient.queryAllRiskInfo(search, username, password);
// IPage<RmBaseResponseDTO.DataItem> page = new Page<>(1, 10);
// page.setRecords(rmBaseResponseDTO.getData());
// page.setTotal(rmBaseResponseDTO.getDatasize());
return BaseResponse.success(rmBaseResponseDTO.getData());
}
/**
* 风险系统判断逻辑
*/
@GetMapping("/riskLevel")
public String riskLevel(RmBaseResponseDTO dto) {
if (dto == null || dto.getDatasize() == 0 || dto.getData() == null || dto.getData().isEmpty()) {
return "无风险";
}
for (RmBaseResponseDTO.DataItem item : dto.getData()) {
String activestatus = item.getActivestatus();
if (activestatus == null) {
continue;
}
if ("Inactive".equalsIgnoreCase(activestatus)) {
return "低风险";
}
if ("Active".equalsIgnoreCase(activestatus)) {
String d1 = item.getD1();
List<String> d2 = item.getD2();
List<String> d3 = item.getD3();
// d1存在才继续判断
if (d1 != null && !d1.isEmpty()) {
if (d2 != null && !d2.isEmpty()) {
if (d2.contains("Other Official Lists")) {
return "中风险";
}
if (d2.contains("Sanctions Lists") || d2.contains("Sanctions Control and Ownership")) {
return "高风险";
}
}
if (d3 != null && !d3.isEmpty()) {
if (d3.contains("Attention")) {
return "中风险";
}
if (d3.contains("Prohibited transactions and interactions")
|| d3.contains("Prohibition")
|| d3.contains("Prohibited transactions and cooperation")) {
return "高风险";
}
}
}
}
}
return "无风险";
}
}

View File

@ -0,0 +1,166 @@
package com.chinaunicom.mall.ebtp.extend.rm.entity;
import com.chinaunicom.mall.ebtp.common.rm.entity.RmBaseResponse;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
*{"code":200,"message":"成功","datasize":3,"data":[{"hit":"11111","hitlight":"11111","similar":100,"hittype":"OFAC Unique ID","activestatus":"Inactive","d1":"Special Interest Entity (SIE)","d2":["Sanctions Lists"],"d3":["Ship"],"countries":["Iran"],"docid":"1016852","systemdate":"2025-02-26 14:31:24.201","nameDetails":{"name":[{"nameType":"Primary Name","nameValue":[{"entityName":["Iran Mahallati"],"firstName":[],"maidenName":[],"middleName":[],"originalScriptName":[],"singleStringName":[],"suffix":[],"surname":[],"titleHonorific":[]}]}]},"idNumberTypes":{"ID":[{"IDType":"OFAC Program ID","IDValue":[{"value":"NPWMD"}]},{"IDType":"OFAC Unique ID","IDValue":[{"value":"11111"}]},{"IDType":"International Maritime Organization (IMO) Ship No.","IDValue":[{"IDnotes":"Vessel Registration Identification","value":"7428823"}]}]},"sanctionsReferences":{"reference":[{"sinceDay":"10","sinceMonth":"Sep","sinceYear":"2008","toDay":"31","toMonth":"Mar","toYear":"2011","value":14}]}},{"hit":"11111","hitlight":"11111","similar":100,"hittype":"HM Treasury Group ID","activestatus":"Inactive","d1":"Politically Exposed Person (PEP)","d2":["Sanctions Lists"],"d3":[],"countries":["Myanmar"],"docid":"1356752","systemdate":"2025-02-26 14:31:24.201","nameDetails":{"name":[{"nameType":"Primary Name","nameValue":[{"entityName":[],"firstName":["Soe"],"maidenName":[],"middleName":[],"originalScriptName":["စိုးမင်း"],"singleStringName":["Soe Min"],"suffix":[],"surname":["Min"],"titleHonorific":[]}]}]},"idNumberTypes":{"ID":[{"IDType":"National ID","IDValue":[{"value":"12/BaTaHta (N) 021423"}]},{"IDType":"EU Sanctions Programme Indicator","IDValue":[{"value":"MMR"}]},{"IDType":"EU Consolidated Electronic List ID","IDValue":[{"value":"5633"}]},{"IDType":"HM Treasury Group ID","IDValue":[{"value":"11111"}]}]},"sanctionsReferences":{"reference":[{"sinceDay":"14","sinceMonth":"May","sinceYear":"2010","toDay":"16","toMonth":"May","toYear":"2012","value":275},{"sinceDay":"03","sinceMonth":"Dec","sinceYear":"2010","toDay":"10","toMonth":"May","toYear":"2012","value":299},{"sinceDay":"18","sinceMonth":"May","sinceYear":"2010","toDay":"14","toMonth":"May","toYear":"2012","value":507},{"sinceDay":"17","sinceMonth":"Jun","sinceYear":"2010","toDay":"08","toMonth":"Jun","toYear":"2012","value":1326},{"sinceDay":"26","sinceMonth":"Apr","sinceYear":"2010","toDay":"12","toMonth":"Apr","toYear":"2011","value":1440},{"sinceDay":"10","sinceMonth":"May","sinceYear":"2010","toDay":"18","toMonth":"Apr","toYear":"2011","value":1446},{"sinceDay":"12","sinceMonth":"Apr","sinceYear":"2011","toDay":"16","toMonth":"Aug","toYear":"2011","value":1694},{"sinceDay":"18","sinceMonth":"Apr","sinceYear":"2011","toDay":"01","toMonth":"Sep","toYear":"2011","value":1696},{"sinceDay":"16","sinceMonth":"Aug","sinceYear":"2011","toDay":"26","toMonth":"Apr","toYear":"2012","value":1802},{"sinceDay":"01","sinceMonth":"Sep","sinceYear":"2011","toDay":"14","toMonth":"May","toYear":"2012","value":1816}]}},{"hit":"11111","hitlight":"11111","similar":100,"hittype":"NSDC Unique ID","activestatus":"Inactive","d1":"Politically Exposed Person (PEP)","d2":["Sanctions Lists"],"d3":[],"countries":["Russia"],"docid":"1659749","systemdate":"2025-04-04 12:10:11.458","nameDetails":{"name":[{"nameType":"Primary Name","nameValue":[{"entityName":[],"firstName":["Vladimir"],"maidenName":[],"middleName":["Leonidovich"],"originalScriptName":["Владимир Леонидович Шемякин"],"singleStringName":[],"suffix":[],"surname":["Shemyakin"],"titleHonorific":[]}]},{"nameType":"Also Known As","nameValue":[{"entityName":[],"firstName":["Volodymyr"],"maidenName":[],"middleName":["Leonidovych"],"originalScriptName":["Володимир Леонідович Шемякін"],"singleStringName":[],"suffix":[],"surname":["Shemyakin"],"titleHonorific":[]},{"entityName":[],"firstName":["Volodymyr"],"maidenName":[],"middleName":[],"originalScriptName":[],"singleStringName":[],"suffix":[],"surname":["Shemiakin"],"titleHonorific":[]}]},{"nameType":"Spelling Variation","nameValue":[{"entityName":[],"firstName":["Vladimir"],"maidenName":[],"middleName":["Leonidovich"],"originalScriptName":[],"singleStringName":[],"suffix":[],"surname":["Shemiakin"],"titleHonorific":[]},{"entityName":[],"firstName":["Volodymyr"],"maidenName":[],"middleName":["Leonidovych"],"originalScriptName":[],"singleStringName":[],"suffix":[],"surname":["Shemiakin"],"titleHonorific":[]}]}]},"idNumberTypes":{"ID":[{"IDType":"Others","IDValue":[{"value":"1045207050712"}]},{"IDType":"National Tax No.","IDValue":[{"value":"5256050056"},{"value":"710600916016"}]},{"IDType":"NSDC Unique ID","IDValue":[{"value":"11111"}]}]},"sanctionsReferences":{"reference":[{"sinceDay":"17","sinceMonth":"Oct","sinceYear":"2016","toDay":"15","toMonth":"May","toYear":"2017","value":2966},{"sinceDay":"15","sinceMonth":"May","sinceYear":"2017","toDay":"24","toMonth":"Jun","toYear":"2024","value":4114}]}}]}
*/
@Data
public class RmBaseResponseDTO {
private int code;
private String message;
private int datasize;
private List<DataItem> data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class DataItem {
private String hit;
private String hitlight;
private int similar;
private String hittype;
private String activestatus;
private String d1;
private List<String> d2;
private List<String> d3;
private List<String> countries;
private String docid;
private String systemdate;
private NameDetails nameDetails;
private IdNumberTypes idNumberTypes;
private SanctionsReferences sanctionsReferences;
// 精确查询 - details字段
private Details details;
@Data
public static class NameDetails {
private List<Name> name;
@Data
public static class Name {
private String nameType;
private List<NameValue> nameValue;
@Data
public static class NameValue {
private List<String> entityName;
private List<String> firstName;
private List<String> maidenName;
private List<String> middleName;
private List<String> originalScriptName;
private List<String> singleStringName;
private List<String> suffix;
private List<String> surname;
private List<String> titleHonorific;
}
}
}
@Data
public static class IdNumberTypes {
private List<ID> ID;
@Data
public static class ID {
private String IDType;
private List<IDValue> IDValue;
@Data
public static class IDValue {
private String value;
private String IDnotes; // 可选
}
}
}
@Data
public static class SanctionsReferences {
private List<Reference> reference;
@Data
public static class Reference {
private String sinceDay;
private String sinceMonth;
private String sinceYear;
private String toDay;
private String toMonth;
private String toYear;
private int value;
}
}
@Data
// 新增 details 对象
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Details {
private String date;
private String gender;
private String deceased;
private Descriptions descriptions;
private Map<String, List<String>> nameDetails; // 如 "Primary Name": [...]
private Map<String, List<String>> idNumberTypes; // 如 "OFAC Unique ID": [...]
private Map<String, List<String>> dateDetails; // 如 "Inactive as of (PEP)": [...]
private List<String> birthPlace;
// private SanctionsReferencesMap sanctionsReferences;
private Map<String, List<SanctionsReferencesMap.SanctionReference>> sanctionsReferences; // 支持动态key
private Map<String, List<String>> countryDetails; // 如 "Resident of": [...]
private String profileNotes;
private List<String> sourceDescription;
private List<String> images;
private List<Associate> associate;
private List<CompanyDetails> companyDetails;
private List<VesselDetails> vesselDetails;
@Data
public static class Descriptions {
private List<Description> description;
@Data
public static class Description {
private String description1;
private String description2;
private String description3;
}
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class SanctionsReferencesMap {
// 如 "Sanctions Lists": [{"date":"...","value":"..."}]
// private Map<String, List<SanctionReference>> sanctionsLists;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class SanctionReference {
private String date;
private String value;
}
}
@Data
public static class Associate {
private String activestatus;
private String d1;
private String d2;
private String docid;
private String ex;
private String primaryName;
private List<String> primaryNameOriginalScriptName;
private String relationship;
}
@Data
public static class CompanyDetails {
private String companyDetails;
}
@Data
public static class VesselDetails {
private String vesselFlag;
private String vesselGRT;
private String vesselOwner;
private String vesselTonnage;
private String vesselType;
}
}
}
}

View File

@ -1,6 +1,6 @@
package com.chinaunicom.mall.ebtp.extend.singlePoint.common;
import groovy.util.logging.Slf4j;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -13,7 +13,7 @@ import java.security.Security;
@Slf4j
public class GmBaseUtil {
private static final Logger logger = LoggerFactory.getLogger(GmBaseUtil.class);
protected GmBaseUtil() {
logger.info("GmBaseUtil");
}

View File

@ -0,0 +1,71 @@
package com.chinaunicom.mall.ebtp.extend.sms.client;
import com.chinaunicom.mall.ebtp.extend.sms.config.SmsFeignConfig;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsBaseResponseDTO;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsSendResponseDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.Map;
@FeignClient(name = "sms-service", url = "${spring.cloopen.url}", configuration = SmsFeignConfig.class)
public interface SmsClient {
/**
* 查询短信模板
* 返回:
{
"statusCode":"000000",
"totalCount":"2",
"TemplateSMS":[
{
"id":"1180212",
"title":"通知2",
"content":"【中远海运】{1}",
"status":"1",
"type":"1",
"dateCreated":"2022-03-27 12:34:28.0",
"dateUpdated":"2022-03-27 12:40:13.0",
"appendcode":"317806"
},{
"id":"142717",
"title":"集团航运管理平台短信通知",
"content":"【中远海运航标平台】{1}",
"status":"1",
"type":"1",
"dateCreated":"2016-12-16 12:04:36.0",
"dateUpdated":"2020-12-10 12:58:25.0",
"appendcode":"319026"
}
]
}
* @param request 请求参数
* @return 模板查询结果
*/
@PostMapping("/${spring.cloopen.softVersion}/Accounts/${spring.cloopen.accountSid}/SMS/QuerySMSTemplate")
SmsBaseResponseDTO querySmsTemplate(@RequestBody Map<String, String> request);
/**
* 发送模板短信
参数:
{
"datas":["测试内容"],// 模板替换参数
"appId":"8aaf070857f4daef0157fe76a316072b",
"to":"18686879363",// 发送给哪些手机号,多个用逗号分隔
"templateId":"1180212"/ 短信模板
}
返回:
{
"statusCode":"000000",
"templateSMS":{
"smsMessageSid":"b21c72a8c9844843b8d87ecdb10304fb",
"dateCreated":"20250620143237"
}
}
* @param request 请求参数
* @return 发送结果
*/
@PostMapping("/${spring.cloopen.softVersion}/Accounts/${spring.cloopen.accountSid}/SMS/TemplateSMS")
SmsSendResponseDTO sendTemplateSms(@RequestBody Map<String, Object> request);
}

View File

@ -0,0 +1,55 @@
package com.chinaunicom.mall.ebtp.extend.sms.config;
import com.chinaunicom.mall.ebtp.extend.sms.client.SmsClient;
import feign.RequestInterceptor;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
/**
* 短信服务Feign配置类
* SigParameter是REST API 验证参数
* URL后必须带有sig参数例如sig=ABCDEFG。
* 使用MD5加密账户ACCOUNT SID+ 账户授权令牌AUTH TOKEN + 时间戳。其中账户Id和账户授权令牌根据url的验证级别对应主账户或子账户短信业务接口对应主账户及主账号授权令牌。
* 时间戳是当前系统时间,格式"yyyyMMddHHmmss"。
* SigParameter参数需要大写
*/
@Configuration
public class SmsFeignConfig {
@Value("${spring.cloopen.accountSid}")
private String accountSid;
@Value("${spring.cloopen.authToken}")
private String authToken;
@Bean
@ConditionalOnBean(SmsClient.class)
public RequestInterceptor requestInterceptor() {
return template -> {
// 生成时间戳
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
// 计算Authorization
String auth = accountSid + ":" + timestamp;
String authorization = Base64.getEncoder().encodeToString(auth.getBytes());
// 计算SigParameter
String sig = DigestUtils.md5Hex(accountSid + authToken + timestamp).toUpperCase();
// 设置请求头
template.header("Accept", "application/json");
template.header("Content-Type", "application/json;charset=utf-8");
template.header("Authorization", authorization);
// 添加sig参数
template.query("sig", sig);
};
}
}

View File

@ -0,0 +1,103 @@
package com.chinaunicom.mall.ebtp.extend.sms.constant;
public enum SmsErrorEnum {
ACCOUNT_URL_FORMAT_ERROR("111100", "【账号】请求URL中账号格式不正确"),
ACCOUNT_AUTHORIZATION_HEADER_EMPTY("111101", "【账号】请求包头Authorization参数为空"),
ACCOUNT_AUTHORIZATION_BASE64_DECODE_FAIL("111102", "【账号】请求包头Authorization参数Base64解码失败"),
ACCOUNT_AUTHORIZATION_DECODE_FORMAT_ERROR("111103", "【账号】请求包头Authorization参数解码后格式有误"),
ACCOUNT_AUTHORIZATION_DECODE_ID_EMPTY("111104", "【账号】请求包头Authorization参数解码后账户ID为空"),
ACCOUNT_AUTHORIZATION_DECODE_TIMESTAMP_EMPTY("111105", "【账号】请求包头Authorization参数解码后时间戳为空"),
ACCOUNT_AUTHORIZATION_DECODE_TIMESTAMP_EXPIRED("111106", "【账号】请求包头Authorization参数解码后时间戳过期"),
ACCOUNT_AUTHORIZATION_ID_NOT_MATCH("111107", "【账号】请求包头Authorization参数中账户ID跟请求地址中的账户ID不一致"),
ACCOUNT_SIG_PARAM_EMPTY("111108", "【账号】请求地址Sig参数为空"),
ACCOUNT_SIG_VERIFY_FAIL("111109", "【账号】请求地址Sig校验失败"),
ACCOUNT_SOFTVERSION_ERROR("111110", "【账号】请求地址SoftVersion参数有误"),
ACCOUNT_CONCURRENT_LIMIT("111111", "【账号】接口请求超过规定的并发数"),
ACCOUNT_AUTHORIZATION_TIMESTAMP_FORMAT_ERROR("111113", "【账号】请求包头Authorization参数中时间戳格式有误请参考yyyyMMddHHmmss"),
ACCOUNT_PAUSED("111139", "【账号】主账户已暂停"),
ACCOUNT_CLOSED("111140", "【账号】主账户已关闭"),
ACCOUNT_NOT_EXIST("111141", "【账号】主账号不存在"),
ACCOUNT_BALANCE_NOT_ENOUGH("121002", "【账号】余额不足"),
SMS_TEST_TEMPLATE_NOT_BIND("113302", "【短信】正在使用云通讯测试模板且短信接收者不是绑定的测试号码"),
SMS_PARAM_PARSE_FAIL("160031", "【短信】参数解析失败"),
SMS_TEMPLATE_INVALID("160032", "【短信】短信模板无效"),
SMS_BLACK_WORD("160033", "【短信】短信存在黑词"),
SMS_NUMBER_BLACKLIST("160034", "【短信】号码黑名单"),
SMS_CONTENT_EMPTY("160035", "【短信】短信下发内容为空"),
SMS_TEMPLATE_TYPE_UNKNOWN("160036", "【短信】短信模板类型未知"),
SMS_CONTENT_LENGTH_LIMIT("160037", "【短信】短信内容长度限制"),
SMS_CODE_SEND_TOO_FREQUENT("160038", "【短信】短信验证码发送过频繁"),
SMS_TEMPLATE_NUMBER_DAY_LIMIT("160039", "【短信】超出同模板同号天发送次数上限"),
SMS_CODE_NUMBER_DAY_LIMIT("160040", "【短信】验证码超出同模板同号码天发送上限"),
SMS_NOTIFY_NUMBER_DAY_LIMIT("160041", "【短信】通知超出同模板同号码天发送上限"),
SMS_NUMBER_FORMAT_ERROR("160042", "【短信】号码格式有误"),
SMS_APP_TEMPLATE_ID_NOT_MATCH("160043", "【短信】应用与模板id不匹配"),
SMS_SEND_NUMBER_EMPTY("160044", "【短信】发送号码为空"),
SMS_BATCH_NUMBER_DUPLICATE("160045", "【短信】群发号码重复"),
SMS_MARKETING_CONTENT_AUDIT_FAIL("160046", "【短信】营销短信发送内容审核未通过"),
SMS_STATUS_REPORT_PARSE_FAIL("160047", "【短信】查询状态报告包体解析失败"),
SMS_BATCH_NUMBER_LIMIT("160048", "【短信】群发号码个数超限制"),
SMS_CONTENT_SENSITIVE_WORD("160049", "【短信】短信内容含敏感词"),
SMS_SEND_FAIL("160050", "【短信】短信发送失败"),
SMS_MARKETING_UNSUBSCRIBE_NUMBER("160051", "【短信】营销退订号码"),
SMS_TEMPLATE_VARIABLE_FORMAT_ERROR("160052", "【短信】模板变量格式有误"),
SMS_IP_AUTH_FAIL("160053", "【短信】IP鉴权失败"),
SMS_REQUEST_REPEAT("160054", "【短信】请求重复"),
SMS_REQID_TOO_LONG("160055", "【短信】请求reqId超长"),
SMS_SAME_NUMBER_CONTENT_REPEAT("160056", "【短信】同号码请求内容重复"),
SMS_TEMPLATE_ID_MUST_NUMBER("160057", "【短信】短信模板ID要求为数字"),
SMS_INTL_NO_PERMISSION("160058", "【国际短信】账户无国际短信权限"),
SMS_INTL_BATCH_NOT_SUPPORT("160059", "【国际短信】国际短信暂不支持群发"),
SMS_INTL_MARKETING_NO_PERMISSION("160060", "【国际短信】国际短信账户无营销短信权限"),
SMS_INTL_UNSUPPORTED_COUNTRY_CODE("160061", "【国际短信】暂不支持的国家码号"),
SMS_INTL_COUNTRY_CODE_NOT_OPEN("160062", "【国际短信】未开通此国家码号"),
SMS_SEND_FAIL_2("160063", "【短信】短信发送失败"),
SMS_SEND_FAIL_3("160064", "【短信】短信发送失败"),
SMS_SUB_EXT_INVALID("160065", "【短信】子扩展不符合要求"),
SMS_SCHEDULE_TIME_INVALID("160066", "【短信】定时发送时间不符合平台规则"),
SMS_SCHEDULE_TIME_FORMAT_ERROR("160067", "【短信】定时发送时间格式有误"),
SMS_ALL_UNSUBSCRIBE_BLACKLIST("160068", "【短信】平台全部退订号码黑名单"),
SMS_TEST_TEMPLATE_VAR_NOT_NUMBER("160069", "【短信】测试模板变量非数字"),
SMS_CUSTOM_TEMPLATE_NOT_SUPPORT_INTL("160070", "【短信】自定义短信模板不支持国际短信"),
SMS_MARKETING_TIME_NOT_ALLOWED("160071", "【短信】营销短信不在允许发送时间段"),
SMS_KEY_NUMBER_BLACKLIST("160072", "【短信】关键号码黑名单"),
SMS_OPERATOR_COMPLAINT_BLACKLIST("160073", "【短信】运营商投诉号码黑名单"),
SMS_CONTENT_BLACK_LINK("160074", "【短信】内容含黑链接"),
SMS_TEMPLATE_VAR_TOO_LONG("160078", "【短信】模板变量超长"),
SMS_NUMBER_BLACKLIST_2("160079", "【短信】号码黑名单"),
SMS_DAY_LIMIT("160080", "【短信】日发送量超出限额"),
SMS_MONTH_LIMIT("160081", "【短信】月发送量超出限额"),
SMS_NOT_WHITE_TEMPLATE("160082", "【短信】非报备白模板内容"),
SMS_CUSTOM_APP_NOT_EXIST("160083", "【短信】自定义应用不存在"),
SMS_NOT_CUSTOM_APP("160084", "【短信】非自定义应用"),
SMS_CUSTOM_APP_DISABLED("160085", "【短信】自定义应用已停用"),
SMS_NUMBER_BLIND_ZONE("160086", "【短信】号码盲区"),
SMS_SIGNATURE_BLOCKED("160087", "【短信】系统屏蔽签名"),
SMS_INTL_NUMBER_FORMAT_INVALID("160088", "【国际短信】号码格式无效");
private final String code;
private final String message;
SmsErrorEnum(String code, String message) {
this.code = code;
this.message = message;
}
public String code() {
return code;
}
public String message() {
return message;
}
public static String getMessageByCode(String code) {
for (SmsErrorEnum e : SmsErrorEnum.values()) {
if (e.code().equals(code)) {
return e.message;
}
}
return "未知错误";
}
}

View File

@ -0,0 +1,106 @@
package com.chinaunicom.mall.ebtp.extend.sms.controller;
import com.chinaunicom.mall.ebtp.common.base.entity.BaseResponse;
import com.chinaunicom.mall.ebtp.extend.sms.constant.SmsErrorEnum;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsBaseResponseDTO;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsCallbackRequestDTO;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsSendResponseDTO;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsTemplateDTO;
import com.chinaunicom.mall.ebtp.extend.sms.service.SmsCallbackService;
import com.chinaunicom.mall.ebtp.extend.sms.service.SmsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/sms")
public class SmsController {
@Resource
private SmsService smsService;
@Resource
private SmsCallbackService smsCallbackService;
private static final String SMS_SUCCESS_CODE = "000000";// 短信接口成功代码
private static final String SMS_ERRER_DESC = "sms错误代码/描述: ";
/**
* 查询短信模板
* @param templateId
* @return
*/
@GetMapping("/queryTemplate")
public BaseResponse<List<SmsTemplateDTO>> queryTemplate(@RequestParam(value = "templateId", required = false) String templateId) {
// public SmsBaseResponseDTO queryTemplate(@RequestParam(value = "templateId", required = false) String templateId) {
SmsBaseResponseDTO smsBaseResponseDTO = smsService.querySmsTemplate(templateId);// 空(null和'')查询所有模板
if (!SMS_SUCCESS_CODE.equals(smsBaseResponseDTO.getStatusCode())) {
return BaseResponse.fail(SMS_ERRER_DESC + smsBaseResponseDTO.getStatusCode() + SmsErrorEnum.getMessageByCode(smsBaseResponseDTO.getStatusCode()), smsBaseResponseDTO.getTemplateSMS());
}
return BaseResponse.success(smsBaseResponseDTO.getTemplateSMS());
}
/**
* 发送短信
* @return
* 返回:
{
"statusCode": "000000",
"templateSMS": {
"smsMessageSid": "b9fb1cc5acc3428c8d40c87428380b72",
"dateCreated": "20250618100527",
"reqId": "1687056327_1"// 业务ID
}
}
发送短信,保存发送记录, 与callback接口配合使用
reqId: 自定义消息ID比如可以用某个业务的名字
callback收到后会根据业务去处理调用对应业务的方法
*/
@GetMapping("/send")
public BaseResponse<SmsSendResponseDTO.TemplateSMS> sendSms(
@RequestParam String[] mobiles,
@RequestParam String templateId,
@RequestParam String[] datas,
@RequestParam String reqId,
@RequestParam(required = false) String subAppend) {
// 字符串数组转逗号拼接字符串
String mobileStr = String.join(",", mobiles);
SmsSendResponseDTO smsSendResponseDTO = smsService.sendTemplateSms(
mobileStr, // 接收手机号
templateId, // 模板ID
datas, // 模板参数
subAppend, // 子扩展码
reqId // 自定义消息ID
);
if (!SMS_SUCCESS_CODE.equals(smsSendResponseDTO.getStatusCode())) {
return BaseResponse.fail(SMS_ERRER_DESC + smsSendResponseDTO.getStatusCode() + SmsErrorEnum.getMessageByCode(smsSendResponseDTO.getStatusCode()), smsSendResponseDTO.getTemplateSMS());
}
return BaseResponse.success(smsSendResponseDTO.getTemplateSMS());
}
/**
* 2.2.上行短信/状态报告推送接口
* 短信回调接口
* 该接口用于接收短信服务商的回调请求,包括上行短信和状态报告等信息。
* 短信平台不处理任何返回只关注接口请求是否200, 所以返回信息没有实际意义
* smsType String 必选 短信类型0上行短信1手机接收状态报告
* 消息总线模式各个模块哪个需要接收全都调用一次根据业务ID自行去对应业务表中判断处理
* @param callbackRequestDTO 回调请求数据
* @return 返回处理结果
*/
@PostMapping("/callback")
public BaseResponse<String> smsCallback(@RequestBody SmsCallbackRequestDTO callbackRequestDTO) {
log.info(callbackRequestDTO.toString());
// 保存 回调信息
boolean saveResult = smsCallbackService.save(callbackRequestDTO.getRequest());
// TODO 转发各个业务平台接口 (需在脚手架配置feign)
return BaseResponse.success("Callback received");
}
}

View File

@ -0,0 +1,9 @@
package com.chinaunicom.mall.ebtp.extend.sms.dao;
import com.chinaunicom.mall.ebtp.common.base.dao.IBaseMapper;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsCallbackRequestDTO;
import com.chinaunicom.mall.ebtp.extend.tianyancha.entity.TianyanchaNormalDTO;
public interface SmsCallbackMapper extends IBaseMapper<SmsCallbackRequestDTO.Request> {
}

View File

@ -0,0 +1,45 @@
package com.chinaunicom.mall.ebtp.extend.sms.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
*
{
"statusCode": "000000",
"totalCount": "2",
"TemplateSMS": [
{
"id": "1180212",
"title": "通知2",
"content": "【中远海运】{1}",
"status": "1",
"type": "1",
"dateCreated": "2022-03-27 12:34:28.0",
"dateUpdated": "2022-03-27 12:40:13.0",
"appendcode": "317806"
},
{
"id": "142717",
"title": "集团航运管理平台短信通知",
"content": "【中远海运航标平台】{1}",
"status": "1",
"type": "1",
"dateCreated": "2016-12-16 12:04:36.0",
"dateUpdated": "2020-12-10 12:58:25.0",
"appendcode": "319026"
}
]
}
*/
@Data
public class SmsBaseResponseDTO {
private String statusCode;
private String totalCount;
@JsonProperty("TemplateSMS")
private List<SmsTemplateDTO> templateSMS;
}

View File

@ -0,0 +1,37 @@
package com.chinaunicom.mall.ebtp.extend.sms.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonProperty;
@Data
@ApiModel(value = "短信回调表", description = "短信回调表")
public class SmsCallbackRequestDTO {
@JsonProperty("Request")
private Request request;
@Data
@TableName(value = "sms_callback", autoResultMap = true)
public static class Request {
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;// 主键ID
private String action; // 必选 功能操作标识 SMSArrived
private String smsType; // 必选 短信类型0上行短信1手机接收状态报告
private String recvTime; // 必选 收到上行短信/短信送达手机时间格式yyyyMMddHHmmss
private String apiVersion; // 必选 REST API版本号格式YYYY-MM-DD
private String fromNum; // 必选 发送/接收短信的手机号码
private String content; // 必选 短信内容或短信ID
private String appendCode; // 可选 短信扩展码
private String subAppend; // 可选 自定义短信扩展码
private String status; // 可选 短信到达状态
private String dateSent; // 可选 短信发送时间
private String deliverCode; // 可选 到达状态描述
private String reqId; // 可选 第三方自定义消息id
private String smsCount; // 可选 下行短信计费条数
private String spCode; // 可选 下行通道码号
}
}

View File

@ -0,0 +1,27 @@
package com.chinaunicom.mall.ebtp.extend.sms.entity;
import lombok.Data;
/**
*
{
"statusCode":"000000",
"templateSMS":{
"smsMessageSid":"b21c72a8c9844843b8d87ecdb10304fb",
"dateCreated":"20250620143237"
}
}
*/
@Data
public class SmsSendResponseDTO {
private String statusCode;
private TemplateSMS templateSMS;
@Data
public static class TemplateSMS {
private String smsMessageSid;
private String dateCreated;
}
}

View File

@ -0,0 +1,26 @@
package com.chinaunicom.mall.ebtp.extend.sms.entity;
import lombok.Data;
/**
*
"id": "142717",
"title": "集团航运管理平台短信通知",
"content": "【中远海运航标平台】{1}",
"status": "1",
"type": "1",
"dateCreated": "2016-12-16 12:04:36.0",
"dateUpdated": "2020-12-10 12:58:25.0",
"appendcode": "319026"
*/
@Data
public class SmsTemplateDTO {
private String id;
private String title;
private String content;
private String status;
private String type;
private String dateCreated;
private String dateUpdated;
private String appendcode;
}

View File

@ -0,0 +1,10 @@
package com.chinaunicom.mall.ebtp.extend.sms.service;
import com.chinaunicom.mall.ebtp.common.base.service.IBaseService;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsCallbackRequestDTO;
import org.springframework.stereotype.Service;
@Service
public interface SmsCallbackService extends IBaseService<SmsCallbackRequestDTO.Request> {
}

View File

@ -0,0 +1,65 @@
package com.chinaunicom.mall.ebtp.extend.sms.service;
import cn.hutool.core.util.ObjectUtil;
import com.chinaunicom.mall.ebtp.extend.sms.client.SmsClient;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsBaseResponseDTO;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsSendResponseDTO;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsTemplateDTO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class SmsService {
@Autowired
private SmsClient yunSmsClient;
@Value("${spring.cloopen.appId}")
private String appId;
/**
* 查询短信模板
* @param templateId 模板id
* @return 模板查询结果
*/
public SmsBaseResponseDTO querySmsTemplate(String templateId) {
Map<String, String> request = new HashMap<>();
request.put("appId", appId);
if (StringUtils.isNotEmpty(templateId)) {
request.put("templateId", templateId);
}
return yunSmsClient.querySmsTemplate(request);
}
/**
* 发送模板短信
* @param to 接收手机号,多个用逗号分隔
* @param templateId 模板ID
* @param datas 模板参数
* @param subAppend 子扩展码,可选
* @param reqId 自定义消息ID可选
* @return 发送结果
*/
public SmsSendResponseDTO sendTemplateSms(String to, String templateId, String[] datas,
String subAppend, String reqId) {
Map<String, Object> request = new HashMap<>();
request.put("to", to);
request.put("appId", appId);
request.put("templateId", templateId);
request.put("datas", datas);
if (subAppend != null && !subAppend.isEmpty()) {
request.put("subAppend", subAppend);
}
if (reqId != null && !reqId.isEmpty()) {
request.put("reqId", reqId);
}
return yunSmsClient.sendTemplateSms(request);
}
}

View File

@ -0,0 +1,21 @@
package com.chinaunicom.mall.ebtp.extend.sms.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chinaunicom.mall.ebtp.extend.sms.dao.SmsCallbackMapper;
import com.chinaunicom.mall.ebtp.extend.sms.entity.SmsCallbackRequestDTO;
import com.chinaunicom.mall.ebtp.extend.sms.service.SmsCallbackService;
import com.chinaunicom.mall.ebtp.extend.tianyancha.dao.TianyanchaNormalMapper;
import com.chinaunicom.mall.ebtp.extend.tianyancha.entity.TianyanchaNormalDTO;
import com.chinaunicom.mall.ebtp.extend.tianyancha.service.TianyanchaNormalService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
@Slf4j
public class SmsCallbackServiceImpl extends ServiceImpl<SmsCallbackMapper, SmsCallbackRequestDTO.Request> implements SmsCallbackService {
}

View File

@ -15,33 +15,29 @@ spring:
application:
name: biz-service-ebtp-extend
shardingsphere:
datasource:
names: ds0
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: Unicom@2024
jdbc-url: jdbc:mysql://59.110.10.99:53306/ebtp_mall_extend?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
url: jdbc:mysql://59.110.10.99:53306/ebtp_mall_extend?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
filters: stat,wall,log4j
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
connection-properties: druid.stat.mergeSql=ture;druid.stat.slowSqlMillis=5000
props:
sql:
show: true
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: Unicom@2024
url: jdbc:mysql://59.110.10.99:53306/ebtp_mall_extend?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
druid:
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
connection-properties: druid.stat.mergeSql=ture;druid.stat.slowSqlMillis=5000
props:
sql:
show: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
@ -51,7 +47,8 @@ spring:
# 天宫Kafka增加了安全认证需要配置安全属性
kafka:
bootstrap-servers: 10.125.164.192:32005,10.125.164.193:32005,10.125.164.194:32005
# bootstrap-servers: 10.125.164.192:32005,10.125.164.193:32005,10.125.164.194:32005
bootstrap-servers: 127.0.0.1:2181
template:
default-topic: jl_test
@ -76,14 +73,13 @@ spring:
redis:
sentinel:
master: mymaster
# nodes: 10.60.161.59:26379, 10.60.161.59:26380, 10.60.161.59:26381
nodes: localhost:26379, localhost:26380, localhost:26381
nodes: localhost:26379
password: pass
database:
idempotent: 1
sharding: 1
cache: 1
userinfo: 1
cache: 2
idempotent: 3
userinfo: 4
# 邮件配置
mail:
@ -120,6 +116,29 @@ spring:
username: SRM
password: Fjm123!@#
token: e48fa67e-d991-3542-b486-94e26a05425d
# 数据底座
dt:
url: http://10.12.0.168:8560
app_key: 5312cb20cca64fe9ba190e72ccec117d
app_secret: SnAc5SPNm5s=
# ioa测试环境配置
ioa:
url: http://10.18.55.175:19997
app_id: g8Jdg7kJLc
app_secret: d72d9660ac344f6af782ec9c3cb78648cb2250c7
# 合规风险系统
rm:
# url: https://rm.coscoshipping.com
url: http://192.168.1.153:8888/
username: esourcing@coscoshipping.com
password: JTsrm_2243
# 云通讯平台配置(短信)
cloopen:
url: https://app.cloopen.com:8883/
softVersion: 2013-12-26
accountSid: 8aaf070857f4daef0157fe76a1220724
authToken: fb5198e122fc4d1e9344525e5288029d
appId: 8aaf070857f4daef0157fe76a316072b
mybatis-plus:
@ -148,6 +167,7 @@ feign:
default:
connect-timeout: 20000
read-timeout: 20000
logger-level: full
hystrix:
command:
@ -186,6 +206,10 @@ mconfig:
file:
font-address: /storage/fonts/
upload-address: /storage/reviewReport/
ioa:
app_id: g8Jdg7kJLc
app_secret: d72d9660ac344f6af782ec9c3cb78648cb2250c7
base_url: http://10.18.55.175:19997
document:
clientHttpUrl: http://10.242.31.158:8100/auth/oauth/token?grant_type=client_credentials&client_id=bVS46ElU&client_secret=58ea04ba02475c8da2321cc99849d2a10f15b749

View File

@ -6,12 +6,6 @@ spring:
profiles:
active: dev
# Apollo 配置信息
apollo:
bootstrap:
enabled: true
namespace: application
client:
clientHttpUrl: 1