mirror of
https://gitee.com/dromara/sms4j.git
synced 2025-12-08 01:48:38 +08:00
Compare commits
218 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc5af3a68a | ||
|
|
3be4e34d6b | ||
|
|
9baa8bba57 | ||
|
|
03d30271dd | ||
|
|
920f9304ea | ||
|
|
cedae2696b | ||
|
|
7582982d44 | ||
|
|
6aa173501e | ||
|
|
5bc7d0152e | ||
|
|
bfdc75d538 | ||
|
|
4e3845e2ce | ||
|
|
b168d5235f | ||
|
|
bc8e329bfa | ||
|
|
09a3dba016 | ||
|
|
a62bca40b6 | ||
|
|
8498fafa65 | ||
|
|
aeecaaa8f5 | ||
|
|
2ef232d9ff | ||
|
|
e6173c5d2f | ||
|
|
44287e62d4 | ||
|
|
49c5af9f70 | ||
|
|
98e779376d | ||
|
|
ff4d6d8bf4 | ||
|
|
76e9ec060e | ||
|
|
d58f2d0eaf | ||
|
|
ec501e441d | ||
|
|
f9f197935a | ||
|
|
197dc65cf8 | ||
|
|
1c0d518fdb | ||
|
|
46f3ccfbf2 | ||
|
|
109f50cfd1 | ||
|
|
30c4488cde | ||
|
|
8a0dd4bc3d | ||
|
|
8aca0fd957 | ||
|
|
1a31bb5189 | ||
|
|
e81eeffe40 | ||
|
|
95601131cd | ||
|
|
3f9c22a04d | ||
|
|
463f3e568b | ||
|
|
298e7c48e5 | ||
|
|
d7589aa7a1 | ||
|
|
2ee0389eda | ||
|
|
36348b54b1 | ||
|
|
2df3db09d9 | ||
|
|
ea6e6a9fa6 | ||
|
|
eea34ace98 | ||
|
|
319a0d2cd1 | ||
|
|
21fe49f627 | ||
|
|
cf8b977a9e | ||
|
|
506acbf906 | ||
|
|
12ab72d6d1 | ||
|
|
b045df8be0 | ||
|
|
d921679af4 | ||
|
|
2dec57ad80 | ||
|
|
4fbea6e6a1 | ||
|
|
a8e2ddb420 | ||
|
|
1a3abc4be3 | ||
|
|
f0e018b855 | ||
|
|
8ec8cf8bd6 | ||
|
|
5020771a7a | ||
|
|
9623b28b31 | ||
|
|
98fa90ee1f | ||
|
|
780caef8cd | ||
|
|
f5ce5b9be4 | ||
|
|
a2a740a339 | ||
|
|
f7c53c5f4e | ||
|
|
4a5e342d15 | ||
|
|
bea0c20e67 | ||
|
|
fb198304a0 | ||
|
|
f3327c33fa | ||
|
|
b5744a92ed | ||
|
|
2b36985012 | ||
|
|
154b7c3d3e | ||
|
|
525166c102 | ||
|
|
2d1c50337b | ||
|
|
a5f7ba37a4 | ||
|
|
b19287cd58 | ||
|
|
81341fcf7c | ||
|
|
9366a4d0f5 | ||
|
|
7efa3319c7 | ||
|
|
ef44c1a2df | ||
|
|
d4b213c8f2 | ||
|
|
f54331acc5 | ||
|
|
6c29ca195d | ||
|
|
fc4830b58c | ||
|
|
067c08ab46 | ||
|
|
c2adc018e0 | ||
|
|
157a19bc8e | ||
|
|
18fe81286a | ||
|
|
40e0460e15 | ||
|
|
8f60ed63a9 | ||
|
|
40469512e0 | ||
|
|
a120bc9370 | ||
|
|
b1a18b6972 | ||
|
|
fed06f2ad5 | ||
|
|
9511e4c7a7 | ||
|
|
0bf3aa562a | ||
|
|
0187272b94 | ||
|
|
a31f8a056c | ||
|
|
ca13119acc | ||
|
|
e7991ddba4 | ||
|
|
5a1641aed0 | ||
|
|
b6c7c93c38 | ||
|
|
b7d9ea5d64 | ||
|
|
a5535665f7 | ||
|
|
97e9ec7a57 | ||
|
|
8b788fa127 | ||
|
|
264795afbe | ||
|
|
7599a20814 | ||
|
|
6872a55860 | ||
|
|
0c1e1c5d62 | ||
|
|
7865774b08 | ||
|
|
a9d65a4703 | ||
|
|
d67963cc3c | ||
|
|
9c8418d0d5 | ||
|
|
afe1906fd4 | ||
|
|
fde178dc47 | ||
|
|
ccf9ca4daf | ||
|
|
900eef7ca7 | ||
|
|
c31d23bdbf | ||
|
|
a68734bf62 | ||
|
|
aa51489624 | ||
|
|
6ef9c692b8 | ||
|
|
deb13be650 | ||
|
|
d627c09774 | ||
|
|
a3b7397f3c | ||
|
|
83d86bfe48 | ||
|
|
5add6ef80f | ||
|
|
9e32e3403a | ||
|
|
fc623cf5a9 | ||
|
|
55c957ca27 | ||
|
|
5340233764 | ||
|
|
81468a3c19 | ||
|
|
bed318c1e6 | ||
|
|
7c127b6a01 | ||
|
|
f8875d1a1d | ||
|
|
00c374d5a8 | ||
|
|
1d91497d67 | ||
|
|
2b67f22b82 | ||
|
|
0750a39ec4 | ||
|
|
046918aff5 | ||
|
|
6494ea506a | ||
|
|
b325d2ac0d | ||
|
|
ae1ac3c93f | ||
|
|
0fb1050677 | ||
|
|
1304cb0c17 | ||
|
|
38813ac4b9 | ||
|
|
0b4f3bf6b8 | ||
|
|
a36cc7eeca | ||
|
|
135459c544 | ||
|
|
d359a1070a | ||
|
|
fb369032e6 | ||
|
|
8bacbbce1d | ||
|
|
c5da3a642b | ||
|
|
d6c81cdefe | ||
|
|
d92ff2bf46 | ||
|
|
b21a48e772 | ||
|
|
c1dc5062a4 | ||
|
|
f2e273f0ef | ||
|
|
56565d5871 | ||
|
|
22ddf9bc83 | ||
|
|
fffcffd65b | ||
|
|
67d6625924 | ||
|
|
91807cfe61 | ||
|
|
2fa6cc008c | ||
|
|
53de8851de | ||
|
|
39b1ec6b86 | ||
|
|
920cb94ed5 | ||
|
|
c4d59559a7 | ||
|
|
2fd2f4ba60 | ||
|
|
ad28a7ac67 | ||
|
|
f255abe930 | ||
|
|
5cbbdd209c | ||
|
|
df36fb802a | ||
|
|
eb3157529a | ||
|
|
f0185e5671 | ||
|
|
51fc622761 | ||
|
|
363dc530aa | ||
|
|
2766343f09 | ||
|
|
2cfadb574d | ||
|
|
7cadeff264 | ||
|
|
7167810be5 | ||
|
|
4616be9cef | ||
|
|
d1068c66e1 | ||
|
|
70208b5e32 | ||
|
|
a6f03a7f54 | ||
|
|
6fe40808eb | ||
|
|
37149454e9 | ||
|
|
a998b96ff6 | ||
|
|
714e85fac0 | ||
|
|
5eb6f95284 | ||
|
|
1153c509a2 | ||
|
|
3d56442115 | ||
|
|
3530f05831 | ||
|
|
3052fbadae | ||
|
|
fe0541c59e | ||
|
|
bd35558532 | ||
|
|
9c219eaa50 | ||
|
|
2763b2b70f | ||
|
|
436a41508f | ||
|
|
a73f5c6d11 | ||
|
|
76cb93c4cc | ||
|
|
484f85f9d8 | ||
|
|
3840df9848 | ||
|
|
a5adf2afdb | ||
|
|
6b6460b9ea | ||
|
|
2d65ffe0e1 | ||
|
|
d07676370c | ||
|
|
94ebc18d88 | ||
|
|
905eca3a98 | ||
|
|
8c8326a155 | ||
|
|
953a2a1472 | ||
|
|
b24d27b9b2 | ||
|
|
03876dfdf1 | ||
|
|
e2fde737bf | ||
|
|
5ec641c35d | ||
|
|
687be3ba54 | ||
|
|
2861cf4f00 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -36,3 +36,6 @@ build/
|
||||
.flattened-pom.xml
|
||||
**/.flattened-pom.xml
|
||||
/.fastRequest/**
|
||||
|
||||
### Others
|
||||
*.Ds_Store
|
||||
100
README.md
100
README.md
@ -1,9 +1,9 @@
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sms4j v3.0.0</h1>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sms4j v3.3.5</h1>
|
||||
<h4 align="center" style="margin: 30px 0 30px; font-weight: bold;">sms4j -- 让发送短信变的更简单</h4>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/dromara/sms4j/stargazers"><img src="https://gitee.com/dromara/sms4j/badge/star.svg?theme=gvp"></a>
|
||||
<a href="https://gitee.com/dromara/sms4j/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green"></a>
|
||||
<a href="https://gitee.com/dromara/sms4j"><img src="https://img.shields.io/badge/version-v3.0.0-blue"></a>
|
||||
<a href="https://gitee.com/dromara/sms4j"><img src="https://img.shields.io/badge/version-v3.3.5-blue"></a>
|
||||
</p>
|
||||
<img src="/public/logo.png">
|
||||
|
||||
@ -16,8 +16,9 @@
|
||||
如果我们的项目对你产生了帮助,或者你觉得还算值得鼓励,请用你发财的小手帮助点上一个start
|
||||
[gitee](https://gitee.com/dromara/sms4j)
|
||||
[github](https://github.com/dromara/sms4j)
|
||||
[gitcode](https://gitcode.com/dromara/SMS4J)
|
||||
|
||||
#### [官方文档](http://wind.kim)
|
||||
#### [官方文档](https://sms4j.com)
|
||||
#### [JavaDoc文档](https://apidoc.gitee.com/dromara/sms4j/)
|
||||
|
||||
## 支持厂商一览
|
||||
@ -29,9 +30,19 @@
|
||||
- **[容联云国内短信(原云通讯)](https://www.yuntongxun.com/sms/note-inform)**
|
||||
- **[网易云信短信](https://netease.im/sms)**
|
||||
- **[天翼云短信](https://www.ctyun.cn/products/10020341)**
|
||||
- **[七牛云短信](https://www.qiniu.com/products/sms)**
|
||||
- **[合一短信](https://unisms.apistd.com/)**
|
||||
- **[云片短信](https://www.yunpian.com/product/domestic-sms)**
|
||||
- **[助通短信](https://www.ztinfo.cn/products/sms)**
|
||||
- **[联麓短信](https://console.shlianlu.com/#/document/smsDoc)**
|
||||
- **[鼎众短信](http://demoapi.321sms.com:8201/index.html)**
|
||||
- **[创蓝短信](https://doc.chuanglan.com/document/HAQYSZKH9HT5Z50L)**
|
||||
- **[极光短信](https://docs.jiguang.cn/jsms)**
|
||||
- **[布丁云短信](https://console-docs.apipost.cn/preview/986c24caf79228ed/d3d8a6d5faf6ef51)**
|
||||
- **[中国移动 云MAS短信](https://mas.10086.cn/)**
|
||||
- **[螺丝帽短信](https://luosimao.com)**
|
||||
- **[SUBMAIL短信](https://www.mysubmail.com/)**
|
||||
- **[单米科技短信](https://www.danmi.com/)**
|
||||
|
||||
## 在SpringBoot环境集成
|
||||
|
||||
@ -48,42 +59,44 @@
|
||||
|
||||
```yaml
|
||||
sms:
|
||||
alibaba:
|
||||
#阿里云的accessKey
|
||||
accessKeyId: 您的accessKey
|
||||
#阿里云的accessKeySecret
|
||||
accessKeySecret: 您的accessKeySecret
|
||||
#短信签名
|
||||
signature: 测试签名
|
||||
#模板ID 用于发送固定模板短信使用
|
||||
templateId: SMS_215125134
|
||||
#模板变量 上述模板的变量
|
||||
templateName: code
|
||||
#请求地址 默认为dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
|
||||
requestUrl: dysmsapi.aliyuncs.com
|
||||
huawei:
|
||||
#华为短信appKey
|
||||
appKey: 5N6fvXXXX920HaWhVXXXXXX7fYa
|
||||
#华为短信appSecret
|
||||
app-secret: Wujt7EYzZTBXXXXXXEhSP6XXXX
|
||||
#短信签名
|
||||
signature: 华为短信测试
|
||||
#通道号
|
||||
sender: 8823040504797
|
||||
#模板ID 如果使用自定义模板发送方法可不设定
|
||||
template-id: acXXXXXXXXc274b2a8263479b954c1ab5
|
||||
#华为回调地址,如不需要可不设置或为空
|
||||
statusCallBack:
|
||||
#华为分配的app请求地址
|
||||
url: https://XXXXX.cn-north-4.XXXXXXXX.com:443
|
||||
zhutong:
|
||||
#助通短信
|
||||
#助通终端用户管理的用户名 username 必填;非登录账号密码,请登录后台管理地址进行查看:http://mix2.zthysms.com/login
|
||||
accessKeyId: tushu1122XXX
|
||||
#助通终端用户管理的用户名 passwrod 必填;
|
||||
accessKeySecret: UbXXX4SL
|
||||
#短信签名,可选;可选的时候,只能使用自定义短信不能使用模板短信; 具体在这里查看审核过的短信签名:https://mix2.zthysms.com/index.html#/SignatureManagement
|
||||
signature: 上海千XXXX
|
||||
config-type: yaml
|
||||
blends:
|
||||
自定义标识1:
|
||||
#阿里云的accessKey
|
||||
accessKeyId: 您的accessKey
|
||||
#阿里云的accessKeySecret
|
||||
accessKeySecret: 您的accessKeySecret
|
||||
#短信签名
|
||||
signature: 测试签名
|
||||
#模板ID 用于发送固定模板短信使用
|
||||
templateId: SMS_215125134
|
||||
#模板变量 上述模板的变量
|
||||
templateName: code
|
||||
#请求地址 默认为dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
|
||||
requestUrl: dysmsapi.aliyuncs.com
|
||||
自定义标识2:
|
||||
#华为短信appKey
|
||||
appKey: 5N6fvXXXX920HaWhVXXXXXX7fYa
|
||||
#华为短信appSecret
|
||||
app-secret: Wujt7EYzZTBXXXXXXEhSP6XXXX
|
||||
#短信签名
|
||||
signature: 华为短信测试
|
||||
#通道号
|
||||
sender: 8823040504797
|
||||
#模板ID 如果使用自定义模板发送方法可不设定
|
||||
template-id: acXXXXXXXXc274b2a8263479b954c1ab5
|
||||
#华为回调地址,如不需要可不设置或为空
|
||||
statusCallBack:
|
||||
#华为分配的app请求地址
|
||||
url: https://XXXXX.cn-north-4.XXXXXXXX.com:443
|
||||
自定义标识3:
|
||||
#助通短信
|
||||
#助通终端用户管理的用户名 username 必填;非登录账号密码,请登录后台管理地址进行查看:https://mix2.zthysms.com/login
|
||||
accessKeyId: tusxxxxxxXXX
|
||||
#助通终端用户管理的用户名 passwrod 必填;
|
||||
accessKeySecret: UbXXXxxx
|
||||
#短信签名,可选;可选的时候,只能使用自定义短信不能使用模板短信; 具体在这里查看审核过的短信签名:https://mix2.zthysms.com/index.html#/SignatureManagement
|
||||
signature: 上海千XXXX
|
||||
```
|
||||
|
||||
3. 方法使用
|
||||
@ -95,11 +108,11 @@ public class DemoController {
|
||||
|
||||
// 测试发送固定模板短信
|
||||
@RequestMapping("/")
|
||||
public void doLogin(String username, String password) {
|
||||
public void send() {
|
||||
//阿里云向此手机号发送短信
|
||||
SmsFactory.createSmsBlend(SupplierType.ALIBABA).sendMessage("18888888888","123456");
|
||||
SmsFactory.getSmsBlend("自定义标识1").sendMessage("18888888888","123456");
|
||||
//华为短信向此手机号发送短信
|
||||
SmsFactory.createSmsBlend(SupplierType.HUAWEI).sendMessage("16666666666","000000");
|
||||
SmsFactory.getSmsBlend("自定义标识2").sendMessage("16666666666","000000");
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -135,11 +148,12 @@ sms:
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码
|
||||
4. 新建 Pull Request 到 dev-3.x 分支
|
||||
4. 新建 Pull Request 到 dev-3.0.x 分支
|
||||
```
|
||||
## 贡献原则
|
||||
- 我们原则上欢迎任何人为sms4j添加加瓦贡献代码
|
||||
- 贡献代码应注释完备,按照javaDoc标准对 类,方法,变量,参数,返回值等信息说明
|
||||
- 如新增短信厂商需要同時以MD格式编写厂商的使用文档
|
||||
- 新增的方法模块不能破坏原有结构和兼容性
|
||||
- 如果我们关闭了你的issues或者pr请查看回复内容,我们会在回复中做出解释
|
||||
|
||||
|
||||
74
pom.xml
74
pom.xml
@ -17,17 +17,20 @@
|
||||
<module>sms4j-provider</module>
|
||||
<module>sms4j-core</module>
|
||||
<module>sms4j-spring-boot-starter</module>
|
||||
<module>sms4j-solon-plugin</module>
|
||||
<module>sms4j-spring-boot-example</module>
|
||||
<module>sms4j-solon-plugin</module>
|
||||
<module>sms4j-solon-plugin-example</module>
|
||||
<module>sms4j-javase-plugin</module>
|
||||
<module>sms4j-Email-plugin</module>
|
||||
<module>sms4j-oa-plugin</module>
|
||||
<module>sms4j-email-jakarta</module>
|
||||
</modules>
|
||||
|
||||
<!-- 开源协议 Apache 2.0 -->
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache 2</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
<comments>A business-friendly OSS license</comments>
|
||||
</license>
|
||||
@ -50,20 +53,19 @@
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<revision>3.0.1-SNAPSHOT</revision>
|
||||
<revision>3.3.5</revision>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<spring.boot.version>2.7.15</spring.boot.version>
|
||||
<solon.version>2.5.4</solon.version>
|
||||
<spring.boot.version>2.7.18</spring.boot.version>
|
||||
<solon.version>3.0.1</solon.version>
|
||||
<redisson.version>3.17.0</redisson.version>
|
||||
<jdcloud.version>1.3.3</jdcloud.version>
|
||||
<hutool.version>5.8.20</hutool.version>
|
||||
<hutool.version>5.8.28</hutool.version>
|
||||
<xmlblend.version>2.3.0</xmlblend.version>
|
||||
<activation.version>1.1.1</activation.version>
|
||||
<mail.version>1.6.2</mail.version>
|
||||
<sunactivation.version>1.2.0</sunactivation.version>
|
||||
<jakarta.activation.version>1.2.2</jakarta.activation.version>
|
||||
<snakeyaml.version>2.0</snakeyaml.version>
|
||||
</properties>
|
||||
|
||||
@ -76,6 +78,16 @@
|
||||
<version>${spring.boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>jakarta.activation</groupId>
|
||||
<artifactId>jakarta.activation-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>jakarta.mail</groupId>
|
||||
<artifactId>jakarta.mail-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@ -168,12 +180,6 @@
|
||||
<version>${sunactivation.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.activation</groupId>
|
||||
<artifactId>jakarta.activation-api</artifactId>
|
||||
<version>${jakarta.activation.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@ -237,20 +243,20 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- <!– GPG –>-->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.apache.maven.plugins</groupId>-->
|
||||
<!-- <artifactId>maven-gpg-plugin</artifactId>-->
|
||||
<!-- <version>1.6</version>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <phase>verify</phase>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>sign</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- </plugin>-->
|
||||
<!-- GPG -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!--Compiler -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@ -308,6 +314,20 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.6.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>ossrh</publishingServerId>
|
||||
<!-- 自动发布 -->
|
||||
<autoPublish>true</autoPublish>
|
||||
<!-- 等待发布 -->
|
||||
<waitUntil>published</waitUntil>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@ -57,11 +57,6 @@
|
||||
<version>${sunactivation.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.activation</groupId>
|
||||
<artifactId>jakarta.activation-api</artifactId>
|
||||
<version>${jakarta.activation.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
|
||||
Binary file not shown.
@ -1,18 +0,0 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
|
||||
Binary file not shown.
@ -1,18 +0,0 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
|
||||
@ -26,6 +26,11 @@ public class MailSmtpConfig {
|
||||
* */
|
||||
private String fromAddress;
|
||||
|
||||
/**
|
||||
* 发送人昵称
|
||||
* */
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 服务器地址
|
||||
* */
|
||||
@ -45,23 +50,23 @@ public class MailSmtpConfig {
|
||||
* 是否开启ssl 默认开启
|
||||
* */
|
||||
@Builder.Default
|
||||
private String isSSL = "true";
|
||||
private final String isSSL = "true";
|
||||
|
||||
/**
|
||||
* 是否开启验证 默认开启
|
||||
* */
|
||||
@Builder.Default
|
||||
private String isAuth = "true";
|
||||
private final String isAuth = "true";
|
||||
|
||||
/**
|
||||
* 重试间隔(单位:秒),默认为5秒
|
||||
*/
|
||||
@Builder.Default
|
||||
private int retryInterval = 5;
|
||||
private final int retryInterval = 5;
|
||||
|
||||
/**
|
||||
* 重试次数,默认为1次
|
||||
*/
|
||||
@Builder.Default
|
||||
private int maxRetries = 1;
|
||||
private final int maxRetries = 1;
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package org.dromara.email.comm.entity;
|
||||
import lombok.Getter;
|
||||
import org.dromara.email.comm.utils.ReflectUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -27,6 +28,12 @@ public class MailMessage {
|
||||
/** html模板文件的输入流,可来自任意可读取位置*/
|
||||
private InputStream htmlInputStream;
|
||||
|
||||
/** html内容,可以存在模板变量*/
|
||||
private String htmlContent;
|
||||
|
||||
/** html 模板文件的File对象*/
|
||||
private File htmlFile;
|
||||
|
||||
/** html 模板参数*/
|
||||
private Map<String,String> htmlValues;
|
||||
|
||||
@ -141,6 +148,18 @@ public class MailMessage {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html模板文件的File对象*/
|
||||
public MailsBuilder html(File htmlFile){
|
||||
mailMessage.htmlFile = htmlFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html内容直接输入*/
|
||||
public MailsBuilder htmlContent(String htmlContent){
|
||||
mailMessage.htmlContent = htmlContent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html 模板参数*/
|
||||
public MailsBuilder htmlValues(String key, String value){
|
||||
if (mailMessage.htmlValues == null){
|
||||
|
||||
@ -22,8 +22,6 @@ import java.util.Objects;
|
||||
**/
|
||||
public final class HtmlUtil {
|
||||
|
||||
private static final HtmlUtil htmlUtil = new HtmlUtil();
|
||||
|
||||
private HtmlUtil() {
|
||||
}
|
||||
|
||||
@ -34,8 +32,8 @@ public final class HtmlUtil {
|
||||
* @param name 模板文件名
|
||||
* @author :Wind
|
||||
*/
|
||||
public static List<String> readHtml(String name) throws MailException {
|
||||
try (InputStream is = HtmlUtil.class.getResourceAsStream("/template/" + name)) {
|
||||
public static List<String> readHtml(String name,Class<?> clazz) throws MailException {
|
||||
try (InputStream is = clazz.getResourceAsStream("/template/" + name)) {
|
||||
return readHtml(is);
|
||||
} catch (IOException e) {
|
||||
throw new MailException(e);
|
||||
@ -77,6 +75,12 @@ public final class HtmlUtil {
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new MailException(e);
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
Binary file not shown.
@ -1,18 +0,0 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
|
||||
@ -13,10 +13,6 @@
|
||||
<name>sms4j-Email-core</name>
|
||||
<description>sms4j-Email-core</description>
|
||||
|
||||
<properties>
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
@ -27,13 +23,6 @@
|
||||
<groupId>com.sun.activation</groupId>
|
||||
<artifactId>javax.activation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.activation</groupId>
|
||||
<artifactId>jakarta.activation-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
|
||||
@ -17,7 +17,7 @@ import java.util.Map;
|
||||
* 2023/6/8 22:35
|
||||
**/
|
||||
public class MailFactory{
|
||||
private static final Map<Object,MailSmtpConfig> configs = new HashMap<>();
|
||||
private static final Map<Object,MailSmtpConfig> CONFIGS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* createMailClient
|
||||
@ -27,7 +27,7 @@ public class MailFactory{
|
||||
*/
|
||||
public static MailClient createMailClient(Object key){
|
||||
try {
|
||||
return MailBuild.build(configs.get(key));
|
||||
return MailBuild.build(CONFIGS.get(key));
|
||||
} catch (MessagingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
@ -43,7 +43,7 @@ public class MailFactory{
|
||||
*/
|
||||
public static MailClient createMailClient(Object key, Blacklist blacklist){
|
||||
try {
|
||||
return MailBuild.build(configs.get(key),blacklist);
|
||||
return MailBuild.build(CONFIGS.get(key),blacklist);
|
||||
} catch (MessagingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
@ -57,7 +57,7 @@ public class MailFactory{
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void put(Object key, MailSmtpConfig config){
|
||||
configs.put(key,config);
|
||||
CONFIGS.put(key,config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ import java.util.Map;
|
||||
**/
|
||||
public class MonitorFactory {
|
||||
|
||||
private final static Map<String, MonitorService> services = new HashMap<>();
|
||||
private final static Map<String, MonitorService> SERVICES = new HashMap<>();
|
||||
|
||||
/**
|
||||
* put
|
||||
@ -30,7 +30,7 @@ public class MonitorFactory {
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void put(String key, MailImapConfig config, Monitor monitor){
|
||||
services.put(key,new MonitorService(config,monitor));
|
||||
SERVICES.put(key,new MonitorService(config,monitor));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,7 +40,7 @@ public class MonitorFactory {
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void start(String key){
|
||||
services.get(key).start();
|
||||
SERVICES.get(key).start();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,7 +50,7 @@ public class MonitorFactory {
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void stop(String key){
|
||||
services.get(key).stop();
|
||||
SERVICES.get(key).stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,6 +60,6 @@ public class MonitorFactory {
|
||||
* @author :Wind
|
||||
*/
|
||||
public static MailImapConfig getConfig(String key) {
|
||||
return services.get(key).getMailImapConfig();
|
||||
return SERVICES.get(key).getMailImapConfig();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package org.dromara.email.core.service;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Data;
|
||||
import org.dromara.email.api.Blacklist;
|
||||
import org.dromara.email.api.MailClient;
|
||||
@ -15,6 +16,7 @@ import javax.mail.Session;
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -43,12 +45,21 @@ public class MailBuild {
|
||||
props.put("mail.smtp.ssl.enable", config.getIsSSL());
|
||||
// props.put("mail.smtp.ssl.socketFactory", new MailSSLSocketFactory());
|
||||
this.session = Session.getInstance(props, new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(config.getUsername(), config.getPassword());
|
||||
}
|
||||
});
|
||||
this.message = new MimeMessage(session);
|
||||
this.message.setFrom(new InternetAddress(config.getFromAddress()));
|
||||
try {
|
||||
if (StrUtil.isEmpty(config.getNickName())){
|
||||
this.message.setFrom(new InternetAddress(config.getFromAddress()));
|
||||
}else {
|
||||
this.message.setFrom(new InternetAddress(config.getFromAddress(),config.getNickName()));
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
this.config = config;
|
||||
this.retryInterval = config.getRetryInterval();
|
||||
this.maxRetries = config.getMaxRetries();
|
||||
@ -63,12 +74,21 @@ public class MailBuild {
|
||||
// props.put("mail.smtp.ssl.socketFactory", new MailSSLSocketFactory());
|
||||
this.session = Session.getInstance(props,
|
||||
new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(config.getUsername(), config.getPassword());
|
||||
}
|
||||
});
|
||||
this.message = new MimeMessage(session);
|
||||
this.message.setFrom(new InternetAddress(config.getFromAddress()));
|
||||
try {
|
||||
if (StrUtil.isEmpty(config.getNickName())){
|
||||
this.message.setFrom(new InternetAddress(config.getFromAddress()));
|
||||
}else {
|
||||
this.message.setFrom(new InternetAddress(config.getFromAddress(),config.getNickName()));
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
this.config = config;
|
||||
this.blacklist = blacklist;
|
||||
this.retryInterval = config.getRetryInterval();
|
||||
@ -94,8 +114,9 @@ public class MailBuild {
|
||||
if (Objects.isNull(blacklist)) {
|
||||
return InternetAddress.parse(Objects.requireNonNull(CollUtil.join(source, ",")));
|
||||
}
|
||||
for (String s : blacklist.getBlacklist()) {
|
||||
if (!source.contains(s)) {
|
||||
List<String> black = blacklist.getBlacklist();
|
||||
for (String s : source) {
|
||||
if (!black.contains(s)) {
|
||||
list.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ import javax.mail.util.ByteArrayDataSource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -33,8 +34,8 @@ import java.util.logging.Logger;
|
||||
|
||||
public class MailService implements MailClient {
|
||||
|
||||
private static Logger logger = Logger.getLogger("mailLog");
|
||||
private MailBuild mailBuild;
|
||||
private static final Logger logger = Logger.getLogger("mailLog");
|
||||
private final MailBuild mailBuild;
|
||||
|
||||
private MailService(MailBuild mailBuild) {
|
||||
this.mailBuild = mailBuild;
|
||||
@ -50,8 +51,14 @@ public class MailService implements MailClient {
|
||||
if (mailMessage.getHtmlInputStream() != null) {
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlInputStream());
|
||||
}
|
||||
if (StrUtil.isNotBlank(mailMessage.getHtmlPath())){
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlPath());
|
||||
if (StrUtil.isNotBlank(mailMessage.getHtmlPath())) {
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlPath(), MailService.class);
|
||||
}
|
||||
if (mailMessage.getHtmlFile() != null) {
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlFile());
|
||||
}
|
||||
if (StrUtil.isNotBlank(mailMessage.getHtmlContent())) {
|
||||
html = Arrays.asList(mailMessage.getHtmlContent().split("\n"));
|
||||
}
|
||||
send(mailMessage.getMailAddress(),
|
||||
mailMessage.getTitle(),
|
||||
@ -130,7 +137,8 @@ public class MailService implements MailClient {
|
||||
List<String> cc,
|
||||
List<String> bcc) {
|
||||
int maxRetries = mailBuild.getMaxRetries();
|
||||
int retryCount = 1; // 初始值为1;则while循环中少发送一次,最后一次发送在判断 retryCount >= maxRetries 这里。
|
||||
// 初始值为1;则while循环中少发送一次,最后一次发送在判断 retryCount >= maxRetries 这里。
|
||||
int retryCount = 1;
|
||||
boolean retryOnFailure = true;
|
||||
|
||||
while (retryOnFailure && retryCount < maxRetries) {
|
||||
@ -143,7 +151,8 @@ public class MailService implements MailClient {
|
||||
message = messageBuild(mailAddress, title, body, null, null, zipName, cc, bcc, files);
|
||||
}
|
||||
Transport.send(message);
|
||||
retryOnFailure = false; // 发送成功,停止重试
|
||||
// 发送成功,停止重试
|
||||
retryOnFailure = false;
|
||||
} catch (MessagingException | IOException e) {
|
||||
retryCount++;
|
||||
try {
|
||||
@ -184,11 +193,17 @@ public class MailService implements MailClient {
|
||||
message.setSubject(title);
|
||||
|
||||
Multipart multipart = new MimeMultipart("alternative");
|
||||
if (CollUtil.isNotEmpty(html) && MapUtil.isNotEmpty(parameter)) {
|
||||
//读取模板并进行变量替换
|
||||
List<String> strings = HtmlUtil.replacePlaceholder(html, parameter);
|
||||
//拼合HTML数据
|
||||
String htmlData = HtmlUtil.pieceHtml(strings);
|
||||
if (CollUtil.isNotEmpty(html)) {
|
||||
String htmlData = null;
|
||||
List<String> strings;
|
||||
if (MapUtil.isNotEmpty(parameter)) {
|
||||
//读取模板并进行变量替换
|
||||
strings = HtmlUtil.replacePlaceholder(html, parameter);
|
||||
//拼合HTML数据
|
||||
htmlData = HtmlUtil.pieceHtml(strings);
|
||||
}else {
|
||||
htmlData = HtmlUtil.pieceHtml(html);
|
||||
}
|
||||
MimeBodyPart htmlPart = new MimeBodyPart();
|
||||
htmlPart.setContent(htmlData, "text/html;charset=UTF-8");
|
||||
multipart.addBodyPart(htmlPart);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.dromara.email.core.service;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.dromara.email.api.Monitor;
|
||||
import org.dromara.email.comm.config.MailImapConfig;
|
||||
import org.dromara.email.comm.entity.MonitorMessage;
|
||||
@ -32,6 +33,7 @@ import java.util.TimerTask;
|
||||
public class MonitorService{
|
||||
private final Store store;
|
||||
private Monitor monitor;
|
||||
@Getter
|
||||
private MailImapConfig mailImapConfig;
|
||||
private Timer timer;
|
||||
|
||||
@ -127,7 +129,4 @@ public class MonitorService{
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
public MailImapConfig getMailImapConfig() {
|
||||
return mailImapConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import java.util.List;
|
||||
/**
|
||||
* SmsBlend
|
||||
* <p> 通用接口,定义国内短信方法
|
||||
*
|
||||
* @author :Wind
|
||||
* 2023/5/16 16:03
|
||||
**/
|
||||
@ -16,12 +17,14 @@ public interface SmsBlend {
|
||||
|
||||
/**
|
||||
* 获取短信实例唯一标识
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getConfigId();
|
||||
|
||||
/**
|
||||
* 获取供应商标识
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getSupplier();
|
||||
@ -39,6 +42,16 @@ public interface SmsBlend {
|
||||
*/
|
||||
SmsResponse sendMessage(String phone, String message);
|
||||
|
||||
/**
|
||||
* sendMessage
|
||||
* <p>说明:发送固定消息模板多模板参数短信
|
||||
*
|
||||
* @param phone 接收短信的手机号
|
||||
* @param messages 模板内容
|
||||
* @author :Wind
|
||||
*/
|
||||
SmsResponse sendMessage(String phone, LinkedHashMap<String, String> messages);
|
||||
|
||||
/**
|
||||
* <p>说明:使用自定义模板发送短信
|
||||
* sendMessage
|
||||
@ -151,4 +164,43 @@ public interface SmsBlend {
|
||||
*/
|
||||
void delayMassTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages, Long delayedTime);
|
||||
|
||||
/**
|
||||
* <p>说明:加入黑名单【这个需要有全局操作的同时需要操作缓存,那么不给smsblend实际处理,代理部分处理】
|
||||
* joinInBlacklist
|
||||
*
|
||||
* @param phone 需要加入黑名单的手机号
|
||||
* @author :sh1yu
|
||||
*/
|
||||
default void joinInBlacklist(String phone) {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>说明:从黑名单移除【为了sms4j组件有统一入口,同时这个需要有全局操作的同时需要操作缓存,那么不给smsblend实际处理,代理部分处理】
|
||||
* removeFromBlacklist
|
||||
*
|
||||
* @param phone 需要加入黑名单的手机号
|
||||
* @author :sh1yu
|
||||
*/
|
||||
default void removeFromBlacklist(String phone) {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>说明:批量加入黑名单【为了sms4j组件有统一入口,同时这个需要有全局操作的同时需要操作缓存,那么不给smsblend实际处理,代理部分处理】
|
||||
* batchJoinBlacklist
|
||||
*
|
||||
* @param phones 需要加入黑名单的手机号数组
|
||||
* @author :sh1yu
|
||||
*/
|
||||
default void batchJoinBlacklist(List<String> phones) {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>说明:批量从黑名单移除【为了sms4j组件有统一入口,同时这个需要有全局操作的同时需要操作缓存,那么不给smsblend实际处理,代理部分处理】
|
||||
* batchRemovalFromBlacklist
|
||||
*
|
||||
* @param phones 需要移除黑名单的手机号数组
|
||||
* @author :sh1yu
|
||||
*/
|
||||
default void batchRemovalFromBlacklist(List<String> phones) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,15 @@ public interface SmsDao {
|
||||
*/
|
||||
Object get(String key);
|
||||
|
||||
/**
|
||||
* remove
|
||||
* <p> 根据key移除缓存
|
||||
* @param key 缓存键
|
||||
* @return 被删除的value
|
||||
* @author :Wind
|
||||
*/
|
||||
Object remove(String key);
|
||||
|
||||
/**
|
||||
* 清空
|
||||
*/
|
||||
|
||||
@ -81,6 +81,11 @@ public class SmsDaoDefaultImpl implements SmsDao {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object remove(String key) {
|
||||
return DATA_MAP.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clean() {
|
||||
DATA_MAP.clear();
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
package org.dromara.sms4j.api.proxy;
|
||||
|
||||
import org.dromara.sms4j.comm.constant.NumberOfParasmeters;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
/**
|
||||
* 核心方法执行器接口
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
public interface CoreMethodProcessor extends SmsProcessor {
|
||||
@Override
|
||||
default Object[] preProcessor(Method method, Object source, Object[] param) {
|
||||
String name = method.getName();
|
||||
int parameterCount = method.getParameterCount();
|
||||
if ("sendMessage".equals(name)) {
|
||||
if (NumberOfParasmeters.TWO == NumberOfParasmeters.getNumberOfParasmetersEnum(parameterCount)) {
|
||||
sendMessagePreProcess((String) param[0], param[1]);
|
||||
return param;
|
||||
}
|
||||
if (NumberOfParasmeters.THREE == NumberOfParasmeters.getNumberOfParasmetersEnum(parameterCount)) {
|
||||
if (null == param[2]){
|
||||
param[2] = new LinkedHashMap<>();
|
||||
}
|
||||
sendMessageByTemplatePreProcess((String)param[0],(String) param[1],(LinkedHashMap<String, String>)param[2]);
|
||||
return param;
|
||||
}
|
||||
}
|
||||
if ("massTexting".equals(name)) {
|
||||
if (NumberOfParasmeters.TWO == NumberOfParasmeters.getNumberOfParasmetersEnum(parameterCount)) {
|
||||
massTextingPreProcess((List<String>)param[0],(String)param[1]);
|
||||
return param;
|
||||
}
|
||||
if (NumberOfParasmeters.THREE == NumberOfParasmeters.getNumberOfParasmetersEnum(parameterCount)) {
|
||||
if (null == param[2]){
|
||||
param[2] = new LinkedHashMap<>();
|
||||
}
|
||||
massTextingByTemplatePreProcess((List<String>)param[0],(String)param[1],(LinkedHashMap<String, String>)param[2]);
|
||||
return param;
|
||||
}
|
||||
}
|
||||
return param;
|
||||
}
|
||||
void sendMessagePreProcess(String phone, Object message);
|
||||
void sendMessageByTemplatePreProcess(String phone, String templateId, LinkedHashMap<String, String> messages);
|
||||
void massTextingPreProcess(List<String> phones, String message);
|
||||
void massTextingByTemplatePreProcess(List<String> phones, String templateId, LinkedHashMap<String, String> messages);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package org.dromara.sms4j.api.proxy;
|
||||
/**
|
||||
* 排序接口 用户拦截器排序并进行有序执行,请注意,排序值应大于等于0。
|
||||
* 拦截器的排序依照从小到大的规则进行排序
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
public interface Order {
|
||||
|
||||
default int getOrder(){
|
||||
return 999;
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
package org.dromara.sms4j.api.proxy;
|
||||
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
|
||||
/**
|
||||
* 短信拦截处理接口
|
||||
*/
|
||||
public interface RestrictedProcess {
|
||||
|
||||
/**
|
||||
* 拦截校验过程
|
||||
* @param phone
|
||||
* @return
|
||||
*/
|
||||
SmsBlendException process(String phone);
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package org.dromara.sms4j.api.proxy;
|
||||
|
||||
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
/**
|
||||
* 执行器接口
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
public interface SmsProcessor extends Order {
|
||||
/**
|
||||
* preProcessor
|
||||
* <p> 前置拦截方法 此方法将在短信发送方法之前进行执行,但请勿在此方法中修改实例对象或者方法对象,否则可能会导致并发问题
|
||||
* @param method 方法对象
|
||||
* @param source 实例对象
|
||||
* @param param 参数列表
|
||||
* @author :Wind
|
||||
*/
|
||||
default Object[] preProcessor(Method method, Object source, Object[] param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* postProcessor
|
||||
* <p> 后置拦截方法 此方法执行在发送方法执行完毕之后获取到返回值之后
|
||||
* @param result 返回值
|
||||
* @param param 参数列表
|
||||
* @author :Wind
|
||||
*/
|
||||
default Object postProcessor(SmsResponse result, Object[] param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* exceptionHandleProcessor
|
||||
* <p> 异常拦截执行器,在发送方法执行过程中发生异常,将会通过此方法进行反馈。
|
||||
* <p> 请注意,此方法一旦捕捉到相应异常并抛出新异常后,会中断后续执行器的进行 如果在所有的异常执行器中均没有抛出异常,则后续会进入后置方法执行器
|
||||
* @param method 方法对象
|
||||
* @param source 实例对象
|
||||
* @param param 参数列表
|
||||
* @param exception 异常
|
||||
* @author :Wind
|
||||
*/
|
||||
default void exceptionHandleProcessor(Method method, Object source, Object[] param, Exception exception) throws RuntimeException {
|
||||
throw new SmsBlendException(exception);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package org.dromara.sms4j.api.proxy;
|
||||
|
||||
import java.util.List;
|
||||
/**
|
||||
* 支持接口,如果执行器需要根据支持厂商加载,那可以实现此接口
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
public interface SuppotFilter{
|
||||
List<String> getSupports();
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package org.dromara.sms4j.api.proxy.aware;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 厂商配置感知接口
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
public interface SmsBlendConfigAware {
|
||||
void setSmsBlendsConfig(Map<String, Map<String, Object>> blends);
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package org.dromara.sms4j.api.proxy.aware;
|
||||
|
||||
|
||||
/**
|
||||
* 系统配置感知接口
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
public interface SmsConfigAware {
|
||||
void setSmsConfig(Object config);
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package org.dromara.sms4j.api.proxy.aware;
|
||||
|
||||
import org.dromara.sms4j.api.dao.SmsDao;
|
||||
/**
|
||||
* 缓存感知接口
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
public interface SmsDaoAware {
|
||||
void setSmsDao(SmsDao smsDao);
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package org.dromara.sms4j.api.universal;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 短信代理配置类
|
||||
*/
|
||||
@Data
|
||||
public class ProxyConfig implements Serializable {
|
||||
|
||||
/**
|
||||
* 是否启用代理 默认不启用
|
||||
*/
|
||||
private Boolean enable = false;
|
||||
|
||||
/**
|
||||
* 代理服务器地址
|
||||
*/
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* 代理服务器端口
|
||||
*/
|
||||
private Integer port;
|
||||
}
|
||||
@ -22,4 +22,9 @@ public interface SupplierConfig {
|
||||
*/
|
||||
String getSupplier();
|
||||
|
||||
/**
|
||||
* 获取代理配置
|
||||
*
|
||||
*/
|
||||
ProxyConfig getProxy();
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
package org.dromara.sms4j.api.utils;
|
||||
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
|
||||
public class SmsRespUtils {
|
||||
private SmsRespUtils() {
|
||||
} //私有构造防止实例化
|
||||
|
||||
public static SmsResponse error(){
|
||||
return error("error no response", null);
|
||||
}
|
||||
|
||||
public static SmsResponse error(String configId){
|
||||
return error("error no response", configId);
|
||||
}
|
||||
|
||||
public static SmsResponse error(String detailMessage, String configId){
|
||||
return resp(detailMessage, false, configId);
|
||||
}
|
||||
|
||||
public static SmsResponse success(){
|
||||
return success(null);
|
||||
}
|
||||
|
||||
public static SmsResponse success(Object data){
|
||||
return success(data, null);
|
||||
}
|
||||
|
||||
public static SmsResponse resp(Object data, boolean success){
|
||||
return resp(data, success, null);
|
||||
}
|
||||
|
||||
public static SmsResponse success(Object data, String configId){
|
||||
return resp(data, true, configId);
|
||||
}
|
||||
|
||||
public static SmsResponse resp(boolean success){
|
||||
return success ? success() : error();
|
||||
}
|
||||
|
||||
public static SmsResponse resp(boolean success, String configId){
|
||||
return resp(null, success, configId);
|
||||
}
|
||||
|
||||
public static SmsResponse resp(Object data, boolean success, String configId){
|
||||
SmsResponse error = new SmsResponse();
|
||||
error.setSuccess(success);
|
||||
error.setData(data);
|
||||
error.setConfigId(configId);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.sms4j.api.verify;
|
||||
|
||||
/**
|
||||
* PhoneVerify
|
||||
* <p> 实现校验手机号合规的接口
|
||||
* @author :Wind
|
||||
* 2024/3/28 14:15
|
||||
**/
|
||||
public interface PhoneVerify{
|
||||
|
||||
/**
|
||||
* verifyPhone
|
||||
* <p>用于校验手机号是否合理的规则方法,可以尝试重写此方法以改变规则,例如你可以选择使用正则表达式来进行
|
||||
* 一系列更加精准和严格的校验,此校验优先级最高,会在黑名单和其他拦截之前执行。
|
||||
* 当此校验触发时候,将会直接以异常形式进行抛出,并终止后续向厂商请求的动作,故而不会有返回值。
|
||||
* <p>当校验手机号合格时应返回 true 否则返回 false
|
||||
* @param phone 被校验的手机号
|
||||
* @author :Wind
|
||||
*/
|
||||
default boolean verifyPhone(String phone){
|
||||
return phone.length() == 11;
|
||||
}
|
||||
}
|
||||
@ -8,28 +8,54 @@ package org.dromara.sms4j.comm.constant;
|
||||
* 2023/3/31 19:33
|
||||
**/
|
||||
public abstract class Constant {
|
||||
/** 项目版本号*/
|
||||
public static final String VERSION = "V 3.0.1";
|
||||
/**
|
||||
* 项目版本号
|
||||
*/
|
||||
public static final String VERSION = "V 3.3.5";
|
||||
|
||||
/**
|
||||
* 用于格式化鉴权头域,给"Authorization"参数赋值
|
||||
*/
|
||||
public static final String HUAWEI_AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"";
|
||||
|
||||
/**
|
||||
* 用于格式化鉴权头域,给"X-WSSE"参数赋值
|
||||
*/
|
||||
public static final String HUAWEI_WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
|
||||
|
||||
/**
|
||||
* 华为云国内短信访问URI
|
||||
*/
|
||||
public static final String HUAWEI_REQUEST_URL = "/sms/batchSendSms/v1";
|
||||
|
||||
/**
|
||||
* Content-Type
|
||||
*/
|
||||
public static final String FROM_URLENCODED = "application/x-www-form-urlencoded";
|
||||
public static final String CONTENT_TYPE = "Content-Type";
|
||||
|
||||
public static final String ACCEPT = "application/json";
|
||||
/**
|
||||
* Authorization
|
||||
*/
|
||||
public static final String AUTHORIZATION = "Authorization";
|
||||
|
||||
/**
|
||||
* Accept
|
||||
*/
|
||||
public static final String ACCEPT = "Accept";
|
||||
|
||||
/**
|
||||
* x-www-form-urlencoded
|
||||
*/
|
||||
public static final String APPLICATION_FROM_URLENCODED = "application/x-www-form-urlencoded";
|
||||
|
||||
/**
|
||||
* application/json
|
||||
*/
|
||||
public static final String APPLICATION_JSON = "application/json";
|
||||
|
||||
/**
|
||||
* application/json; charset=utf-8
|
||||
*/
|
||||
public static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";
|
||||
|
||||
/**
|
||||
@ -37,8 +63,10 @@ public abstract class Constant {
|
||||
*/
|
||||
public static final String HUAWEI_JAVA_DATE = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
|
||||
/** 云片短信国内短信请求地址*/
|
||||
public static final String YUNPIAN_URL = "https://sms.yunpian.com/v2";
|
||||
/**
|
||||
* 云片短信国内短信请求地址
|
||||
*/
|
||||
public static final String YUNPIAN_URL = Constant.HTTPS_PREFIX + "sms.yunpian.com/v2";
|
||||
|
||||
/**
|
||||
* https请求前缀
|
||||
@ -50,6 +78,11 @@ public abstract class Constant {
|
||||
*/
|
||||
public static final String SUPPLIER_KEY = "supplier";
|
||||
|
||||
/**
|
||||
* 自定义实现工厂路径
|
||||
*/
|
||||
public static final String FACTORY_PATH = "factory";
|
||||
|
||||
private Constant() {
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
package org.dromara.sms4j.comm.constant;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* NumberOfParasmeters
|
||||
* <p> 重载方法的参数个数
|
||||
*
|
||||
* @author :sh1yu
|
||||
* 2023/11/01 19:33
|
||||
**/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum NumberOfParasmeters {
|
||||
//一个参数
|
||||
ONE(1),
|
||||
//两个参数
|
||||
TWO(2),
|
||||
//三个参数
|
||||
THREE(3);
|
||||
private final int code;
|
||||
|
||||
public static NumberOfParasmeters getNumberOfParasmetersEnum(int index) {
|
||||
switch (index) {
|
||||
case 1:
|
||||
return NumberOfParasmeters.ONE;
|
||||
case 2:
|
||||
return NumberOfParasmeters.TWO;
|
||||
case 3:
|
||||
return NumberOfParasmeters.THREE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new IllegalArgumentException("building enum NumberOfParasmeters error,param not match");
|
||||
}
|
||||
|
||||
}
|
||||
@ -10,7 +10,7 @@ public abstract class SupplierConstant {
|
||||
*/
|
||||
public static final String ALIBABA = "alibaba";
|
||||
/**
|
||||
* 容连云
|
||||
* 容联云
|
||||
*/
|
||||
public static final String CLOOPEN = "cloopen";
|
||||
/**
|
||||
@ -49,5 +49,52 @@ public abstract class SupplierConstant {
|
||||
* 助通
|
||||
*/
|
||||
public static final String ZHUTONG = "zhutong";
|
||||
|
||||
/**
|
||||
* 联麓
|
||||
*/
|
||||
public static final String LIANLU = "lianlu";
|
||||
/**
|
||||
* 鼎众
|
||||
*/
|
||||
public static final String DINGZHONG = "dingzhong";
|
||||
/**
|
||||
* 七牛云
|
||||
*/
|
||||
public static final String QINIU = "qiniu";
|
||||
/**
|
||||
* 创蓝
|
||||
*/
|
||||
public static final String CHUANGLAN = "chuanglan";
|
||||
/**
|
||||
* 极光
|
||||
*/
|
||||
public static final String JIGUANG = "jiguang";
|
||||
/**
|
||||
* 布丁云V2
|
||||
*/
|
||||
public static final String BUDING_V2 = "buding_v2";
|
||||
/**
|
||||
* 中国移动 云MAS
|
||||
*/
|
||||
public static final String MAS = "mas";
|
||||
/**
|
||||
* 百度云 sms
|
||||
*/
|
||||
public static final String BAIDU = "baidu";
|
||||
/**
|
||||
* 螺丝帽 sms
|
||||
*/
|
||||
public static final String LUO_SI_MAO = "luosimao";
|
||||
/**
|
||||
* SUBMAIL sms
|
||||
*/
|
||||
public static final String MY_SUBMAIL = "mysubmail";
|
||||
/**
|
||||
* danmi sms
|
||||
*/
|
||||
public static final String DAN_MI = "danmi";
|
||||
/**
|
||||
* 联通一信通 sms
|
||||
*/
|
||||
public static final String YIXINTONG = "yixintong";
|
||||
}
|
||||
|
||||
@ -5,9 +5,14 @@ import lombok.Data;
|
||||
import java.util.TimerTask;
|
||||
|
||||
@Data
|
||||
public class Task{
|
||||
|
||||
private TimerTask runnable;//描述要执行的任务
|
||||
private Long time;//什么时间执行,用时间戳来表示
|
||||
public class Task {
|
||||
/**
|
||||
* 描述要执行的任务
|
||||
*/
|
||||
private TimerTask runnable;
|
||||
/**
|
||||
* 什么时间执行,用时间戳来表示
|
||||
*/
|
||||
private Long time;
|
||||
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
package org.dromara.sms4j.comm.enumerate;
|
||||
package org.dromara.sms4j.comm.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* ConfigType
|
||||
@ -6,6 +8,7 @@ package org.dromara.sms4j.comm.enumerate;
|
||||
* @author :Wind
|
||||
* 2023/4/5 19:08
|
||||
**/
|
||||
@Getter
|
||||
public enum ConfigType {
|
||||
/** yaml配置文件 */
|
||||
YAML("yaml"),
|
||||
@ -18,7 +21,4 @@ public enum ConfigType {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@ package org.dromara.sms4j.comm.exception;
|
||||
|
||||
public class SmsBlendException extends RuntimeException{
|
||||
public String code;
|
||||
public String message;
|
||||
public final String message;
|
||||
public String requestId;
|
||||
|
||||
public SmsBlendException(String message) {
|
||||
@ -22,4 +22,9 @@ public class SmsBlendException extends RuntimeException{
|
||||
this.code = code;
|
||||
this.requestId = requestId;
|
||||
}
|
||||
|
||||
public SmsBlendException(Exception e){
|
||||
super(e);
|
||||
this.message = e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
package org.dromara.sms4j.comm.exception;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
@Setter
|
||||
public class SmsSqlException extends RuntimeException{
|
||||
/**
|
||||
* -- SETTER --
|
||||
* 设置 message
|
||||
*/
|
||||
private String message;
|
||||
@Override
|
||||
public String getMessage() {
|
||||
@ -11,10 +18,4 @@ public class SmsSqlException extends RuntimeException{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 message
|
||||
*/
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,215 @@
|
||||
package org.dromara.sms4j.comm.utils;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* <p>类名: SmsDateUtils
|
||||
* <p>说明: 时间日期工具类
|
||||
*
|
||||
* @author :bleachtred
|
||||
* 2024/6/21 23:59
|
||||
**/
|
||||
public class SmsDateUtils extends DateUtil {
|
||||
|
||||
private SmsDateUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 格林威治标准时间(GMT)或世界协调时间(UTC)
|
||||
*/
|
||||
private static final String GMT = "GMT";
|
||||
|
||||
/**
|
||||
* 东八区
|
||||
*/
|
||||
private static final String GMT_8 = SmsDateUtils.GMT + "+8:00";
|
||||
|
||||
/**
|
||||
* 天翼云、七牛云时间格式
|
||||
*/
|
||||
private static final String PURE_DATE_UTC_PATTERN = "yyyyMMdd'T'HHmmss'Z'";
|
||||
|
||||
/**
|
||||
* 获取格林威治标准时间(GMT)或世界协调时间(UTC)
|
||||
* @return TimeZone
|
||||
*/
|
||||
public static TimeZone gmt(){
|
||||
return getTimeZone(GMT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取东八区时区
|
||||
* @return TimeZone
|
||||
*/
|
||||
public static TimeZone gmt8(){
|
||||
return getTimeZone(GMT_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时区
|
||||
* @param zoneId zoneId
|
||||
* @return TimeZone
|
||||
*/
|
||||
public static TimeZone getTimeZone(String zoneId){
|
||||
return TimeZone.getTimeZone(zoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SimpleDateFormat
|
||||
* @param pattern 时间格式
|
||||
* @return SimpleDateFormat
|
||||
*/
|
||||
public static SimpleDateFormat sdfGmt(String pattern){
|
||||
return sdf(pattern, gmt());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SimpleDateFormat
|
||||
* @param pattern 时间格式
|
||||
* @return SimpleDateFormat
|
||||
*/
|
||||
public static SimpleDateFormat sdfGmt8(String pattern){
|
||||
return sdf(pattern, gmt8());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SimpleDateFormat
|
||||
* @param pattern 时间格式
|
||||
* @param timeZone 时区
|
||||
* @return 获取SimpleDateFormat
|
||||
*/
|
||||
public static SimpleDateFormat sdf(String pattern, TimeZone timeZone){
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
|
||||
sdf.setTimeZone(timeZone);
|
||||
return sdf;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
* @param date 时间
|
||||
* @param pattern 时间格式
|
||||
* @return String
|
||||
*/
|
||||
public static String formatGmtDateToStr(Date date, String pattern){
|
||||
SimpleDateFormat sdf = sdfGmt(pattern);
|
||||
return sdf.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
* @param date 时间
|
||||
* @param pattern 时间格式
|
||||
* @return String
|
||||
*/
|
||||
public static String formatGmt8DateToStr(Date date, String pattern){
|
||||
SimpleDateFormat sdf = sdfGmt8(pattern);
|
||||
return sdf.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
* @param date 时间
|
||||
* @param pattern 时间格式
|
||||
* @param timeZone 时区
|
||||
* @return String
|
||||
*/
|
||||
public static String formatDateToStr(Date date, String pattern, TimeZone timeZone){
|
||||
SimpleDateFormat sdf = sdf(pattern, timeZone);
|
||||
return sdf.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式:yyyy-MM-dd'T'HH:mm:ss'Z'
|
||||
* @param date 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String utcGmt(Date date){
|
||||
return formatGmtDateToStr(date, DatePattern.UTC_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式:yyyy-MM-dd'T'HH:mm:ss'Z'
|
||||
* @param date 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String utcGmt8(Date date){
|
||||
return formatGmt8DateToStr(date, DatePattern.UTC_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式:yyyyMMdd
|
||||
* @param date 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String pureDateGmt(Date date){
|
||||
return formatGmtDateToStr(date, DatePattern.PURE_DATE_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式:yyyyMMdd
|
||||
* @param date 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String pureDateGmt8(Date date){
|
||||
return formatGmt8DateToStr(date, DatePattern.PURE_DATE_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 天翼云、七牛云时间格式:yyyyMMdd'T'HHmmss'Z'
|
||||
* @param date 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String pureDateUtcGmt(Date date){
|
||||
return formatGmtDateToStr(date, PURE_DATE_UTC_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 天翼云、七牛云时间格式:yyyyMMdd'T'HHmmss'Z'
|
||||
* @param date 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String pureDateUtcGmt8(Date date){
|
||||
return formatGmt8DateToStr(date, PURE_DATE_UTC_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式:yyyy-MM-dd
|
||||
* @param date 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String normDateGmt(Date date){
|
||||
return formatGmtDateToStr(date, DatePattern.NORM_DATE_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式:yyyy-MM-dd
|
||||
* @param date 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String normDateGmt8(Date date){
|
||||
return formatGmt8DateToStr(date, DatePattern.NORM_DATE_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式:yyyy-MM-dd HH:mm:ss
|
||||
* @param date 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String normDatetimeGmt(Date date){
|
||||
return formatGmtDateToStr(date, DatePattern.NORM_DATETIME_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式:yyyy-MM-dd HH:mm:ss
|
||||
* @param date 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static String normDatetimeGmt8(Date date){
|
||||
return formatGmt8DateToStr(date, DatePattern.NORM_DATETIME_PATTERN);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,11 @@
|
||||
package org.dromara.sms4j.comm.utils;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.Method;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
@ -11,69 +14,238 @@ import java.util.Map;
|
||||
|
||||
public class SmsHttpUtils {
|
||||
|
||||
/**
|
||||
* 是否启用代理 默认不启用
|
||||
*/
|
||||
private final Boolean enable;
|
||||
|
||||
/**
|
||||
* 代理服务器地址
|
||||
*/
|
||||
private final String host;
|
||||
|
||||
/**
|
||||
* 代理服务器端口
|
||||
*/
|
||||
private final Integer port;
|
||||
|
||||
// 无代理单例(饿汉式加载)
|
||||
private static final SmsHttpUtils NON_PROXY_INSTANCE = new SmsHttpUtils();
|
||||
|
||||
// 代理单例(双重校验锁延迟加载)
|
||||
private static volatile SmsHttpUtils PROXY_INSTANCE;
|
||||
|
||||
// 无代理构造方法
|
||||
private SmsHttpUtils() {
|
||||
this.enable = false;
|
||||
this.host = null;
|
||||
this.port = null;
|
||||
}
|
||||
|
||||
private static class SmsHttpHolder {
|
||||
private static final SmsHttpUtils INSTANCE = new SmsHttpUtils();
|
||||
// 代理构造方法
|
||||
private SmsHttpUtils(String host, Integer port) {
|
||||
this.enable = true;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取无代理单例
|
||||
*/
|
||||
public static SmsHttpUtils instance() {
|
||||
return SmsHttpHolder.INSTANCE;
|
||||
return NON_PROXY_INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理单例(线程安全 + 参数校验)
|
||||
*/
|
||||
public static SmsHttpUtils instance(String host, Integer port) {
|
||||
if (PROXY_INSTANCE == null) {
|
||||
synchronized (SmsHttpUtils.class) {
|
||||
if (PROXY_INSTANCE == null) {
|
||||
validateProxyParams(host, port);
|
||||
PROXY_INSTANCE = new SmsHttpUtils(host, port);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 二次调用时校验参数一致性
|
||||
if (!PROXY_INSTANCE.host.equals(host) || !PROXY_INSTANCE.port.equals(port)) {
|
||||
throw new IllegalStateException("Proxy parameters cannot be modified after initialization");
|
||||
}
|
||||
}
|
||||
return PROXY_INSTANCE;
|
||||
}
|
||||
|
||||
// 代理参数校验
|
||||
private static void validateProxyParams(String host, Integer port) {
|
||||
if (StrUtil.isBlank(host) || port == null || port <= 0) {
|
||||
throw new IllegalArgumentException("Invalid proxy host or port");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置请求 是否走代理
|
||||
* @param url 请求地址
|
||||
* @return HttpRequest
|
||||
*/
|
||||
private HttpRequest request(String url){
|
||||
HttpRequest request = HttpRequest.of(url);
|
||||
if (enable){
|
||||
request.setHttpProxy(host, port);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造post请求
|
||||
* @param url 请求地址
|
||||
* @return HttpRequest
|
||||
*/
|
||||
private HttpRequest post(String url){
|
||||
HttpRequest post = request(url);
|
||||
post.setMethod(Method.POST);
|
||||
return post;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造get请求
|
||||
* @param url 请求地址
|
||||
* @return HttpRequest
|
||||
*/
|
||||
private HttpRequest get(String url){
|
||||
HttpRequest get = request(url);
|
||||
get.setMethod(Method.GET);
|
||||
return get;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送post json请求
|
||||
* @param url 请求地址
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param headers 请求头
|
||||
* @param body 请求体(json格式字符串)
|
||||
* @param body 请求体(json格式字符串)
|
||||
* @return 返回体
|
||||
*/
|
||||
public JSONObject postJson(String url, Map<String, String> headers, String body){
|
||||
try(HttpResponse response = HttpRequest.post(url)
|
||||
public JSONObject postJson(String url, Map<String, String> headers, String body) {
|
||||
try (HttpResponse response = post(url)
|
||||
.addHeaders(headers)
|
||||
.body(body)
|
||||
.execute()){
|
||||
.execute()) {
|
||||
return JSONUtil.parseObj(response.body());
|
||||
}catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送post json请求
|
||||
* @param url 请求地址
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param headers 请求头
|
||||
* @param body 请求体(map格式请求体)
|
||||
* @param body 请求体(map格式请求体)
|
||||
* @return 返回体
|
||||
*/
|
||||
public JSONObject postJson(String url, Map<String, String> headers, Map<String, Object> body){
|
||||
public JSONObject postJson(String url, Map<String, String> headers, Map<String, Object> body) {
|
||||
return postJson(url, headers, JSONUtil.toJsonStr(body));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送post form 请求
|
||||
* @param url 请求地址
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param headers 请求头
|
||||
* @param body 请求体(map格式请求体)
|
||||
* @param body 请求体(map格式请求体)
|
||||
* @return 返回体
|
||||
*/
|
||||
public JSONObject postFrom(String url, Map<String, String> headers, Map<String, Object> body){
|
||||
try(HttpResponse response = HttpRequest.post(url)
|
||||
public JSONObject postFrom(String url, Map<String, String> headers, Map<String, Object> body) {
|
||||
try (HttpResponse response = post(url)
|
||||
.addHeaders(headers)
|
||||
.form(body)
|
||||
.execute()){
|
||||
.execute()) {
|
||||
return JSONUtil.parseObj(response.body());
|
||||
}catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送post form 请求
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param headers 请求头
|
||||
* @param body 请求体(map格式请求体)
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @return 返回体
|
||||
*/
|
||||
public JSONObject postBasicFrom(String url, Map<String, String> headers, String username, String password, Map<String, Object> body) {
|
||||
try (HttpResponse response = post(url)
|
||||
.addHeaders(headers)
|
||||
.basicAuth(username, password)
|
||||
.form(body)
|
||||
.execute()) {
|
||||
return JSONUtil.parseObj(response.body());
|
||||
} catch (Exception e) {
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送post url 参数拼装url传输
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param headers 请求头
|
||||
* @param params 请求参数
|
||||
* @return 返回体
|
||||
*/
|
||||
public JSONObject postUrl(String url, Map<String, String> headers, Map<String, Object> params) {
|
||||
String urlWithParams = url + "?" + URLUtil.buildQuery(params, null);
|
||||
try (HttpResponse response = post(urlWithParams)
|
||||
.addHeaders(headers)
|
||||
.execute()) {
|
||||
return JSONUtil.parseObj(response.body());
|
||||
} catch (Exception e) {
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送get
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @return 返回体
|
||||
*/
|
||||
public JSONObject getBasic(String url, String username, String password) {
|
||||
try (HttpResponse response = get(url)
|
||||
.basicAuth(username, password)
|
||||
.execute()) {
|
||||
return JSONUtil.parseObj(response.body());
|
||||
} catch (Exception e) {
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送get
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @return 返回体
|
||||
*/
|
||||
public JSONObject getUrl(String url) {
|
||||
try (HttpResponse response = get(url)
|
||||
.execute()) {
|
||||
return JSONUtil.parseObj(response.body());
|
||||
} catch (Exception e) {
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 线程睡眠
|
||||
*
|
||||
* @param retryInterval 秒
|
||||
*/
|
||||
public void safeSleep(int retryInterval){
|
||||
public void safeSleep(int retryInterval) {
|
||||
ThreadUtil.safeSleep(retryInterval * 1000L);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,15 +2,22 @@ package org.dromara.sms4j.comm.utils;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author wind
|
||||
@ -113,18 +120,38 @@ public class SmsUtils {
|
||||
* @param list 要转换的list
|
||||
* @author :Wind
|
||||
*/
|
||||
public static String listToString(List<String> list) {
|
||||
return CollUtil.join(list, ",");
|
||||
public static String joinComma(List<String> list) {
|
||||
return CollUtil.join(list, StrUtil.COMMA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以 conjunction 为分隔符将集合转换为字符串
|
||||
* 切分字符串
|
||||
*
|
||||
* @param list 集合
|
||||
* @param str 被切分的字符串
|
||||
* @return 分割后的数据列表
|
||||
*/
|
||||
public static List<String> splitTrimComma(String str) {
|
||||
return StrUtil.splitTrim(str, StrUtil.COMMA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将手机号码 添加+86中国的电话国际区号前缀
|
||||
*
|
||||
* @param phones 手机号码集合
|
||||
* @return 结果字符串
|
||||
*/
|
||||
public static String arrayToString(List<String> list) {
|
||||
return CollUtil.join(list, ",", str -> StrUtil.addPrefixIfNot(str, "+86"));
|
||||
public static String addCodePrefixIfNot(List<String> phones) {
|
||||
return CollUtil.join(phones, StrUtil.COMMA, SmsUtils::addCodePrefixIfNot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将手机号码 添加+86电话区号前缀
|
||||
*
|
||||
* @param phone 手机号码
|
||||
* @return 结果字符串
|
||||
*/
|
||||
public static String addCodePrefixIfNot(String phone) {
|
||||
return StrUtil.addPrefixIfNot(phone, "+86");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,10 +160,10 @@ public class SmsUtils {
|
||||
* @param list 集合
|
||||
* @return 结果字符串
|
||||
*/
|
||||
public static String[] listToArray(List<String> list) {
|
||||
public static String[] addCodePrefixIfNotToArray(List<String> list) {
|
||||
List<String> toStr = new ArrayList<>();
|
||||
for (String s : list) {
|
||||
toStr.add(StrUtil.addPrefixIfNot(s, "+86"));
|
||||
toStr.add(addCodePrefixIfNot(s));
|
||||
}
|
||||
return toStr.toArray(new String[list.size()]);
|
||||
}
|
||||
@ -144,20 +171,20 @@ public class SmsUtils {
|
||||
/**
|
||||
* 将Map中所有key的分隔符转换为新的分隔符
|
||||
* @param map map对象
|
||||
* @param seperator 旧分隔符
|
||||
* @param newSeperator 新分隔符
|
||||
* @param separator 旧分隔符
|
||||
* @param newSeparator 新分隔符
|
||||
*/
|
||||
public static void replaceKeysSeperator(Map<String, Object> map, String seperator, String newSeperator) {
|
||||
public static void replaceKeysSeparator(Map<String, Object> map, String separator, String newSeparator) {
|
||||
if(CollUtil.isEmpty(map)) {
|
||||
return;
|
||||
}
|
||||
List<String> keySet = new ArrayList<>(map.keySet());
|
||||
for(String key : keySet) {
|
||||
if(StrUtil.isEmpty(key) || !key.contains(seperator)) {
|
||||
if(StrUtil.isEmpty(key) || !key.contains(separator)) {
|
||||
continue;
|
||||
}
|
||||
String value = String.valueOf(map.get(key));
|
||||
String newKey = key.replaceAll(seperator, newSeperator);
|
||||
String newKey = key.replaceAll(separator, newSeparator);
|
||||
map.putIfAbsent(newKey, value);
|
||||
map.remove(key);
|
||||
}
|
||||
@ -172,4 +199,76 @@ public class SmsUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static LinkedHashMap<String, String> buildMessageByAmpersand(String message) {
|
||||
if (isEmpty(message)){
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
String[] split = message.split("&");
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>(split.length);
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
map.put(String.valueOf(i), split[i]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将任意类型集合转成想要的数组
|
||||
* @param list 需要转换的集合
|
||||
* @param predicate 过滤条件
|
||||
* @param mapper 对此流的元素执行函数
|
||||
* @param array 想要的数组
|
||||
* @return 数组
|
||||
* @param <T> 集合泛型
|
||||
* @param <E> 想要的数组类型
|
||||
*/
|
||||
public static <E, T> E[] toArray(Collection<T> list, Predicate<T> predicate, Function<? super T, ? extends E> mapper, E[] array) {
|
||||
if (isEmpty(list)) {
|
||||
return array.clone();
|
||||
}
|
||||
return list.stream().filter(predicate).map(mapper).toArray(size -> array.clone());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将map的value转成数组
|
||||
* @param map Map
|
||||
* @return 数组
|
||||
*/
|
||||
public static String[] toArray(Map<String, String> map){
|
||||
if (isEmpty(map)) {
|
||||
return new String[0];
|
||||
}
|
||||
return toArray(map.values(), SmsUtils::isNotEmpty, s -> s, new String[map.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将所有提交的参数升序排列,并排除部分key字段后,将key与value用"="连接起来 组成"key=value" + "&"(连接符)+ "key=value" 的方式
|
||||
* @param params 参数Map
|
||||
* @param excludes 排除的key
|
||||
* @return String
|
||||
*/
|
||||
public static String sortedParamsAsc(Map<String, Object> params, String... excludes) {
|
||||
if (MapUtil.isEmpty(params)){
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
List<String> keys = new ArrayList<>(params.keySet());
|
||||
if (CollUtil.isEmpty(keys)){
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
if (ArrayUtil.isNotEmpty(excludes)){
|
||||
ArrayList<String> excludeKeys = CollUtil.toList(excludes);
|
||||
keys.removeIf(key -> excludeKeys.stream().anyMatch(exclude -> exclude.equals(key)));
|
||||
if (CollUtil.isEmpty(keys)){
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
}
|
||||
Collections.sort(keys);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : keys) {
|
||||
sb.append(key).append("=").append(Convert.toStr(params.get(key))).append("&");
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
sb.setLength(sb.length() - 1); // Remove the last '&'
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@ -6,12 +6,11 @@ import org.dromara.sms4j.api.universal.SupplierConfig;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.core.datainterface.SmsReadConfig;
|
||||
import org.dromara.sms4j.core.load.SmsLoad;
|
||||
import org.dromara.sms4j.core.proxy.SmsInvocationHandler;
|
||||
import org.dromara.sms4j.core.proxy.SmsProxyFactory;
|
||||
import org.dromara.sms4j.provider.config.BaseConfig;
|
||||
import org.dromara.sms4j.provider.factory.BaseProviderFactory;
|
||||
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -34,7 +33,7 @@ public abstract class SmsFactory {
|
||||
* <p>key: configId,短信服务对象的唯一标识</p>
|
||||
* <p>value: 短信服务对象</p>
|
||||
*/
|
||||
private final static Map<String, SmsBlend> blends = new ConcurrentHashMap<>();
|
||||
private final static Map<String, SmsBlend> BLENDS = new ConcurrentHashMap<>();
|
||||
|
||||
private SmsFactory() {
|
||||
}
|
||||
@ -92,9 +91,9 @@ public abstract class SmsFactory {
|
||||
* @param config 短信配置
|
||||
* @author :Wind
|
||||
*/
|
||||
@Deprecated
|
||||
public static void createRestrictedSmsBlend(SupplierConfig config) {
|
||||
SmsBlend smsBlend = create(config);
|
||||
smsBlend = renderWithRestricted(smsBlend);
|
||||
register(smsBlend);
|
||||
}
|
||||
|
||||
@ -108,10 +107,10 @@ public abstract class SmsFactory {
|
||||
* @param configId 配置ID
|
||||
* @author :Wind
|
||||
*/
|
||||
@Deprecated
|
||||
public static void createRestrictedSmsBlend(SmsReadConfig smsReadConfig, String configId) {
|
||||
BaseConfig supplierConfig = smsReadConfig.getSupplierConfig(configId);
|
||||
SmsBlend smsBlend = create(supplierConfig);
|
||||
smsBlend = renderWithRestricted(smsBlend);
|
||||
register(smsBlend);
|
||||
}
|
||||
|
||||
@ -124,11 +123,11 @@ public abstract class SmsFactory {
|
||||
* @param smsReadConfig 读取额外配置接口
|
||||
* @author :Wind
|
||||
*/
|
||||
@Deprecated
|
||||
public static void createRestrictedSmsBlend(SmsReadConfig smsReadConfig) {
|
||||
List<BaseConfig> supplierConfigList = smsReadConfig.getSupplierConfigList();
|
||||
supplierConfigList.forEach(supplierConfig -> {
|
||||
SmsBlend smsBlend = create(supplierConfig);
|
||||
smsBlend = renderWithRestricted(smsBlend);
|
||||
register(smsBlend);
|
||||
});
|
||||
}
|
||||
@ -138,18 +137,21 @@ public abstract class SmsFactory {
|
||||
if (factory == null) {
|
||||
throw new SmsBlendException("不支持当前供应商配置");
|
||||
}
|
||||
return factory.createSms(config);
|
||||
SmsBlend sms = factory.createSms(config);
|
||||
return renderWithProxy(sms);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* renderWithRestricted
|
||||
* <p> 构建smsBlend对象的代理对象
|
||||
*
|
||||
* @author :Wind
|
||||
*/
|
||||
private static SmsBlend renderWithRestricted(SmsBlend sms) {
|
||||
SmsInvocationHandler smsInvocationHandler = SmsInvocationHandler.newSmsInvocationHandler(sms);
|
||||
return (SmsBlend) Proxy.newProxyInstance(sms.getClass().getClassLoader(), new Class[]{SmsBlend.class}, smsInvocationHandler);
|
||||
@Deprecated
|
||||
private static SmsBlend renderWithProxy(SmsBlend sms) {
|
||||
return SmsProxyFactory.getProxySmsBlend(sms);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,7 +170,7 @@ public abstract class SmsFactory {
|
||||
* @return 返回短信服务对象。如果未找到则返回null
|
||||
*/
|
||||
public static SmsBlend getSmsBlend(String configId) {
|
||||
return blends.get(configId);
|
||||
return BLENDS.get(configId);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,7 +184,7 @@ public abstract class SmsFactory {
|
||||
if (StrUtil.isEmpty(supplier)) {
|
||||
throw new SmsBlendException("供应商标识不能为空");
|
||||
}
|
||||
return blends.values().stream().filter(smsBlend -> supplier.equals(smsBlend.getSupplier())).findFirst().orElse(null);
|
||||
return BLENDS.values().stream().filter(smsBlend -> supplier.equals(smsBlend.getSupplier())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,7 +198,7 @@ public abstract class SmsFactory {
|
||||
if (StrUtil.isEmpty(supplier)) {
|
||||
throw new SmsBlendException("供应商标识不能为空");
|
||||
}
|
||||
list = blends.values().stream().filter(smsBlend -> supplier.equals(smsBlend.getSupplier())).collect(Collectors.toList());
|
||||
list = BLENDS.values().stream().filter(smsBlend -> supplier.equals(smsBlend.getSupplier())).collect(Collectors.toList());
|
||||
return list;
|
||||
}
|
||||
|
||||
@ -206,7 +208,7 @@ public abstract class SmsFactory {
|
||||
* @return 短信服务对象列表
|
||||
*/
|
||||
public static List<SmsBlend> getAll() {
|
||||
return new ArrayList<>(blends.values());
|
||||
return new ArrayList<>(BLENDS.values());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -218,7 +220,7 @@ public abstract class SmsFactory {
|
||||
if (smsBlend == null) {
|
||||
throw new SmsBlendException("短信服务对象不能为空");
|
||||
}
|
||||
blends.put(smsBlend.getConfigId(), smsBlend);
|
||||
BLENDS.put(smsBlend.getConfigId(), smsBlend);
|
||||
SmsLoad.starConfig(smsBlend, 1);
|
||||
}
|
||||
|
||||
@ -231,7 +233,7 @@ public abstract class SmsFactory {
|
||||
if (smsBlend == null) {
|
||||
throw new SmsBlendException("短信服务对象不能为空");
|
||||
}
|
||||
blends.put(smsBlend.getConfigId(), smsBlend);
|
||||
BLENDS.put(smsBlend.getConfigId(), smsBlend);
|
||||
SmsLoad.starConfig(smsBlend, weight);
|
||||
}
|
||||
|
||||
@ -248,10 +250,10 @@ public abstract class SmsFactory {
|
||||
throw new SmsBlendException("短信服务对象不能为空");
|
||||
}
|
||||
String configId = smsBlend.getConfigId();
|
||||
if (blends.containsKey(configId)) {
|
||||
if (BLENDS.containsKey(configId)) {
|
||||
return false;
|
||||
}
|
||||
blends.put(configId, smsBlend);
|
||||
BLENDS.put(configId, smsBlend);
|
||||
SmsLoad.starConfig(smsBlend, 1);
|
||||
return true;
|
||||
}
|
||||
@ -272,10 +274,10 @@ public abstract class SmsFactory {
|
||||
throw new SmsBlendException("短信服务对象不能为空");
|
||||
}
|
||||
String configId = smsBlend.getConfigId();
|
||||
if (blends.containsKey(configId)) {
|
||||
if (BLENDS.containsKey(configId)) {
|
||||
return false;
|
||||
}
|
||||
blends.put(configId, smsBlend);
|
||||
BLENDS.put(configId, smsBlend);
|
||||
SmsLoad.starConfig(smsBlend, weight);
|
||||
return true;
|
||||
}
|
||||
@ -290,9 +292,35 @@ public abstract class SmsFactory {
|
||||
* <p>当configId不存在时,返回false</p>
|
||||
*/
|
||||
public static boolean unregister(String configId) {
|
||||
SmsBlend blend = blends.remove(configId);
|
||||
SmsBlend blend = BLENDS.remove(configId);
|
||||
SmsLoad.getBeanLoad().removeLoadServer(blend);
|
||||
return blend != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* reload
|
||||
* <p> 重新读取并刷新缓存内短信实例
|
||||
*
|
||||
* @param configId 配置标识
|
||||
* @param smsReadConfig 配置接口实现对象
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void reload(String configId, SmsReadConfig smsReadConfig) {
|
||||
SmsFactory.unregister(configId);
|
||||
SmsFactory.createRestrictedSmsBlend(smsReadConfig, configId);
|
||||
}
|
||||
|
||||
/**
|
||||
* reloadAll
|
||||
* <p> 重新读取并刷新全部短信实例
|
||||
* @param smsReadConfig 配置接口实现对象
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void reloadAll(SmsReadConfig smsReadConfig) {
|
||||
List<BaseConfig> supplierConfigList = smsReadConfig.getSupplierConfigList();
|
||||
for (BaseConfig baseConfig : supplierConfigList) {
|
||||
reload(baseConfig.getConfigId(),smsReadConfig);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.universal.SupplierConfig;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -19,7 +20,7 @@ public class SmsLoad {
|
||||
// 服务器列表,每个服务器有一个权重和当前权重
|
||||
private final List<LoadServer> LoadServers = new ArrayList<>();
|
||||
|
||||
private static final SmsLoad smsLoad = new SmsLoad();
|
||||
private static final SmsLoad SMS_LOAD = new SmsLoad();
|
||||
|
||||
private SmsLoad() {
|
||||
}
|
||||
@ -49,9 +50,11 @@ public class SmsLoad {
|
||||
* @author :Wind
|
||||
*/
|
||||
public void removeLoadServer(SmsBlend LoadServer) {
|
||||
for (int i = 0; i < LoadServers.size(); i++) {
|
||||
if (LoadServers.get(i).getSmsServer().equals(LoadServer)) {
|
||||
LoadServers.remove(i);
|
||||
Iterator<LoadServer> iterator = LoadServers.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
LoadServer server = iterator.next();
|
||||
if (server.getSmsServer().getConfigId().equals(LoadServer.getConfigId())) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -97,15 +100,15 @@ public class SmsLoad {
|
||||
public static void starConfig(SmsBlend smsBlend, SupplierConfig supplierConfig) {
|
||||
Map<String, Object> supplierConfigMap = BeanUtil.beanToMap(supplierConfig);
|
||||
Object weight = supplierConfigMap.getOrDefault("weight", 1);
|
||||
smsLoad.addLoadServer(smsBlend, Integer.parseInt(weight.toString()));
|
||||
SMS_LOAD.addLoadServer(smsBlend, Integer.parseInt(weight.toString()));
|
||||
}
|
||||
|
||||
public static void starConfig(SmsBlend smsBlend,Integer weight) {
|
||||
smsLoad.addLoadServer(smsBlend,weight);
|
||||
SMS_LOAD.addLoadServer(smsBlend,weight);
|
||||
}
|
||||
|
||||
public static SmsLoad getBeanLoad() {
|
||||
return smsLoad;
|
||||
return SMS_LOAD;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
package org.dromara.sms4j.core.proxy;
|
||||
|
||||
import org.dromara.sms4j.provider.config.SmsConfig;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 环境信息持有
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
public class EnvirmentHolder {
|
||||
private static SmsConfig smsConfig = null;
|
||||
private static Map<String, Map<String, Object>> blends = null;
|
||||
|
||||
public static void frozenEnvirmet(SmsConfig smsConfig, Map<String, Map<String, Object>> blends) {
|
||||
if (null!=EnvirmentHolder.smsConfig||null!=EnvirmentHolder.blends){
|
||||
return;
|
||||
}
|
||||
EnvirmentHolder.smsConfig = smsConfig;
|
||||
EnvirmentHolder.blends = blends;
|
||||
}
|
||||
|
||||
//只有核心包执行器部分才能获取
|
||||
static SmsConfig getSmsConfig() {
|
||||
return smsConfig;
|
||||
}
|
||||
|
||||
//只有核心包执行器部分才能获取
|
||||
static Map<String, Map<String, Object>> getBlends() {
|
||||
return blends;
|
||||
}
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
package org.dromara.sms4j.core.proxy;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.dao.SmsDao;
|
||||
import org.dromara.sms4j.api.proxy.RestrictedProcess;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.comm.utils.SmsUtils;
|
||||
import org.dromara.sms4j.provider.config.SmsConfig;
|
||||
import org.dromara.sms4j.provider.factory.BeanFactory;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
public class RestrictedProcessDefaultImpl implements RestrictedProcess {
|
||||
static Long minTimer = 60 * 1000L;
|
||||
static Long accTimer = 24 * 60 * 60 * 1000L;
|
||||
|
||||
/**
|
||||
* 缓存实例
|
||||
*/
|
||||
@Setter
|
||||
private SmsDao smsDao;
|
||||
|
||||
public SmsBlendException process(String phone) {
|
||||
if (Objects.isNull(smsDao)) {
|
||||
throw new SmsBlendException("The dao tool could not be found");
|
||||
}
|
||||
SmsConfig config = BeanFactory.getSmsConfig();
|
||||
Integer accountMax = config.getAccountMax(); // 每日最大发送量
|
||||
Integer minuteMax = config.getMinuteMax(); // 每分钟最大发送量
|
||||
if (SmsUtils.isNotEmpty(accountMax)) { // 是否配置了每日限制
|
||||
Integer i = (Integer) smsDao.get(phone + "max");
|
||||
if (SmsUtils.isEmpty(i)) {
|
||||
smsDao.set(phone + "max", 1, accTimer);
|
||||
} else if (i >= accountMax) {
|
||||
log.info("The phone:" + phone + ",number of short messages reached the maximum today");
|
||||
return new SmsBlendException("The phone:" + phone + ",number of short messages reached the maximum today");
|
||||
} else {
|
||||
smsDao.set(phone + "max", i + 1, accTimer);
|
||||
}
|
||||
}
|
||||
if (SmsUtils.isNotEmpty(minuteMax)) { // 是否配置了每分钟最大限制
|
||||
Integer o = (Integer) smsDao.get(phone);
|
||||
if (SmsUtils.isNotEmpty(o)) {
|
||||
if (o < minuteMax) {
|
||||
smsDao.set(phone, o + 1, minTimer);
|
||||
} else {
|
||||
log.info("The phone:" + phone + " Text messages are sent too often!");
|
||||
return new SmsBlendException("The phone:", phone + " Text messages are sent too often!");
|
||||
}
|
||||
} else {
|
||||
smsDao.set(phone, 1, minTimer);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -2,45 +2,75 @@ package org.dromara.sms4j.core.proxy;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.proxy.RestrictedProcess;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.proxy.SmsProcessor;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* SmsBlend增强,封装smsblend和执行器
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
@Slf4j
|
||||
public class SmsInvocationHandler implements InvocationHandler {
|
||||
private final SmsBlend smsBlend;
|
||||
private static RestrictedProcess restrictedProcess = new RestrictedProcessDefaultImpl();
|
||||
|
||||
private SmsInvocationHandler(SmsBlend smsBlend) {
|
||||
|
||||
public SmsInvocationHandler(SmsBlend smsBlend) {
|
||||
this.smsBlend = smsBlend;
|
||||
}
|
||||
|
||||
public static SmsInvocationHandler newSmsInvocationHandler(SmsBlend smsBlend) {
|
||||
return new SmsInvocationHandler(smsBlend);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
|
||||
Object result;
|
||||
if ("sendMessage".equals(method.getName()) || "massTexting".equals(method.getName())) {
|
||||
//取手机号作为参数
|
||||
String phone = (String) objects[0];
|
||||
SmsBlendException smsBlendException = restrictedProcess.process(phone);
|
||||
if (!Objects.isNull(smsBlendException)) {
|
||||
throw smsBlendException;
|
||||
}
|
||||
public Object invoke(Object o, Method method, Object[] objects) {
|
||||
Object result = null;
|
||||
//前置执行器
|
||||
objects = doPreProcess(smsBlend, method, objects);
|
||||
try {
|
||||
result = method.invoke(smsBlend, objects);
|
||||
} catch (Exception e) {
|
||||
//错误执行器
|
||||
doErrorHandleProcess(smsBlend, method, objects,e);
|
||||
}
|
||||
result = method.invoke(smsBlend, objects);
|
||||
return result;
|
||||
//后置执行器
|
||||
return doPostrocess(smsBlend, method, objects, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 restrictedProcess
|
||||
*/
|
||||
public static void setRestrictedProcess(RestrictedProcess restrictedProcess) {
|
||||
SmsInvocationHandler.restrictedProcess = restrictedProcess;
|
||||
* 前置执行器
|
||||
* */
|
||||
public Object[] doPreProcess(Object o, Method method, Object[] objects) {
|
||||
for (SmsProcessor processor : SmsProxyFactory.getProcessors()) {
|
||||
objects = processor.preProcessor(method, o, objects);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常执行器
|
||||
* */
|
||||
public void doErrorHandleProcess(Object o, Method method, Object[] objects,Exception e) throws RuntimeException{
|
||||
for (SmsProcessor processor : SmsProxyFactory.getProcessors()) {
|
||||
processor.exceptionHandleProcessor(method, o, objects,e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后置执行器
|
||||
* */
|
||||
public Object doPostrocess(Object o, Method method, Object[] objects, Object result) {
|
||||
for (SmsProcessor processor : SmsProxyFactory.getProcessors()) {
|
||||
if (Objects.nonNull(result) && result instanceof SmsResponse){
|
||||
Object overrideResult = processor.postProcessor((SmsResponse)result, objects);
|
||||
if (overrideResult != null) {
|
||||
return overrideResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,147 @@
|
||||
package org.dromara.sms4j.core.proxy;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.dao.SmsDao;
|
||||
import org.dromara.sms4j.api.dao.SmsDaoDefaultImpl;
|
||||
import org.dromara.sms4j.api.proxy.Order;
|
||||
import org.dromara.sms4j.api.proxy.SmsProcessor;
|
||||
import org.dromara.sms4j.api.proxy.SuppotFilter;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsBlendConfigAware;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsConfigAware;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsDaoAware;
|
||||
import org.dromara.sms4j.api.verify.PhoneVerify;
|
||||
import org.dromara.sms4j.core.proxy.processor.CoreMethodParamValidateProcessor;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代理工厂
|
||||
* 可用于增加和移除拦截器
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class SmsProxyFactory {
|
||||
private static final LinkedList<SmsProcessor> PROCESSORS = new LinkedList<>();
|
||||
|
||||
public static SmsBlend getProxySmsBlend(SmsBlend smsBlend) {
|
||||
// LinkedList<SmsProcessor> ownerProcessors = PROCESSORS.stream().filter(processor -> !shouldSkipProcess(processor,smsBlend)).collect(Collectors.toCollection(LinkedList::new));
|
||||
return (SmsBlend) Proxy.newProxyInstance(smsBlend.getClass().getClassLoader(), new Class[]{SmsBlend.class}, new SmsInvocationHandler(smsBlend));
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加拦截器
|
||||
*/
|
||||
public static void addPreProcessor(SmsProcessor processor) {
|
||||
//校验拦截器是否正确
|
||||
processorValidate(processor);
|
||||
awareTransfer(processor);
|
||||
PROCESSORS.add(processor);
|
||||
PROCESSORS.sort(Comparator.comparingInt(Order::getOrder));
|
||||
}
|
||||
|
||||
/**
|
||||
* removeProcessor
|
||||
* <p> 移除拦截器
|
||||
* @param processor 拦截器对象
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void removePreProcessor(SmsProcessor processor) {
|
||||
PROCESSORS.remove(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* getProcessors
|
||||
* <p> 获取全部拦截器
|
||||
* @author :Wind
|
||||
*/
|
||||
public static LinkedList<SmsProcessor> getProcessors() {
|
||||
return PROCESSORS;
|
||||
}
|
||||
|
||||
/**
|
||||
* setPhoneProcessor
|
||||
* <p> 添加手机号验证器,手机号验证器只且只能存在一个,如果重复置入则会替换先前的验证器。
|
||||
* 如果在验证器之后还需进行额外操作,请参考使用前置拦截器进行处理
|
||||
* @param phoneVerify 手机号验证器
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void setPhoneProcessor(PhoneVerify phoneVerify) {
|
||||
PROCESSORS.forEach(processor -> {
|
||||
if (processor instanceof CoreMethodParamValidateProcessor){
|
||||
((CoreMethodParamValidateProcessor) processor).setPhoneVerify(phoneVerify);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* @see SuppotFilter
|
||||
*/
|
||||
public static boolean shouldSkipProcess(SmsProcessor processor, SmsBlend smsBlend) {
|
||||
//判断当前的执行器有没有开厂商过滤,支不支持当前厂商
|
||||
if (processor instanceof SuppotFilter) {
|
||||
List<String> supports = ((SuppotFilter) processor).getSupports();
|
||||
boolean exsit = supports.stream().anyMatch(support -> support.equals(smsBlend.getSupplier()));
|
||||
return !exsit;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//所有处理器需要的各个参数可以通过这种aware接口形式传给对象
|
||||
private static void awareTransfer(SmsProcessor processor) {
|
||||
if (processor instanceof SmsDaoAware){
|
||||
((SmsDaoAware) processor).setSmsDao(getSmsDaoFromFramework());
|
||||
}
|
||||
if (processor instanceof SmsConfigAware){
|
||||
((SmsConfigAware) processor).setSmsConfig(EnvirmentHolder.getSmsConfig());
|
||||
}
|
||||
if (processor instanceof SmsBlendConfigAware){
|
||||
((SmsBlendConfigAware) processor).setSmsBlendsConfig(EnvirmentHolder.getBlends());
|
||||
}
|
||||
}
|
||||
|
||||
//校验拦截器是否正确
|
||||
private static void processorValidate(SmsProcessor processor) {
|
||||
|
||||
}
|
||||
|
||||
//获取Sms的实现
|
||||
private static SmsDao getSmsDaoFromFramework() {
|
||||
SmsDao smsDao;
|
||||
smsDao = getSmsDaoFromFramework("org.dromara.sms4j.javase.config.SESmsDaoHolder", "JavaSE");
|
||||
if (null != smsDao) {
|
||||
return smsDao;
|
||||
}
|
||||
smsDao = getSmsDaoFromFramework("org.dromara.sms4j.starter.holder.SpringSmsDaoHolder", "SpringBoot");
|
||||
if (null != smsDao) {
|
||||
return smsDao;
|
||||
}
|
||||
smsDao = getSmsDaoFromFramework("org.dromara.sms4j.solon.holder.SolonSmsDaoHolder", "Solon");
|
||||
if (null != smsDao) {
|
||||
return smsDao;
|
||||
}
|
||||
log.debug("未找到合适框架加载,最终使用默认SmsDao!如无自实现SmsDao请忽略本消息!");
|
||||
return SmsDaoDefaultImpl.getInstance();
|
||||
}
|
||||
|
||||
//获取Sms的实现
|
||||
private static SmsDao getSmsDaoFromFramework(String className, String frameworkName) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
Method getSmsDao = clazz.getMethod("getSmsDao", null);
|
||||
SmsDao smsDao = (SmsDao) getSmsDao.invoke(null, null);
|
||||
log.debug("{}:加载SmsDao成功,使用{}", frameworkName,smsDao.getClass().getName());
|
||||
return smsDao;
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
log.debug("{}:尝试其他框架加载......", frameworkName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package org.dromara.sms4j.core.proxy.processor;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.dao.SmsDao;
|
||||
import org.dromara.sms4j.api.proxy.CoreMethodProcessor;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsDaoAware;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 黑名单前置拦截执行器
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
@Setter
|
||||
@Slf4j
|
||||
public class BlackListProcessor implements CoreMethodProcessor, SmsDaoAware {
|
||||
SmsDao smsDao;
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessagePreProcess(String phone, Object message) {
|
||||
doRestricted(Collections.singletonList(phone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageByTemplatePreProcess(String phone, String templateId, LinkedHashMap<String, String> messages) {
|
||||
doRestricted(Collections.singletonList(phone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void massTextingPreProcess(List<String> phones, String message) {
|
||||
doRestricted(phones);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void massTextingByTemplatePreProcess(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
|
||||
doRestricted(phones);
|
||||
}
|
||||
|
||||
public void doRestricted(List<String> phones) {
|
||||
ArrayList<String> blackList = (ArrayList<String>) smsDao.get("sms:blacklist:global");
|
||||
if(null==blackList){
|
||||
return;
|
||||
}
|
||||
for (String phone : phones) {
|
||||
if (blackList.stream().anyMatch(black -> black.replace("-","").equals(phone))) {
|
||||
throw new SmsBlendException(String.format("The phone: %s hit global blacklist!", phone));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
package org.dromara.sms4j.core.proxy.processor;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.dao.SmsDao;
|
||||
import org.dromara.sms4j.api.proxy.SmsProcessor;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsConfigAware;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsDaoAware;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 黑名单前置拦截执行器
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
@Setter
|
||||
@Slf4j
|
||||
public class BlackListRecordingProcessor implements SmsProcessor, SmsDaoAware, SmsConfigAware {
|
||||
SmsDao smsDao;
|
||||
|
||||
Object smsConfig;
|
||||
|
||||
@Override
|
||||
public int getOrder(){
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] preProcessor(Method method, Object source, Object[] param) {
|
||||
//添加到黑名单
|
||||
if ("joinInBlacklist".equals(method.getName())) {
|
||||
String cacheKey = getCacheKey();
|
||||
ArrayList<String> blackList = getBlackList(cacheKey);
|
||||
blackList.add((String) param[0]);
|
||||
flushBlackList(cacheKey,blackList);
|
||||
}
|
||||
//从黑名单移除
|
||||
if ("removeFromBlacklist".equals(method.getName())) {
|
||||
String cacheKey = getCacheKey();
|
||||
ArrayList<String> blackList = getBlackList(cacheKey);
|
||||
blackList.remove((String) param[0]);
|
||||
flushBlackList(cacheKey,blackList);
|
||||
}
|
||||
//批量添加到黑名单
|
||||
if ("batchJoinBlacklist".equals(method.getName())) {
|
||||
String cacheKey = getCacheKey();
|
||||
ArrayList<String> blackList = getBlackList(cacheKey);
|
||||
blackList.addAll((List<String>) param[0]);
|
||||
flushBlackList(cacheKey,blackList);
|
||||
}
|
||||
//批量从黑名单移除
|
||||
if ("batchRemovalFromBlacklist".equals(method.getName())) {
|
||||
String cacheKey = getCacheKey();
|
||||
ArrayList<String> blackList = getBlackList(cacheKey);
|
||||
blackList.removeAll((List<String>) param[0]);
|
||||
flushBlackList(cacheKey,blackList);
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建CacheKey
|
||||
*
|
||||
* @return CacheKey
|
||||
*/
|
||||
public String getCacheKey(){
|
||||
return "sms:blacklist:global";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取黑名单,没有就新建
|
||||
*
|
||||
* @param cacheKey 缓存key
|
||||
* @return 黑名单
|
||||
*/
|
||||
public ArrayList<String> getBlackList(String cacheKey) {
|
||||
ArrayList<String> blackList;
|
||||
Object cache = smsDao.get(cacheKey);
|
||||
if (null != cache) {
|
||||
blackList = (ArrayList<String>) cache;
|
||||
return blackList;
|
||||
}
|
||||
blackList = new ArrayList<>();
|
||||
smsDao.set("sms:blacklist:global", blackList);
|
||||
return blackList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 让黑名单生效
|
||||
*
|
||||
* @param cacheKey 缓存key
|
||||
* @param blackList 黑命令
|
||||
*/
|
||||
public void flushBlackList(String cacheKey ,ArrayList<String> blackList) {
|
||||
smsDao.set(cacheKey, blackList);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
package org.dromara.sms4j.core.proxy.processor;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.proxy.CoreMethodProcessor;
|
||||
import org.dromara.sms4j.api.verify.PhoneVerify;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* 核心方法参数校验前置拦截执行器
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
@Setter
|
||||
@Slf4j
|
||||
public class CoreMethodParamValidateProcessor implements CoreMethodProcessor {
|
||||
|
||||
/**
|
||||
* -- SETTER --
|
||||
* 设置 phoneVerify
|
||||
*/
|
||||
private PhoneVerify phoneVerify;
|
||||
|
||||
public CoreMethodParamValidateProcessor(PhoneVerify phoneVerify) {
|
||||
this.phoneVerify = phoneVerify;
|
||||
}
|
||||
|
||||
/**
|
||||
* setter
|
||||
* <p> 用于设置手机号验证器,可以在通过重写验证器规则来实现自己的验证器逻辑
|
||||
* <p> 默认验证规则仅仅验证手机号是否为空和手机号是否为11位
|
||||
* @param phoneVerify 手机号验证器
|
||||
* @author :Wind
|
||||
*/
|
||||
public void setPhoneVerify(PhoneVerify phoneVerify) {
|
||||
this.phoneVerify = phoneVerify;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -100;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessagePreProcess(String phone, Object message) {
|
||||
validatePhone(phone);
|
||||
validateMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageByTemplatePreProcess(String phone, String templateId, LinkedHashMap<String, String> messages) {
|
||||
validatePhone(phone);
|
||||
validateMessages(templateId, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void massTextingPreProcess(List<String> phones, String message) {
|
||||
validateMessage(message);
|
||||
validatePhones(phones);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void massTextingByTemplatePreProcess(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
|
||||
validatePhones(phones);
|
||||
validateMessages(templateId, messages);
|
||||
}
|
||||
|
||||
public void validateMessage(Object messageObj) {
|
||||
if(Objects.isNull(messageObj)){
|
||||
throw new SmsBlendException("cant send a null message!");
|
||||
}
|
||||
if (messageObj instanceof String){
|
||||
String message = (String) messageObj;
|
||||
if (StrUtil.isBlank(message)) {
|
||||
throw new SmsBlendException("cant send a null message!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void validatePhone(String phone) {
|
||||
if (StrUtil.isBlank(phone)) {
|
||||
throw new SmsBlendException("cant send message to null!");
|
||||
}
|
||||
if (phoneVerify != null && !phoneVerify.verifyPhone(phone)){
|
||||
throw new SmsBlendException("The mobile phone number format is invalid!");
|
||||
}
|
||||
}
|
||||
|
||||
public void validatePhones(List<String> phones) {
|
||||
if (null == phones) {
|
||||
throw new SmsBlendException("cant send message to null!");
|
||||
}
|
||||
for (String phone : phones) {
|
||||
if (StrUtil.isNotBlank(phone)) {
|
||||
if (phoneVerify != null && !phoneVerify.verifyPhone(phone)){
|
||||
throw new SmsBlendException("The mobile phone number format is invalid!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void validateMessages(String templateId, LinkedHashMap<String, String> messages) {
|
||||
if (StrUtil.isEmpty(templateId)) {
|
||||
throw new SmsBlendException("cant use template without template param");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
package org.dromara.sms4j.core.proxy.processor;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.dao.SmsDao;
|
||||
import org.dromara.sms4j.api.proxy.CoreMethodProcessor;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsDaoAware;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.comm.utils.SmsUtils;
|
||||
import org.dromara.sms4j.provider.config.SmsConfig;
|
||||
import org.dromara.sms4j.provider.factory.BeanFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* 短信发送账号级上限前置拦截执行器
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
@Setter
|
||||
@Slf4j
|
||||
public class RestrictedProcessor implements CoreMethodProcessor, SmsDaoAware {
|
||||
static Long minTimer = 60 * 1000L;
|
||||
private static final String REDIS_KEY = "sms:restricted:";
|
||||
|
||||
/**
|
||||
* 缓存实例
|
||||
*/
|
||||
private SmsDao smsDao;
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessagePreProcess(String phone, Object message) {
|
||||
doRestricted(Collections.singletonList(phone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageByTemplatePreProcess(String phone, String templateId, LinkedHashMap<String, String> messages) {
|
||||
doRestricted(Collections.singletonList(phone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void massTextingPreProcess(List<String> phones, String message) {
|
||||
doRestricted(phones);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void massTextingByTemplatePreProcess(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
|
||||
doRestricted(phones);
|
||||
}
|
||||
|
||||
private long calculateExpiryTime() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime tomorrowMidnight = now.toLocalDate().plusDays(1).atStartOfDay();
|
||||
Duration duration = Duration.between(now, tomorrowMidnight);
|
||||
return duration.getSeconds();
|
||||
}
|
||||
|
||||
public void doRestricted(List<String> phones) {
|
||||
if (Objects.isNull(smsDao)) {
|
||||
throw new SmsBlendException("The smsDao tool could not be found");
|
||||
}
|
||||
SmsConfig config = BeanFactory.getSmsConfig();
|
||||
// 每日最大发送量
|
||||
Integer accountMax = config.getAccountMax();
|
||||
// 每分钟最大发送量
|
||||
Integer minuteMax = config.getMinuteMax();
|
||||
// 配置了每日最大发送量
|
||||
boolean dailyMaxLimitExists = SmsUtils.isNotEmpty(accountMax);
|
||||
// 配置了每分钟最大发送量
|
||||
boolean perMinuteLimitExists = SmsUtils.isNotEmpty(minuteMax);
|
||||
// 如果未开启限制或未配置任何限制发送量,不做处理
|
||||
boolean isNoProcessing = !config.getRestricted() || (!dailyMaxLimitExists && !perMinuteLimitExists);
|
||||
if (isNoProcessing) {
|
||||
return;
|
||||
}
|
||||
for (String phone : phones) {
|
||||
// 分钟发送量缓存key
|
||||
String minuteMaxKey = REDIS_KEY + phone;
|
||||
// 天发送量缓存key
|
||||
String accountMaxKey = minuteMaxKey.concat("max");
|
||||
// 是否配置了每日限制
|
||||
if (dailyMaxLimitExists) {
|
||||
Integer dailyCount = (Integer) smsDao.get(accountMaxKey);
|
||||
if (SmsUtils.isEmpty(dailyCount)) {
|
||||
smsDao.set(accountMaxKey, 1, calculateExpiryTime());
|
||||
} else if (dailyCount >= accountMax) {
|
||||
log.info("The phone: {} number of short messages reached the maximum today", phone);
|
||||
throw new SmsBlendException(String.format("The phone: %s number of short messages reached the maximum today", phone));
|
||||
} else {
|
||||
smsDao.set(accountMaxKey, dailyCount + 1, calculateExpiryTime());
|
||||
}
|
||||
}
|
||||
// 是否配置了每分钟最大限制
|
||||
if (perMinuteLimitExists) {
|
||||
Integer minuteCount = (Integer) smsDao.get(REDIS_KEY + phone);
|
||||
if (SmsUtils.isNotEmpty(minuteCount)) {
|
||||
if (minuteCount < minuteMax) {
|
||||
smsDao.set(minuteMaxKey, minuteCount + 1, minTimer / 1000);
|
||||
} else {
|
||||
//如果能走到这里且存在每日限制,说明每日限制已经计数,这里将之前的计数减一次
|
||||
if (dailyMaxLimitExists) {
|
||||
Integer dailyCount = (Integer) smsDao.get(accountMaxKey);
|
||||
if (dailyCount > 1) {
|
||||
smsDao.set(accountMaxKey, dailyCount - 1, calculateExpiryTime());
|
||||
} else {
|
||||
smsDao.remove(accountMaxKey);
|
||||
}
|
||||
}
|
||||
log.info("The phone: {} text messages are sent too often!", phone);
|
||||
throw new SmsBlendException(String.format("The phone: %s text messages are sent too often!", phone));
|
||||
}
|
||||
} else {
|
||||
smsDao.set(minuteMaxKey, 1, minTimer / 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
sms4j-email-jakarta/pom.xml
Normal file
70
sms4j-email-jakarta/pom.xml
Normal file
@ -0,0 +1,70 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
<artifactId>sms4j-email-jakarta</artifactId>
|
||||
|
||||
<modules>
|
||||
<module>sms4j-email-jakarta-api</module>
|
||||
<module>sms4j-email-jakarta-comm</module>
|
||||
<module>sms4j-email-jakarta-core</module>
|
||||
</modules>
|
||||
|
||||
<name>sms4j-email-jakarta</name>
|
||||
<description>JDK 11 或更高版本中使用</description>
|
||||
|
||||
<properties>
|
||||
<jakarta-mail.version>2.1.2</jakarta-mail.version>
|
||||
<jakarta-eclipse.version>2.0.2</jakarta-eclipse.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-email-jakarta-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-email-jakarta-comm</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-email-jakarta-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.angus</groupId>
|
||||
<artifactId>jakarta.mail</artifactId>
|
||||
<version>${jakarta-eclipse.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.mail</groupId>
|
||||
<artifactId>jakarta.mail-api</artifactId>
|
||||
<version>${jakarta-mail.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.activation</groupId>
|
||||
<artifactId>jakarta.activation-api</artifactId>
|
||||
<version>${jakarta-mail.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
</project>
|
||||
23
sms4j-email-jakarta/sms4j-email-jakarta-api/pom.xml
Normal file
23
sms4j-email-jakarta/sms4j-email-jakarta-api/pom.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-email-jakarta</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>sms4j-email-jakarta-api</artifactId>
|
||||
<description>email-api</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-email-jakarta-comm</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,14 @@
|
||||
package org.dromara.email.jakarta.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Blacklist
|
||||
* <p> 黑名单实现 实现此接口,发送邮件时将自动排除调黑名单中的收件人
|
||||
* @author :Wind
|
||||
* 2023/6/8 23:05
|
||||
**/
|
||||
public interface Blacklist {
|
||||
|
||||
List<String> getBlacklist();
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package org.dromara.email.jakarta.api;
|
||||
|
||||
import org.dromara.email.jakarta.comm.entity.MailMessage;
|
||||
|
||||
public interface MailClient {
|
||||
|
||||
/**
|
||||
* 发送邮件,可以通过对象构造群体发送或者单体发送,取决于添加进去的收件人,同时可以添加
|
||||
* 密送人,抄送人,附件等参数
|
||||
* @param mailMessage 发送邮件参数对象
|
||||
* @author :Wind
|
||||
*/
|
||||
void send(MailMessage mailMessage);
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package org.dromara.email.jakarta.api;
|
||||
|
||||
import org.dromara.email.jakarta.comm.entity.MonitorMessage;
|
||||
|
||||
/**
|
||||
* Monitor
|
||||
* <p> 监听接口,实现此接口用于监听并获取邮件消息
|
||||
* @author :Wind
|
||||
* 2023/7/18 15:57
|
||||
**/
|
||||
public interface Monitor {
|
||||
|
||||
/**
|
||||
* monitor
|
||||
* <p> 监听系统的邮件消息,
|
||||
* @param monitorMessage 系统监听到的消息内容
|
||||
* @return true为标记已读,否则不标记
|
||||
* @author :Wind
|
||||
*/
|
||||
boolean monitor(MonitorMessage monitorMessage);
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* <p> 邮件插件api模块
|
||||
* @author :Wind
|
||||
* 2024/10/23 10:58
|
||||
**/
|
||||
package org.dromara.email.jakarta.api;
|
||||
33
sms4j-email-jakarta/sms4j-email-jakarta-comm/pom.xml
Normal file
33
sms4j-email-jakarta/sms4j-email-jakarta-comm/pom.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-email-jakarta</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>sms4j-email-jakarta-comm</artifactId>
|
||||
<description>email通用配置</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.mail</groupId>
|
||||
<artifactId>jakarta.mail-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,19 @@
|
||||
package org.dromara.email.jakarta.comm.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class MailImapConfig {
|
||||
|
||||
/** imap服务地址*/
|
||||
private String imapServer;
|
||||
|
||||
/** 要监听的邮箱账号*/
|
||||
private String username;
|
||||
|
||||
/** 要监听的邮箱授权码或密码*/
|
||||
private String accessToken;
|
||||
|
||||
/** 监听周期(秒)*/
|
||||
private Integer cycle = 5;
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package org.dromara.email.jakarta.comm.config;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* MailSmtpConfig
|
||||
* <p> smtp协议配置文件
|
||||
* @author :Wind
|
||||
* 2023/6/7 21:19
|
||||
**/
|
||||
@Builder
|
||||
@ToString
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class MailSmtpConfig {
|
||||
/**
|
||||
* 端口号
|
||||
* */
|
||||
private String port;
|
||||
|
||||
/**
|
||||
* 发件人地址
|
||||
* */
|
||||
private String fromAddress;
|
||||
|
||||
/**
|
||||
* 发送人昵称
|
||||
* */
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 服务器地址
|
||||
* */
|
||||
private String smtpServer;
|
||||
|
||||
/**
|
||||
* 账号
|
||||
* */
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
* */
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 是否开启ssl 默认开启
|
||||
* */
|
||||
@Builder.Default
|
||||
private final String isSSL = "true";
|
||||
|
||||
/**
|
||||
* 是否开启验证 默认开启
|
||||
* */
|
||||
@Builder.Default
|
||||
private final String isAuth = "true";
|
||||
|
||||
/**
|
||||
* 重试间隔(单位:秒),默认为5秒
|
||||
*/
|
||||
@Builder.Default
|
||||
private final int retryInterval = 5;
|
||||
|
||||
/**
|
||||
* 重试次数,默认为1次
|
||||
*/
|
||||
@Builder.Default
|
||||
private final int maxRetries = 1;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package org.dromara.email.jakarta.comm.constants;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class FileConstants {
|
||||
public static final String IO_FILE_TYPE = "application/octet-stream";
|
||||
}
|
||||
@ -0,0 +1,197 @@
|
||||
package org.dromara.email.jakarta.comm.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.dromara.email.jakarta.comm.utils.ReflectUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
public class MailMessage {
|
||||
|
||||
/** 收件人地址*/
|
||||
private List<String> mailAddress;
|
||||
|
||||
/** 邮件主题*/
|
||||
private String title;
|
||||
|
||||
/** 文字正文*/
|
||||
private String body;
|
||||
|
||||
/** html模板文件路径(resources目录下的路径)*/
|
||||
private String htmlPath;
|
||||
|
||||
/** html模板文件的输入流,可来自任意可读取位置*/
|
||||
private InputStream htmlInputStream;
|
||||
|
||||
/** html内容,可以存在模板变量*/
|
||||
private String htmlContent;
|
||||
|
||||
/** html 模板文件的File对象*/
|
||||
private File htmlFile;
|
||||
|
||||
/** html 模板参数*/
|
||||
private Map<String,String> htmlValues;
|
||||
|
||||
/** 抄送人*/
|
||||
private List<String> cc;
|
||||
|
||||
/** 密送人*/
|
||||
private List<String> bcc;
|
||||
|
||||
/** 附件*/
|
||||
private Map<String,String> files;
|
||||
|
||||
/** 压缩文件名称*/
|
||||
private String zipName;
|
||||
|
||||
public static MailsBuilder Builder(){
|
||||
return new MailsBuilder();
|
||||
}
|
||||
|
||||
public static class MailsBuilder{
|
||||
private final MailMessage mailMessage = new MailMessage();
|
||||
public MailsBuilder() {
|
||||
}
|
||||
public MailMessage build(){
|
||||
return mailMessage;
|
||||
}
|
||||
|
||||
/** 收件人地址*/
|
||||
public MailsBuilder mailAddress(List<String> mailAddress) {
|
||||
mailMessage.mailAddress = mailAddress;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 收件人地址*/
|
||||
public MailsBuilder mailAddress(String mailAddress){
|
||||
if ( mailMessage.mailAddress == null){
|
||||
mailMessage.mailAddress = new ArrayList<>();
|
||||
}
|
||||
mailMessage.mailAddress.add(mailAddress);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 邮件主题*/
|
||||
public MailsBuilder title(String title){
|
||||
mailMessage.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 文字正文*/
|
||||
public MailsBuilder body(String body){
|
||||
mailMessage.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 抄送人*/
|
||||
public MailsBuilder cc(List<String> cc){
|
||||
mailMessage.cc = cc;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 抄送人*/
|
||||
public MailsBuilder cc(String cc){
|
||||
if (mailMessage.cc == null){
|
||||
mailMessage.cc = new ArrayList<>();
|
||||
}
|
||||
mailMessage.cc.add(cc);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 密送人*/
|
||||
public MailsBuilder bcc(List<String> bcc){
|
||||
mailMessage.bcc = bcc;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 密送人*/
|
||||
public MailsBuilder bcc(String bcc){
|
||||
if (mailMessage.bcc == null){
|
||||
mailMessage.bcc = new ArrayList<>();
|
||||
}
|
||||
mailMessage.bcc.add(bcc);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 附件*/
|
||||
public MailsBuilder files(Map<String, String> files){
|
||||
if (mailMessage.files == null){
|
||||
mailMessage.files = new HashMap<>();
|
||||
}
|
||||
mailMessage.files.putAll(files);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 附件*/
|
||||
public MailsBuilder files(String fileName,String filePath){
|
||||
if (mailMessage.files == null){
|
||||
mailMessage.files = new HashMap<>();
|
||||
}
|
||||
mailMessage.files.put(fileName,filePath);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html模板文件路径(resources目录下的路径)*/
|
||||
public MailsBuilder html(String htmlPath){
|
||||
mailMessage.htmlPath = htmlPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html模板文件的输入流,可来自任意可读取位置*/
|
||||
public MailsBuilder html(InputStream htmlInputStream){
|
||||
mailMessage.htmlInputStream = htmlInputStream;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html模板文件的File对象*/
|
||||
public MailsBuilder html(File htmlFile){
|
||||
mailMessage.htmlFile = htmlFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html内容直接输入*/
|
||||
public MailsBuilder htmlContent(String htmlContent){
|
||||
mailMessage.htmlContent = htmlContent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html 模板参数*/
|
||||
public MailsBuilder htmlValues(String key, String value){
|
||||
if (mailMessage.htmlValues == null){
|
||||
mailMessage.htmlValues = new HashMap<>();
|
||||
}
|
||||
mailMessage.htmlValues.put(key,value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html 模板参数*/
|
||||
public MailsBuilder htmlValues(Map<String,String> htmlValues){
|
||||
if (mailMessage.htmlValues == null){
|
||||
mailMessage.htmlValues = new HashMap<>();
|
||||
}
|
||||
mailMessage.htmlValues.putAll(htmlValues);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html 模板参数*/
|
||||
public MailsBuilder htmlValues(Parameter parameter){
|
||||
Map<String, String> values = ReflectUtil.getValues(parameter);
|
||||
if (mailMessage.htmlValues == null){
|
||||
mailMessage.htmlValues = new HashMap<>();
|
||||
}
|
||||
mailMessage.htmlValues.putAll(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 压缩文件名称*/
|
||||
public MailsBuilder zipName(String zipName){
|
||||
mailMessage.zipName = zipName;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package org.dromara.email.jakarta.comm.entity;
|
||||
|
||||
import jakarta.mail.Multipart;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MonitorMessage
|
||||
* <p> 监听获取到的邮件内容
|
||||
* @author :Wind
|
||||
* 2023/7/18 15:59
|
||||
**/
|
||||
@Data
|
||||
public class MonitorMessage {
|
||||
|
||||
/** 邮件主题*/
|
||||
private String title;
|
||||
|
||||
/** 邮件文字内容*/
|
||||
private String text;
|
||||
|
||||
/** 解析的html内容*/
|
||||
private String htmlText;
|
||||
|
||||
/** 邮件发送时间*/
|
||||
private Date sendDate;
|
||||
|
||||
/** 邮件内容(复杂内容)*/
|
||||
private Multipart body;
|
||||
|
||||
/** 发送人*/
|
||||
private String fromAddress;
|
||||
|
||||
/** 邮件消息编号*/
|
||||
private Integer messageIndex;
|
||||
|
||||
/** 接收时间*/
|
||||
private Long acceptTime;
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package org.dromara.email.jakarta.comm.entity;
|
||||
|
||||
/**
|
||||
* Parameter
|
||||
* <p> 空接口,用于标定用户自己的实体类型
|
||||
* 用于发送html模板邮件时候 用户传递自己的实体序列化进行的类型标定
|
||||
* @author :Wind
|
||||
* 2023/6/8 19:36
|
||||
**/
|
||||
public interface Parameter {
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.email.jakarta.comm.errors;
|
||||
|
||||
public class MailException extends RuntimeException {
|
||||
public MailException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MailException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MailException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MailException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
protected MailException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* <p> 邮件插件通用模块
|
||||
* @author :Wind
|
||||
* 2024/10/23 10:58
|
||||
**/
|
||||
package org.dromara.email.jakarta.comm;
|
||||
@ -0,0 +1,15 @@
|
||||
package org.dromara.email.jakarta.comm.utils;
|
||||
|
||||
public class BaseUtil {
|
||||
|
||||
/**
|
||||
* getPathName
|
||||
* <p>分隔文件路径,并获取文件名
|
||||
* @param path 文件路径
|
||||
* @author :Wind
|
||||
*/
|
||||
public static String getPathName(String path) {
|
||||
String[] split = path.split("/");
|
||||
return split[split.length - 1];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
package org.dromara.email.jakarta.comm.utils;
|
||||
|
||||
import org.dromara.email.jakarta.comm.errors.MailException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* HtmlUtil
|
||||
* <p> Html相关工具
|
||||
*
|
||||
* @author :Wind
|
||||
* 2023/6/7 20:15
|
||||
**/
|
||||
public final class HtmlUtil {
|
||||
|
||||
private HtmlUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* readHtml
|
||||
* <p>从resource读取模板文件
|
||||
*
|
||||
* @param name 模板文件名
|
||||
* @author :Wind
|
||||
*/
|
||||
public static List<String> readHtml(String name,Class<?> clazz) throws MailException {
|
||||
try (InputStream is = clazz.getResourceAsStream("/template/" + name)) {
|
||||
return readHtml(is);
|
||||
} catch (IOException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* readHtml
|
||||
* <p>从自定义路径读取模板文件
|
||||
*
|
||||
* @param file 自定义路径file
|
||||
* @author :Wind
|
||||
*/
|
||||
public static List<String> readHtml(File file) throws MailException {
|
||||
try (InputStream ip = Files.newInputStream(file.toPath())) {
|
||||
return readHtml(ip);
|
||||
} catch (IOException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* readHtml
|
||||
* <p>从输入流读取模板文件
|
||||
*
|
||||
* @param inputStream 输入流
|
||||
* @author :Wind
|
||||
*/
|
||||
public static List<String> readHtml(InputStream inputStream) throws MailException {
|
||||
List<String> data = new ArrayList<>();
|
||||
if (Objects.isNull(inputStream)) {
|
||||
throw new MailException("The template could not be found!");
|
||||
}
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
data.add(line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new MailException(e);
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* replacePlaceholder
|
||||
* <p>将所包含占位符的字符串替换为固定值
|
||||
*
|
||||
* @param data 源数据
|
||||
* @param parameter key为占位符名称 value为占位符应替换的值
|
||||
* @author :Wind
|
||||
*/
|
||||
public static List<String> replacePlaceholder(List<String> data, Map<String, String> parameter) {
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
for (Map.Entry<String, String> s : parameter.entrySet()) {
|
||||
String piece = piece(s.getKey());
|
||||
if (data.get(i).contains(piece)){
|
||||
String replace = data.get(i).replace(piece, s.getValue());
|
||||
data.set(i,replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* pieceHtml
|
||||
* <p>将数据拼合为html
|
||||
*
|
||||
* @param data 需要拼合的数据
|
||||
* @author :Wind
|
||||
*/
|
||||
public static String pieceHtml(List<String> data) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String datum : data) {
|
||||
sb.append(datum);
|
||||
sb.append("\r\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* piece
|
||||
* <p>将参数拼合为完整占位符
|
||||
*
|
||||
* @author :Wind
|
||||
*/
|
||||
public static String piece(String parameter) {
|
||||
return "#{" + parameter + "}";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package org.dromara.email.jakarta.comm.utils;
|
||||
|
||||
import org.dromara.email.jakarta.comm.entity.Parameter;
|
||||
import org.dromara.email.jakarta.comm.errors.MailException;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ReflectUtil {
|
||||
|
||||
/**
|
||||
* 反射获取接口对象的原类名
|
||||
*/
|
||||
public static String getObjectName(Parameter parameter) {
|
||||
return parameter.getClass().getTypeName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象的属性和属性值变为map
|
||||
* */
|
||||
public static Map<String, String> getValues(Parameter parameter) {
|
||||
try {
|
||||
Map<String ,String> map = new HashMap<>();
|
||||
Class<?> clazz = Class.forName(getObjectName(parameter));
|
||||
Field[] declaredFields = clazz.getDeclaredFields();
|
||||
for (Field declaredField : declaredFields) {
|
||||
declaredField.setAccessible(true);
|
||||
map.put(declaredField.getName(), (String) declaredField.get(parameter));
|
||||
}
|
||||
return map;
|
||||
} catch (Exception e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
package org.dromara.email.jakarta.comm.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.Pipe;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* 压缩包处理类
|
||||
*
|
||||
* @author Bleachtred
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class ZipUtils extends ZipUtil {
|
||||
private final static Integer TEMP_SIZE = 2048;
|
||||
|
||||
/**
|
||||
* 压缩方法(支持 本地文件/目录 + oss网络路径 混合)
|
||||
* @param files 文件列表
|
||||
* @author Bleachtred
|
||||
*/
|
||||
public static void zipFilePip(Map<String, String> files, OutputStream outputStream) {
|
||||
try(WritableByteChannel out = Channels.newChannel(outputStream)) {
|
||||
Pipe pipe = Pipe.open();
|
||||
//异步任务
|
||||
CompletableFuture.runAsync(() -> runTask(pipe, files));
|
||||
//获取读通道
|
||||
try (ReadableByteChannel readableByteChannel = pipe.source()) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(TEMP_SIZE);
|
||||
while (readableByteChannel.read(buffer) >= 0) {
|
||||
buffer.flip();
|
||||
out.write(buffer);
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void runTask(Pipe pipe, Map<String, String> files) {
|
||||
try(ZipOutputStream zos = new ZipOutputStream(Channels.newOutputStream(pipe.sink()));
|
||||
WritableByteChannel out = Channels.newChannel(zos)) {
|
||||
for (Map.Entry<String, String> entry : files.entrySet()) {
|
||||
taskFunction(zos, out, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打包文件
|
||||
* @param zos 压缩包输出
|
||||
* @param out 缓冲区通道
|
||||
* @param fileName 文件名称
|
||||
* @param file 文件
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
private static void taskFunction(ZipOutputStream zos, WritableByteChannel out, String fileName, File file) throws IOException {
|
||||
// 是否为目录
|
||||
if (file.isDirectory()) {
|
||||
File[] files = file.listFiles();
|
||||
fileName = StrUtil.isEmpty(fileName) ? file.getName() + "/" : fileName + "/";
|
||||
if (files == null || files.length == 0){
|
||||
return;
|
||||
}
|
||||
for (File child : files) {
|
||||
taskFunction(zos, out, fileName + child.getName(), child);
|
||||
}
|
||||
} else {
|
||||
fileName = StrUtil.isEmpty(fileName) ? file.getName() : fileName;
|
||||
zos.putNextEntry(new ZipEntry(fileName));
|
||||
try(FileInputStream fis = new FileInputStream(file.getAbsolutePath())){
|
||||
FileChannel fileChannel = fis.getChannel();
|
||||
fileChannel.transferTo(0, fileChannel.size(), out);
|
||||
fileChannel.close();
|
||||
}catch (IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void taskFunction(ZipOutputStream zos, WritableByteChannel out, String fileName, String file) throws IOException {
|
||||
// 网络文件
|
||||
if (file.startsWith("http")) {
|
||||
zos.putNextEntry(new ZipEntry(fileName));
|
||||
byte[] bytes = HttpUtil.downloadBytes(file);
|
||||
out.write(ByteBuffer.wrap(bytes));
|
||||
}else {
|
||||
taskFunction(zos, out, fileName, new File(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
32
sms4j-email-jakarta/sms4j-email-jakarta-core/pom.xml
Normal file
32
sms4j-email-jakarta/sms4j-email-jakarta-core/pom.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-email-jakarta</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>sms4j-email-jakarta-core</artifactId>
|
||||
<description>email依赖引入包</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-email-jakarta-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.angus</groupId>
|
||||
<artifactId>jakarta.mail</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.activation</groupId>
|
||||
<artifactId>jakarta.activation-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,63 @@
|
||||
package org.dromara.email.jakarta.core.factory;
|
||||
|
||||
import jakarta.mail.MessagingException;
|
||||
import org.dromara.email.jakarta.api.Blacklist;
|
||||
import org.dromara.email.jakarta.api.MailClient;
|
||||
import org.dromara.email.jakarta.comm.config.MailSmtpConfig;
|
||||
import org.dromara.email.jakarta.comm.errors.MailException;
|
||||
import org.dromara.email.jakarta.core.service.MailBuild;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MailFactory
|
||||
* <p> 配置工厂
|
||||
* @author :Wind
|
||||
* 2023/6/8 22:35
|
||||
**/
|
||||
public class MailFactory{
|
||||
private static final Map<Object,MailSmtpConfig> CONFIGS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* createMailClient
|
||||
* <p>从工厂获取一个邮件发送实例
|
||||
* @param key 配置的标识key
|
||||
* @author :Wind
|
||||
*/
|
||||
public static MailClient createMailClient(Object key){
|
||||
try {
|
||||
return MailBuild.build(CONFIGS.get(key));
|
||||
} catch (MessagingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* createMailClient
|
||||
* <p>从工厂获取一个邮件发送实例,该实例发送短信将依照黑名单中的数据进行过滤
|
||||
* @param key 配置的标识key
|
||||
* @param blacklist 黑名单接口,实例将从这里获取黑名单数据
|
||||
* @author :Wind
|
||||
*/
|
||||
public static MailClient createMailClient(Object key, Blacklist blacklist){
|
||||
try {
|
||||
return MailBuild.build(CONFIGS.get(key),blacklist);
|
||||
} catch (MessagingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set
|
||||
* <p>将一个配置对象交给工厂
|
||||
* @param key 标识
|
||||
* @param config 配置对象
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void put(Object key, MailSmtpConfig config){
|
||||
CONFIGS.put(key,config);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package org.dromara.email.jakarta.core.factory;
|
||||
|
||||
import org.dromara.email.jakarta.api.Monitor;
|
||||
import org.dromara.email.jakarta.comm.config.MailImapConfig;
|
||||
import org.dromara.email.jakarta.core.service.MonitorService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MonitorFactory
|
||||
* <p> 监听系统工厂,通过向工厂添加配置,可以启动或关闭监听
|
||||
* 监听器通过连接指定的imap服务器监听指定的邮箱,并以异步轮询的形式进行消息的监听,除去轮询时间开销外,个别imap服务器本身存在延迟,故而邮件的监听可能存在较大延迟,
|
||||
* 在配置中可以设置轮询的间隔时间,以增大或缩小监听灵敏度。灵敏度调节周期为秒级,默认周期为5秒。
|
||||
* <p>需要注意的是,监听器所使用的线程并非任何线程池中的线程,如想停止某邮箱的监听,只需要调用stop方法即可,他会在完成当前接收的任务后正常的终结线程。
|
||||
* 现成终结后可以通过调用start方法重新启用。
|
||||
* @author :Wind
|
||||
* 2023/7/18 17:06
|
||||
**/
|
||||
public class MonitorFactory {
|
||||
|
||||
private final static Map<String, MonitorService> SERVICES = new HashMap<>();
|
||||
|
||||
/**
|
||||
* put
|
||||
* <p> 添加一个配置至系统中,并绑定接收消息的对象
|
||||
* @param key 监听标识
|
||||
* @param config 监听配置
|
||||
* @param monitor 回调对象
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void put(String key, MailImapConfig config, Monitor monitor){
|
||||
SERVICES.put(key,new MonitorService(config,monitor));
|
||||
}
|
||||
|
||||
/**
|
||||
* start
|
||||
* <p> 开始监听指定标识的邮箱
|
||||
* @param key 标识
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void start(String key){
|
||||
SERVICES.get(key).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* stop
|
||||
* <p> 停止监听指定标识的邮箱
|
||||
* @param key 标识
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void stop(String key){
|
||||
SERVICES.get(key).stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* getConfig
|
||||
* <p> 获取指定标识的配置信息
|
||||
* @param key 标识
|
||||
* @author :Wind
|
||||
*/
|
||||
public static MailImapConfig getConfig(String key) {
|
||||
return SERVICES.get(key).getMailImapConfig();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* <p> 邮件插件核心模块
|
||||
* @author :Wind
|
||||
* 2023/7/27 10:58
|
||||
**/
|
||||
package org.dromara.email.jakarta.core;
|
||||
@ -0,0 +1,128 @@
|
||||
package org.dromara.email.jakarta.core.service;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.mail.Authenticator;
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.PasswordAuthentication;
|
||||
import jakarta.mail.Session;
|
||||
import jakarta.mail.internet.AddressException;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import lombok.Data;
|
||||
import org.dromara.email.jakarta.api.Blacklist;
|
||||
import org.dromara.email.jakarta.api.MailClient;
|
||||
import org.dromara.email.jakarta.comm.config.MailSmtpConfig;
|
||||
import org.dromara.email.jakarta.comm.errors.MailException;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
@Data
|
||||
public class MailBuild {
|
||||
|
||||
private Message message;
|
||||
|
||||
private Session session;
|
||||
|
||||
private MailSmtpConfig config;
|
||||
|
||||
private Blacklist blacklist;
|
||||
|
||||
private int retryInterval;
|
||||
|
||||
private int maxRetries;
|
||||
|
||||
private MailBuild(MailSmtpConfig config) throws MessagingException {
|
||||
Properties props = new Properties();
|
||||
props.put("mail.smtp.host", config.getSmtpServer());
|
||||
props.put("mail.smtp.auth", config.getIsAuth());
|
||||
props.put("mail.smtp.port", config.getPort());
|
||||
props.put("mail.smtp.ssl.enable", config.getIsSSL());
|
||||
// props.put("mail.smtp.ssl.socketFactory", new MailSSLSocketFactory());
|
||||
this.session = Session.getInstance(props, new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(config.getUsername(), config.getPassword());
|
||||
}
|
||||
});
|
||||
this.message = new MimeMessage(session);
|
||||
try {
|
||||
if (StrUtil.isEmpty(config.getNickName())){
|
||||
this.message.setFrom(new InternetAddress(config.getFromAddress()));
|
||||
}else {
|
||||
this.message.setFrom(new InternetAddress(config.getFromAddress(),config.getNickName()));
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
this.config = config;
|
||||
this.retryInterval = config.getRetryInterval();
|
||||
this.maxRetries = config.getMaxRetries();
|
||||
}
|
||||
|
||||
private MailBuild(MailSmtpConfig config,Blacklist blacklist)throws MessagingException{
|
||||
Properties props = new Properties();
|
||||
props.put("mail.smtp.host", config.getSmtpServer());
|
||||
props.put("mail.smtp.auth", config.getIsAuth());
|
||||
props.put("mail.smtp.port", config.getPort());
|
||||
props.put("mail.smtp.ssl.enable", config.getIsSSL());
|
||||
// props.put("mail.smtp.ssl.socketFactory", new MailSSLSocketFactory());
|
||||
this.session = Session.getInstance(props,
|
||||
new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(config.getUsername(), config.getPassword());
|
||||
}
|
||||
});
|
||||
this.message = new MimeMessage(session);
|
||||
try {
|
||||
if (StrUtil.isEmpty(config.getNickName())){
|
||||
this.message.setFrom(new InternetAddress(config.getFromAddress()));
|
||||
}else {
|
||||
this.message.setFrom(new InternetAddress(config.getFromAddress(),config.getNickName()));
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
this.config = config;
|
||||
this.blacklist = blacklist;
|
||||
this.retryInterval = config.getRetryInterval();
|
||||
this.maxRetries = config.getMaxRetries();
|
||||
}
|
||||
|
||||
public static MailClient build(MailSmtpConfig config) throws MessagingException {
|
||||
return MailService.instance(new MailBuild(config));
|
||||
}
|
||||
public static MailClient build(MailSmtpConfig config,Blacklist blacklist)throws MessagingException {
|
||||
return MailService.instance(new MailBuild(config,blacklist));
|
||||
}
|
||||
|
||||
/**
|
||||
* eliminate
|
||||
* <p>过滤黑名单内容
|
||||
* @param source 需要过滤的源数据
|
||||
* @author :Wind
|
||||
*/
|
||||
public InternetAddress[] eliminate(List<String> source) {
|
||||
List<String> list = new ArrayList<>();
|
||||
try {
|
||||
if (Objects.isNull(blacklist)) {
|
||||
return InternetAddress.parse(Objects.requireNonNull(CollUtil.join(source, ",")));
|
||||
}
|
||||
List<String> black = blacklist.getBlacklist();
|
||||
for (String s : source) {
|
||||
if (!black.contains(s)) {
|
||||
list.add(s);
|
||||
}
|
||||
}
|
||||
return InternetAddress.parse(CollUtil.join(list, ","));
|
||||
} catch (AddressException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,242 @@
|
||||
package org.dromara.email.jakarta.core.service;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import jakarta.activation.DataHandler;
|
||||
import jakarta.activation.DataSource;
|
||||
import jakarta.activation.FileDataSource;
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.Multipart;
|
||||
import jakarta.mail.Transport;
|
||||
import jakarta.mail.internet.MimeBodyPart;
|
||||
import jakarta.mail.internet.MimeMultipart;
|
||||
import jakarta.mail.util.ByteArrayDataSource;
|
||||
import org.dromara.email.jakarta.api.MailClient;
|
||||
import org.dromara.email.jakarta.comm.constants.FileConstants;
|
||||
import org.dromara.email.jakarta.comm.entity.MailMessage;
|
||||
import org.dromara.email.jakarta.comm.errors.MailException;
|
||||
import org.dromara.email.jakarta.comm.utils.HtmlUtil;
|
||||
import org.dromara.email.jakarta.comm.utils.ZipUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class MailService implements MailClient {
|
||||
|
||||
private static final Logger logger = Logger.getLogger("mailLog");
|
||||
private final MailBuild mailBuild;
|
||||
|
||||
private MailService(MailBuild mailBuild) {
|
||||
this.mailBuild = mailBuild;
|
||||
}
|
||||
|
||||
public static MailClient instance(MailBuild mailBuild) {
|
||||
return new MailService(mailBuild);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(MailMessage mailMessage) {
|
||||
List<String> html = null;
|
||||
if (mailMessage.getHtmlInputStream() != null) {
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlInputStream());
|
||||
}
|
||||
if (StrUtil.isNotBlank(mailMessage.getHtmlPath())) {
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlPath(), MailService.class);
|
||||
}
|
||||
if (mailMessage.getHtmlFile() != null) {
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlFile());
|
||||
}
|
||||
if (StrUtil.isNotBlank(mailMessage.getHtmlContent())) {
|
||||
html = Arrays.asList(mailMessage.getHtmlContent().split("\n"));
|
||||
}
|
||||
send(mailMessage.getMailAddress(),
|
||||
mailMessage.getTitle(),
|
||||
mailMessage.getBody(),
|
||||
html,
|
||||
mailMessage.getHtmlValues(),
|
||||
mailMessage.getZipName(),
|
||||
mailMessage.getFiles(),
|
||||
mailMessage.getCc(),
|
||||
mailMessage.getBcc()
|
||||
);
|
||||
}
|
||||
|
||||
private void forFiles(Multipart multipart, Map<String, String> files) throws MessagingException {
|
||||
for (Map.Entry<String, String> entry : files.entrySet()) {
|
||||
String k = entry.getKey();
|
||||
String v = entry.getValue();
|
||||
// 设置附件消息部分
|
||||
MimeBodyPart messageBodyPart = new MimeBodyPart();
|
||||
DataSource source;
|
||||
if (v.startsWith("http")) {
|
||||
byte[] bytes = HttpUtil.downloadBytes(v);
|
||||
source = new ByteArrayDataSource(bytes, FileConstants.IO_FILE_TYPE);
|
||||
} else {
|
||||
source = new FileDataSource(v);
|
||||
}
|
||||
messageBodyPart.setDataHandler(new DataHandler(source));
|
||||
messageBodyPart.setFileName(k);
|
||||
multipart.addBodyPart(messageBodyPart);
|
||||
}
|
||||
}
|
||||
|
||||
private void zipFiles(Multipart multipart, String zipName, Map<String, String> files) throws MessagingException, IOException {
|
||||
// 设置附件消息部分
|
||||
MimeBodyPart messageBodyPart = new MimeBodyPart();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ZipUtils.zipFilePip(files, os);
|
||||
ByteArrayInputStream stream = IoUtil.toStream(os);
|
||||
DataSource source = new ByteArrayDataSource(stream, FileConstants.IO_FILE_TYPE);
|
||||
messageBodyPart.setDataHandler(new DataHandler(source));
|
||||
messageBodyPart.setFileName(StrUtil.isNotBlank(zipName) ? zipName : UUID.fastUUID() + ".zip");
|
||||
multipart.addBodyPart(messageBodyPart);
|
||||
}
|
||||
|
||||
private void send(List<String> mailAddress,
|
||||
String title,
|
||||
String body,
|
||||
List<String> html,
|
||||
Map<String, String> parameter,
|
||||
String zipName,
|
||||
Map<String, String> files,
|
||||
List<String> cc,
|
||||
List<String> bcc) {
|
||||
try {
|
||||
Message message = messageBuild(mailAddress, title, body, html, parameter, zipName, cc, bcc, files);
|
||||
Transport.send(message);
|
||||
logger.info("邮件发送成功!^_^");
|
||||
} catch (MessagingException | IOException e) {
|
||||
// 防止 maxRetries 数值小于0带来的其他问题
|
||||
if (mailBuild.getMaxRetries() > 0) {
|
||||
ReSendList(mailAddress, title, body, html, parameter, zipName, files, cc, bcc);
|
||||
} else {
|
||||
logger.warning(e.getMessage());
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReSendList(List<String> mailAddress,
|
||||
String title,
|
||||
String body,
|
||||
List<String> html,
|
||||
Map<String, String> parameter,
|
||||
String zipName,
|
||||
Map<String, String> files,
|
||||
List<String> cc,
|
||||
List<String> bcc) {
|
||||
int maxRetries = mailBuild.getMaxRetries();
|
||||
// 初始值为1;则while循环中少发送一次,最后一次发送在判断 retryCount >= maxRetries 这里。
|
||||
int retryCount = 1;
|
||||
boolean retryOnFailure = true;
|
||||
|
||||
while (retryOnFailure && retryCount < maxRetries) {
|
||||
try {
|
||||
logger.warning("邮件第 {" + retryCount + "} 次重新发送");
|
||||
Message message;
|
||||
if (html != null || parameter != null) {
|
||||
message = messageBuild(mailAddress, title, body, html, parameter, zipName, cc, bcc, files);
|
||||
} else {
|
||||
message = messageBuild(mailAddress, title, body, null, null, zipName, cc, bcc, files);
|
||||
}
|
||||
Transport.send(message);
|
||||
// 发送成功,停止重试
|
||||
retryOnFailure = false;
|
||||
} catch (MessagingException | IOException e) {
|
||||
retryCount++;
|
||||
try {
|
||||
// 间隔秒数
|
||||
TimeUnit.SECONDS.sleep(mailBuild.getRetryInterval());
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (retryCount >= maxRetries) {
|
||||
try {
|
||||
Message message;
|
||||
if (html != null || parameter != null) {
|
||||
message = messageBuild(mailAddress, title, body, html, parameter, null, cc, bcc, files);
|
||||
} else {
|
||||
message = messageBuild(mailAddress, title, body, null, null, null, cc, bcc, files);
|
||||
}
|
||||
Transport.send(message);
|
||||
} catch (MessagingException | IOException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Message messageBuild(List<String> mailAddress,
|
||||
String title,
|
||||
String body,
|
||||
List<String> html,
|
||||
Map<String, String> parameter,
|
||||
String zipName,
|
||||
List<String> cc,
|
||||
List<String> bcc,
|
||||
Map<String, String> files) throws MessagingException, IOException {
|
||||
Message message = mailBuild.getMessage();
|
||||
message.setRecipients(Message.RecipientType.TO, mailBuild.eliminate(mailAddress));
|
||||
message.setSubject(title);
|
||||
|
||||
Multipart multipart = new MimeMultipart("alternative");
|
||||
if (CollUtil.isNotEmpty(html)) {
|
||||
String htmlData;
|
||||
List<String> strings;
|
||||
if (MapUtil.isNotEmpty(parameter)) {
|
||||
//读取模板并进行变量替换
|
||||
strings = HtmlUtil.replacePlaceholder(html, parameter);
|
||||
//拼合HTML数据
|
||||
htmlData = HtmlUtil.pieceHtml(strings);
|
||||
}else {
|
||||
htmlData = HtmlUtil.pieceHtml(html);
|
||||
}
|
||||
MimeBodyPart htmlPart = new MimeBodyPart();
|
||||
htmlPart.setContent(htmlData, "text/html;charset=UTF-8");
|
||||
multipart.addBodyPart(htmlPart);
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(body)) {
|
||||
// 创建文本正文部分
|
||||
MimeBodyPart textPart = new MimeBodyPart();
|
||||
textPart.setText(body);
|
||||
multipart.addBodyPart(textPart);
|
||||
}
|
||||
//添加附件
|
||||
if (MapUtil.isNotEmpty(files) && StrUtil.isNotBlank(zipName)) {
|
||||
zipFiles(multipart, zipName, files);
|
||||
} else {
|
||||
if (MapUtil.isNotEmpty(files)) {
|
||||
forFiles(multipart, files);
|
||||
message.setContent(multipart);
|
||||
}
|
||||
}
|
||||
if (CollUtil.isNotEmpty(cc) || CollUtil.isNotEmpty(bcc)) {
|
||||
addCC(cc, bcc, message);
|
||||
}
|
||||
message.setContent(multipart);
|
||||
return message;
|
||||
}
|
||||
|
||||
private void addCC(List<String> cc, List<String> bcc, Message message) throws MessagingException {
|
||||
if (CollUtil.isNotEmpty(cc)) {
|
||||
message.addRecipients(Message.RecipientType.CC, mailBuild.eliminate(cc));
|
||||
}
|
||||
if (CollUtil.isNotEmpty(bcc)) {
|
||||
message.addRecipients(Message.RecipientType.BCC, mailBuild.eliminate(bcc));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
package org.dromara.email.jakarta.core.service;
|
||||
|
||||
import jakarta.mail.BodyPart;
|
||||
import jakarta.mail.Flags;
|
||||
import jakarta.mail.Folder;
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.Multipart;
|
||||
import jakarta.mail.Session;
|
||||
import jakarta.mail.Store;
|
||||
import jakarta.mail.search.FlagTerm;
|
||||
import lombok.Getter;
|
||||
import org.dromara.email.jakarta.api.Monitor;
|
||||
import org.dromara.email.jakarta.comm.config.MailImapConfig;
|
||||
import org.dromara.email.jakarta.comm.entity.MonitorMessage;
|
||||
import org.dromara.email.jakarta.comm.errors.MailException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Properties;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* MonitorService
|
||||
* <p> 监听器服务
|
||||
*
|
||||
* @author :Wind
|
||||
* 2023/7/18 16:10
|
||||
**/
|
||||
public class MonitorService{
|
||||
private final Store store;
|
||||
private Monitor monitor;
|
||||
@Getter
|
||||
private MailImapConfig mailImapConfig;
|
||||
private Timer timer;
|
||||
|
||||
public MonitorService(MailImapConfig config, Monitor monitor) {
|
||||
Properties props = System.getProperties();
|
||||
props.setProperty("mail.store.protocol", "imaps");
|
||||
try {
|
||||
Session session = Session.getDefaultInstance(props, null);
|
||||
Store store = session.getStore("imaps");
|
||||
store.connect(config.getImapServer(), config.getUsername(), config.getAccessToken());
|
||||
this.store = store;
|
||||
this.monitor = monitor;
|
||||
this.mailImapConfig = config;
|
||||
} catch (Exception e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startListening() {
|
||||
try {
|
||||
Folder inbox = store.getFolder("Inbox");
|
||||
inbox.open(Folder.READ_WRITE);
|
||||
Message[] messages = inbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
|
||||
for (Message message : messages) {
|
||||
MonitorMessage monitorMessage = new MonitorMessage();
|
||||
// 获取邮件的发送者
|
||||
monitorMessage.setFromAddress(message.getFrom()[0].toString());
|
||||
// 获取邮件主题
|
||||
monitorMessage.setTitle(message.getSubject());
|
||||
// 获取邮件的内容
|
||||
if (message.isMimeType("text/plain")) {
|
||||
Object content = message.getContent();
|
||||
if (content == null){
|
||||
StringBuilder stringBuilder = getStringBuilder(message);
|
||||
content = stringBuilder.toString();
|
||||
}
|
||||
monitorMessage.setText(content.toString());
|
||||
} else if (message.isMimeType("multipart/*")) {
|
||||
Multipart mp = (Multipart) message.getContent();
|
||||
for (int i = 0; i < mp.getCount();i++){
|
||||
BodyPart bodyPart = mp.getBodyPart(i);
|
||||
String contentType = bodyPart.getContentType().toLowerCase();
|
||||
if (contentType.startsWith("text/plain")) {
|
||||
// 纯文本内容
|
||||
monitorMessage.setText(bodyPart.getContent().toString());
|
||||
} else if (contentType.startsWith("text/html")) {
|
||||
// HTML内容
|
||||
monitorMessage.setHtmlText(bodyPart.getContent().toString());
|
||||
}
|
||||
}
|
||||
monitorMessage.setBody(mp);
|
||||
}
|
||||
monitorMessage.setMessageIndex(message.getMessageNumber());
|
||||
monitorMessage.setSendDate(message.getSentDate());
|
||||
monitorMessage.setAcceptTime(System.currentTimeMillis());
|
||||
if (this.monitor.monitor(monitorMessage)) {
|
||||
message.setFlag(Flags.Flag.SEEN, true);
|
||||
}
|
||||
}
|
||||
inbox.close();
|
||||
} catch (Exception e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static StringBuilder getStringBuilder(Message message) throws IOException, MessagingException {
|
||||
InputStream inputStream = message.getInputStream();
|
||||
|
||||
// 解析输入流以获取内容
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
stringBuilder.append(line);
|
||||
}
|
||||
}
|
||||
return stringBuilder;
|
||||
}
|
||||
|
||||
public MonitorService start(){
|
||||
Timer timer = new Timer();
|
||||
this.timer = timer;
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
startListening();
|
||||
}
|
||||
},0,mailImapConfig.getCycle()*1000);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void stop(){
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
}
|
||||
@ -15,30 +15,53 @@ import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.dao.SmsDao;
|
||||
import org.dromara.sms4j.api.dao.SmsDaoDefaultImpl;
|
||||
import org.dromara.sms4j.api.universal.SupplierConfig;
|
||||
import org.dromara.sms4j.api.verify.PhoneVerify;
|
||||
import org.dromara.sms4j.baidu.config.BaiduFactory;
|
||||
import org.dromara.sms4j.budingyun.config.BudingV2Factory;
|
||||
import org.dromara.sms4j.chuanglan.config.ChuangLanFactory;
|
||||
import org.dromara.sms4j.cloopen.config.CloopenFactory;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.comm.utils.SmsUtils;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
import org.dromara.sms4j.core.proxy.RestrictedProcessDefaultImpl;
|
||||
import org.dromara.sms4j.core.proxy.SmsInvocationHandler;
|
||||
import org.dromara.sms4j.core.proxy.EnvirmentHolder;
|
||||
import org.dromara.sms4j.core.proxy.SmsProxyFactory;
|
||||
import org.dromara.sms4j.core.proxy.processor.BlackListProcessor;
|
||||
import org.dromara.sms4j.core.proxy.processor.BlackListRecordingProcessor;
|
||||
import org.dromara.sms4j.core.proxy.processor.CoreMethodParamValidateProcessor;
|
||||
import org.dromara.sms4j.core.proxy.processor.RestrictedProcessor;
|
||||
import org.dromara.sms4j.ctyun.config.CtyunFactory;
|
||||
import org.dromara.sms4j.danmi.config.DanMiFactory;
|
||||
import org.dromara.sms4j.dingzhong.config.DingZhongFactory;
|
||||
import org.dromara.sms4j.emay.config.EmayFactory;
|
||||
import org.dromara.sms4j.huawei.config.HuaweiFactory;
|
||||
import org.dromara.sms4j.javase.util.YamlUtils;
|
||||
import org.dromara.sms4j.jdcloud.config.JdCloudFactory;
|
||||
import org.dromara.sms4j.jg.config.JgFactory;
|
||||
import org.dromara.sms4j.lianlu.config.LianLuFactory;
|
||||
import org.dromara.sms4j.luosimao.config.LuoSiMaoFactory;
|
||||
import org.dromara.sms4j.mas.config.MasFactory;
|
||||
import org.dromara.sms4j.netease.config.NeteaseFactory;
|
||||
import org.dromara.sms4j.provider.config.SmsConfig;
|
||||
import org.dromara.sms4j.provider.factory.BaseProviderFactory;
|
||||
import org.dromara.sms4j.provider.factory.BeanFactory;
|
||||
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
|
||||
import org.dromara.sms4j.qiniu.config.QiNiuFactory;
|
||||
import org.dromara.sms4j.submail.config.SubMailFactory;
|
||||
import org.dromara.sms4j.tencent.config.TencentFactory;
|
||||
import org.dromara.sms4j.unisms.config.UniFactory;
|
||||
import org.dromara.sms4j.yixintong.config.YiXintongFactory;
|
||||
import org.dromara.sms4j.yunpian.config.YunPianFactory;
|
||||
import org.dromara.sms4j.zhutong.config.ZhutongFactory;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* 初始化类
|
||||
@ -85,7 +108,7 @@ public class SEInitializer {
|
||||
/**
|
||||
* 从配置bean对象中加载配置
|
||||
*
|
||||
* @param smsConfig 短信公共配置
|
||||
* @param smsConfig 短信公共配置
|
||||
* @param configList 各短信服务商配置列表
|
||||
*/
|
||||
public void fromConfig(SmsConfig smsConfig, List<SupplierConfig> configList) {
|
||||
@ -94,20 +117,49 @@ public class SEInitializer {
|
||||
// 初始化SmsConfig整体配置文件
|
||||
BeanUtil.copyProperties(smsConfig, BeanFactory.getSmsConfig());
|
||||
// 创建短信服务对象
|
||||
if(CollUtil.isEmpty(configList)) {
|
||||
return ;
|
||||
if (CollUtil.isEmpty(configList)) {
|
||||
return;
|
||||
}
|
||||
for(SupplierConfig supplierConfig : configList) {
|
||||
if(Boolean.TRUE.equals(smsConfig.getRestricted())) {
|
||||
SmsFactory.createRestrictedSmsBlend(supplierConfig);
|
||||
} else {
|
||||
SmsFactory.createSmsBlend(supplierConfig);
|
||||
try{
|
||||
Map<String, Map<String, Object>> blends = new HashMap<>();
|
||||
for (SupplierConfig supplierConfig : configList) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
String channel = supplierConfig.getSupplier();
|
||||
Class<? extends SupplierConfig> clazz = supplierConfig.getClass();
|
||||
BeanInfo beanInfo = Introspector.getBeanInfo(clazz, Object.class);
|
||||
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
|
||||
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
|
||||
Method readMethod = propertyDescriptor.getReadMethod();
|
||||
Object item = readMethod.invoke(supplierConfig);
|
||||
param.put(propertyDescriptor.getName(),item);
|
||||
}
|
||||
blends.put(channel,param);
|
||||
}
|
||||
//持有初始化配置信息
|
||||
EnvirmentHolder.frozenEnvirmet(smsConfig, blends);
|
||||
|
||||
//注册执行器实现
|
||||
SmsProxyFactory.addPreProcessor(new RestrictedProcessor());
|
||||
SmsProxyFactory.addPreProcessor(new BlackListProcessor());
|
||||
SmsProxyFactory.addPreProcessor(new BlackListRecordingProcessor());
|
||||
//如果手机号校验器存在实现,则注册手机号校验器
|
||||
ServiceLoader<PhoneVerify> loader = ServiceLoader.load(PhoneVerify.class);
|
||||
if (loader.iterator().hasNext()) {
|
||||
loader.forEach(f -> SmsProxyFactory.addPreProcessor(new CoreMethodParamValidateProcessor(f)));
|
||||
} else {
|
||||
SmsProxyFactory.addPreProcessor(new CoreMethodParamValidateProcessor(null));
|
||||
}
|
||||
}catch (Exception e){
|
||||
log.error("配置对象转换配置信息失败,但不影响基础功能的使用。【注意】:未加载SMS4J扩展功能模块,拦截器,参数校验可能失效!");
|
||||
}
|
||||
for (SupplierConfig supplierConfig : configList) {
|
||||
SmsFactory.createSmsBlend(supplierConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册服务商工厂
|
||||
*
|
||||
* @param factory 服务商工厂
|
||||
*/
|
||||
public SEInitializer registerFactory(BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> factory) {
|
||||
@ -117,16 +169,15 @@ public class SEInitializer {
|
||||
|
||||
/**
|
||||
* 注册DAO实例
|
||||
*
|
||||
* @param smsDao DAO实例
|
||||
*/
|
||||
public SEInitializer registerSmsDao(SmsDao smsDao) {
|
||||
if(smsDao == null) {
|
||||
if (smsDao == null) {
|
||||
throw new SmsBlendException("注册DAO实例失败,实例不能为空");
|
||||
}
|
||||
RestrictedProcessDefaultImpl process = new RestrictedProcessDefaultImpl();
|
||||
process.setSmsDao(smsDao);
|
||||
SmsInvocationHandler.setRestrictedProcess(process);
|
||||
this.smsDao = smsDao;
|
||||
SESmsDaoHolder.setSmsDao(smsDao);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -142,7 +193,7 @@ public class SEInitializer {
|
||||
}
|
||||
|
||||
//注册默认DAO实例
|
||||
if(this.smsDao == null) {
|
||||
if (this.smsDao == null) {
|
||||
this.registerSmsDao(SmsDaoDefaultImpl.getInstance());
|
||||
}
|
||||
|
||||
@ -151,27 +202,39 @@ public class SEInitializer {
|
||||
|
||||
//初始化SmsConfig整体配置文件
|
||||
BeanUtil.copyProperties(smsConfig, BeanFactory.getSmsConfig());
|
||||
|
||||
// 解析服务商配置
|
||||
Map<String, Map<String, Object>> blends = smsConfig.getBlends();
|
||||
for(String configId : blends.keySet()) {
|
||||
|
||||
//持有初始化配置信息
|
||||
EnvirmentHolder.frozenEnvirmet(smsConfig, blends);
|
||||
|
||||
//注册执行器实现
|
||||
SmsProxyFactory.addPreProcessor(new RestrictedProcessor());
|
||||
SmsProxyFactory.addPreProcessor(new BlackListProcessor());
|
||||
SmsProxyFactory.addPreProcessor(new BlackListRecordingProcessor());
|
||||
//如果手机号校验器存在实现,则注册手机号校验器
|
||||
ServiceLoader<PhoneVerify> loader = ServiceLoader.load(PhoneVerify.class);
|
||||
if (loader.iterator().hasNext()) {
|
||||
loader.forEach(f -> SmsProxyFactory.addPreProcessor(new CoreMethodParamValidateProcessor(f)));
|
||||
} else {
|
||||
SmsProxyFactory.addPreProcessor(new CoreMethodParamValidateProcessor(null));
|
||||
}
|
||||
for (String configId : blends.keySet()) {
|
||||
Map<String, Object> configMap = blends.get(configId);
|
||||
Object supplierObj = configMap.get(Constant.SUPPLIER_KEY);
|
||||
String supplier = supplierObj == null ? "" : String.valueOf(supplierObj);
|
||||
supplier = StrUtil.isEmpty(supplier) ? configId : supplier;
|
||||
BaseProviderFactory<SmsBlend, SupplierConfig> providerFactory = (BaseProviderFactory<SmsBlend, SupplierConfig>) ProviderFactoryHolder.requireForSupplier(supplier);
|
||||
if(providerFactory == null) {
|
||||
if (providerFactory == null) {
|
||||
log.warn("创建\"{}\"的短信服务失败,未找到服务商为\"{}\"的服务", configId, supplier);
|
||||
continue;
|
||||
}
|
||||
configMap.put("config-id", configId);
|
||||
SmsUtils.replaceKeysSeperator(configMap, "-", "_");
|
||||
SmsUtils.replaceKeysSeparator(configMap, "-", "_");
|
||||
JSONObject configJson = new JSONObject(configMap);
|
||||
SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass());
|
||||
if(Boolean.TRUE.equals(smsConfig.getRestricted())) {
|
||||
SmsFactory.createRestrictedSmsBlend(supplierConfig);
|
||||
} else {
|
||||
SmsFactory.createSmsBlend(supplierConfig);
|
||||
}
|
||||
SmsFactory.createSmsBlend(supplierConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,7 +252,19 @@ public class SEInitializer {
|
||||
ProviderFactoryHolder.registerFactory(UniFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(YunPianFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(ZhutongFactory.instance());
|
||||
if(SmsUtils.isClassExists("com.jdcloud.sdk.auth.CredentialsProvider")) {
|
||||
ProviderFactoryHolder.registerFactory(LianLuFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(DingZhongFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(QiNiuFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(ChuangLanFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(JgFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(BudingV2Factory.instance());
|
||||
ProviderFactoryHolder.registerFactory(MasFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(BaiduFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(LuoSiMaoFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(SubMailFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(DanMiFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(YiXintongFactory.instance());
|
||||
if (SmsUtils.isClassExists("com.jdcloud.sdk.auth.CredentialsProvider")) {
|
||||
ProviderFactoryHolder.registerFactory(JdCloudFactory.instance());
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
package org.dromara.sms4j.javase.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.dromara.sms4j.api.dao.SmsDao;
|
||||
|
||||
public class SESmsDaoHolder {
|
||||
|
||||
@Getter
|
||||
private static SmsDao smsDao = null;
|
||||
|
||||
public static void setSmsDao(SmsDao smsDao) {
|
||||
SESmsDaoHolder.smsDao = smsDao;
|
||||
}
|
||||
}
|
||||
91
sms4j-oa-plugin/pom.xml
Normal file
91
sms4j-oa-plugin/pom.xml
Normal file
@ -0,0 +1,91 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
<artifactId>sms4j-oa-plugin</artifactId>
|
||||
|
||||
<modules>
|
||||
<module>sms4j-oa-comm</module>
|
||||
<module>sms4j-oa-api</module>
|
||||
<module>sms4j-oa-core</module>
|
||||
</modules>
|
||||
|
||||
<name>sms4j-oa-plugin</name>
|
||||
<description>sms4j-oa-plugin</description>
|
||||
|
||||
<properties>
|
||||
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-oa-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-oa-comm</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-oa-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.activation</groupId>
|
||||
<artifactId>javax.activation</artifactId>
|
||||
<version>${sunactivation.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.activation</groupId>
|
||||
<artifactId>jakarta.activation-api</artifactId>
|
||||
<version>${jakarta.activation.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!--配置文件提示-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.feishu.openplatform</groupId>-->
|
||||
<!-- <artifactId>feishu-sdk-java</artifactId>-->
|
||||
<!-- <version>${feishu.openplatform.version}</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
</project>
|
||||
29
sms4j-oa-plugin/sms4j-oa-api/pom.xml
Normal file
29
sms4j-oa-plugin/sms4j-oa-api/pom.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-oa-plugin</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>sms4j-oa-api</artifactId>
|
||||
|
||||
<name>sms4j-oa-api</name>
|
||||
<description>sms4j-oa-api</description>
|
||||
|
||||
<properties>
|
||||
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-oa-comm</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,12 @@
|
||||
package org.dromara.oa.api;
|
||||
|
||||
import org.dromara.oa.comm.entity.Response;
|
||||
|
||||
/**
|
||||
* @author dongfeng
|
||||
* 2023-10-28 14:26
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OaCallBack {
|
||||
void callBack(Response smsResponse);
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package org.dromara.oa.api;
|
||||
|
||||
import org.dromara.oa.comm.entity.Request;
|
||||
import org.dromara.oa.comm.entity.Response;
|
||||
import org.dromara.oa.comm.enums.MessageType;
|
||||
|
||||
public interface OaSender {
|
||||
|
||||
/**
|
||||
* 获取通知webhook实例唯一标识
|
||||
*/
|
||||
String getConfigId();
|
||||
|
||||
/**
|
||||
* 获取供应商标识
|
||||
*/
|
||||
String getSupplier();
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
Response sender(Request request, MessageType messageType);
|
||||
|
||||
/**
|
||||
* 异步(回调)
|
||||
*/
|
||||
void senderAsync(Request request, MessageType messageType);
|
||||
|
||||
/**
|
||||
* 异步(不回调)
|
||||
* @param request oa请求体
|
||||
* @param messageType 消息类型
|
||||
* @param callBack 回调方法
|
||||
*/
|
||||
void senderAsync(Request request, MessageType messageType, OaCallBack callBack);
|
||||
|
||||
/**
|
||||
* 发送带优先级的消息
|
||||
* @param request oa请求体
|
||||
* @param messageType 消息类型
|
||||
*/
|
||||
|
||||
void senderAsyncByPriority(Request request, MessageType messageType);
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package org.dromara.oa.api;
|
||||
|
||||
import org.dromara.oa.comm.entity.Request;
|
||||
import org.dromara.oa.comm.entity.Response;
|
||||
import org.dromara.oa.comm.enums.MessageType;
|
||||
|
||||
public interface OaSender {
|
||||
|
||||
Response Sender(Request request, MessageType messageType);
|
||||
}
|
||||
37
sms4j-oa-plugin/sms4j-oa-comm/pom.xml
Normal file
37
sms4j-oa-plugin/sms4j-oa-comm/pom.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-oa-plugin</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>sms4j-oa-comm</artifactId>
|
||||
<name>sms4j-oa-comm</name>
|
||||
<description>sms4j-oa-comm</description>
|
||||
|
||||
<properties>
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.slf4j</groupId>-->
|
||||
<!-- <artifactId>slf4j-api</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.oa.comm.config;
|
||||
|
||||
/**
|
||||
* @author dongfeng
|
||||
* 2023-10-19 13:36
|
||||
*/
|
||||
public interface OaSupplierConfig {
|
||||
|
||||
/**
|
||||
* 获取配置标识名(唯一)
|
||||
*/
|
||||
String getConfigId();
|
||||
|
||||
/**
|
||||
* 获取供应商
|
||||
*/
|
||||
String getSupplier();
|
||||
|
||||
/**
|
||||
* 获取是否使用
|
||||
*/
|
||||
Boolean getIsEnable();
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package org.dromara.oa.comm.content;
|
||||
|
||||
/**
|
||||
* @author dongfeng
|
||||
* 2023-10-22 13:50
|
||||
*/
|
||||
public class OaContent {
|
||||
/**
|
||||
* 供应商配置键名
|
||||
*/
|
||||
public static final String SUPPLIER_KEY = "supplier";
|
||||
|
||||
/**
|
||||
* 钉钉
|
||||
*/
|
||||
public static final String DING_TALK = "ding_ding";
|
||||
/**
|
||||
* 飞书
|
||||
*/
|
||||
public static final String BYTE_TALK = "byte_talk";
|
||||
/**
|
||||
* 微信
|
||||
*/
|
||||
public static final String WE_TALK = "we_talk";
|
||||
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package org.dromara.oa.comm.entity;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.oa.comm.enums.MessageType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Request implements Comparable<Request> {
|
||||
// 标题
|
||||
private String title;
|
||||
|
||||
// 消息内容
|
||||
private String content;
|
||||
|
||||
// link类型的参数
|
||||
private String picUrl;
|
||||
|
||||
// link类型的参数
|
||||
private String messageUrl;
|
||||
|
||||
// news类型的参数
|
||||
private List<WeTalkRequestArticle> articleList;
|
||||
|
||||
private List<String> phoneList;
|
||||
|
||||
// 用于@,唯一标识:userId/openId
|
||||
private List<String> userIdList;
|
||||
|
||||
private List<String> userNamesList;
|
||||
|
||||
private Boolean isNoticeAll = false;
|
||||
|
||||
// oa类型
|
||||
private String oaType;
|
||||
|
||||
// 优先级
|
||||
private Integer priority;
|
||||
|
||||
// 消息类型,用于优先级队列
|
||||
private MessageType messageType;
|
||||
|
||||
@Override
|
||||
public int compareTo(Request other) {
|
||||
return Integer.compare(other.priority,this.priority);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.oa.comm.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class Response {
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 厂商原返回体
|
||||
*/
|
||||
private Object data;
|
||||
|
||||
/**
|
||||
* 配置标识名 如未配置取对应渠道名例如
|
||||
*/
|
||||
private String oaConfigId;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package org.dromara.oa.comm.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SignTimesTamp {
|
||||
String sign;
|
||||
|
||||
Long timestamp;
|
||||
|
||||
public SignTimesTamp(String newSign, Long timestamp) {
|
||||
this.sign = newSign;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package org.dromara.oa.comm.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 图文消息,一个图文消息支持1到8条图文
|
||||
* @author dongfeng
|
||||
* 2024-03-23 19:25
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class WeTalkRequestArticle {
|
||||
// 标题,不超过128个字节,超过会自动截断
|
||||
private String title;
|
||||
|
||||
// 描述,不超过512个字节,超过会自动截断
|
||||
private String description;
|
||||
|
||||
// 点击后跳转的链接。
|
||||
private String url;
|
||||
|
||||
// 图文消息的图片链接,支持JPG、PNG格式,较好的效果为大图 1068*455,小图150*150。
|
||||
private String picUrl;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package org.dromara.oa.comm.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum MessageType {
|
||||
// 钉钉支持类型
|
||||
DING_TALK_TEXT("text"),
|
||||
|
||||
DING_TALK_MARKDOWN("markdown"),
|
||||
|
||||
DING_TALK_LINK("link"),
|
||||
|
||||
// 飞书支持类型
|
||||
BYTE_TALK_TEXT("text"),
|
||||
// BYTE_TALK_MARKDOWN("markdown"),
|
||||
// BYTE_TALK_LINK("link"),
|
||||
|
||||
// 企业微信支持类型
|
||||
WE_TALK_TEXT("text"),
|
||||
|
||||
WE_TALK_MARKDOWN("markdown"),
|
||||
WE_TALK_NEWS("news");
|
||||
//暂未支持
|
||||
// WE_TALK_IMAGE("image"),
|
||||
// WE_TALK_FILE("file"),
|
||||
// WE_TALK_VOICE("voice"),
|
||||
// WE_TALK_TEMPLATE_CARD("template_card");
|
||||
|
||||
MessageType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
private final String name;
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package org.dromara.oa.comm.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public enum OaType {
|
||||
/**
|
||||
* 钉钉
|
||||
*/
|
||||
DING_TALK("ding_ding", "https://oapi.dingtalk.com/robot/send?access_token=", true),
|
||||
/**
|
||||
* 微信
|
||||
*/
|
||||
WE_TALK("we_talk", "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=", true),
|
||||
/**
|
||||
* 飞书
|
||||
*/
|
||||
BYTE_TALK("byte_talk", "https://open.feishu.cn/open-apis/bot/v2/hook/", true),
|
||||
;
|
||||
|
||||
@Getter
|
||||
private final String type;
|
||||
private final String robotUrl;
|
||||
|
||||
|
||||
OaType(String type, String robotUrl, boolean enabled) {
|
||||
this.type = type;
|
||||
this.robotUrl = robotUrl;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return robotUrl;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package org.dromara.oa.comm.errors;
|
||||
|
||||
public class OaException extends RuntimeException {
|
||||
|
||||
public OaException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public OaException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public OaException(String message,String configId) {
|
||||
super("configId为{"+configId+"}抛出异常:"+message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package org.dromara.oa.comm.task.delayed;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class DelayedTime {
|
||||
|
||||
private final Timer timer = new Timer(true);
|
||||
|
||||
|
||||
/**
|
||||
* 延迟队列添加新任务
|
||||
*/
|
||||
public void schedule(TimerTask task, long delay) {
|
||||
timer.schedule(task,delay);
|
||||
}
|
||||
|
||||
}
|
||||
63
sms4j-oa-plugin/sms4j-oa-core/pom.xml
Normal file
63
sms4j-oa-plugin/sms4j-oa-core/pom.xml
Normal file
@ -0,0 +1,63 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-oa-plugin</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>sms4j-oa-core</artifactId>
|
||||
<name>sms4j-oa-core</name>
|
||||
<description>sms4j-oa-core</description>
|
||||
|
||||
<properties>
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.dromara.sms4j</groupId>
|
||||
<artifactId>sms4j-oa-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
<version>5.8.18</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.feishu.openplatform</groupId>-->
|
||||
<!-- <artifactId>feishu-sdk-java</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
|
||||
<!--配置文件提示-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user