Compare commits

..

218 Commits

Author SHA1 Message Date
wind
bc5af3a68a 3.3.5发布 2025-05-10 13:38:46 +08:00
wind
3be4e34d6b 腾讯云,不再强制发送+86号码,如果需要发送非+86手机,可在手机号前拼接 - 号,表示不强制添加+86 2025-04-27 21:38:55 +08:00
风如歌
9baa8bba57
!194 移动云mas的params格式问题修复
Merge pull request !194 from dusttosky/bugfix-mas
2025-04-02 10:44:39 +00:00
Charles7c
03d30271dd 修复部分提示 2025-03-28 20:42:34 +08:00
dusttosky
920f9304ea 移动云mas的params格式问题修复 2025-03-26 16:07:18 +08:00
wind
cedae2696b 3.3.4发布 2025-03-07 21:38:28 +08:00
wind
7582982d44 3.3.4发布 2025-03-07 21:36:32 +08:00
wind
6aa173501e 3.3.4发布 2025-03-07 20:56:01 +08:00
wind
5bc7d0152e 3.3.4发布 2025-03-07 20:55:29 +08:00
wind
bfdc75d538 Merge branch 'dev-3.0.x' of https://gitee.com/dromara/sms4j
# Conflicts:
#	pom.xml
#	sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/Constant.java
2025-03-07 19:55:56 +08:00
风如歌
4e3845e2ce
!191 add 新增http代理模式
Merge pull request !191 from Bleachtred/dev-3.0.x
2025-03-03 11:16:24 +00:00
bleachtred
b168d5235f add 新增http代理模式 2025-03-03 16:43:33 +08:00
wind
bc8e329bfa 更新版本号 2025-02-26 19:50:13 +08:00
wind
09a3dba016 修复,异常状态下 短信重试无限次重试问题,布丁云短信群发采用自实现 2025-02-26 19:40:51 +08:00
wind
a62bca40b6 优化,每日最大发送量计时截止每日0点,而不是持续24小时 2025-02-26 19:25:31 +08:00
wind
8498fafa65 修复,云片短信无限重试问题 2025-02-26 19:16:39 +08:00
风如歌
aeecaaa8f5
!190 sms4j-solon-plugin: 优化适配,保持与 spring 的体验一致
Merge pull request !190 from 西东/dev-3.0.x
2025-02-06 09:14:41 +00:00
noear
2ef232d9ff sms4j-solon-plugin: 优化适配,保持与 spring 的体验一致 2025-01-05 08:15:05 +08:00
风如歌
e6173c5d2f
!189 sms4j-solon-plugin:重新适配(同步 springboot 的适配代码)
Merge pull request !189 from 西东/dev-3.0.x
2025-01-04 11:07:50 +00:00
noear
44287e62d4 sms4j-solon-plugin-example: springboot 的测试代码 2025-01-04 18:44:27 +08:00
noear
49c5af9f70 sms4j-solon-plugin:重新适配(同步 springboot 的适配代码) 2025-01-04 18:33:57 +08:00
noear
98e779376d sms4j-solon-plugin:优化 SupplierConfig 适配类 2025-01-03 17:19:58 +08:00
noear
ff4d6d8bf4 solon 升为 3.0.1(兼容 2.x) 2025-01-03 16:28:38 +08:00
noear
76e9ec060e sms4j-solon-plugin-example 简化测试代码 2025-01-03 16:27:46 +08:00
风如歌
d58f2d0eaf
!188 【修复】发送oa消息通知,线程池资源在while循环中未能释放,导致服务器cpu占用过高
Merge pull request !188 from 东风/oa-fix-high-cpu
2024-12-24 08:26:20 +00:00
风如歌
ec501e441d
!187 修复luosimao对接手机号+86前缀报错问题
Merge pull request !187 from LiYaoheng/dev-3.0.x
2024-12-24 08:25:58 +00:00
dongfeng
f9f197935a 修复oa导致的cpu过高的问题 2024-11-11 19:33:25 +08:00
风如歌
197dc65cf8
!186 优化导入依赖
Merge pull request !186 from handy/dev-3.0.x
2024-11-05 00:40:04 +00:00
LiYaoheng
1c0d518fdb fix luosimao的手机号不需要+86前缀 2024-11-04 17:07:09 +08:00
handy
46f3ccfbf2 优化导入依赖 2024-10-25 14:27:42 +08:00
bleachtred
109f50cfd1 fix 两个邮件插件核心代码不同步 #I9CWJI 2024-10-23 13:21:58 +08:00
bleachtred
30c4488cde update 包名adepter => adaptor #I98OR2 2024-10-23 11:27:07 +08:00
bleachtred
8a0dd4bc3d fix 修复调用百度接口提示缺少请求头 #IAR2RU 2024-10-23 11:23:40 +08:00
Bleachtred
8aca0fd957
update sms4j-provider/src/main/java/org/dromara/sms4j/mas/utils/MasUtils.java.
fix 修复中国移动 云MAS addSerial参数为空时没有放到map中导致发送失败

Signed-off-by: Bleachtred <bleachtred@163.com>
2024-10-23 03:13:00 +00:00
Bleachtred
1a31bb5189
update sms4j-provider/src/main/java/org/dromara/sms4j/submail/service/SubMailSmsImpl.java.
fix 修复SubMail获取timestamp时,由于取值key错误导致无法获取timestamp,导致短信无法发送

Signed-off-by: Bleachtred <bleachtred@163.com>
2024-10-23 03:07:44 +00:00
风如歌
e81eeffe40
!185 重提PR(179 解决 Sms 和 Oa 的厂商配置信息Map会在同时存在时发生冲突的Bug)
Merge pull request !185 from 东风/oa-fix-bean
2024-10-21 06:36:51 +00:00
dongfeng
95601131cd 添加 Qualifier 指定配置Map注入Bean,解决OA与Sms同时存在Bug 2024-10-17 22:39:01 +08:00
dongfeng
3f9c22a04d 修复oa的yml案例 2024-10-17 22:36:52 +08:00
风如歌
463f3e568b
!181 add 联通一信通平台短信通道
Merge pull request !181 from moatg/dev-3.0.x
2024-09-23 01:20:53 +00:00
wind
298e7c48e5 3.3.3升级
1. https://gitee.com/dromara/sms4j/issues/IAN87I
2. 解决腾讯云多参数发送报错问题
修复异常执行器再未进行自定义的情况下,会将异常堆栈吞掉的问题
3. 天翼云eop-date参数实际生产少8小时
2024-09-07 17:55:58 +08:00
wind
d7589aa7a1 Merge branch 'refs/heads/dev-3.0.x'
# Conflicts:
#	README.md
2024-09-07 17:53:19 +08:00
wind
2ee0389eda #https://gitee.com/dromara/sms4j/issues/IAN87I
在调用SmsBlend.sendMessage方法时,传入三个参数,第三个参数map,当这个参数大小不为0的时候,该方法一直返回null
2024-09-06 15:04:12 +08:00
风如歌
36348b54b1
!184 fix 天翼云eop-date参数实际生产少8小时 bug #IAKMPG
Merge pull request !184 from Bleachtred/dev-3.0.x
2024-08-28 12:14:58 +00:00
wind
2df3db09d9 修复异常执行器再未进行自定义的情况下,会将异常堆栈吞掉的问题 2024-08-28 17:38:39 +08:00
bleachtred
ea6e6a9fa6 fix 天翼云eop-date参数实际生产少8小时 bug #IAKMPG 2024-08-19 19:10:33 +08:00
风如歌
eea34ace98
!183 修改文档地址
Merge pull request !183 from handy/dev-3.0.x
2024-08-16 07:39:04 +00:00
风如歌
319a0d2cd1
update README.md.
Signed-off-by: 风如歌 <wzsf1810@qq.com>
2024-08-15 02:12:31 +00:00
handy
21fe49f627 修改文档地址 2024-08-15 10:10:12 +08:00
风如歌
cf8b977a9e
update README.md.
Signed-off-by: 风如歌 <wzsf1810@qq.com>
2024-08-14 03:13:28 +00:00
wind
506acbf906 3.3.2 修复开启短信拦截后导致发送失败的问题 2024-08-08 19:24:07 +08:00
wind
12ab72d6d1 Merge branch 'refs/heads/dev-3.0.x' 2024-08-08 19:19:56 +08:00
wind
b045df8be0 修复 开启拦截器后导致短信发送失败的问题 2024-08-08 19:19:28 +08:00
moat
d921679af4 fix: 增加短信前置签名编号配置项 2024-08-02 10:09:30 +08:00
风如歌
2dec57ad80
update README.md.
Signed-off-by: 风如歌 <wzsf1810@qq.com>
2024-08-02 01:21:12 +00:00
wind
4fbea6e6a1 升级3.3.0 2024-08-01 19:26:11 +08:00
wind
a8e2ddb420 升级3.3.1 修复错误PR导致的yml配置加载失败问题 2024-08-01 17:57:51 +08:00
wind
1a3abc4be3 修复错误PR导致的yml配置加载失败问题 2024-08-01 15:52:12 +08:00
moat
f0e018b855 add 联通一信通平台短信通道 2024-07-31 11:45:13 +08:00
wind
8ec8cf8bd6 拦截器相关优化,用户可以自定义添加拦截器并且实现相关能力 2024-07-29 11:19:36 +08:00
wind
5020771a7a 拦截器相关优化,用户可以自定义添加拦截器并且实现相关能力 2024-07-29 10:58:32 +08:00
Bleachtred
9623b28b31 !180 add 添加螺丝帽短信接入 并添加时间日期工具类
* add danmi短信接入
* update 补充配置案例
* add 添加SUBMAIL短信接入
* fix 腾讯云请求签名时间赋值错误
* update 提取短信异常发送结果
* add 添加螺丝帽短信接入 并添加时间日期工具类
2024-06-24 07:53:49 +00:00
wind
98fa90ee1f 修复错误的方法 2024-06-17 12:12:01 +08:00
风如歌
780caef8cd
!177 极光短信 创蓝短信
Merge pull request !177 from Bleachtred/dev_jg
2024-05-27 01:58:14 +00:00
风如歌
f5ce5b9be4
!176 sms4j-solon-plugin: 优化 SupplierConfig 处理
Merge pull request !176 from 西东/dev-3.0.x
2024-05-27 01:55:47 +00:00
bleachtred
a2a740a339 update 通用返回 2024-05-13 03:09:22 +08:00
bleachtred
f7c53c5f4e update 更新测试用例 2024-05-13 01:09:04 +08:00
bleachtred
4a5e342d15 update 优化 2024-05-13 00:53:33 +08:00
bleachtred
bea0c20e67 update 优化报错 2024-05-13 00:43:55 +08:00
bleachtred
fb198304a0 update 添加作者 2024-05-12 17:30:45 +08:00
bleachtred
f3327c33fa fix 创蓝 2024-05-12 17:27:19 +08:00
bleachtred
b5744a92ed fix 极光短信 2024-05-12 17:04:53 +08:00
风如歌
2b36985012
!173 解决分钟限制和每日限制存在的一个计数问题
Merge pull request !173 from kratos/dev-3.0.x
2024-05-10 06:46:27 +00:00
noear
154b7c3d3e 添加 sms4j-solon-plugin-example 模块 2024-05-08 12:24:53 +08:00
noear
525166c102 sms4j-solon-plugin: 优化 SupplierConfig 处理 2024-05-08 12:24:20 +08:00
kratos
2d1c50337b fix: 解决分钟限制和每日限制存在的一个计数问题
问题1:日最大限制未达到,但分钟计数达到限制时,此时短信发送异常,但日限制计数已经+1
此问题可能会导致分钟限制触发,分钟内多次请求发送短信,导致日限额超过,但实际未收到短信
问题2:日志打印,引起误导,应该是手误复制后忘记改动
2024-04-29 15:16:30 +08:00
风如歌
a5f7ba37a4
!171 add 百度智能云 sms
Merge pull request !171 from Bleachtred/dev_M
2024-04-29 04:00:03 +00:00
bleachtred
b19287cd58 add 百度智能云 sms 2024-04-25 20:25:44 +08:00
Bleachtred
81341fcf7c !170 add 中国移动 云MAS短信配置
* Merge branch 'dev-3.0.x' of https://gitee.com/Bleachtred/sms4j into dev-3.0.x
* add 中国移动 云MAS短信配置器
2024-04-24 02:27:16 +00:00
风如歌
9366a4d0f5
!169 常量使用大写
Merge pull request !169 from handy/dev-3.0.x
2024-04-23 02:16:46 +00:00
handy
7efa3319c7 常量使用大写 2024-04-22 10:36:06 +08:00
风如歌
ef44c1a2df
!168 格式化代码
Merge pull request !168 from 致远/dev-3.0.x
2024-04-19 06:27:09 +00:00
oddfar
d4b213c8f2 格式化代码 2024-04-18 22:19:58 +08:00
风如歌
f54331acc5
!167 修复网易云信短信群发时发送消息与模板id参数传参顺序错误问题
Merge pull request !167 from 骑着蜗牛码代码/fix-dev.3.x
2024-04-17 01:37:59 +00:00
cwg
6c29ca195d fix:修复网易云信短信群发时发送消息与模板id参数传参顺序错误问题 2024-04-15 14:09:26 +08:00
Bleachtred
fc4830b58c !165 update 简化map
* update 简化map
* Merge branch 'dev-3.0.x' of https://gitee.com/Bleachtred/sms4j into dev-3.0.x
* update 简化map
2024-04-08 10:09:30 +00:00
wind
067c08ab46 1.修复群发短信始终抛出异常的问题 2024-04-08 15:51:56 +08:00
风如歌
c2adc018e0
!159 添加 布丁云V2 短信Provider
Merge pull request !159 from NicholasLD/dev-3.0.x-budingv2
贡献者:NicholasLD
2024-04-03 07:06:19 +00:00
NicholasLD
157a19bc8e Merge branch 'dev-3.0.x' of gitee.com:dromara/sms4j into dev-3.0.x-budingv2
Signed-off-by: NicholasLD <nicholasld505@gmail.com>
2024-04-02 03:51:36 +00:00
wind
18fe81286a 1.升级hutool工具版本。
2.修复被异常执行器吞掉的Exception。
3.华为云无参数模板发送报错的问题。
2024-03-29 16:54:40 +08:00
wind
40e0460e15 添加一个校验手机号是否合法的接口,用户可以通过实现此接口进行手机号合法性校验 2024-03-28 15:40:44 +08:00
风如歌
8f60ed63a9
!161 oa更新
Merge pull request !161 from 东风/new_oa
2024-03-25 06:16:11 +00:00
东风
40469512e0 oa测试类更新 2024-03-25 12:03:07 +08:00
东风
a120bc9370 1.oa规范类型命名. 2.企业微信新增news消息类型 2024-03-25 11:59:29 +08:00
wind
b1a18b6972 补充注释 2024-03-24 16:47:19 +08:00
wind
fed06f2ad5 补充注释 2024-03-24 16:22:59 +08:00
wind
9511e4c7a7 Merge remote-tracking branch 'origin/dev-3.0.x' into dev-3.0.x 2024-03-22 15:22:51 +08:00
wind
0bf3aa562a 防止过度校验 2024-03-22 15:22:37 +08:00
风如歌
0187272b94
!157 修复助通短信渠道-发送模板短信请求地址错误的问题
Merge pull request !157 from JackZhai/dev-3.0.x
2024-03-22 07:12:07 +00:00
风如歌
a31f8a056c
!156 修复网易云信平台发送短信问题
Merge pull request !156 from 骑着蜗牛码代码/fix-dev-3.0.x
2024-03-21 06:15:03 +00:00
NicholasLD
ca13119acc 添加 布丁云短信V2 Provider 2024-03-21 02:52:20 +08:00
JackZhai
e7991ddba4 Fixed:修复了手机号空值校验逻辑错误的问题。 2024-03-20 16:17:02 +08:00
JackZhai
5a1641aed0 Fixed:修复了助通短信-发送自定义短信时请求地址错误的问题。 2024-03-20 16:13:53 +08:00
JackZhai
b6c7c93c38 Fixed:修复了助通短信发送模板短信时请求 URL地址错误的问题。 2024-03-20 12:07:54 +08:00
cwg
b7d9ea5d64 fix:修复网易云短信发送返回414(模板id为null和params必须为json问题) 2024-03-15 11:42:28 +08:00
风如歌
a5535665f7
!152 移除实例未生效问题
Merge pull request !152 from 一直在原地/Feat_zp2
2024-03-13 09:23:01 +00:00
zhoupan
97e9ec7a57 修复解除绑定时,没有从负载均衡器中移除实例的问题。
修复循环里移除自身产生的IndexOutOfBoundsException
2024-03-13 10:28:38 +08:00
风如歌
8b788fa127
!150 移除多余导入,部分字段设置为final,移除多余泛型,注释调整不使用尾行注释
Merge pull request !150 from handy/dev-3.0.x
2024-03-13 01:58:04 +00:00
handy
264795afbe 移除多余导入,部分字段设置为final,移除多余泛型,注释调整不使用尾行注释 2024-03-12 17:11:29 +08:00
风如歌
7599a20814
!149 update 优化变量名称
Merge pull request !149 from Bleachtred/dev-3.0.x
2024-03-12 08:59:36 +00:00
bleachtred
6872a55860 update 优化变量名称 2024-03-12 16:55:55 +08:00
wind
0c1e1c5d62 去除多余判断和日志
修复:当拦截未开启时,不在进行拦截
2024-02-23 14:40:04 +08:00
wind
7865774b08 修复,各厂商无参数模板 messages为null时候的错误
修复,拦截器过于严苛的校验
2024-02-20 14:59:23 +08:00
wind
a9d65a4703 Merge remote-tracking branch 'origin/dev-3.0.x' into dev-3.0.x 2024-02-20 14:53:34 +08:00
wind
d67963cc3c 修复,日志级别问题,去除info级别无用日志改为debug级别 2024-02-20 14:52:30 +08:00
风如歌
9c8418d0d5
!138 update 使用hutool.crypto替换javax.crypto
Merge pull request !138 from Bleachtred/dev-3.0.x
2024-02-19 02:29:18 +00:00
风如歌
afe1906fd4
!140 修复报错
Merge pull request !140 from handy/dev-3.0.x
2024-02-19 02:28:23 +00:00
handy
fde178dc47 修复报错 2024-02-19 10:27:17 +08:00
wind
ccf9ca4daf 修复 邮件发送黑名单问题 2024-02-19 10:22:16 +08:00
wind
900eef7ca7 Merge remote-tracking branch 'origin/dev-3.0.x' into dev-3.0.x 2024-02-19 10:06:22 +08:00
wind
c31d23bdbf 添加功能,邮件发送可用于发送携带发送人昵称的邮件 2024-02-19 10:05:58 +08:00
bleachtred
a68734bf62 update 使用hutool.crypto替换javax.crypto 2024-02-19 09:02:50 +08:00
Charles7c
aa51489624 优化部分日志使用 2024-02-03 18:53:09 +08:00
风如歌
6ef9c692b8
!134 七牛云短信对接
Merge pull request !134 from yym/dev-3.0.x  贡献者 yym
2024-02-02 01:15:46 +00:00
风如歌
deb13be650
!133 优化一些
Merge pull request !133 from handy/dev-3.0.x
2024-02-01 03:16:58 +00:00
YYM
d627c09774 七牛云添加重试 2024-02-01 11:08:47 +08:00
YYM
a3b7397f3c 去除测试信息 2024-01-31 10:08:04 +08:00
YYM
83d86bfe48 七牛云短信 2024-01-31 10:02:12 +08:00
疯狂的狮子Li
5add6ef80f
fix 修复 类型转换bug string不可强转int
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2024-01-30 14:51:45 +00:00
YYM
9e32e3403a 七牛云初始化 2024-01-30 18:24:09 +08:00
handy
fc623cf5a9 移除冗余异常抛出和使用lombok简化,并优化点代码 2024-01-30 17:40:50 +08:00
风如歌
55c957ca27
!132 sms4j-solon-plugin 升级 solon 为 2.6.5
Merge pull request !132 from 西东/dev-3.0.x
2024-01-23 01:42:18 +00:00
noear
5340233764 sms4j-solon-plugin 升级 solon 为 2.6.5 2024-01-23 09:40:31 +08:00
wind
81468a3c19 添加方法 setExecutor(Executor exec)可用于自定义线程池或虚拟线程,来替换sms4j内部原有线程池 2024-01-18 18:14:50 +08:00
风如歌
bed318c1e6
!130 优化SmsDao的日志输出,修复SE环境下SmsDao的获取方法
Merge pull request !130 from sh1yu/dev-3.0.x
2024-01-16 01:09:41 +00:00
sh1yu
7c127b6a01 优化SmsDao的日志输出,修复SE环境下SmsDao的获取方法 2024-01-15 17:31:54 +08:00
wind
f8875d1a1d SmsDao接口添加移除缓存方法
Object remove(String key)
2024-01-15 16:34:55 +08:00
wind
00c374d5a8 黑名单配置 配置不支持yaml读取 2024-01-15 14:18:37 +08:00
wind
1d91497d67 删除过时的配置 2024-01-15 14:16:14 +08:00
Charles7c
2b67f22b82 优化部分 Map 集合代码风格 2024-01-13 22:09:38 +08:00
风如歌
0750a39ec4
!129 修复在Springboot环境下使用SmsReadConfig导致拦截器报错的问题
Merge pull request !129 from sh1yu/dev-3.0.x
2024-01-13 10:18:20 +00:00
sh1yu
046918aff5 去除无用代码 2024-01-12 20:50:19 +08:00
sh1yu
6494ea506a 1.修改多框架自动查找Smsdao时的error日志为info
2.修复在springboot的依赖下,使用smsReadConfig配置会发生渠道级上线拦截器异常的问题
2024-01-12 20:41:45 +08:00
wind
b325d2ac0d 修改联麓短信配置 2024-01-09 21:54:52 +08:00
风如歌
ae1ac3c93f
!127 fix code 修复包缺失问题
Merge pull request !127 from Bleachtred/dev-3.0.x
2024-01-09 13:16:20 +00:00
bleachtred
0fb1050677 fix code 修复包缺失问题 2024-01-09 20:38:39 +08:00
Bleachtred
1304cb0c17
update sms4j-email-jakarta/pom.xml.
引入版本错误

Signed-off-by: Bleachtred <bleachtred@163.com>
2024-01-09 12:14:12 +00:00
风如歌
38813ac4b9
!126 fix code Not provider of jakarta.mail.util.StreamProvider was found
Merge pull request !126 from Bleachtred/dev-3.0.x
2024-01-08 15:14:46 +00:00
bleachtred
0b4f3bf6b8 fix code Not provider of jakarta.mail.util.StreamProvider was found 2024-01-08 23:12:57 +08:00
Bleachtred
a36cc7eeca !125 add 新增sms4j-email-jakarta分支
* update 删除多余的内容
* add 新增sms4j-email-jakarta分支
2024-01-04 00:34:22 +00:00
heng
135459c544 添加一些类注释 2023-12-28 15:42:05 +08:00
风如歌
d359a1070a
!124 接入鼎众短信,修复JavaSE配置对象启动未经过拦截器处理的问题
Merge pull request !124 from sh1yu/dev-3.0.x
2023-12-27 10:37:30 +00:00
15946460103
fb369032e6 去除无用代码 2023-12-27 14:43:38 +08:00
15946460103
8bacbbce1d 去除测试信息 2023-12-27 14:40:45 +08:00
15946460103
c5da3a642b 增加测试用例,剔除模板申请临时逻辑 2023-12-27 14:38:26 +08:00
15946460103
d6c81cdefe 新增鼎众短信的基础测试用例 2023-12-27 14:28:28 +08:00
15946460103
d92ff2bf46 新增 鼎众短信 渠道接入以及配置对象方式启动执行器相关设计的加载 2023-12-27 14:26:15 +08:00
wind
b21a48e772 sendMessage重载方法增加null判断,适用于无参数模板短信发送 2023-12-27 12:11:08 +08:00
wind
c1dc5062a4 修改错误的README 2023-12-26 11:25:05 +08:00
wind
f2e273f0ef 新增方法 reload 和 reloadAll 用于重新从接口实现中读取并重新实例化短信对象 2023-12-26 10:48:30 +08:00
风如歌
56565d5871
!120 oa的完善
Merge pull request !120 from 东风/oa-plugin
2023-12-21 00:54:55 +00:00
风如歌
22ddf9bc83
!121 update 删除无用内容&&忽略mac下文件Ds_Store
Merge pull request !121 from Bleachtred/Feat_3.0
2023-12-21 00:51:09 +00:00
bleachtred
fffcffd65b update 忽略mac下文件Ds_Store 2023-12-18 17:39:59 +08:00
bleachtred
67d6625924 update 删除无用内容 2023-12-18 17:39:25 +08:00
东风
91807cfe61 Merge branch 'dev-3.0.x' into oa-plugin 2023-12-15 10:34:40 +08:00
风如歌
2fa6cc008c
!117 新增功能测试及修复
Merge pull request !117 from sh1yu/dev-3.0.x
2023-12-15 02:21:03 +00:00
风如歌
53de8851de
!119 http修改为https
Merge pull request !119 from handy/dev-3.0.x
2023-12-15 02:19:36 +00:00
handy
39b1ec6b86 http修改为https 2023-12-15 10:16:47 +08:00
15946460103
920cb94ed5 复杂测试案例添加 2023-12-06 13:51:01 +08:00
东风
c4d59559a7 根据规范修改引入,按需引入 2023-12-06 10:10:42 +08:00
15946460103
2fd2f4ba60 完善代码 2023-12-05 09:01:55 +08:00
东风
ad28a7ac67 bug:1、修复企业微信@all重复的bug.2、完善飞书和企业微信的测试案例.3、重新调整消息类型,更加明确. 2023-12-04 19:44:43 +08:00
风如歌
f255abe930
!115 新增核心方法适配
Merge pull request !115 from sh1yu/dev-3.0.x
2023-11-29 07:48:26 +00:00
风如歌
5cbbdd209c
!116 oa-plugin 1.0开发完成
Merge pull request !116 from 铁甲小宝同学/oa-plugin
2023-11-28 03:58:49 +00:00
东风
df36fb802a 恢复合并影响的配置demo 2023-11-27 23:50:08 +08:00
东风
eb3157529a Merge branch 'dingtalk' into oa-plugin
# Conflicts:
#	sms4j-spring-boot-example/src/main/resources/application.yml
2023-11-27 23:16:08 +08:00
东风
f0185e5671 oa更新:格式化javadoc 2023-11-27 22:20:52 +08:00
东风
51fc622761 oa更新:1.支持text,markdown,link消息格式。2.支持异步发送,优先级消息。3.yaml配置新增属性--该配置是否开启。 2023-11-27 22:03:45 +08:00
Sh1yu
363dc530aa 新增核心方法适配 2023-11-27 18:07:29 +08:00
wind
2766343f09 邮件发送可以直接发送html字符串,并且可以存在模板变量 2023-11-27 17:57:11 +08:00
wind
2cfadb574d 添加一个方法,该方法用于发送固定模板下的多模板参数短信,方法签名为 SmsResponse sendMessage(String phone, LinkedHashMap<String, String> messages) 2023-11-27 17:38:57 +08:00
wind
7cadeff264 修复unisms短信 HMAC模式下签名无效的问题 2023-11-27 17:15:08 +08:00
风如歌
7167810be5
!113 #I8INMR修复
Merge pull request !113 from sh1yu/dev-3.0.x
2023-11-27 06:04:19 +00:00
风如歌
4616be9cef
!109 应用扩展,黑名单实现、渠道上限拦截实现、基础参数统一校验实现
Merge pull request !109 from sh1yu/dev-3.0.x
2023-11-27 03:55:17 +00:00
风如歌
d1068c66e1
!114 I8INMR 其他厂商修复
Merge pull request !114 from handy/handy
2023-11-27 03:50:21 +00:00
handy
70208b5e32 移除 System.out.println(e); 2023-11-23 16:03:51 +08:00
handy
a6f03a7f54 移除冗余类型转换 2023-11-23 16:03:10 +08:00
handy
6fe40808eb 移除多余导入 2023-11-23 16:02:36 +08:00
handy
37149454e9 I8INMR 其他产商修复 2023-11-23 15:56:44 +08:00
Sh1yu
a998b96ff6 修改抛错导致无限循环问题 2023-11-23 15:00:07 +08:00
wind
714e85fac0 邮件HTML支持发送不带模板参数的邮件,
邮件HTML支持File对象传入
修复HTML模板输入流未关闭的问题
2023-11-15 09:32:44 +08:00
风如歌
5eb6f95284
!112 扩展短信厂商的问题
Merge pull request !112 from 4n/feat_4n
2023-11-10 02:24:51 +00:00
Charles7c
1153c509a2 移除错误配置 2023-11-08 09:42:32 +08:00
Sh1yu
3d56442115 黑名单的动态修改,魔法值修改;同时用户所使用的sms4j的唯一接口应该就是Smsblend,所以黑名单操作的方法建立在Smsblend上,但是因为涉及全局和缓存等,需要代理执行,增加BlackListRecordingProcessor.java 2023-11-01 14:34:48 +08:00
Fn
3530f05831 手机号脱敏隐藏 2023-10-31 17:15:41 +08:00
Fn
3052fbadae 新增短信自定义适配,新增广州掌骏自定义实现demo(忘记的提交) 2023-10-31 17:00:04 +08:00
Fn
fe0541c59e 新增短信自定义适配,新增广州掌骏自定义实现demo 2023-10-31 16:49:03 +08:00
Sh1yu
bd35558532 BlackListProcessor 黑名单拦截
CoreMethodParamValidateProcessor  核心方法参数校验拦截器
RestrictedProcessor  账号上限拦截器
SingleBlendRestrictedProcessor  厂商上限拦截器
2023-10-30 17:02:04 +08:00
铁甲小宝同学
9c219eaa50
!107 sms4j的oa插件---dingtalk部分的完成
Merge pull request !107 from 东风/dingtalk_dev
2023-10-27 15:07:30 +00:00
Charles7c
2763b2b70f 修复容联云短信发送失败的问题 2023-10-27 18:14:41 +08:00
Sh1yu
436a41508f 提交经测试版本 2023-10-27 15:43:35 +08:00
zhangyang
a73f5c6d11 sms4j-oa-plugin支持钉钉、飞书、微信的webhook普通信息发送---支持yaml配置和java代码配置两种方式 2023-10-23 10:20:04 +08:00
wind
76cb93c4cc 修复异常代码行 2023-10-19 20:40:41 +08:00
风如歌
484f85f9d8
!108 云片NPE异常和逻辑判断处理
Merge pull request !108 from 呆呆/dev-3.0.x
2023-10-19 12:39:42 +00:00
daishanling
3840df9848 fix:代码规范 2023-10-19 16:34:19 +08:00
wind
a5adf2afdb 修复,异步短信无法获取线程池问题 2023-10-19 16:28:45 +08:00
daishanling
6b6460b9ea fix:云片NPE异常 2023-10-19 16:12:20 +08:00
zhangyang
2d65ffe0e1 sms4j-oa-plugin支持钉钉 2023-10-18 22:44:56 +08:00
风如歌
d07676370c
!102 feat:新增联麓短信
Merge pull request !102 from lym/dev-3.0.x
2023-10-18 06:38:55 +00:00
wind
94ebc18d88 修复云片短信无模板时报错的问题 2023-10-17 20:21:02 +08:00
Charles7c
905eca3a98 优化 URL 参数拼接 2023-10-17 14:47:52 +08:00
风如歌
8c8326a155
!106 fix:亿美短信普通短信请求参数格式调整
Merge pull request !106 from 呆呆/dev-3.0.x
2023-10-17 06:42:07 +00:00
daishanling
953a2a1472 fix:亿美普通短信请求参数格式调整 2023-10-17 14:25:23 +08:00
lym
b24d27b9b2 新增联麓短信
(cherry picked from commit 86cbe08694a10c7b56b7a9484cbee15a5bbae15e)
2023-10-12 15:36:38 +08:00
lym
03876dfdf1 fix:补充salon和javase的注册
(cherry picked from commit 71f9851446dd4bec61fbd4c934aa85ebd2bcf1c6)
2023-10-12 15:36:34 +08:00
heng
e2fde737bf 修改configType设置为yaml,没有正确初始化全局配置的问题 2023-10-10 14:38:58 +08:00
heng
5ec641c35d 修改通过配置读取接口读取指定configId时,如果读取到的配置不存在configId,就无法缓存到框架中的问题 2023-10-10 10:32:47 +08:00
风如歌
687be3ba54
!99 修改腾讯短信Success返回值判断修复
Merge pull request !99 from handy/dev-3.0.x
2023-09-25 08:39:41 +00:00
handy
2861cf4f00 修改腾讯短信Success返回值判断修复 2023-09-25 16:33:43 +08:00
257 changed files with 13602 additions and 1461 deletions

View File

@ -1,11 +0,0 @@
# 版本信息 (必填):
当前使用的版本必填2.0.X 或其它
JDK 版本(必填) : JDK 8 或其他一定写明至具体的小版本号
# 问题描述:
# 报错截图
# 日志信息
# 重现步骤

View File

@ -1,71 +0,0 @@
name: Bug 反馈
description: 当你中发现了一个 Bug导致应用崩溃或抛出异常或者有一个组件存在问题或者某些地方看起来不对劲。
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
感谢对项目的支持与关注。在提出问题之前,请确保您已查看相关开发或使用文档:
- https://wind.kim
- type: checkboxes
attributes:
label: 这个问题是否已经存在?
options:
- label: 我已经搜索过现有的问题 (https://gitee.com/dromara/sms4j/issues)
required: true
- type: textarea
attributes:
label: 如何复现
description: 请详细告诉我们如何复现你遇到的问题,如涉及代码,可提供一个最小代码示例,并使用反引号```附上它
placeholder: |
1. ...
2. ...
3. ...
validations:
required: true
- type: textarea
attributes:
label: 错误堆栈
description: 请将错误堆栈抹除关键信息后贴到这里。
validations:
required: true
- type: textarea
attributes:
label: 实际结果
description: 请告诉我们实际发生了什么。
validations:
required: true
- type: textarea
attributes:
label: 截图或视频
description: 如果可以的话,上传任何关于 bug 的截图。
value: |
[在这里上传图片]
- type: dropdown
id: jdk_version
attributes:
label: JDK 版本
description: 你当前正在使用 JDK 的哪个版本?
options:
- JDK8
- JDK17
validations:
required: true
- type: dropdown
id: version
attributes:
label: SMS4J 版本
description: 你当前正在使用我们软件的哪个版本/分支?
options:
- 3.0.0
- 2.2.0
- 2.1.1
- 2.1.0
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.0.4
validations:
required: true

View File

@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: sms4j文档中心
url: https://wind.kim
about: 提供 sms4j 搭建使用指南、平台基本开发使用方式、介绍、基础知识和常见问题解答

View File

@ -1,43 +0,0 @@
name: 功能建议
description: 对本项目提出一个功能建议
title: "[功能建议]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
感谢提出功能建议我们将仔细考虑请持续关注该issues在加入计划后我们会有贡献者设置为负责人同时状态成为进行中。
- type: textarea
id: related-problem
attributes:
label: 你的功能建议是否和某个问题相关?
description: 清晰并简洁地描述问题是什么,例如,当我...时,我总是感到困扰。
validations:
required: false
- type: textarea
id: desired-solution
attributes:
label: 你希望看到什么解决方案?
description: 清晰并简洁地描述你希望发生的事情。
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 你考虑过哪些替代方案?
description: 清晰并简洁地描述你考虑过的任何替代解决方案或功能。
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: 你有其他上下文或截图吗?
description: 在此处添加有关功能请求的任何其他上下文或截图。
validations:
required: false
- type: checkboxes
attributes:
label: 意向参与贡献
options:
- label: 我有意向参与具体功能的开发实现并将代码贡献回到上游社区
required: false

View File

@ -1,10 +0,0 @@
### 相关的Issue
### 原因(目的、解决的问题等)
### 描述(做了什么,变更了什么)
### 测试用例(新增、改动、可能影响的功能)

7
.gitignore vendored
View File

@ -3,9 +3,7 @@ target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
/docs/node_modules
/docs/.idea
/docs/.github
### STS ###
.apt_generated
.classpath
@ -38,3 +36,6 @@ build/
.flattened-pom.xml
**/.flattened-pom.xml
/.fastRequest/**
### Others
*.Ds_Store

BIN
.idea/icon.png generated

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

View File

@ -1,53 +0,0 @@
version: '1.0'
name: branch-pipeline
displayName: BranchPipeline
stages:
- stage:
name: compile
displayName: 编译
steps:
- step: build@maven
name: build_maven
displayName: Maven 构建
# 支持6、7、8、9、10、11六个版本
jdkVersion: 8
# 支持2.2.1、3.2.5、3.3.9、3.5.2、3.5.3、3.5.4、3.6.1、3.6.3八个版本
mavenVersion: 3.3.9
# 构建命令
commands:
- mvn -B clean package -Dmaven.test.skip=true
# 非必填字段开启后表示将构建产物暂存但不会上传到制品库中7天后自动清除
artifacts:
# 构建产物名字作为产物的唯一标识可向下传递支持自定义默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
- name: BUILD_ARTIFACT
# 构建产物获取路径是指代码编译完毕之后构建物的所在路径如通常jar包在target目录下。当前目录为代码库根目录
path:
- ./target
- step: publish@general_artifacts
name: publish_general_artifacts
displayName: 上传制品
# 上游构建任务定义的产物名默认BUILD_ARTIFACT
dependArtifact: BUILD_ARTIFACT
# 上传到制品库时的制品命名默认output
artifactName: output
dependsOn: build_maven
- stage:
name: release
displayName: 发布
steps:
- step: publish@release_artifacts
name: publish_release_artifacts
displayName: '发布'
# 上游上传制品任务的产出
dependArtifact: output
# 发布制品版本号
version: '1.0.0.0'
# 是否开启版本号自增,默认开启
autoIncrement: true
triggers:
push:
branches:
exclude:
- master
include:
- .*

View File

@ -1,51 +0,0 @@
version: '1.0'
name: master-pipeline
displayName: MasterPipeline
stages:
- stage:
name: compile
displayName: 编译
steps:
- step: build@maven
name: build_maven
displayName: Maven 构建
# 支持6、7、8、9、10、11六个版本
jdkVersion: 8
# 支持2.2.1、3.2.5、3.3.9、3.5.2、3.5.3、3.5.4、3.6.1、3.6.3八个版本
mavenVersion: 3.3.9
# 构建命令
commands:
- mvn -B clean package -Dmaven.test.skip=true
# 非必填字段开启后表示将构建产物暂存但不会上传到制品库中7天后自动清除
artifacts:
# 构建产物名字作为产物的唯一标识可向下传递支持自定义默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
- name: BUILD_ARTIFACT
# 构建产物获取路径是指代码编译完毕之后构建物的所在路径如通常jar包在target目录下。当前目录为代码库根目录
path:
- ./target
- step: publish@general_artifacts
name: publish_general_artifacts
displayName: 上传制品
# 上游构建任务定义的产物名默认BUILD_ARTIFACT
dependArtifact: BUILD_ARTIFACT
# 上传到制品库时的制品命名默认output
artifactName: output
dependsOn: build_maven
- stage:
name: release
displayName: 发布
steps:
- step: publish@release_artifacts
name: publish_release_artifacts
displayName: '发布'
# 上游上传制品任务的产出
dependArtifact: output
# 发布制品版本号
version: '1.0.0.0'
# 是否开启版本号自增,默认开启
autoIncrement: true
triggers:
push:
branches:
include:
- master

View File

@ -1,40 +0,0 @@
version: '1.0'
name: pr-pipeline
displayName: PRPipeline
stages:
- stage:
name: compile
displayName: 编译
steps:
- step: build@maven
name: build_maven
displayName: Maven 构建
# 支持6、7、8、9、10、11六个版本
jdkVersion: 8
# 支持2.2.1、3.2.5、3.3.9、3.5.2、3.5.3、3.5.4、3.6.1、3.6.3八个版本
mavenVersion: 3.3.9
# 构建命令
commands:
- mvn -B clean package -Dmaven.test.skip=true
# 非必填字段开启后表示将构建产物暂存但不会上传到制品库中7天后自动清除
artifacts:
# 构建产物名字作为产物的唯一标识可向下传递支持自定义默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
- name: BUILD_ARTIFACT
# 构建产物获取路径是指代码编译完毕之后构建物的所在路径如通常jar包在target目录下。当前目录为代码库根目录
path:
- ./target
- step: publish@general_artifacts
name: publish_general_artifacts
displayName: 上传制品
# 上游构建任务定义的产物名默认BUILD_ARTIFACT
dependArtifact: BUILD_ARTIFACT
# 构建产物制品库默认default系统默认创建
artifactRepository: default
# 上传到制品库时的制品命名默认output
artifactName: output
dependsOn: build_maven
triggers:
pr:
branches:
include:
- master

102
README.md
View File

@ -1,9 +1,9 @@
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sms4j v3.0.1</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.1-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环境集成
@ -41,49 +52,51 @@
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-spring-boot-starter</artifactId>
<version>3.0.1</version>
<version>最新版本</version>
</dependency>
```
2. 设置配置文件
```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请查看回复内容我们会在回复中做出解释

46
pom.xml
View File

@ -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</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>
@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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){

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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) {
}
}

View File

@ -34,6 +34,15 @@ public interface SmsDao {
*/
Object get(String key);
/**
* remove
* <p> 根据key移除缓存
* @param key 缓存键
* @return 被删除的value
* @author :Wind
*/
Object remove(String key);
/**
* 清空
*/

View File

@ -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();

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -22,4 +22,9 @@ public interface SupplierConfig {
*/
String getSupplier();
/**
* 获取代理配置
*
*/
ProxyConfig getProxy();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {
}
}

View File

@ -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");
}
}

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}
}

View File

@ -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);
}
}
}
}
}

View 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>

View 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>

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -0,0 +1,6 @@
/**
* <p> 邮件插件api模块
* @author :Wind
* 2024/10/23 10:58
**/
package org.dromara.email.jakarta.api;

View 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>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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";
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -0,0 +1,11 @@
package org.dromara.email.jakarta.comm.entity;
/**
* Parameter
* <p> 空接口用于标定用户自己的实体类型
* 用于发送html模板邮件时候 用户传递自己的实体序列化进行的类型标定
* @author :Wind
* 2023/6/8 19:36
**/
public interface Parameter {
}

View File

@ -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);
}
}

View File

@ -0,0 +1,6 @@
/**
* <p> 邮件插件通用模块
* @author :Wind
* 2024/10/23 10:58
**/
package org.dromara.email.jakarta.comm;

View File

@ -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];
}
}

View File

@ -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 + "}";
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}
}

View 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>

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,6 @@
/**
* <p> 邮件插件核心模块
* @author :Wind
* 2023/7/27 10:58
**/
package org.dromara.email.jakarta.core;

View File

@ -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);
}
}
}

View File

@ -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));
}
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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
View 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>

View 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>

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View 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>

View File

@ -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();
}

View File

@ -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";
}

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