This commit is contained in:
dzc committed 2025-04-11 15:25:32 +08:00
commit 29e1cddcf6
10 files changed
+333 -1

No files matched your search

@@ -134,7 +134,7 @@ public class VisitorsController {
@Operation(summary = "app新增访客表" , description = "app新增访客表" )
@SysLog("新增访客表" )
@PostMapping("/app/save")
@Idempotent(key = "#key", expireTime = 10, info = "请勿重复提交")
@Idempotent(key = "#", expireTime = 10, info = "请勿重复提交")
@Inner(value = false)
public R save(@RequestBody VisitorsAppDTO visitors) {
@@ -0,0 +1,18 @@
package com.bsd.pjcloud.thirdparty.dto.sms;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SmsRequest {
private String secretName;
private String secretKey;
private String mobile;
private String content;
private String extCode;
}
@@ -0,0 +1,10 @@
package com.bsd.pjcloud.thirdparty.dto.sms;
import lombok.Data;
@Data
public class SmsResponse {
private String code;
private String message;
private String taskId;
}
@@ -0,0 +1,86 @@
package com.bsd.pjcloud.thirdparty.config;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Map;
@Configuration
@ConfigurationProperties(prefix = "sms")
@Getter
@Setter
@Validated
public class SmsConfig {
@NotNull
private ApiConfig api;
@NotEmpty
private Map<String, TemplateConfig> templates;
@NotNull
private Settings settings;
// API配置
@Getter
@Setter
public static class ApiConfig {
@NotBlank
private String baseUrl;
@Valid
@NotNull
private Secret secret;
@Valid
@NotNull
private Endpoints endpoints;
@Getter
@Setter
public static class Secret {
@NotBlank
private String name;
@NotBlank
private String key;
}
@Getter
@Setter
public static class Endpoints {
@NotBlank
private String send;
}
}
// 模板配置
@Getter
@Setter
public static class TemplateConfig {
@NotBlank
private String content;
@NotEmpty
private List<String> params;
}
// 通用设置
@Getter
@Setter
public static class Settings {
@NotBlank
private String defaultExtCode;
@Min(60)
private int timeoutSeconds;
}
}
@@ -0,0 +1,9 @@
package com.bsd.pjcloud.thirdparty.config;
import lombok.Data;
@Data
public class SmsTemplateConfig {
private String content;
private String signName;
}
@@ -0,0 +1,33 @@
package com.bsd.pjcloud.thirdparty.controller;
import com.bsd.pjcloud.common.security.annotation.Inner;
import com.bsd.pjcloud.thirdparty.service.SmsService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/sms")
@RequiredArgsConstructor
public class SmsController {
private final SmsService smsService;
// 简单发送
@PostMapping("/verify-code")
public void sendVerifyCode(@RequestParam String mobile) {
smsService.sendSms(mobile, "20657");
}
// 带参数发送
@PostMapping("/order-notify")
@Inner(value = false)
public void sendOrderNotify(
@RequestParam String mobile,
@RequestBody Map<String, String> params
) {
smsService.sendSms(mobile, "20657", params);
}
}
@@ -0,0 +1,11 @@
package com.bsd.pjcloud.thirdparty.exception;
public class SmsException extends RuntimeException {
public SmsException(String message) {
super(message);
}
public SmsException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,44 @@
package com.bsd.pjcloud.thirdparty.service;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Component
@RequiredArgsConstructor
public class SmsHttpClient {
private static final RestTemplate restTemplate = new RestTemplate();
@Value("${sms.settings.timeout-seconds}")
private int timeoutSeconds;
public String post(String url, Object body) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Object> entity = new HttpEntity<>(body, headers);
return restTemplate.exchange(
url,
HttpMethod.POST,
entity,
String.class
).getBody();
}
// @Bean
// public RestTemplate restTemplate() {
// SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// factory.setConnectTimeout(timeoutSeconds * 1000);
// factory.setReadTimeout(timeoutSeconds * 1000);
// return new RestTemplate(factory);
// }
}
@@ -0,0 +1,102 @@
package com.bsd.pjcloud.thirdparty.service;
import com.alibaba.fastjson.JSON;
import com.bsd.pjcloud.thirdparty.config.SmsConfig;
import com.bsd.pjcloud.thirdparty.dto.sms.SmsRequest;
import com.bsd.pjcloud.thirdparty.dto.sms.SmsResponse;
import com.bsd.pjcloud.thirdparty.exception.SmsException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
@Slf4j
public class SmsService {
private final SmsConfig config;
private final SmsHttpClient httpClient;
/**
* 发送短信(最简版)
* @param mobile 手机号
* @param templateId 模板ID
*/
public void sendSms(String mobile, String templateId) {
validateMobile(mobile);
SmsConfig.TemplateConfig template = getTemplate(templateId);
if (!template.getParams().isEmpty()) {
throw new SmsException("模板需要参数,请使用sendSmsWithParams方法");
}
sendRequest(buildRequest(mobile, template.getContent()));
}
/**
* 发送含参数的短信
* @param mobile 手机号
* @param templateId 模板ID
* @param params 参数Map
*/
public void sendSms(String mobile, String templateId, Map<String, String> params) {
validateMobile(mobile);
SmsConfig.TemplateConfig template = getTemplate(templateId);
String content = replacePlaceholders(template.getContent(), params, template.getParams());
sendRequest(buildRequest(mobile, content));
}
private SmsRequest buildRequest(String mobile, String content) {
return SmsRequest.builder()
.secretName(config.getApi().getSecret().getName())
.secretKey(config.getApi().getSecret().getKey())
.mobile(mobile)
.content(content)
.extCode(config.getSettings().getDefaultExtCode())
.build();
}
private void sendRequest(SmsRequest request) {
String url = config.getApi().getBaseUrl() + config.getApi().getEndpoints().getSend();
String response = httpClient.post(url, request);
handleResponse(response);
}
private void handleResponse(String response) {
SmsResponse resp = JSON.parseObject(response, SmsResponse.class);
if (!"0".equals(resp.getCode())) {
throw new SmsException("短信发送失败: " + resp.getMessage());
}
}
private SmsConfig.TemplateConfig getTemplate(String templateId) {
SmsConfig.TemplateConfig template = config.getTemplates().get(templateId);
if (template == null) {
throw new SmsException("无效的模板ID: " + templateId);
}
return template;
}
private String replacePlaceholders(String template, Map<String, String> params, List<String> requiredParams) {
for (String param : requiredParams) {
if (!params.containsKey(param)) {
throw new SmsException("缺少必要参数: " + param);
}
}
String result = template;
for (Map.Entry<String, String> entry : params.entrySet()) {
result = result.replace("{" + entry.getKey() + "}", entry.getValue());
}
return result;
}
private void validateMobile(String mobile) {
if (!mobile.matches("^1[3-9]\\d{9}$")) {
throw new SmsException("无效的手机号码: " + mobile);
}
}
}
@@ -88,3 +88,22 @@ ks-discern:
sms:
api:
base-url: "https://api.028lk.com/Sms/Api/"
secret:
name: "${SMS_SECRET_NAME:ZJSK00022796}" # 从环境变量读取,带默认值
key: "${SMS_SECRET_KEY:zxy@1234}"
endpoints:
send: "Send"
templates:
20657: # 验证码模板
content: "您好,您的验证码是{code},有效时间{s1}分钟,请勿转发!"
params: ["code", "date"] # 需要的参数
settings:
default-ext-code: "123"
timeout-seconds: 300