Commit fb2fbbffb8a4cf7d1bc6ec9691682f9136ada3cc

Authored by YunaiV
2 parents a1b7697e 4a322ed7

Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/multi-datasource

 Conflicts:
	pom.xml
	src/main/resources/application-local.yaml
Showing 158 changed files with 8755 additions and 286 deletions
README.md
... ... @@ -102,9 +102,9 @@
102 102 | [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.1.7 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
103 103 | [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
104 104 | [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.2 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
105   -| [Resilience4j](https://github.com/quartz-scheduler) | 服务保障组件 | 1.7.0 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |
  105 +| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.0 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |
106 106 | [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.6.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
107   -| [Spring Boot Admin](https://github.com/skywalking) | Spring Boot 监控平台 | 8.6.0 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
  107 +| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 8.6.0 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
108 108 | [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.11.4 | |
109 109 | [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
110 110 | [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
... ...
... ... @@ -30,6 +30,7 @@
30 30 <mysql-connector-java.version>5.1.46</mysql-connector-java.version>
31 31 <druid.version>1.2.4</druid.version>
32 32 <mybatis-plus.version>3.4.2</mybatis-plus.version>
  33 + <dynamic-datasource.version>3.3.2</dynamic-datasource.version>
33 34 <redisson.version>3.15.1</redisson.version>
34 35 <!-- Config 配置中心相关 -->
35 36 <apollo.version>1.7.0</apollo.version>
... ... @@ -46,7 +47,12 @@
46 47 <easyexcel.verion>2.2.7</easyexcel.verion>
47 48 <velocity.version>2.2</velocity.version>
48 49 <screw.version>1.0.5</screw.version>
49   - <dynamic-datasource.version>3.3.2</dynamic-datasource.version>
  50 + <podam.version>7.2.6.RELEASE</podam.version>
  51 + <jedis-mock.version>0.1.16</jedis-mock.version>
  52 + <!-- 三方云服务相关 -->
  53 + <aliyun-java-sdk-core.version>4.5.18</aliyun-java-sdk-core.version>
  54 + <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
  55 + <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
50 56 </properties>
51 57  
52 58 <!-- 依赖声明 -->
... ... @@ -134,6 +140,11 @@
134 140 <artifactId>mybatis-plus-boot-starter</artifactId>
135 141 <version>${mybatis-plus.version}</version>
136 142 </dependency>
  143 + <dependency>
  144 + <groupId>com.baomidou</groupId>
  145 + <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
  146 + <version>${dynamic-datasource.version}</version>
  147 + </dependency>
137 148  
138 149 <dependency>
139 150 <groupId>org.redisson</groupId>
... ... @@ -213,14 +224,14 @@
213 224 <dependency>
214 225 <groupId>com.github.fppt</groupId> <!-- 单元测试,我们采用内嵌的 Redis 数据库 -->
215 226 <artifactId>jedis-mock</artifactId>
216   - <version>0.1.16</version>
  227 + <version>${jedis-mock.version}</version>
217 228 <scope>test</scope>
218 229 </dependency>
219 230  
220 231 <dependency>
221 232 <groupId>uk.co.jemos.podam</groupId> <!-- 单元测试,随机生成 POJO 类 -->
222 233 <artifactId>podam</artifactId>
223   - <version>7.2.6.RELEASE</version>
  234 + <version>${podam.version}</version>
224 235 <scope>test</scope>
225 236 </dependency>
226 237  
... ... @@ -272,13 +283,26 @@
272 283 <version>${screw.version}</version>
273 284 </dependency>
274 285  
275   - <!-- 多数据源 -->
  286 + <!-- 三方云服务相关 -->
  287 +
  288 + <!-- SMS SDK begin -->
276 289 <dependency>
277   - <groupId>com.baomidou</groupId>
278   - <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
279   - <version>${dynamic-datasource.version}</version>
  290 + <groupId>com.yunpian.sdk</groupId>
  291 + <artifactId>yunpian-java-sdk</artifactId>
  292 + <version>${yunpian-java-sdk.version}</version>
280 293 </dependency>
281 294  
  295 + <dependency>
  296 + <groupId>com.aliyun</groupId>
  297 + <artifactId>aliyun-java-sdk-core</artifactId>
  298 + <version>${aliyun-java-sdk-core.version}</version>
  299 + </dependency>
  300 + <dependency>
  301 + <groupId>com.aliyun</groupId>
  302 + <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
  303 + <version>${aliyun-java-sdk-dysmsapi.version}</version>
  304 + </dependency>
  305 + <!-- SMS SDK end -->
282 306  
283 307 </dependencies>
284 308  
... ...
ruoyi-ui/.env.demo1024 0 → 100644
  1 +NODE_ENV = production
  2 +
  3 +# 测试环境配置
  4 +ENV = 'staging'
  5 +
  6 +# 芋道管理系统/测试环境
  7 +VUE_APP_BASE_API = 'http://127.0.0.1:48080'
... ...
ruoyi-ui/package.json
... ... @@ -8,6 +8,7 @@
8 8 "dev": "vue-cli-service serve",
9 9 "build:prod": "vue-cli-service build",
10 10 "build:stage": "vue-cli-service build --mode staging",
  11 + "build:demo1024": "vue-cli-service build --mode demo1024",
11 12 "preview": "node build/index.js --preview",
12 13 "lint": "eslint --ext .js,.vue src"
13 14 },
... ...
ruoyi-ui/src/api/system/sms/smsChannel.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// 创建短信渠道
  4 +export function createSmsChannel(data) {
  5 + return request({
  6 + url: '/system/sms-channel/create',
  7 + method: 'post',
  8 + data: data
  9 + })
  10 +}
  11 +
  12 +// 更新短信渠道
  13 +export function updateSmsChannel(data) {
  14 + return request({
  15 + url: '/system/sms-channel/update',
  16 + method: 'put',
  17 + data: data
  18 + })
  19 +}
  20 +
  21 +// 删除短信渠道
  22 +export function deleteSmsChannel(id) {
  23 + return request({
  24 + url: '/system/sms-channel/delete?id=' + id,
  25 + method: 'delete'
  26 + })
  27 +}
  28 +
  29 +// 获得短信渠道
  30 +export function getSmsChannel(id) {
  31 + return request({
  32 + url: '/system/sms-channel/get?id=' + id,
  33 + method: 'get'
  34 + })
  35 +}
  36 +
  37 +// 获得短信渠道分页
  38 +export function getSmsChannelPage(query) {
  39 + return request({
  40 + url: '/system/sms-channel/page',
  41 + method: 'get',
  42 + params: query
  43 + })
  44 +}
  45 +
  46 +// 获得短信渠道精简列表
  47 +export function getSimpleSmsChannels() {
  48 + return request({
  49 + url: '/system/sms-channel/list-all-simple',
  50 + method: 'get',
  51 + })
  52 +}
... ...
ruoyi-ui/src/api/system/sms/smsLog.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// 获得短信日志分页
  4 +export function getSmsLogPage(query) {
  5 + return request({
  6 + url: '/system/sms-log/page',
  7 + method: 'get',
  8 + params: query
  9 + })
  10 +}
  11 +
  12 +// 导出短信日志 Excel
  13 +export function exportSmsLogExcel(query) {
  14 + return request({
  15 + url: '/system/sms-log/export-excel',
  16 + method: 'get',
  17 + params: query,
  18 + responseType: 'blob'
  19 + })
  20 +}
... ...
ruoyi-ui/src/api/system/sms/smsTemplate.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// 创建短信模板
  4 +export function createSmsTemplate(data) {
  5 + return request({
  6 + url: '/system/sms-template/create',
  7 + method: 'post',
  8 + data: data
  9 + })
  10 +}
  11 +
  12 +// 更新短信模板
  13 +export function updateSmsTemplate(data) {
  14 + return request({
  15 + url: '/system/sms-template/update',
  16 + method: 'put',
  17 + data: data
  18 + })
  19 +}
  20 +
  21 +// 删除短信模板
  22 +export function deleteSmsTemplate(id) {
  23 + return request({
  24 + url: '/system/sms-template/delete?id=' + id,
  25 + method: 'delete'
  26 + })
  27 +}
  28 +
  29 +// 获得短信模板
  30 +export function getSmsTemplate(id) {
  31 + return request({
  32 + url: '/system/sms-template/get?id=' + id,
  33 + method: 'get'
  34 + })
  35 +}
  36 +
  37 +// 获得短信模板分页
  38 +export function getSmsTemplatePage(query) {
  39 + return request({
  40 + url: '/system/sms-template/page',
  41 + method: 'get',
  42 + params: query
  43 + })
  44 +}
  45 +
  46 +// 创建短信模板
  47 +export function sendSms(data) {
  48 + return request({
  49 + url: '/system/sms-template/send-sms',
  50 + method: 'post',
  51 + data: data
  52 + })
  53 +}
  54 +
  55 +// 导出短信模板 Excel
  56 +export function exportSmsTemplateExcel(query) {
  57 + return request({
  58 + url: '/system/sms-template/export-excel',
  59 + method: 'get',
  60 + params: query,
  61 + responseType: 'blob'
  62 + })
  63 +}
  64 +
... ...
ruoyi-ui/src/utils/dict.js
... ... @@ -17,6 +17,10 @@ export const DICT_TYPE = {
17 17 SYS_OPERATE_TYPE: 'sys_operate_type',
18 18 SYS_LOGIN_RESULT: 'sys_login_result',
19 19 SYS_CONFIG_TYPE: 'sys_config_type',
  20 + SYS_SMS_CHANNEL_CODE: 'sys_sms_channel_code',
  21 + SYS_SMS_TEMPLATE_TYPE: 'sys_sms_template_type',
  22 + SYS_SMS_SEND_STATUS: 'sys_sms_send_status',
  23 + SYS_SMS_RECEIVE_STATUS: 'sys_sms_receive_status',
20 24  
21 25 INF_REDIS_TIMEOUT_TYPE: 'inf_redis_timeout_type',
22 26 INF_JOB_STATUS: 'inf_job_status',
... ...
ruoyi-ui/src/utils/request.js
... ... @@ -73,6 +73,18 @@ service.interceptors.response.use(res =&gt; {
73 73 type: 'error'
74 74 })
75 75 return Promise.reject(new Error(msg))
  76 + } else if (code === 901) {
  77 + Message({
  78 + type: 'error',
  79 + duration: 0,
  80 + dangerouslyUseHTMLString: true,
  81 + message: '<div>演示模式,不发进行写操作</div>'
  82 + + '<div> &nbsp; </div>'
  83 + + '<div>参考 https://www.iocoder.cn/Yudao/build-debugger-environment 教程</div>'
  84 + + '<div> &nbsp; </div>'
  85 + + '<div>5 分钟搭建本地环境</div>',
  86 + })
  87 + return Promise.reject(new Error(msg))
76 88 } else if (code !== 200) {
77 89 Notification.error({
78 90 title: msg
... ...
ruoyi-ui/src/views/system/operatelog/index.vue
... ... @@ -163,7 +163,6 @@ export default {
163 163 businessType: undefined,
164 164 status: undefined
165 165 },
166   -
167 166 };
168 167 },
169 168 created() {
... ...
ruoyi-ui/src/views/system/sms/smsChannel.vue 0 → 100644
  1 +<template>
  2 + <div class="app-container">
  3 +
  4 + <!-- 搜索工作栏 -->
  5 + <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
  6 + <el-form-item label="短信签名" prop="signature">
  7 + <el-input v-model="queryParams.signature" placeholder="请输入短信签名" clearable size="small" @keyup.enter.native="handleQuery"/>
  8 + </el-form-item>
  9 + <el-form-item label="启用状态" prop="status">
  10 + <el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable size="small">
  11 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
  12 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  13 + </el-select>
  14 + </el-form-item>
  15 + <el-form-item label="创建时间">
  16 + <el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
  17 + type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
  18 + </el-form-item>
  19 + <el-form-item>
  20 + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  21 + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  22 + </el-form-item>
  23 + </el-form>
  24 +
  25 + <!-- 操作工具栏 -->
  26 + <el-row :gutter="10" class="mb8">
  27 + <el-col :span="1.5">
  28 + <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
  29 + v-hasPermi="['system:sms-channel:create']">新增</el-button>
  30 + </el-col>
  31 + <el-col :span="1.5">
  32 + <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
  33 + v-hasPermi="['system:sms-channel:export']">导出</el-button>
  34 + </el-col>
  35 + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  36 + </el-row>
  37 +
  38 + <!-- 列表 -->
  39 + <el-table v-loading="loading" :data="list">
  40 + <el-table-column label="编号" align="center" prop="id" />
  41 + <el-table-column label="短信签名" align="center" prop="signature" />
  42 + <el-table-column label="渠道编码" align="center" prop="code">
  43 + <template slot-scope="scope">
  44 + <span>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.code) }}</span>
  45 + </template>
  46 + </el-table-column>>
  47 + <el-table-column label="启用状态" align="center" prop="status">
  48 + <template slot-scope="scope">
  49 + <span>{{ getDictDataLabel(DICT_TYPE.SYS_COMMON_STATUS, scope.row.status) }}</span>
  50 + </template>
  51 + </el-table-column>>
  52 + <el-table-column label="备注" align="center" prop="remark" />
  53 + <el-table-column label="短信 API 的账号" align="center" prop="apiKey" />
  54 + <el-table-column label="短信 API 的秘钥" align="center" prop="apiSecret" />
  55 + <el-table-column label="短信发送回调 URL" align="center" prop="callbackUrl" />
  56 + <el-table-column label="创建时间" align="center" prop="createTime" width="180">
  57 + <template slot-scope="scope">
  58 + <span>{{ parseTime(scope.row.createTime) }}</span>
  59 + </template>
  60 + </el-table-column>
  61 + <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  62 + <template slot-scope="scope">
  63 + <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
  64 + v-hasPermi="['system:sms-channel:update']">修改</el-button>
  65 + <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
  66 + v-hasPermi="['system:sms-channel:delete']">删除</el-button>
  67 + </template>
  68 + </el-table-column>
  69 + </el-table>
  70 + <!-- 分页组件 -->
  71 + <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  72 + @pagination="getList"/>
  73 +
  74 + <!-- 对话框(添加 / 修改) -->
  75 + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
  76 + <el-form ref="form" :model="form" :rules="rules" label-width="80px">
  77 + <el-form-item label="短信签名" prop="signature">
  78 + <el-input v-model="form.signature" placeholder="请输入短信签名" />
  79 + </el-form-item>
  80 + <el-form-item label="渠道编码" prop="code">
  81 + <el-input v-model="form.code" placeholder="请输入渠道编码" />
  82 + </el-form-item>
  83 + <el-form-item label="启用状态">
  84 + <el-radio-group v-model="form.status">
  85 + <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
  86 + :key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
  87 + </el-radio-group>
  88 + </el-form-item>
  89 + <el-form-item label="备注" prop="remark">
  90 + <el-input v-model="form.remark" placeholder="请输入备注" />
  91 + </el-form-item>
  92 + <el-form-item label="短信 API 的账号" prop="apiKey">
  93 + <el-input v-model="form.apiKey" placeholder="请输入短信 API 的账号" />
  94 + </el-form-item>
  95 + <el-form-item label="短信 API 的秘钥" prop="apiSecret">
  96 + <el-input v-model="form.apiSecret" placeholder="请输入短信 API 的秘钥" />
  97 + </el-form-item>
  98 + <el-form-item label="短信发送回调 URL" prop="callbackUrl">
  99 + <el-input v-model="form.callbackUrl" placeholder="请输入短信发送回调 URL" />
  100 + </el-form-item>
  101 + </el-form>
  102 + <div slot="footer" class="dialog-footer">
  103 + <el-button type="primary" @click="submitForm">确 定</el-button>
  104 + <el-button @click="cancel">取 消</el-button>
  105 + </div>
  106 + </el-dialog>
  107 + </div>
  108 +</template>
  109 +
  110 +<script>
  111 +import { createSmsChannel, updateSmsChannel, deleteSmsChannel, getSmsChannel, getSmsChannelPage,
  112 + getSimpleSmsChannels } from "@/api/system/sms/smsChannel";
  113 +
  114 +export default {
  115 + name: "SmsChannel",
  116 + components: {
  117 + },
  118 + data() {
  119 + return {
  120 + // 遮罩层
  121 + loading: true,
  122 + // 显示搜索条件
  123 + showSearch: true,
  124 + // 总条数
  125 + total: 0,
  126 + // 短信渠道列表
  127 + list: [],
  128 + // 弹出层标题
  129 + title: "",
  130 + // 是否显示弹出层
  131 + open: false,
  132 + dateRangeCreateTime: [],
  133 + // 查询参数
  134 + queryParams: {
  135 + pageNo: 1,
  136 + pageSize: 10,
  137 + signature: null,
  138 + status: null,
  139 + },
  140 + // 表单参数
  141 + form: {},
  142 + // 表单校验
  143 + rules: {
  144 + signature: [{ required: true, message: "短信签名不能为空", trigger: "blur" }],
  145 + code: [{ required: true, message: "渠道编码不能为空", trigger: "blur" }],
  146 + status: [{ required: true, message: "启用状态不能为空", trigger: "blur" }],
  147 + apiKey: [{ required: true, message: "短信 API 的账号不能为空", trigger: "blur" }],
  148 + },
  149 + };
  150 + },
  151 + created() {
  152 + this.getList();
  153 + },
  154 + methods: {
  155 + /** 查询列表 */
  156 + getList() {
  157 + this.loading = true;
  158 + // 处理查询参数
  159 + let params = {...this.queryParams};
  160 + this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
  161 + // 执行查询
  162 + getSmsChannelPage(params).then(response => {
  163 + this.list = response.data.list;
  164 + this.total = response.data.total;
  165 + this.loading = false;
  166 + });
  167 + },
  168 + /** 取消按钮 */
  169 + cancel() {
  170 + this.open = false;
  171 + this.reset();
  172 + },
  173 + /** 表单重置 */
  174 + reset() {
  175 + this.form = {
  176 + id: undefined,
  177 + signature: undefined,
  178 + code: undefined,
  179 + status: undefined,
  180 + remark: undefined,
  181 + apiKey: undefined,
  182 + apiSecret: undefined,
  183 + callbackUrl: undefined,
  184 + };
  185 + this.resetForm("form");
  186 + },
  187 + /** 搜索按钮操作 */
  188 + handleQuery() {
  189 + this.queryParams.pageNo = 1;
  190 + this.getList();
  191 + },
  192 + /** 重置按钮操作 */
  193 + resetQuery() {
  194 + this.dateRangeCreateTime = [];
  195 + this.resetForm("queryForm");
  196 + this.handleQuery();
  197 + },
  198 + /** 新增按钮操作 */
  199 + handleAdd() {
  200 + this.reset();
  201 + this.open = true;
  202 + this.title = "添加短信渠道";
  203 + },
  204 + /** 修改按钮操作 */
  205 + handleUpdate(row) {
  206 + this.reset();
  207 + const id = row.id;
  208 + getSmsChannel(id).then(response => {
  209 + this.form = response.data;
  210 + this.open = true;
  211 + this.title = "修改短信渠道";
  212 + });
  213 + },
  214 + /** 提交按钮 */
  215 + submitForm() {
  216 + this.$refs["form"].validate(valid => {
  217 + if (!valid) {
  218 + return;
  219 + }
  220 + // 修改的提交
  221 + if (this.form.id != null) {
  222 + updateSmsChannel(this.form).then(response => {
  223 + this.msgSuccess("修改成功");
  224 + this.open = false;
  225 + this.getList();
  226 + });
  227 + return;
  228 + }
  229 + // 添加的提交
  230 + createSmsChannel(this.form).then(response => {
  231 + this.msgSuccess("新增成功");
  232 + this.open = false;
  233 + this.getList();
  234 + });
  235 + });
  236 + },
  237 + /** 删除按钮操作 */
  238 + handleDelete(row) {
  239 + const id = row.id;
  240 + this.$confirm('是否确认删除短信渠道编号为"' + id + '"的数据项?', "警告", {
  241 + confirmButtonText: "确定",
  242 + cancelButtonText: "取消",
  243 + type: "warning"
  244 + }).then(function() {
  245 + return deleteSmsChannel(id);
  246 + }).then(() => {
  247 + this.getList();
  248 + this.msgSuccess("删除成功");
  249 + })
  250 + },
  251 + /** 导出按钮操作 */
  252 + handleExport() {
  253 + // 处理查询参数
  254 + let params = {...this.queryParams};
  255 + params.pageNo = undefined;
  256 + params.pageSize = undefined;
  257 + this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
  258 + // 执行导出
  259 + this.$confirm('是否确认导出所有短信渠道数据项?', "警告", {
  260 + confirmButtonText: "确定",
  261 + cancelButtonText: "取消",
  262 + type: "warning"
  263 + }).then(function() {
  264 + return exportSmsChannelExcel(params);
  265 + }).then(response => {
  266 + this.downloadExcel(response, '短信渠道.xls');
  267 + })
  268 + }
  269 + }
  270 +};
  271 +</script><template>
  272 + <div class="app-container">
  273 +
  274 + <!-- 搜索工作栏 -->
  275 + <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
  276 + <el-form-item label="短信签名" prop="signature">
  277 + <el-input v-model="queryParams.signature" placeholder="请输入短信签名" clearable size="small" @keyup.enter.native="handleQuery"/>
  278 + </el-form-item>
  279 + <el-form-item label="启用状态" prop="status">
  280 + <el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable size="small">
  281 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
  282 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  283 + </el-select>
  284 + </el-form-item>
  285 + <el-form-item label="创建时间">
  286 + <el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
  287 + type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
  288 + </el-form-item>
  289 + <el-form-item>
  290 + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  291 + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  292 + </el-form-item>
  293 + </el-form>
  294 +
  295 + <!-- 操作工具栏 -->
  296 + <el-row :gutter="10" class="mb8">
  297 + <el-col :span="1.5">
  298 + <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
  299 + v-hasPermi="['system:sms-channel:create']">新增</el-button>
  300 + </el-col>
  301 + <el-col :span="1.5">
  302 + <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
  303 + v-hasPermi="['system:sms-channel:export']">导出</el-button>
  304 + </el-col>
  305 + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  306 + </el-row>
  307 +
  308 + <!-- 列表 -->
  309 + <el-table v-loading="loading" :data="list">
  310 + <el-table-column label="编号" align="center" prop="id" />
  311 + <el-table-column label="短信签名" align="center" prop="signature" />
  312 + <el-table-column label="渠道编码" align="center" prop="code">
  313 + <template slot-scope="scope">
  314 + <span>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.code) }}</span>
  315 + </template>
  316 + </el-table-column>>
  317 + <el-table-column label="启用状态" align="center" prop="status">
  318 + <template slot-scope="scope">
  319 + <span>{{ getDictDataLabel(DICT_TYPE.SYS_COMMON_STATUS, scope.row.status) }}</span>
  320 + </template>
  321 + </el-table-column>>
  322 + <el-table-column label="备注" align="center" prop="remark" />
  323 + <el-table-column label="创建时间" align="center" prop="createTime" width="180">
  324 + <template slot-scope="scope">
  325 + <span>{{ parseTime(scope.row.createTime) }}</span>
  326 + </template>
  327 + </el-table-column>
  328 + <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  329 + <template slot-scope="scope">
  330 + <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
  331 + v-hasPermi="['system:sms-channel:update']">修改</el-button>
  332 + <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
  333 + v-hasPermi="['system:sms-channel:delete']">删除</el-button>
  334 + </template>
  335 + </el-table-column>
  336 + </el-table>
  337 + <!-- 分页组件 -->
  338 + <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  339 + @pagination="getList"/>
  340 +
  341 + <!-- 对话框(添加 / 修改) -->
  342 + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
  343 + <el-form ref="form" :model="form" :rules="rules" label-width="130px">
  344 + <el-form-item label="短信签名" prop="signature">
  345 + <el-input v-model="form.signature" placeholder="请输入短信签名" />
  346 + </el-form-item>
  347 + <el-form-item label="渠道编码" prop="code">
  348 + <el-select v-model="form.code" placeholder="请选择渠道编码" :disabled="form.id > 0">
  349 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_SMS_CHANNEL_CODE)"
  350 + :key="dict.value" :label="dict.label" :value="dict.value" />
  351 + </el-select>
  352 + </el-form-item>
  353 + <el-form-item label="启用状态">
  354 + <el-radio-group v-model="form.status">
  355 + <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
  356 + :key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
  357 + </el-radio-group>
  358 + </el-form-item>
  359 + <el-form-item label="备注" prop="remark">
  360 + <el-input v-model="form.remark" placeholder="请输入备注" />
  361 + </el-form-item>
  362 + <el-form-item label="短信 API 的账号" prop="apiKey">
  363 + <el-input v-model="form.apiKey" placeholder="请输入短信 API 的账号" />
  364 + </el-form-item>
  365 + <el-form-item v-if="form.code !== 'YUN_PIAN'" label="短信 API 的秘钥" prop="apiSecret">
  366 + <el-input v-model="form.apiSecret" placeholder="请输入短信 API 的秘钥" />
  367 + </el-form-item>
  368 + <el-form-item label="短信发送回调 URL" prop="callbackUrl">
  369 + <el-input v-model="form.callbackUrl" placeholder="请输入短信发送回调 URL" />
  370 + </el-form-item>
  371 + </el-form>
  372 + <div slot="footer" class="dialog-footer">
  373 + <el-button type="primary" @click="submitForm">确 定</el-button>
  374 + <el-button @click="cancel">取 消</el-button>
  375 + </div>
  376 + </el-dialog>
  377 + </div>
  378 +</template>
  379 +
  380 +<script>
  381 +import { createSmsChannel, updateSmsChannel, deleteSmsChannel, getSmsChannel, getSmsChannelPage } from "@/api/system/sms/smsChannel";
  382 +
  383 +export default {
  384 + name: "SmsChannel",
  385 + components: {
  386 + },
  387 + data() {
  388 + return {
  389 + // 遮罩层
  390 + loading: true,
  391 + // 显示搜索条件
  392 + showSearch: true,
  393 + // 总条数
  394 + total: 0,
  395 + // 短信渠道列表
  396 + list: [],
  397 + // 弹出层标题
  398 + title: "",
  399 + // 是否显示弹出层
  400 + open: false,
  401 + dateRangeCreateTime: [],
  402 + // 查询参数
  403 + queryParams: {
  404 + pageNo: 1,
  405 + pageSize: 10,
  406 + signature: null,
  407 + status: null,
  408 + },
  409 + // 表单参数
  410 + form: {},
  411 + // 表单校验
  412 + apiKeyEnableChannelCodes: ['YUN_PIAN'],
  413 + rules: {
  414 + signature: [{ required: true, message: "短信签名不能为空", trigger: "blur" }],
  415 + code: [{ required: true, message: "渠道编码不能为空", trigger: "blur" }],
  416 + status: [{ required: true, message: "启用状态不能为空", trigger: "blur" }],
  417 + apiKey: [{ required: true, message: "短信 API 的账号不能为空", trigger: "blur" }],
  418 + apiSecret: [{ required: true, message: "短信 API 的秘钥不能为空", trigger: "blur" }],
  419 + }
  420 + };
  421 + },
  422 + created() {
  423 + this.getList();
  424 + },
  425 + methods: {
  426 + /** 查询列表 */
  427 + getList() {
  428 + this.loading = true;
  429 + // 处理查询参数
  430 + let params = {...this.queryParams};
  431 + this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
  432 + // 执行查询
  433 + getSmsChannelPage(params).then(response => {
  434 + this.list = response.data.list;
  435 + this.total = response.data.total;
  436 + this.loading = false;
  437 + });
  438 + },
  439 + /** 取消按钮 */
  440 + cancel() {
  441 + this.open = false;
  442 + this.reset();
  443 + },
  444 + /** 表单重置 */
  445 + reset() {
  446 + this.form = {
  447 + id: undefined,
  448 + signature: undefined,
  449 + code: undefined,
  450 + status: undefined,
  451 + remark: undefined,
  452 + apiKey: undefined,
  453 + apiSecret: undefined,
  454 + callbackUrl: undefined,
  455 + };
  456 + this.resetForm("form");
  457 + },
  458 + /** 搜索按钮操作 */
  459 + handleQuery() {
  460 + this.queryParams.pageNo = 1;
  461 + this.getList();
  462 + },
  463 + /** 重置按钮操作 */
  464 + resetQuery() {
  465 + this.dateRangeCreateTime = [];
  466 + this.resetForm("queryForm");
  467 + this.handleQuery();
  468 + },
  469 + /** 新增按钮操作 */
  470 + handleAdd() {
  471 + this.reset();
  472 + this.open = true;
  473 + this.title = "添加短信渠道";
  474 + },
  475 + /** 修改按钮操作 */
  476 + handleUpdate(row) {
  477 + this.reset();
  478 + const id = row.id;
  479 + getSmsChannel(id).then(response => {
  480 + this.form = response.data;
  481 + this.open = true;
  482 + this.title = "修改短信渠道";
  483 + });
  484 + },
  485 + /** 提交按钮 */
  486 + submitForm() {
  487 + this.$refs["form"].validate(valid => {
  488 + if (!valid) {
  489 + return;
  490 + }
  491 + // 修改的提交
  492 + if (this.form.id != null) {
  493 + updateSmsChannel(this.form).then(response => {
  494 + this.msgSuccess("修改成功");
  495 + this.open = false;
  496 + this.getList();
  497 + });
  498 + return;
  499 + }
  500 + // 添加的提交
  501 + createSmsChannel(this.form).then(response => {
  502 + this.msgSuccess("新增成功");
  503 + this.open = false;
  504 + this.getList();
  505 + });
  506 + });
  507 + },
  508 + /** 删除按钮操作 */
  509 + handleDelete(row) {
  510 + const id = row.id;
  511 + this.$confirm('是否确认删除短信渠道编号为"' + id + '"的数据项?', "警告", {
  512 + confirmButtonText: "确定",
  513 + cancelButtonText: "取消",
  514 + type: "warning"
  515 + }).then(function() {
  516 + return deleteSmsChannel(id);
  517 + }).then(() => {
  518 + this.getList();
  519 + this.msgSuccess("删除成功");
  520 + })
  521 + },
  522 + /** 导出按钮操作 */
  523 + handleExport() {
  524 + // 处理查询参数
  525 + let params = {...this.queryParams};
  526 + params.pageNo = undefined;
  527 + params.pageSize = undefined;
  528 + this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
  529 + // 执行导出
  530 + this.$confirm('是否确认导出所有短信渠道数据项?', "警告", {
  531 + confirmButtonText: "确定",
  532 + cancelButtonText: "取消",
  533 + type: "warning"
  534 + }).then(function() {
  535 + return exportSmsChannelExcel(params);
  536 + }).then(response => {
  537 + this.downloadExcel(response, '短信渠道.xls');
  538 + })
  539 + }
  540 + }
  541 +};
  542 +</script>
... ...
ruoyi-ui/src/views/system/sms/smsLog.vue 0 → 100644
  1 +<template>
  2 + <div class="app-container">
  3 +
  4 + <!-- 搜索工作栏 -->
  5 + <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
  6 + <el-form-item label="手机号" prop="mobile">
  7 + <el-input v-model="queryParams.mobile" placeholder="请输入手机号" clearable size="small" @keyup.enter.native="handleQuery"/>
  8 + </el-form-item>
  9 + <el-form-item label="短信渠道" prop="channelId">
  10 + <el-select v-model="queryParams.channelId" placeholder="请选择短信渠道" clearable size="small">
  11 + <el-option v-for="channel in channelOptions"
  12 + :key="channel.id" :value="channel.id"
  13 + :label="channel.signature + '【' + getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, channel.code) + '】'" />
  14 + </el-select>
  15 + </el-form-item>
  16 + <el-form-item label="模板编号" prop="templateId">
  17 + <el-input v-model="queryParams.templateId" placeholder="请输入模板编号" clearable size="small" @keyup.enter.native="handleQuery"/>
  18 + </el-form-item>
  19 + <el-form-item label="发送状态" prop="sendStatus">
  20 + <el-select v-model="queryParams.sendStatus" placeholder="请选择发送状态" clearable size="small">
  21 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_SMS_SEND_STATUS)"
  22 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  23 + </el-select>
  24 + </el-form-item>
  25 + <el-form-item label="发送时间">
  26 + <el-date-picker v-model="dateRangeSendTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
  27 + type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
  28 + </el-form-item>
  29 + <el-form-item label="接收状态" prop="receiveStatus">
  30 + <el-select v-model="queryParams.receiveStatus" placeholder="请选择接收状态" clearable size="small">
  31 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_SMS_RECEIVE_STATUS)"
  32 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  33 + </el-select>
  34 + </el-form-item>
  35 + <el-form-item label="接收时间">
  36 + <el-date-picker v-model="dateRangeReceiveTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
  37 + type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
  38 + </el-form-item>
  39 + <el-form-item>
  40 + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  41 + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  42 + </el-form-item>
  43 + </el-form>
  44 +
  45 + <!-- 操作工具栏 -->
  46 + <el-row :gutter="10" class="mb8">
  47 + <el-col :span="1.5">
  48 + <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
  49 + v-hasPermi="['system:sms-log:create']">新增</el-button>
  50 + </el-col>
  51 + <el-col :span="1.5">
  52 + <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
  53 + v-hasPermi="['system:sms-log:export']">导出</el-button>
  54 + </el-col>
  55 + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  56 + </el-row>
  57 +
  58 + <!-- 列表 -->
  59 + <el-table v-loading="loading" :data="list">
  60 + <el-table-column label="编号" align="center" prop="id" />
  61 + <el-table-column label="创建时间" align="center" prop="createTime" width="180">
  62 + <template slot-scope="scope">
  63 + <span>{{ parseTime(scope.row.createTime) }}</span>
  64 + </template>
  65 + </el-table-column>
  66 + <el-table-column label="手机号" align="center" prop="mobile" width="120">
  67 + <template slot-scope="scope">
  68 + <div>{{ scope.row.mobile }}</div>
  69 + <div v-if="scope.row.userType && scope.row.userId">
  70 + {{ getDictDataLabel(DICT_TYPE.USER_TYPE, scope.row.userType) + '(' + scope.row.userId + ')' }}
  71 + </div>
  72 + </template>
  73 + </el-table-column>
  74 + <el-table-column label="短信内容" align="center" prop="templateContent" width="300" />
  75 + <el-table-column label="发送状态" align="center" width="180">
  76 + <template slot-scope="scope">
  77 + <div>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_SEND_STATUS, scope.row.sendStatus) }}</div>
  78 + <div>{{ parseTime(scope.row.sendTime) }}</div>
  79 + </template>
  80 + </el-table-column>
  81 + <el-table-column label="接收状态" align="center" width="180">
  82 + <template slot-scope="scope">
  83 + <div>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_RECEIVE_STATUS, scope.row.receiveStatus) }}</div>
  84 + <div>{{ parseTime(scope.row.receiveTime) }}</div>
  85 + </template>
  86 + </el-table-column>
  87 + <el-table-column label="短信渠道" align="center" width="120">
  88 + <template slot-scope="scope">
  89 + <div>{{ formatChannelSignature(scope.row.channelId) }}</div>
  90 + <div>【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}】</div>
  91 + </template>
  92 + </el-table-column>
  93 + <el-table-column label="模板编号" align="center" prop="templateId" />
  94 + <el-table-column label="短信类型" align="center" prop="templateType">
  95 + <template slot-scope="scope">
  96 + <span>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_TEMPLATE_TYPE, scope.row.templateType) }}</span>
  97 + </template>
  98 + </el-table-column>
  99 + <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  100 + <template slot-scope="scope">
  101 + <el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row,scope.index)"
  102 + v-hasPermi="['system:sms-log:query']">详细</el-button>
  103 + </template>
  104 + </el-table-column>
  105 + </el-table>
  106 + <!-- 分页组件 -->
  107 + <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  108 + @pagination="getList"/>
  109 +
  110 + <!-- 短信日志详细 -->
  111 + <el-dialog title="短信日志详细" :visible.sync="open" width="700px" append-to-body>
  112 + <el-form ref="form" :model="form" label-width="140px" size="mini">
  113 + <el-row>
  114 + <el-col :span="24">
  115 + <el-form-item label="日志主键:">{{ form.id }}</el-form-item>
  116 + </el-col>
  117 + <el-col :span="24">
  118 + <el-form-item label="短信渠道:">
  119 + {{ formatChannelSignature(form.channelId) }}【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, form.channelCode) }}】
  120 + </el-form-item>
  121 + </el-col>
  122 + <el-col :span="24">
  123 + <el-form-item label="短信模板:">
  124 + {{ form.templateId }} | {{ form.templateCode}} | {{ getDictDataLabel(DICT_TYPE.SYS_SMS_TEMPLATE_TYPE, form.templateType) }}
  125 + </el-form-item>
  126 + </el-col>
  127 + <el-col :span="24">
  128 + <el-form-item label="API 的模板编号:">{{ form.apiTemplateId }}</el-form-item>
  129 + </el-col>
  130 + <el-col :span="24">
  131 + <el-form-item label="用户信息:">{{ form.mobile }}
  132 + <span v-if="form.userType && form.userId"> | {{ getDictDataLabel(DICT_TYPE.USER_TYPE, form.userType) }} | {{ form.userId }}</span>
  133 + </el-form-item>
  134 + </el-col>
  135 + <el-col :span="24">
  136 + <el-form-item label="短信内容:">{{ form.templateContent }}</el-form-item>
  137 + </el-col>
  138 + <el-col :span="24">
  139 + <el-form-item label="短信参数:">{{ form.templateParams }}</el-form-item>
  140 + </el-col>
  141 + <el-col :span="24">
  142 + <el-form-item label="创建时间:">{{ parseTime(form.createTime) }}</el-form-item>
  143 + </el-col>
  144 + <el-col :span="24">
  145 + <el-form-item label="发送状态:">{{ getDictDataLabel(DICT_TYPE.SYS_SMS_SEND_STATUS, form.sendStatus) }}</el-form-item>
  146 + </el-col>
  147 + <el-col :span="24">
  148 + <el-form-item label="发送时间:">{{ parseTime(form.sendTime) }}</el-form-item>
  149 + </el-col>
  150 + <el-col :span="24">
  151 + <el-form-item label="发送结果:">{{ form.sendCode }} | {{ form.sendMsg }}
  152 + </el-form-item>
  153 + </el-col>
  154 + <el-col :span="24">
  155 + <el-form-item label="API 发送结果:">{{ form.apiSendCode }} | {{ form.apiSendMsg }}</el-form-item>
  156 + </el-col>
  157 + <el-col :span="24">
  158 + <el-form-item label="API 短信编号:">{{ form.apiSerialNo }}</el-form-item>
  159 + </el-col>
  160 + <el-col :span="24">
  161 + <el-form-item label="API 请求编号:">{{ form.apiRequestId }}</el-form-item>
  162 + </el-col>
  163 + <el-col :span="24">
  164 + <el-form-item label="接收状态:">{{ getDictDataLabel(DICT_TYPE.SYS_SMS_RECEIVE_STATUS, form.receiveStatus) }}</el-form-item>
  165 + </el-col>
  166 + <el-col :span="24">
  167 + <el-form-item label="接收时间:">{{ parseTime(form.receiveTime) }}</el-form-item>
  168 + </el-col>
  169 + <el-col :span="24">
  170 + <el-form-item label="API 接收结果:">{{ form.apiReceiveCode }} | {{ form.apiReceiveMsg }}
  171 + </el-form-item>
  172 + </el-col>
  173 + </el-row>
  174 + </el-form>
  175 + <div slot="footer" class="dialog-footer">
  176 + <el-button @click="open = false">关 闭</el-button>
  177 + </div>
  178 + </el-dialog>
  179 +
  180 + </div>
  181 +</template>
  182 +
  183 +<script>
  184 +import { getSmsLogPage, exportSmsLogExcel } from "@/api/system/sms/smsLog";
  185 +import { getSimpleSmsChannels } from "@/api/system/sms/smsChannel";
  186 +
  187 +export default {
  188 + name: "SmsLog",
  189 + components: {
  190 + },
  191 + data() {
  192 + return {
  193 + // 遮罩层
  194 + loading: true,
  195 + // 显示搜索条件
  196 + showSearch: true,
  197 + // 总条数
  198 + total: 0,
  199 + // 短信日志列表
  200 + list: [],
  201 + // 弹出层标题
  202 + title: "",
  203 + // 是否显示弹出层
  204 + open: false,
  205 + dateRangeSendTime: [],
  206 + dateRangeReceiveTime: [],
  207 + // 表单参数
  208 + form: {},
  209 + // 查询参数
  210 + queryParams: {
  211 + pageNo: 1,
  212 + pageSize: 10,
  213 + channelId: null,
  214 + templateId: null,
  215 + mobile: null,
  216 + sendStatus: null,
  217 + receiveStatus: null,
  218 + },
  219 + // 短信渠道
  220 + channelOptions: [],
  221 + };
  222 + },
  223 + created() {
  224 + this.getList();
  225 + // 获得短信渠道
  226 + getSimpleSmsChannels().then(response => {
  227 + this.channelOptions = response.data;
  228 + })
  229 + },
  230 + methods: {
  231 + /** 查询列表 */
  232 + getList() {
  233 + this.loading = true;
  234 + // 处理查询参数
  235 + let params = {...this.queryParams};
  236 + this.addBeginAndEndTime(params, this.dateRangeSendTime, 'sendTime');
  237 + this.addBeginAndEndTime(params, this.dateRangeReceiveTime, 'receiveTime');
  238 + // 执行查询
  239 + getSmsLogPage(params).then(response => {
  240 + this.list = response.data.list;
  241 + this.total = response.data.total;
  242 + this.loading = false;
  243 + });
  244 + },
  245 + /** 取消按钮 */
  246 + cancel() {
  247 + this.open = false;
  248 + this.reset();
  249 + },
  250 + /** 搜索按钮操作 */
  251 + handleQuery() {
  252 + this.queryParams.pageNo = 1;
  253 + this.getList();
  254 + },
  255 + /** 重置按钮操作 */
  256 + resetQuery() {
  257 + this.dateRangeSendTime = [];
  258 + this.dateRangeReceiveTime = [];
  259 + this.resetForm("queryForm");
  260 + this.handleQuery();
  261 + },
  262 + /** 导出按钮操作 */
  263 + handleExport() {
  264 + // 处理查询参数
  265 + let params = {...this.queryParams};
  266 + params.pageNo = undefined;
  267 + params.pageSize = undefined;
  268 + this.addBeginAndEndTime(params, this.dateRangeSendTime, 'sendTime');
  269 + this.addBeginAndEndTime(params, this.dateRangeReceiveTime, 'receiveTime');
  270 + // 执行导出
  271 + this.$confirm('是否确认导出所有短信日志数据项?', "警告", {
  272 + confirmButtonText: "确定",
  273 + cancelButtonText: "取消",
  274 + type: "warning"
  275 + }).then(function() {
  276 + return exportSmsLogExcel(params);
  277 + }).then(response => {
  278 + this.downloadExcel(response, '短信日志.xls');
  279 + })
  280 + },
  281 + /** 详细按钮操作 */
  282 + handleView(row) {
  283 + this.open = true;
  284 + this.form = row;
  285 + },
  286 + /** 格式化短信渠道 */
  287 + formatChannelSignature(channelId) {
  288 + for (const channel of this.channelOptions) {
  289 + if (channel.id === channelId) {
  290 + return channel.signature;
  291 + }
  292 + }
  293 + return '找不到签名:' + channelId;
  294 + }
  295 + }
  296 +};
  297 +</script>
... ...
ruoyi-ui/src/views/system/sms/smsTemplate.vue 0 → 100644
  1 +<template>
  2 + <div class="app-container">
  3 +
  4 + <!-- 搜索工作栏 -->
  5 + <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="150px">
  6 + <el-form-item label="短信类型" prop="type">
  7 + <el-select v-model="queryParams.type" placeholder="请选择短信类型" clearable size="small">
  8 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_SMS_TEMPLATE_TYPE)"
  9 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  10 + </el-select>
  11 + </el-form-item>
  12 + <el-form-item label="开启状态" prop="status">
  13 + <el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable size="small">
  14 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
  15 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  16 + </el-select>
  17 + </el-form-item>
  18 + <el-form-item label="模板编码" prop="code">
  19 + <el-input v-model="queryParams.code" placeholder="请输入模板编码" clearable size="small" @keyup.enter.native="handleQuery"/>
  20 + </el-form-item>
  21 + <el-form-item label="短信 API 的模板编号" prop="apiTemplateId">
  22 + <el-input v-model="queryParams.apiTemplateId" placeholder="请输入短信 API 的模板编号" clearable size="small" @keyup.enter.native="handleQuery"/>
  23 + </el-form-item>
  24 + <el-form-item label="短信渠道" prop="channelId">
  25 + <el-select v-model="queryParams.channelId" placeholder="请选择短信渠道" clearable size="small">
  26 + <el-option v-for="channel in channelOptions"
  27 + :key="channel.id" :value="channel.id"
  28 + :label="channel.signature + '【' + getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, channel.code) + '】'" />
  29 + </el-select>
  30 + </el-form-item>
  31 + <el-form-item label="创建时间">
  32 + <el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
  33 + type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
  34 + </el-form-item>
  35 + <el-form-item>
  36 + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  37 + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  38 + </el-form-item>
  39 + </el-form>
  40 +
  41 + <!-- 操作工具栏 -->
  42 + <el-row :gutter="10" class="mb8">
  43 + <el-col :span="1.5">
  44 + <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
  45 + v-hasPermi="['system:sms-template:create']">新增</el-button>
  46 + </el-col>
  47 + <el-col :span="1.5">
  48 + <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
  49 + v-hasPermi="['system:sms-template:export']">导出</el-button>
  50 + </el-col>
  51 + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  52 + </el-row>
  53 +
  54 + <!-- 列表 -->
  55 + <el-table v-loading="loading" :data="list">
  56 + <el-table-column label="模板编码" align="center" prop="code" />
  57 + <el-table-column label="模板名称" align="center" prop="name" />
  58 + <el-table-column label="模板内容" align="center" prop="content" width="300" />
  59 + <el-table-column label="短信类型" align="center" prop="type">
  60 + <template slot-scope="scope">
  61 + <span>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_TEMPLATE_TYPE, scope.row.type) }}</span>
  62 + </template>
  63 + </el-table-column>
  64 + <el-table-column label="开启状态" align="center" prop="status">
  65 + <template slot-scope="scope">
  66 + <span>{{ getDictDataLabel(DICT_TYPE.SYS_COMMON_STATUS, scope.row.status) }}</span>
  67 + </template>
  68 + </el-table-column>
  69 + <el-table-column label="备注" align="center" prop="remark" />
  70 + <el-table-column label="短信 API 的模板编号" align="center" prop="apiTemplateId" width="180" />
  71 + <el-table-column label="短信渠道" align="center" width="120">
  72 + <template slot-scope="scope">
  73 + <div>{{ formatChannelSignature(scope.row.channelId) }}</div>
  74 + <div>【{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}】</div>
  75 + </template>
  76 + </el-table-column>
  77 + <el-table-column label="创建时间" align="center" prop="createTime" width="180">
  78 + <template slot-scope="scope">
  79 + <span>{{ parseTime(scope.row.createTime) }}</span>
  80 + </template>
  81 + </el-table-column>
  82 + <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
  83 + <template slot-scope="scope">
  84 + <el-button size="mini" type="text" icon="el-icon-share" @click="handleSendSms(scope.row)"
  85 + v-hasPermi="['system:sms-template:send-sms']">测试</el-button>
  86 + <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
  87 + v-hasPermi="['system:sms-template:update']">修改</el-button>
  88 + <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
  89 + v-hasPermi="['system:sms-template:delete']">删除</el-button>
  90 + </template>
  91 + </el-table-column>
  92 + </el-table>
  93 + <!-- 分页组件 -->
  94 + <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  95 + @pagination="getList"/>
  96 +
  97 + <!-- 对话框(添加 / 修改) -->
  98 + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
  99 + <el-form ref="form" :model="form" :rules="rules" label-width="140px">
  100 + <el-form-item label="短信渠道编号" prop="channelId">
  101 + <el-select v-model="form.channelId" placeholder="请选择短信渠道编号">
  102 + <el-option v-for="channel in channelOptions"
  103 + :key="channel.id" :value="channel.id"
  104 + :label="channel.signature + '【' + getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, channel.code) + '】'" />
  105 + </el-select>
  106 + </el-form-item>
  107 + <el-form-item label="短信类型" prop="type">
  108 + <el-select v-model="form.type" placeholder="请选择短信类型">
  109 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_SMS_TEMPLATE_TYPE)"
  110 + :key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
  111 + </el-select>
  112 + </el-form-item>
  113 + <el-form-item label="模板编号" prop="code">
  114 + <el-input v-model="form.code" placeholder="请输入模板编号" />
  115 + </el-form-item>
  116 + <el-form-item label="模板名称" prop="name">
  117 + <el-input v-model="form.name" placeholder="请输入模板名称" />
  118 + </el-form-item>
  119 + <el-form-item label="模板内容" prop="content">
  120 + <el-input type="textarea" v-model="form.content" placeholder="请输入模板内容" />
  121 + </el-form-item>
  122 + <el-form-item label="开启状态" prop="status">
  123 + <el-radio-group v-model="form.status">
  124 + <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
  125 + :key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
  126 + </el-radio-group>
  127 + </el-form-item>
  128 + <el-form-item label="短信 API 模板编号" prop="apiTemplateId">
  129 + <el-input v-model="form.apiTemplateId" placeholder="请输入短信 API 的模板编号" />
  130 + </el-form-item>
  131 + <el-form-item label="备注" prop="remark">
  132 + <el-input v-model="form.remark" placeholder="请输入备注" />
  133 + </el-form-item>
  134 + </el-form>
  135 + <div slot="footer" class="dialog-footer">
  136 + <el-button type="primary" @click="submitForm">确 定</el-button>
  137 + <el-button @click="cancel">取 消</el-button>
  138 + </div>
  139 + </el-dialog>
  140 +
  141 + <!-- 对话框(发送短信) -->
  142 + <el-dialog title="测试发送短信" :visible.sync="sendSmsOpen" width="500px" append-to-body>
  143 + <el-form ref="sendSmsForm" :model="sendSmsForm" :rules="sendSmsRules" label-width="140px">
  144 + <el-form-item label="模板内容" prop="content">
  145 + <el-input v-model="sendSmsForm.content" type="textarea" placeholder="请输入模板内容" readonly />
  146 + </el-form-item>
  147 + <el-form-item label="手机号" prop="mobile">
  148 + <el-input v-model="sendSmsForm.mobile" placeholder="请输入手机号" />
  149 + </el-form-item>
  150 + <el-form-item v-for="param in sendSmsForm.params" :label="'参数 {' + param + '}'" :prop="'templateParams.' + param">
  151 + <el-input v-model="sendSmsForm.templateParams[param]" :placeholder="'请输入 ' + param + ' 参数'" />
  152 + </el-form-item>
  153 + </el-form>
  154 + <div slot="footer" class="dialog-footer">
  155 + <el-button type="primary" @click="submitSendSmsForm">确 定</el-button>
  156 + <el-button @click="cancelSendSms">取 消</el-button>
  157 + </div>
  158 + </el-dialog>
  159 +
  160 + </div>
  161 +</template>
  162 +
  163 +<script>
  164 +import { createSmsTemplate, updateSmsTemplate, deleteSmsTemplate, getSmsTemplate, getSmsTemplatePage,
  165 + exportSmsTemplateExcel, sendSms } from "@/api/system/sms/smsTemplate";
  166 +import { getSimpleSmsChannels } from "@/api/system/sms/smsChannel";
  167 +
  168 +export default {
  169 + name: "SmsTemplate",
  170 + data() {
  171 + return {
  172 + // 遮罩层
  173 + loading: true,
  174 + // 显示搜索条件
  175 + showSearch: true,
  176 + // 总条数
  177 + total: 0,
  178 + // 短信模板列表
  179 + list: [],
  180 + // 弹出层标题
  181 + title: "",
  182 + // 是否显示弹出层
  183 + open: false,
  184 + dateRangeCreateTime: [],
  185 + // 查询参数
  186 + queryParams: {
  187 + pageNo: 1,
  188 + pageSize: 10,
  189 + type: null,
  190 + status: null,
  191 + code: null,
  192 + content: null,
  193 + apiTemplateId: null,
  194 + channelId: null,
  195 + },
  196 + // 表单参数
  197 + form: {},
  198 + // 表单校验
  199 + rules: {
  200 + type: [{ required: true, message: "短信类型不能为空", trigger: "change" }],
  201 + status: [{ required: true, message: "开启状态不能为空", trigger: "blur" }],
  202 + code: [{ required: true, message: "模板编码不能为空", trigger: "blur" }],
  203 + name: [{ required: true, message: "模板名称不能为空", trigger: "blur" }],
  204 + content: [{ required: true, message: "模板内容不能为空", trigger: "blur" }],
  205 + apiTemplateId: [{ required: true, message: "短信 API 的模板编号不能为空", trigger: "blur" }],
  206 + channelId: [{ required: true, message: "短信渠道编号不能为空", trigger: "change" }],
  207 + },
  208 + // 短信渠道
  209 + channelOptions: [],
  210 + // 发送短信
  211 + sendSmsOpen: false,
  212 + sendSmsForm: {
  213 + params: [], // 模板的参数列表
  214 + },
  215 + sendSmsRules: {
  216 + mobile: [{ required: true, message: "手机不能为空", trigger: "blur" }],
  217 + templateCode: [{ required: true, message: "手机不能为空", trigger: "blur" }],
  218 + templateParams: { }
  219 + }
  220 + };
  221 + },
  222 + created() {
  223 + this.getList();
  224 + // 获得短信渠道
  225 + getSimpleSmsChannels().then(response => {
  226 + this.channelOptions = response.data;
  227 + })
  228 + },
  229 + methods: {
  230 + /** 查询列表 */
  231 + getList() {
  232 + this.loading = true;
  233 + // 处理查询参数
  234 + let params = {...this.queryParams};
  235 + this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
  236 + // 执行查询
  237 + getSmsTemplatePage(params).then(response => {
  238 + this.list = response.data.list;
  239 + this.total = response.data.total;
  240 + this.loading = false;
  241 + });
  242 + },
  243 + /** 取消按钮 */
  244 + cancel() {
  245 + this.open = false;
  246 + this.reset();
  247 + },
  248 + /** 表单重置 */
  249 + reset() {
  250 + this.form = {
  251 + id: undefined,
  252 + type: undefined,
  253 + status: undefined,
  254 + code: undefined,
  255 + name: undefined,
  256 + content: undefined,
  257 + remark: undefined,
  258 + apiTemplateId: undefined,
  259 + channelId: undefined,
  260 + };
  261 + this.resetForm("form");
  262 + },
  263 + /** 搜索按钮操作 */
  264 + handleQuery() {
  265 + this.queryParams.pageNo = 1;
  266 + this.getList();
  267 + },
  268 + /** 重置按钮操作 */
  269 + resetQuery() {
  270 + this.dateRangeCreateTime = [];
  271 + this.resetForm("queryForm");
  272 + this.handleQuery();
  273 + },
  274 + /** 新增按钮操作 */
  275 + handleAdd() {
  276 + this.reset();
  277 + this.open = true;
  278 + this.title = "添加短信模板";
  279 + },
  280 + /** 修改按钮操作 */
  281 + handleUpdate(row) {
  282 + this.reset();
  283 + const id = row.id;
  284 + getSmsTemplate(id).then(response => {
  285 + this.form = response.data;
  286 + this.open = true;
  287 + this.title = "修改短信模板";
  288 + });
  289 + },
  290 + /** 提交按钮 */
  291 + submitForm() {
  292 + this.$refs["form"].validate(valid => {
  293 + if (!valid) {
  294 + return;
  295 + }
  296 + // 修改的提交
  297 + if (this.form.id != null) {
  298 + updateSmsTemplate(this.form).then(response => {
  299 + this.msgSuccess("修改成功");
  300 + this.open = false;
  301 + this.getList();
  302 + });
  303 + return;
  304 + }
  305 + // 添加的提交
  306 + createSmsTemplate(this.form).then(response => {
  307 + this.msgSuccess("新增成功");
  308 + this.open = false;
  309 + this.getList();
  310 + });
  311 + });
  312 + },
  313 + /** 删除按钮操作 */
  314 + handleDelete(row) {
  315 + const id = row.id;
  316 + this.$confirm('是否确认删除短信模板编号为"' + id + '"的数据项?', "警告", {
  317 + confirmButtonText: "确定",
  318 + cancelButtonText: "取消",
  319 + type: "warning"
  320 + }).then(function() {
  321 + return deleteSmsTemplate(id);
  322 + }).then(() => {
  323 + this.getList();
  324 + this.msgSuccess("删除成功");
  325 + })
  326 + },
  327 + /** 导出按钮操作 */
  328 + handleExport() {
  329 + // 处理查询参数
  330 + let params = {...this.queryParams};
  331 + params.pageNo = undefined;
  332 + params.pageSize = undefined;
  333 + this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
  334 + // 执行导出
  335 + this.$confirm('是否确认导出所有短信模板数据项?', "警告", {
  336 + confirmButtonText: "确定",
  337 + cancelButtonText: "取消",
  338 + type: "warning"
  339 + }).then(function() {
  340 + return exportSmsTemplateExcel(params);
  341 + }).then(response => {
  342 + this.downloadExcel(response, '短信模板.xls');
  343 + })
  344 + },
  345 + /** 发送短息按钮 */
  346 + handleSendSms(row) {
  347 + this.resetSendSms(row);
  348 + // 设置参数
  349 + this.sendSmsForm.content = row.content;
  350 + this.sendSmsForm.params = row.params;
  351 + this.sendSmsForm.templateCode = row.code;
  352 + this.sendSmsForm.templateParams = row.params.reduce(function(obj, item) {
  353 + obj[item] = undefined;
  354 + return obj;
  355 + }, {});
  356 + // 根据 row 重置 rules
  357 + this.sendSmsRules.templateParams = row.params.reduce(function(obj, item) {
  358 + obj[item] = { required: true, message: '参数 ' + item + " 不能为空", trigger: "change" };
  359 + return obj;
  360 + }, {});
  361 + // 设置打开
  362 + this.sendSmsOpen = true;
  363 + },
  364 + /** 重置发送短信的表单 */
  365 + resetSendSms() {
  366 + // 根据 row 重置表单
  367 + this.sendSmsForm = {
  368 + content: undefined,
  369 + params: undefined,
  370 + mobile: undefined,
  371 + templateCode: undefined,
  372 + templateParams: {}
  373 + };
  374 + this.resetForm("sendSmsForm");
  375 + },
  376 + /** 取消发送短信 */
  377 + cancelSendSms() {
  378 + this.sendSmsOpen = false;
  379 + this.resetSendSms();
  380 + },
  381 + /** 提交按钮 */
  382 + submitSendSmsForm() {
  383 + this.$refs["sendSmsForm"].validate(valid => {
  384 + if (!valid) {
  385 + return;
  386 + }
  387 + // 添加的提交
  388 + sendSms(this.sendSmsForm).then(response => {
  389 + this.msgSuccess("提交发送成功!发送结果,见发送日志编号:" + response.data);
  390 + this.sendSmsOpen = false;
  391 + });
  392 + });
  393 + },
  394 + /** 格式化短信渠道 */
  395 + formatChannelSignature(channelId) {
  396 + for (const channel of this.channelOptions) {
  397 + if (channel.id === channelId) {
  398 + return channel.signature;
  399 + }
  400 + }
  401 + return '找不到签名:' + channelId;
  402 + }
  403 + }
  404 +};
  405 +</script>
... ...
ruoyi-ui/src/views/system/user/index.vue
... ... @@ -362,7 +362,7 @@ export default {
362 362 created() {
363 363 this.getList();
364 364 this.getTreeselect();
365   - this.getConfigKey("sys.user.initPassword").then(response => {
  365 + this.getConfigKey("sys.user.init-password").then(response => {
366 366 this.initPassword = response.msg;
367 367 });
368 368 },
... ...
sql/ruoyi-vue-pro.sql
1 1 /*
2 2 Navicat Premium Data Transfer
3 3  
4   - Source Server : 127.0.0.1
  4 + Source Server : local-mysql001
5 5 Source Server Type : MySQL
6 6 Source Server Version : 50718
7 7 Source Host : localhost:3306
... ... @@ -11,7 +11,7 @@
11 11 Target Server Version : 50718
12 12 File Encoding : 65001
13 13  
14   - Date: 21/03/2021 18:53:24
  14 + Date: 18/04/2021 00:36:06
15 15 */
16 16  
17 17 SET NAMES utf8mb4;
... ... @@ -43,7 +43,7 @@ CREATE TABLE `inf_api_access_log` (
43 43 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
44 44 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
45 45 PRIMARY KEY (`id`) USING BTREE
46   -) ENGINE=InnoDB AUTO_INCREMENT=1318 DEFAULT CHARSET=utf8mb4 COMMENT='API 访问日志表';
  46 +) ENGINE=InnoDB AUTO_INCREMENT=3750 DEFAULT CHARSET=utf8mb4 COMMENT='API 访问日志表';
47 47  
48 48 -- ----------------------------
49 49 -- Records of inf_api_access_log
... ... @@ -84,7 +84,7 @@ CREATE TABLE `inf_api_error_log` (
84 84 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
85 85 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
86 86 PRIMARY KEY (`id`) USING BTREE
87   -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='系统异常日志';
  87 +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COMMENT='系统异常日志';
88 88  
89 89 -- ----------------------------
90 90 -- Records of inf_api_error_log
... ... @@ -201,7 +201,7 @@ CREATE TABLE `inf_job_log` (
201 201 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
202 202 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
203 203 PRIMARY KEY (`id`) USING BTREE
204   -) ENGINE=InnoDB AUTO_INCREMENT=627 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务日志表';
  204 +) ENGINE=InnoDB AUTO_INCREMENT=2322 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务日志表';
205 205  
206 206 -- ----------------------------
207 207 -- Records of inf_job_log
... ... @@ -264,7 +264,7 @@ CREATE TABLE `sys_dict_data` (
264 264 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
265 265 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
266 266 PRIMARY KEY (`id`) USING BTREE
267   -) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=utf8mb4 COMMENT='字典数æ®è¡¨';
  267 +) ENGINE=InnoDB AUTO_INCREMENT=78 DEFAULT CHARSET=utf8mb4 COMMENT='字典数æ®è¡¨';
268 268  
269 269 -- ----------------------------
270 270 -- Records of sys_dict_data
... ... @@ -324,6 +324,19 @@ INSERT INTO `sys_dict_data` VALUES (61, 2, &#39;管ç†å‘˜&#39;, &#39;2&#39;, &#39;user_type&#39;, 0, NUL
324 324 INSERT INTO `sys_dict_data` VALUES (62, 0, '未处ç†', '0', 'inf_api_error_log_process_status', 0, NULL, '', '2021-02-26 07:07:19', '', '2021-02-26 08:11:23', b'0');
325 325 INSERT INTO `sys_dict_data` VALUES (63, 1, '已处ç†', '1', 'inf_api_error_log_process_status', 0, NULL, '', '2021-02-26 07:07:26', '', '2021-02-26 08:11:29', b'0');
326 326 INSERT INTO `sys_dict_data` VALUES (64, 2, '已忽略', '2', 'inf_api_error_log_process_status', 0, NULL, '', '2021-02-26 07:07:34', '', '2021-02-26 08:11:34', b'0');
  327 +INSERT INTO `sys_dict_data` VALUES (65, 1, '云片', 'YUN_PIAN', 'sys_sms_channel_code', 0, NULL, '1', '2021-04-05 01:05:14', '1', '2021-04-13 00:20:13', b'0');
  328 +INSERT INTO `sys_dict_data` VALUES (66, 2, '阿里云', 'ALIYUN', 'sys_sms_channel_code', 0, NULL, '1', '2021-04-05 01:05:26', '1', '2021-04-13 00:20:16', b'0');
  329 +INSERT INTO `sys_dict_data` VALUES (67, 1, '验è¯ç ', '1', 'sys_sms_template_type', 0, NULL, '1', '2021-04-05 21:50:57', '1', '2021-04-05 21:50:57', b'0');
  330 +INSERT INTO `sys_dict_data` VALUES (68, 2, '通知', '2', 'sys_sms_template_type', 0, NULL, '1', '2021-04-05 21:51:08', '1', '2021-04-05 21:51:08', b'0');
  331 +INSERT INTO `sys_dict_data` VALUES (69, 0, 'è¥é”€', '3', 'sys_sms_template_type', 0, NULL, '1', '2021-04-05 21:51:15', '1', '2021-04-05 21:51:15', b'0');
  332 +INSERT INTO `sys_dict_data` VALUES (70, 0, 'åˆå§‹åŒ–', '0', 'sys_sms_send_status', 0, NULL, '1', '2021-04-11 20:18:33', '1', '2021-04-11 09:30:18', b'0');
  333 +INSERT INTO `sys_dict_data` VALUES (71, 1, 'å‘逿ˆåŠŸ', '10', 'sys_sms_send_status', 0, NULL, '1', '2021-04-11 20:18:43', '1', '2021-04-11 09:30:20', b'0');
  334 +INSERT INTO `sys_dict_data` VALUES (72, 2, 'å‘é€å¤±è´¥', '20', 'sys_sms_send_status', 0, NULL, '1', '2021-04-11 20:18:49', '1', '2021-04-11 09:30:22', b'0');
  335 +INSERT INTO `sys_dict_data` VALUES (73, 3, 'ä¸å‘é€', '30', 'sys_sms_send_status', 0, NULL, '1', '2021-04-11 20:19:44', '1', '2021-04-11 09:30:23', b'0');
  336 +INSERT INTO `sys_dict_data` VALUES (74, 0, '等待结果', '0', 'sys_sms_receive_status', 0, NULL, '1', '2021-04-11 20:27:43', '1', '2021-04-17 23:05:19', b'0');
  337 +INSERT INTO `sys_dict_data` VALUES (75, 1, '接收æˆåŠŸ', '10', 'sys_sms_receive_status', 0, NULL, '1', '2021-04-11 20:29:25', '1', '2021-04-11 20:29:35', b'0');
  338 +INSERT INTO `sys_dict_data` VALUES (76, 2, '接收失败', '20', 'sys_sms_receive_status', 0, NULL, '1', '2021-04-11 20:29:31', '1', '2021-04-11 20:29:39', b'0');
  339 +INSERT INTO `sys_dict_data` VALUES (77, 0, '调试(钉钉)', 'DEBUG_DING_TALK', 'sys_sms_channel_code', 0, NULL, '1', '2021-04-13 00:20:37', '1', '2021-04-13 00:20:37', b'0');
327 340 COMMIT;
328 341  
329 342 -- ----------------------------
... ... @@ -343,7 +356,7 @@ CREATE TABLE `sys_dict_type` (
343 356 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
344 357 PRIMARY KEY (`id`) USING BTREE,
345 358 UNIQUE KEY `dict_type` (`type`)
346   -) ENGINE=InnoDB AUTO_INCREMENT=111 DEFAULT CHARSET=utf8mb4 COMMENT='字典类型表';
  359 +) ENGINE=InnoDB AUTO_INCREMENT=115 DEFAULT CHARSET=utf8mb4 COMMENT='字典类型表';
347 360  
348 361 -- ----------------------------
349 362 -- Records of sys_dict_type
... ... @@ -366,6 +379,10 @@ INSERT INTO `sys_dict_type` VALUES (107, &#39;定时任务状æ€&#39;, &#39;inf_job_status&#39;,
366 379 INSERT INTO `sys_dict_type` VALUES (108, '定时任务日志状æ€', 'inf_job_log_status', 0, NULL, '', '2021-02-08 10:03:51', '', '2021-02-08 10:03:51', b'0');
367 380 INSERT INTO `sys_dict_type` VALUES (109, '用户类型', 'user_type', 0, NULL, '', '2021-02-26 00:15:51', '', '2021-02-26 00:15:51', b'0');
368 381 INSERT INTO `sys_dict_type` VALUES (110, 'API 异常数æ®çš„处ç†çжæ€', 'inf_api_error_log_process_status', 0, NULL, '', '2021-02-26 07:07:01', '', '2021-02-26 07:07:01', b'0');
  382 +INSERT INTO `sys_dict_type` VALUES (111, '短信渠é“ç¼–ç ', 'sys_sms_channel_code', 0, NULL, '1', '2021-04-05 01:04:50', '1', '2021-04-05 01:04:50', b'0');
  383 +INSERT INTO `sys_dict_type` VALUES (112, '短信模æ¿çš„类型', 'sys_sms_template_type', 0, NULL, '1', '2021-04-05 21:50:43', '1', '2021-04-05 21:50:43', b'0');
  384 +INSERT INTO `sys_dict_type` VALUES (113, '短信å‘é€çжæ€', 'sys_sms_send_status', 0, NULL, '1', '2021-04-11 20:18:03', '1', '2021-04-11 09:30:02', b'0');
  385 +INSERT INTO `sys_dict_type` VALUES (114, '短信接收状æ€', 'sys_sms_receive_status', 0, NULL, '1', '2021-04-11 20:27:14', '1', '2021-04-11 20:27:14', b'0');
369 386 COMMIT;
370 387  
371 388 -- ----------------------------
... ... @@ -386,7 +403,7 @@ CREATE TABLE `sys_login_log` (
386 403 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
387 404 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
388 405 PRIMARY KEY (`id`) USING BTREE
389   -) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COMMENT='系统访问记录';
  406 +) ENGINE=InnoDB AUTO_INCREMENT=84 DEFAULT CHARSET=utf8mb4 COMMENT='系统访问记录';
390 407  
391 408 -- ----------------------------
392 409 -- Records of sys_login_log
... ... @@ -415,7 +432,7 @@ CREATE TABLE `sys_menu` (
415 432 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
416 433 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
417 434 PRIMARY KEY (`id`) USING BTREE
418   -) ENGINE=InnoDB AUTO_INCREMENT=1093 DEFAULT CHARSET=utf8mb4 COMMENT='èœå•æƒé™è¡¨';
  435 +) ENGINE=InnoDB AUTO_INCREMENT=1110 DEFAULT CHARSET=utf8mb4 COMMENT='èœå•æƒé™è¡¨';
419 436  
420 437 -- ----------------------------
421 438 -- Records of sys_menu
... ... @@ -530,6 +547,22 @@ INSERT INTO `sys_menu` VALUES (1089, &#39;日志查询&#39;, &#39;infra:api-error-log:query&#39;
530 547 INSERT INTO `sys_menu` VALUES (1090, '文件管ç†', '', 2, 0, 2, 'file', 'upload', 'infra/file/index', 0, '', '2021-03-12 20:16:20', '1', '2021-03-13 11:07:05', b'0');
531 548 INSERT INTO `sys_menu` VALUES (1091, '文件查询', 'infra:file:query', 3, 1, 1090, '', '', '', 0, '', '2021-03-12 20:16:20', '', '2021-03-12 20:16:20', b'0');
532 549 INSERT INTO `sys_menu` VALUES (1092, '文件删除', 'infra:file:delete', 3, 4, 1090, '', '', '', 0, '', '2021-03-12 20:16:20', '', '2021-03-12 20:16:20', b'0');
  550 +INSERT INTO `sys_menu` VALUES (1093, '短信管ç†', '', 1, 11, 1, 'sms', 'validCode', NULL, 0, '1', '2021-04-05 01:10:16', '1', '2021-04-05 01:11:38', b'0');
  551 +INSERT INTO `sys_menu` VALUES (1094, '短信渠é“', '', 2, 0, 1093, 'sms-channel', 'phone', 'system/sms/smsChannel', 0, '', '2021-04-01 11:07:15', '1', '2021-04-09 23:44:07', b'0');
  552 +INSERT INTO `sys_menu` VALUES (1095, 'çŸ­ä¿¡æ¸ é“æŸ¥è¯¢', 'system:sms-channel:query', 3, 1, 1094, '', '', '', 0, '', '2021-04-01 11:07:15', '', '2021-04-01 11:07:15', b'0');
  553 +INSERT INTO `sys_menu` VALUES (1096, '短信渠é“创建', 'system:sms-channel:create', 3, 2, 1094, '', '', '', 0, '', '2021-04-01 11:07:15', '', '2021-04-01 11:07:15', b'0');
  554 +INSERT INTO `sys_menu` VALUES (1097, 'çŸ­ä¿¡æ¸ é“æ›´æ–°', 'system:sms-channel:update', 3, 3, 1094, '', '', '', 0, '', '2021-04-01 11:07:15', '', '2021-04-01 11:07:15', b'0');
  555 +INSERT INTO `sys_menu` VALUES (1098, '短信渠é“删除', 'system:sms-channel:delete', 3, 4, 1094, '', '', '', 0, '', '2021-04-01 11:07:15', '', '2021-04-01 11:07:15', b'0');
  556 +INSERT INTO `sys_menu` VALUES (1100, '短信模æ¿', '', 2, 1, 1093, 'sms-template', 'phone', 'system/sms/smsTemplate', 0, '', '2021-04-01 17:35:17', '1', '2021-04-11 19:34:21', b'0');
  557 +INSERT INTO `sys_menu` VALUES (1101, 'çŸ­ä¿¡æ¨¡æ¿æŸ¥è¯¢', 'system:sms-template:query', 3, 1, 1100, '', '', '', 0, '', '2021-04-01 17:35:17', '', '2021-04-01 17:35:17', b'0');
  558 +INSERT INTO `sys_menu` VALUES (1102, '短信模æ¿åˆ›å»º', 'system:sms-template:create', 3, 2, 1100, '', '', '', 0, '', '2021-04-01 17:35:17', '', '2021-04-01 17:35:17', b'0');
  559 +INSERT INTO `sys_menu` VALUES (1103, 'çŸ­ä¿¡æ¨¡æ¿æ›´æ–°', 'system:sms-template:update', 3, 3, 1100, '', '', '', 0, '', '2021-04-01 17:35:17', '', '2021-04-01 17:35:17', b'0');
  560 +INSERT INTO `sys_menu` VALUES (1104, '短信模æ¿åˆ é™¤', 'system:sms-template:delete', 3, 4, 1100, '', '', '', 0, '', '2021-04-01 17:35:17', '', '2021-04-01 17:35:17', b'0');
  561 +INSERT INTO `sys_menu` VALUES (1105, '短信模æ¿å¯¼å‡º', 'system:sms-template:export', 3, 5, 1100, '', '', '', 0, '', '2021-04-01 17:35:17', '', '2021-04-01 17:35:17', b'0');
  562 +INSERT INTO `sys_menu` VALUES (1106, 'å‘逿µ‹è¯•短信', 'system:sms-template:send-sms', 3, 6, 1100, '', '', '', 0, '1', '2021-04-11 00:26:40', '1', '2021-04-11 00:26:40', b'0');
  563 +INSERT INTO `sys_menu` VALUES (1107, '短信日志', '', 2, 2, 1093, 'sms-log', 'phone', 'system/sms/smsLog', 0, '', '2021-04-11 08:37:05', '1', '2021-04-11 19:34:25', b'0');
  564 +INSERT INTO `sys_menu` VALUES (1108, '短信日志查询', 'system:sms-log:query', 3, 1, 1107, '', '', '', 0, '', '2021-04-11 08:37:05', '', '2021-04-11 08:37:05', b'0');
  565 +INSERT INTO `sys_menu` VALUES (1109, '短信日志导出', 'system:sms-log:export', 3, 5, 1107, '', '', '', 0, '', '2021-04-11 08:37:05', '', '2021-04-11 08:37:05', b'0');
533 566 COMMIT;
534 567  
535 568 -- ----------------------------
... ... @@ -589,7 +622,7 @@ CREATE TABLE `sys_operate_log` (
589 622 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
590 623 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
591 624 PRIMARY KEY (`id`) USING BTREE
592   -) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=utf8mb4 COMMENT='æ“作日志记录';
  625 +) ENGINE=InnoDB AUTO_INCREMENT=177 DEFAULT CHARSET=utf8mb4 COMMENT='æ“作日志记录';
593 626  
594 627 -- ----------------------------
595 628 -- Records of sys_operate_log
... ... @@ -835,6 +868,116 @@ INSERT INTO `sys_role_menu` VALUES (238, 101, 1065, &#39;&#39;, &#39;2021-01-21 03:23:27&#39;, &#39;
835 868 COMMIT;
836 869  
837 870 -- ----------------------------
  871 +-- Table structure for sys_sms_channel
  872 +-- ----------------------------
  873 +DROP TABLE IF EXISTS `sys_sms_channel`;
  874 +CREATE TABLE `sys_sms_channel` (
  875 + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ç¼–å·',
  876 + `signature` varchar(10) NOT NULL COMMENT '短信签å',
  877 + `code` varchar(63) NOT NULL COMMENT '渠é“ç¼–ç ',
  878 + `status` tinyint(4) NOT NULL COMMENT 'å¼€å¯çжæ€',
  879 + `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  880 + `api_key` varchar(128) NOT NULL COMMENT '短信 API 的账å·',
  881 + `api_secret` varchar(128) DEFAULT NULL COMMENT '短信 API 的秘钥',
  882 + `callback_url` varchar(255) DEFAULT NULL COMMENT '短信å‘é€å›žè°ƒ URL',
  883 + `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  884 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  885 + `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  886 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
  887 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
  888 + PRIMARY KEY (`id`) USING BTREE
  889 +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='短信渠é“';
  890 +
  891 +-- ----------------------------
  892 +-- Records of sys_sms_channel
  893 +-- ----------------------------
  894 +BEGIN;
  895 +INSERT INTO `sys_sms_channel` VALUES (1, '芋é“', 'YUN_PIAN', 0, '呵呵呵哒', '1555a14277cb8a608cf45a9e6a80d510', NULL, 'http://java.nat300.top/api/system/sms/callback/sms/yunpian', '', '2021-03-31 06:12:20', '1', '2021-04-13 01:03:19', b'0');
  896 +INSERT INTO `sys_sms_channel` VALUES (2, 'Ballcat', 'ALIYUN', 0, '啦啦啦', 'LTAI5tCnKso2uG3kJ5gRav88', 'fGJ5SNXL7P1NHNRmJ7DJaMJGPyE55C', NULL, '', '2021-03-31 11:53:10', '1', '2021-04-14 00:08:37', b'0');
  897 +INSERT INTO `sys_sms_channel` VALUES (3, '测试', 'YUN_PIAN', 0, '哈哈哈', '23132', NULL, 'http://www.baidu.com', '1', '2021-04-05 21:10:34', '1', '2021-04-11 16:21:58', b'1');
  898 +INSERT INTO `sys_sms_channel` VALUES (4, '测试渠é“', 'DEBUG_DING_TALK', 0, '123', '696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859', 'SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67', NULL, '1', '2021-04-13 00:23:14', '1', '2021-04-14 00:07:10', b'0');
  899 +COMMIT;
  900 +
  901 +-- ----------------------------
  902 +-- Table structure for sys_sms_log
  903 +-- ----------------------------
  904 +DROP TABLE IF EXISTS `sys_sms_log`;
  905 +CREATE TABLE `sys_sms_log` (
  906 + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ç¼–å·',
  907 + `channel_id` bigint(20) NOT NULL COMMENT '短信渠é“ç¼–å·',
  908 + `channel_code` varchar(63) NOT NULL COMMENT '短信渠é“ç¼–ç ',
  909 + `template_id` bigint(20) NOT NULL COMMENT '模æ¿ç¼–å·',
  910 + `template_code` varchar(63) NOT NULL COMMENT '模æ¿ç¼–ç ',
  911 + `template_type` tinyint(4) NOT NULL COMMENT '短信类型',
  912 + `template_content` varchar(255) NOT NULL COMMENT '短信内容',
  913 + `template_params` varchar(255) NOT NULL COMMENT 'çŸ­ä¿¡å‚æ•°',
  914 + `api_template_id` varchar(63) NOT NULL COMMENT '短信 API 的模æ¿ç¼–å·',
  915 + `mobile` varchar(11) NOT NULL COMMENT '手机å·',
  916 + `user_id` bigint(20) DEFAULT NULL COMMENT '用户编å·',
  917 + `user_type` tinyint(4) DEFAULT NULL COMMENT '用户类型',
  918 + `send_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'å‘é€çжæ€',
  919 + `send_time` datetime DEFAULT NULL COMMENT 'å‘逿—¶é—´',
  920 + `send_code` int(11) DEFAULT NULL COMMENT 'å‘é€ç»“果的编ç ',
  921 + `send_msg` varchar(255) DEFAULT NULL COMMENT 'å‘é€ç»“果的æç¤º',
  922 + `api_send_code` varchar(63) DEFAULT NULL COMMENT '短信 API å‘é€ç»“果的编ç ',
  923 + `api_send_msg` varchar(255) DEFAULT NULL COMMENT '短信 API å‘é€å¤±è´¥çš„æç¤º',
  924 + `api_request_id` varchar(255) DEFAULT NULL COMMENT '短信 API å‘é€è¿”回的唯一请求 ID',
  925 + `api_serial_no` varchar(255) DEFAULT NULL COMMENT '短信 API å‘é€è¿”回的åºå·',
  926 + `receive_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '接收状æ€',
  927 + `receive_time` datetime DEFAULT NULL COMMENT '接收时间',
  928 + `api_receive_code` varchar(63) DEFAULT NULL COMMENT 'API 接收结果的编ç ',
  929 + `api_receive_msg` varchar(255) DEFAULT NULL COMMENT 'API 接收结果的说明',
  930 + `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  931 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  932 + `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  933 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
  934 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
  935 + PRIMARY KEY (`id`) USING BTREE
  936 +) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8mb4 COMMENT='短信日志';
  937 +
  938 +-- ----------------------------
  939 +-- Records of sys_sms_log
  940 +-- ----------------------------
  941 +BEGIN;
  942 +COMMIT;
  943 +
  944 +-- ----------------------------
  945 +-- Table structure for sys_sms_template
  946 +-- ----------------------------
  947 +DROP TABLE IF EXISTS `sys_sms_template`;
  948 +CREATE TABLE `sys_sms_template` (
  949 + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ç¼–å·',
  950 + `type` tinyint(4) NOT NULL COMMENT '短信签å',
  951 + `status` tinyint(4) NOT NULL COMMENT 'å¼€å¯çжæ€',
  952 + `code` varchar(63) NOT NULL COMMENT '模æ¿ç¼–ç ',
  953 + `name` varchar(63) NOT NULL COMMENT '模æ¿åç§°',
  954 + `content` varchar(255) NOT NULL COMMENT '模æ¿å†…容',
  955 + `params` varchar(255) NOT NULL COMMENT '傿•°æ•°ç»„',
  956 + `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  957 + `api_template_id` varchar(63) NOT NULL COMMENT '短信 API 的模æ¿ç¼–å·',
  958 + `channel_id` bigint(20) NOT NULL COMMENT '短信渠é“ç¼–å·',
  959 + `channel_code` varchar(63) NOT NULL COMMENT '短信渠é“ç¼–ç ',
  960 + `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  961 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  962 + `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  963 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
  964 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
  965 + PRIMARY KEY (`id`) USING BTREE
  966 +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COMMENT='短信模æ¿';
  967 +
  968 +-- ----------------------------
  969 +-- Records of sys_sms_template
  970 +-- ----------------------------
  971 +BEGIN;
  972 +INSERT INTO `sys_sms_template` VALUES (2, 1, 0, 'test_01', '测试验è¯ç çŸ­ä¿¡', '正在进行登录æ“作{operation},您的验è¯ç æ˜¯{code}', '[\"operation\",\"code\"]', NULL, '4383920', 1, 'YUN_PIAN', '', '2021-03-31 10:49:38', '1', '2021-04-10 01:22:00', b'0');
  973 +INSERT INTO `sys_sms_template` VALUES (3, 1, 0, 'test_02', '公告通知', '您的验è¯ç {code},该验è¯ç 5分钟内有效,请勿泄æ¼äºŽä»–人ï¼', '[\"code\"]', NULL, 'SMS_207945135', 2, 'ALIYUN', '', '2021-03-31 11:56:30', '1', '2021-04-10 01:22:02', b'0');
  974 +INSERT INTO `sys_sms_template` VALUES (4, 3, 0, 'test-ooxx', '测试模æ¿', 'biubiubiu', '[]', '1111', '110', 1, 'YUN_PIAN', '1', '2021-04-09 23:58:42', '1', '2021-04-02 07:47:12', b'1');
  975 +INSERT INTO `sys_sms_template` VALUES (5, 3, 0, 'test-ooxx', '测试模æ¿', 'biubiubiu', '[]', '1111', '110', 1, 'YUN_PIAN', '1', '2021-04-09 23:58:42', '1', '2021-04-02 08:32:22', b'1');
  976 +INSERT INTO `sys_sms_template` VALUES (6, 3, 0, 'test-01', '测试模æ¿', '哈哈哈 {name}', '[\"name\"]', 'f哈哈哈', '4383920', 1, 'YUN_PIAN', '1', '2021-04-10 01:07:21', '1', '2021-04-10 01:22:05', b'0');
  977 +INSERT INTO `sys_sms_template` VALUES (7, 3, 0, 'test-04', '测试下', 'è€é¸¡{name},牛逼{code}', '[\"name\",\"code\"]', NULL, 'suibian', 4, 'DEBUG_DING_TALK', '1', '2021-04-13 00:29:53', '1', '2021-04-14 00:30:38', b'0');
  978 +COMMIT;
  979 +
  980 +-- ----------------------------
838 981 -- Table structure for sys_user
839 982 -- ----------------------------
840 983 DROP TABLE IF EXISTS `sys_user`;
... ... @@ -865,8 +1008,8 @@ CREATE TABLE `sys_user` (
865 1008 -- Records of sys_user
866 1009 -- ----------------------------
867 1010 BEGIN;
868   -INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋é“', '管ç†å‘˜', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://127.0.0.1:8080/api/system/file/get/add5ec1891a7d97d2cc1d60847e16294.jpg', 0, '127.0.0.1', '2021-01-05 17:03:47', 'admin', '2021-01-05 17:03:47', '1', '2021-03-21 18:16:16', b'0');
869   -INSERT INTO `sys_user` VALUES (2, 'ry', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', 'è‹¥ä¾', '测试员', 105, '[2]', 'ry@qq.com', '15666666666', 1, '', 0, '127.0.0.1', '2021-01-05 17:03:47', 'admin', '2021-01-05 17:03:47', '', '2021-01-05 17:03:47', b'0');
  1011 +INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', 'èŠ‹é“æºç ', '管ç†å‘˜', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://api-dashboard.yudao.iocoder.cn/api/infra/file/get/5e8609290e915c4fa8b08e67.jpg', 0, '127.0.0.1', '2021-01-05 17:03:47', 'admin', '2021-01-05 17:03:47', '1', '2021-04-05 02:16:10', b'0');
  1012 +INSERT INTO `sys_user` VALUES (2, 'ry', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', 'è‹¥ä¾', '测试员', 105, '[2]', 'ry@qq.com', '15666666666', 1, '', 0, '127.0.0.1', '2021-01-05 17:03:47', 'admin', '2021-01-05 17:03:47', '', '2021-04-01 04:50:36', b'1');
870 1013 INSERT INTO `sys_user` VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋é“', 'ä¸è¦å“我', 100, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '', NULL, '', '2021-01-07 09:07:17', '1', '2021-03-14 22:35:17', b'0');
871 1014 INSERT INTO `sys_user` VALUES (103, 'yuanma', '', 'æºç ', NULL, 100, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '', NULL, '', '2021-01-13 23:50:35', '', '2021-01-13 23:50:35', b'0');
872 1015 INSERT INTO `sys_user` VALUES (104, 'test', '$2a$10$.TOFpaIiI3PzEwkGrNq0Eu6Cc3rOqJMxTb1DqeSEM8StxaGPBRKoi', '测试å·', NULL, 100, '[]', '', '15601691200', 1, '', 0, '', NULL, '', '2021-01-21 02:13:53', '1', '2021-03-14 22:36:38', b'0');
... ... @@ -924,13 +1067,40 @@ CREATE TABLE `sys_user_session` (
924 1067 -- Records of sys_user_session
925 1068 -- ----------------------------
926 1069 BEGIN;
  1070 +INSERT INTO `sys_user_session` VALUES ('04c6624c7bf14b1ba1a01cb976a9d876', 1, '2021-04-05 21:40:12', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-05 20:21:09', NULL, '2021-04-01 12:25:35', b'1');
  1071 +INSERT INTO `sys_user_session` VALUES ('0e235ce5ae7342a09b372a00bd7d1b41', 1, '2021-04-05 01:43:22', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-05 00:51:03', NULL, '2021-04-01 04:18:06', b'1');
  1072 +INSERT INTO `sys_user_session` VALUES ('0e6943f8ca9b4215a014843eb489ccc7', 1, '2021-04-05 22:53:22', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-05 21:43:59', NULL, '2021-04-01 17:25:41', b'1');
  1073 +INSERT INTO `sys_user_session` VALUES ('12166cd28b4f448ea468d13c471dfc6e', 1, '2021-04-09 19:48:58', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-09 19:18:58', NULL, '2021-04-02 04:33:15', b'1');
  1074 +INSERT INTO `sys_user_session` VALUES ('134d908ae33146bd9b5291471c04f604', 1, '2021-04-10 00:29:28', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-09 23:10:26', NULL, '2021-04-02 08:08:17', b'1');
  1075 +INSERT INTO `sys_user_session` VALUES ('1407ce21e47947b9b8d93bff1b55c7d6', 1, '2021-04-07 01:09:11', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-07 00:07:43', NULL, '2021-04-01 18:10:44', b'1');
  1076 +INSERT INTO `sys_user_session` VALUES ('1477c38290ff4cee8887ebfe593faa02', 1, '2021-04-17 23:34:57', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-17 23:04:57', NULL, '2021-04-12 21:36:29', b'1');
  1077 +INSERT INTO `sys_user_session` VALUES ('3c75ea73e13b4857a18eb57ca2eea80f', 1, '2021-04-11 20:06:52', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-11 19:36:52', NULL, '2021-04-11 09:10:53', b'1');
  1078 +INSERT INTO `sys_user_session` VALUES ('40d532d8900c43b791266429a7911751', 1, '2021-04-05 22:11:34', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-05 21:41:34', NULL, '2021-04-01 12:28:20', b'1');
  1079 +INSERT INTO `sys_user_session` VALUES ('43676e85d0e04980b2a67181f8d9933b', 1, '2021-04-11 10:41:09', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-11 09:56:31', NULL, '2021-04-02 17:32:07', b'1');
927 1080 INSERT INTO `sys_user_session` VALUES ('505b4e7d8b0d4b40aa23bf540da81234', 1, '2021-03-14 01:25:13', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', NULL, '2021-03-14 00:31:43', NULL, '2021-03-13 07:35:26', b'1');
  1081 +INSERT INTO `sys_user_session` VALUES ('56a92b99f7b642ccbb7508042a25cb0c', 1, '2021-04-11 21:16:33', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-11 20:14:54', NULL, '2021-04-11 10:08:20', b'1');
928 1082 INSERT INTO `sys_user_session` VALUES ('5a7248bf87d14e7e9f0578b05969986c', 1, '2021-03-13 10:42:50', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', NULL, '2021-03-13 09:37:36', NULL, '2021-03-12 19:53:07', b'1');
  1083 +INSERT INTO `sys_user_session` VALUES ('5c30d80eb72048daa1a24d3d4f01317b', 1, '2021-04-12 00:31:10', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-11 23:08:14', NULL, '2021-04-11 15:47:45', b'1');
  1084 +INSERT INTO `sys_user_session` VALUES ('5dca80a5c61541479a4dbb6e004c2e28', 1, '2021-04-14 00:57:25', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-13 23:38:30', NULL, '2021-04-12 17:33:48', b'1');
  1085 +INSERT INTO `sys_user_session` VALUES ('7324a76b029a49ee95bf54ceb4164ba9', 1, '2021-04-13 01:29:14', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-13 00:18:40', NULL, '2021-04-11 23:15:00', b'1');
  1086 +INSERT INTO `sys_user_session` VALUES ('749619894bc441bb9773902515f81e6a', 1, '2021-04-11 00:39:51', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-11 00:09:51', NULL, '2021-04-02 16:50:56', b'1');
  1087 +INSERT INTO `sys_user_session` VALUES ('7768ae62ad974fd989f5159649a4be82', 1, '2021-04-11 00:53:39', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-10 23:23:27', NULL, '2021-04-02 17:04:58', b'1');
  1088 +INSERT INTO `sys_user_session` VALUES ('79efcb8f64aa42af9f4b327fb383532f', 1, '2021-04-11 22:44:07', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-11 21:34:53', NULL, '2021-04-11 14:10:45', b'1');
  1089 +INSERT INTO `sys_user_session` VALUES ('87d5b95fdad9447189a95abf8a5152df', 1, '2021-04-17 23:01:18', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-17 22:31:18', NULL, '2021-04-12 21:03:27', b'1');
  1090 +INSERT INTO `sys_user_session` VALUES ('8b3eac5e4a104a4191c8070e03d553ea', 1, '2021-04-05 02:45:12', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-05 02:15:12', NULL, '2021-04-01 11:05:25', b'1');
929 1091 INSERT INTO `sys_user_session` VALUES ('9ae27346d8b7491aad1385f51e8aa196', 1, '2021-03-13 14:02:12', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', NULL, '2021-03-13 10:43:06', NULL, '2021-03-13 06:40:35', b'1');
  1092 +INSERT INTO `sys_user_session` VALUES ('a2fb443b31c049008975ff8ee5499db1', 1, '2021-04-11 09:42:09', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-11 09:12:09', NULL, '2021-04-02 17:16:00', b'1');
  1093 +INSERT INTO `sys_user_session` VALUES ('a71a74adf9d141e2849d2a411d558205', 1, '2021-04-17 18:24:44', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-17 17:54:44', NULL, '2021-04-12 17:54:50', b'1');
930 1094 INSERT INTO `sys_user_session` VALUES ('ae9ee7452ee54e4b983d658188c15c4d', 1, '2021-03-14 21:32:57', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', NULL, '2021-03-14 20:25:00', NULL, '2021-03-13 15:19:10', b'1');
931   -INSERT INTO `sys_user_session` VALUES ('d0adf48f82914212b947e5ab04d9fb65', 1, '2021-03-21 19:16:28', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-03-21 18:13:37', NULL, '2021-03-21 18:46:28', b'0');
  1095 +INSERT INTO `sys_user_session` VALUES ('b727853eccea4c8589e006ffea985146', 1, '2021-04-12 01:36:00', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-12 01:06:00', NULL, '2021-04-11 19:40:07', b'1');
  1096 +INSERT INTO `sys_user_session` VALUES ('c095616db95044c5bed66a3f84519b8b', 1, '2021-04-11 19:59:33', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-11 19:29:33', NULL, '2021-04-11 09:04:19', b'1');
  1097 +INSERT INTO `sys_user_session` VALUES ('d0adf48f82914212b947e5ab04d9fb65', 1, '2021-03-21 19:16:28', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-03-21 18:13:37', NULL, '2021-03-15 05:53:20', b'1');
  1098 +INSERT INTO `sys_user_session` VALUES ('dfbce0af867547f4bb01ac6f2e583337', 1, '2021-04-11 17:06:15', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-11 16:36:15', NULL, '2021-04-11 08:24:03', b'1');
  1099 +INSERT INTO `sys_user_session` VALUES ('e5ecf10e40a5463b8f9b5b453cb1649b', 1, '2021-04-11 17:06:22', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-11 16:36:22', NULL, '2021-04-11 08:24:03', b'1');
  1100 +INSERT INTO `sys_user_session` VALUES ('e80c2400724042a2ab73732166cde8fc', 1, '2021-03-21 21:17:12', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-03-21 20:47:12', NULL, '2021-03-15 08:18:56', b'1');
932 1101 INSERT INTO `sys_user_session` VALUES ('e8872f5192584440a548641b83c877ef', 1, '2021-03-21 18:36:01', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-03-21 17:51:48', NULL, '2021-03-15 03:54:20', b'1');
933 1102 INSERT INTO `sys_user_session` VALUES ('f1ab99b09b5a475795579ff99d60ac78', 1, '2021-03-14 23:04:31', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', NULL, '2021-03-14 21:12:44', NULL, '2021-03-15 03:32:38', b'1');
  1103 +INSERT INTO `sys_user_session` VALUES ('f2a4182f62ea4559b9946a523928fac4', 1, '2021-04-10 01:45:57', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', NULL, '2021-04-10 00:55:38', NULL, '2021-04-02 14:52:48', b'1');
934 1104 INSERT INTO `sys_user_session` VALUES ('f853b50d064340a581e9a49bba9411fc', 1, '2021-03-10 01:55:41', 'admin', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36', NULL, '2021-03-10 01:11:53', NULL, '2021-03-12 18:37:05', b'1');
935 1105 COMMIT;
936 1106  
... ... @@ -964,7 +1134,7 @@ CREATE TABLE `tool_codegen_column` (
964 1134 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
965 1135 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
966 1136 PRIMARY KEY (`id`) USING BTREE
967   -) ENGINE=InnoDB AUTO_INCREMENT=389 DEFAULT CHARSET=utf8mb4 COMMENT='代ç ç”Ÿæˆè¡¨å­—段定义';
  1137 +) ENGINE=InnoDB AUTO_INCREMENT=447 DEFAULT CHARSET=utf8mb4 COMMENT='代ç ç”Ÿæˆè¡¨å­—段定义';
968 1138  
969 1139 -- ----------------------------
970 1140 -- Records of tool_codegen_column
... ... @@ -1126,6 +1296,64 @@ INSERT INTO `tool_codegen_column` VALUES (385, 33, &#39;create_time&#39;, &#39;datetime&#39;, &#39;å
1126 1296 INSERT INTO `tool_codegen_column` VALUES (386, 33, 'updater', 'varchar(64)', '更新者', b'1', b'0', '0', 6, 'String', 'updater', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '1', '2021-03-13 09:43:20', '1', '2021-03-13 11:27:12', b'0');
1127 1297 INSERT INTO `tool_codegen_column` VALUES (387, 33, 'update_time', 'datetime', 'æ›´æ–°æ—¶é—´', b'0', b'0', '0', 7, 'Date', 'updateTime', '', NULL, b'0', b'0', b'0', 'BETWEEN', b'0', 'datetime', '1', '2021-03-13 09:43:20', '1', '2021-03-13 11:27:12', b'0');
1128 1298 INSERT INTO `tool_codegen_column` VALUES (388, 33, 'deleted', 'bit(1)', '是å¦åˆ é™¤', b'0', b'0', '0', 8, 'Boolean', 'deleted', '', NULL, b'0', b'0', b'0', '=', b'0', 'radio', '1', '2021-03-13 09:43:20', '1', '2021-03-13 11:27:12', b'0');
  1299 +INSERT INTO `tool_codegen_column` VALUES (389, 34, 'id', 'bigint(20)', 'ç¼–å·', b'0', b'1', '1', 1, 'Long', 'id', '', '1024', b'0', b'1', b'0', '=', b'1', 'input', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1300 +INSERT INTO `tool_codegen_column` VALUES (390, 34, 'signature', 'varchar(8)', '短信签å', b'0', b'0', '0', 2, 'String', 'signature', '', 'èŠ‹é“æºç ', b'1', b'1', b'1', '=', b'1', 'input', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1301 +INSERT INTO `tool_codegen_column` VALUES (391, 34, 'code', 'varchar(63)', '渠é“ç¼–ç ', b'0', b'0', '0', 3, 'String', 'code', 'sys_sms_channel_code', 'YUN_PIAN', b'1', b'0', b'0', '=', b'1', 'select', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1302 +INSERT INTO `tool_codegen_column` VALUES (392, 34, 'status', 'tinyint(4)', 'å¯ç”¨çжæ€', b'0', b'0', '0', 4, 'Integer', 'status', 'sys_common_status', '1', b'1', b'1', b'1', '=', b'1', 'radio', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1303 +INSERT INTO `tool_codegen_column` VALUES (393, 34, 'remark', 'varchar(255)', '备注', b'1', b'0', '0', 5, 'String', 'remark', '', '好åƒï¼', b'1', b'1', b'0', '=', b'1', 'input', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1304 +INSERT INTO `tool_codegen_column` VALUES (394, 34, 'api_key', 'varchar(63)', '短信 API 的账å·', b'0', b'0', '0', 6, 'String', 'apiKey', '', 'yudao', b'1', b'1', b'0', '=', b'1', 'input', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1305 +INSERT INTO `tool_codegen_column` VALUES (395, 34, 'api_secret', 'varchar(63)', '短信 API 的秘钥', b'1', b'0', '0', 7, 'String', 'apiSecret', '', 'yuanma', b'1', b'1', b'0', '=', b'1', 'input', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1306 +INSERT INTO `tool_codegen_column` VALUES (396, 34, 'callback_url', 'varchar(255)', '短信å‘é€å›žè°ƒ URL', b'1', b'0', '0', 8, 'String', 'callbackUrl', '', 'http://www.iocoder.cn', b'1', b'1', b'0', '=', b'1', 'input', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1307 +INSERT INTO `tool_codegen_column` VALUES (397, 34, 'creator', 'varchar(64)', '创建者', b'1', b'0', '0', 9, 'String', 'creator', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1308 +INSERT INTO `tool_codegen_column` VALUES (398, 34, 'create_time', 'datetime', '创建时间', b'0', b'0', '0', 10, 'Date', 'createTime', '', NULL, b'0', b'0', b'1', 'BETWEEN', b'1', 'datetime', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1309 +INSERT INTO `tool_codegen_column` VALUES (399, 34, 'updater', 'varchar(64)', '更新者', b'1', b'0', '0', 11, 'String', 'updater', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1310 +INSERT INTO `tool_codegen_column` VALUES (400, 34, 'update_time', 'datetime', 'æ›´æ–°æ—¶é—´', b'0', b'0', '0', 12, 'Date', 'updateTime', '', NULL, b'0', b'0', b'0', 'BETWEEN', b'0', 'datetime', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1311 +INSERT INTO `tool_codegen_column` VALUES (401, 34, 'deleted', 'bit(1)', '是å¦åˆ é™¤', b'0', b'0', '0', 13, 'Boolean', 'deleted', '', NULL, b'0', b'0', b'0', '=', b'0', 'radio', '1', '2021-04-05 00:51:34', '1', '2021-04-05 20:52:09', b'0');
  1312 +INSERT INTO `tool_codegen_column` VALUES (402, 35, 'id', 'bigint(20)', 'ç¼–å·', b'0', b'1', '1', 1, 'Long', 'id', '', '1024', b'0', b'1', b'0', '=', b'1', 'input', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1313 +INSERT INTO `tool_codegen_column` VALUES (403, 35, 'type', 'tinyint(4)', '短信签å', b'0', b'0', '0', 2, 'Integer', 'type', 'sys_sms_template_type', '1', b'1', b'1', b'1', '=', b'1', 'select', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1314 +INSERT INTO `tool_codegen_column` VALUES (404, 35, 'status', 'tinyint(4)', 'å¼€å¯çжæ€', b'0', b'0', '0', 3, 'Integer', 'status', 'sys_common_status', '1', b'1', b'1', b'1', '=', b'1', 'radio', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1315 +INSERT INTO `tool_codegen_column` VALUES (405, 35, 'code', 'varchar(63)', '模æ¿ç¼–ç ', b'0', b'0', '0', 4, 'String', 'code', '', 'test_01', b'1', b'1', b'1', 'LIKE', b'1', 'input', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1316 +INSERT INTO `tool_codegen_column` VALUES (406, 35, 'name', 'varchar(63)', '模æ¿åç§°', b'0', b'0', '0', 5, 'String', 'name', '', 'yudao', b'1', b'1', b'0', 'LIKE', b'1', 'input', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1317 +INSERT INTO `tool_codegen_column` VALUES (407, 35, 'content', 'varchar(255)', '模æ¿å†…容', b'0', b'0', '0', 6, 'String', 'content', '', '你好,{name}。你长的太{like}啦ï¼', b'1', b'1', b'1', 'LIKE', b'1', 'editor', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1318 +INSERT INTO `tool_codegen_column` VALUES (408, 35, 'params', 'varchar(255)', '傿•°æ•°ç»„', b'0', b'0', '0', 7, 'String', 'params', '', 'name,code', b'0', b'0', b'0', '=', b'0', 'input', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1319 +INSERT INTO `tool_codegen_column` VALUES (409, 35, 'remark', 'varchar(255)', '备注', b'1', b'0', '0', 8, 'String', 'remark', '', '哈哈哈', b'1', b'1', b'0', '=', b'1', 'input', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1320 +INSERT INTO `tool_codegen_column` VALUES (410, 35, 'api_template_id', 'varchar(63)', '短信 API 的模æ¿ç¼–å·', b'0', b'0', '0', 9, 'String', 'apiTemplateId', '', '4383920', b'1', b'1', b'1', 'LIKE', b'1', 'input', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1321 +INSERT INTO `tool_codegen_column` VALUES (411, 35, 'channel_id', 'bigint(20)', '短信渠é“ç¼–å·', b'0', b'0', '0', 10, 'Long', 'channelId', '', '10', b'1', b'1', b'1', '=', b'1', 'select', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1322 +INSERT INTO `tool_codegen_column` VALUES (412, 35, 'channel_code', 'varchar(63)', '短信渠é“ç¼–ç ', b'0', b'0', '0', 11, 'String', 'channelCode', 'sys_sms_channel_code', 'ALIYUN', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1323 +INSERT INTO `tool_codegen_column` VALUES (413, 35, 'creator', 'varchar(64)', '创建者', b'1', b'0', '0', 12, 'String', 'creator', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1324 +INSERT INTO `tool_codegen_column` VALUES (414, 35, 'create_time', 'datetime', '创建时间', b'0', b'0', '0', 13, 'Date', 'createTime', '', NULL, b'0', b'0', b'1', 'BETWEEN', b'1', 'datetime', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1325 +INSERT INTO `tool_codegen_column` VALUES (415, 35, 'updater', 'varchar(64)', '更新者', b'1', b'0', '0', 14, 'String', 'updater', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1326 +INSERT INTO `tool_codegen_column` VALUES (416, 35, 'update_time', 'datetime', 'æ›´æ–°æ—¶é—´', b'0', b'0', '0', 15, 'Date', 'updateTime', '', NULL, b'0', b'0', b'0', 'BETWEEN', b'0', 'datetime', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1327 +INSERT INTO `tool_codegen_column` VALUES (417, 35, 'deleted', 'bit(1)', '是å¦åˆ é™¤', b'0', b'0', '0', 16, 'Boolean', 'deleted', '', NULL, b'0', b'0', b'0', '=', b'0', 'radio', '1', '2021-04-05 21:42:22', '1', '2021-04-05 22:23:38', b'0');
  1328 +INSERT INTO `tool_codegen_column` VALUES (418, 36, 'id', 'bigint(20)', 'ç¼–å·', b'0', b'1', '1', 1, 'Long', 'id', '', '1024', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1329 +INSERT INTO `tool_codegen_column` VALUES (419, 36, 'channel_id', 'bigint(20)', '短信渠é“ç¼–å·', b'0', b'0', '0', 2, 'Long', 'channelId', '', '10', b'0', b'0', b'1', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1330 +INSERT INTO `tool_codegen_column` VALUES (420, 36, 'channel_code', 'varchar(63)', '短信渠é“ç¼–ç ', b'0', b'0', '0', 3, 'String', 'channelCode', '', 'ALIYUN', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1331 +INSERT INTO `tool_codegen_column` VALUES (421, 36, 'template_id', 'bigint(20)', '模æ¿ç¼–å·', b'0', b'0', '0', 4, 'Long', 'templateId', '', '20', b'0', b'0', b'1', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1332 +INSERT INTO `tool_codegen_column` VALUES (422, 36, 'template_code', 'varchar(63)', '模æ¿ç¼–ç ', b'0', b'0', '0', 5, 'String', 'templateCode', '', 'test-01', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1333 +INSERT INTO `tool_codegen_column` VALUES (423, 36, 'template_type', 'tinyint(4)', '短信类型', b'0', b'0', '0', 6, 'Integer', 'templateType', 'sys_sms_template_type', '1', b'0', b'0', b'0', '=', b'1', 'select', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1334 +INSERT INTO `tool_codegen_column` VALUES (424, 36, 'template_content', 'varchar(255)', '短信内容', b'0', b'0', '0', 7, 'String', 'templateContent', '', '你好,你的验è¯ç æ˜¯ 1024', b'0', b'0', b'0', '=', b'1', 'editor', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1335 +INSERT INTO `tool_codegen_column` VALUES (425, 36, 'template_params', 'varchar(255)', 'çŸ­ä¿¡å‚æ•°', b'0', b'0', '0', 8, 'String', 'templateParams', '', 'name,code', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1336 +INSERT INTO `tool_codegen_column` VALUES (426, 36, 'api_template_id', 'varchar(63)', '短信 API 的模æ¿ç¼–å·', b'0', b'0', '0', 9, 'String', 'apiTemplateId', '', 'SMS_207945135', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1337 +INSERT INTO `tool_codegen_column` VALUES (427, 36, 'mobile', 'varchar(11)', '手机å·', b'0', b'0', '0', 10, 'String', 'mobile', '', '15601691300', b'0', b'0', b'1', 'LIKE', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1338 +INSERT INTO `tool_codegen_column` VALUES (428, 36, 'user_id', 'bigint(20)', '用户编å·', b'1', b'0', '0', 11, 'Long', 'userId', '', '10', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1339 +INSERT INTO `tool_codegen_column` VALUES (429, 36, 'user_type', 'tinyint(4)', '用户类型', b'1', b'0', '0', 12, 'Integer', 'userType', 'user_type', '1', b'0', b'0', b'0', '=', b'1', 'select', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1340 +INSERT INTO `tool_codegen_column` VALUES (430, 36, 'send_status', 'tinyint(4)', 'å‘é€çжæ€', b'0', b'0', '0', 13, 'Integer', 'sendStatus', 'sys_sms_send_status', '1', b'0', b'0', b'1', '=', b'1', 'radio', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1341 +INSERT INTO `tool_codegen_column` VALUES (431, 36, 'send_time', 'datetime', 'å‘逿—¶é—´', b'1', b'0', '0', 14, 'Date', 'sendTime', '', NULL, b'0', b'0', b'1', 'BETWEEN', b'1', 'datetime', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1342 +INSERT INTO `tool_codegen_column` VALUES (432, 36, 'send_code', 'int(11)', 'å‘é€ç»“果的编ç ', b'1', b'0', '0', 15, 'Integer', 'sendCode', '', '0', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1343 +INSERT INTO `tool_codegen_column` VALUES (433, 36, 'send_msg', 'varchar(255)', 'å‘é€ç»“果的æç¤º', b'1', b'0', '0', 16, 'String', 'sendMsg', '', 'æˆåŠŸ', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1344 +INSERT INTO `tool_codegen_column` VALUES (434, 36, 'api_send_code', 'varchar(63)', '短信 API å‘é€ç»“果的编ç ', b'1', b'0', '0', 17, 'String', 'apiSendCode', '', 'SUCCESS', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1345 +INSERT INTO `tool_codegen_column` VALUES (435, 36, 'api_send_msg', 'varchar(255)', '短信 API å‘é€å¤±è´¥çš„æç¤º', b'1', b'0', '0', 18, 'String', 'apiSendMsg', '', 'æˆåŠŸ', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1346 +INSERT INTO `tool_codegen_column` VALUES (436, 36, 'api_request_id', 'varchar(255)', '短信 API å‘é€è¿”回的唯一请求 ID', b'1', b'0', '0', 19, 'String', 'apiRequestId', '', '3837C6D3-B96F-428C-BBB2-86135D4B5B99', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1347 +INSERT INTO `tool_codegen_column` VALUES (437, 36, 'api_serial_no', 'varchar(255)', '短信 API å‘é€è¿”回的åºå·', b'1', b'0', '0', 20, 'String', 'apiSerialNo', '', '62923244790', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1348 +INSERT INTO `tool_codegen_column` VALUES (438, 36, 'receive_status', 'tinyint(4)', '接收状æ€', b'0', b'0', '0', 21, 'Integer', 'receiveStatus', 'sys_sms_receive_status', '0', b'0', b'0', b'1', '=', b'1', 'radio', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1349 +INSERT INTO `tool_codegen_column` VALUES (439, 36, 'receive_time', 'datetime', '接收时间', b'1', b'0', '0', 22, 'Date', 'receiveTime', '', NULL, b'0', b'0', b'1', 'BETWEEN', b'1', 'datetime', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1350 +INSERT INTO `tool_codegen_column` VALUES (440, 36, 'api_receive_code', 'varchar(63)', 'API 接收结果的编ç ', b'1', b'0', '0', 23, 'String', 'apiReceiveCode', '', 'DELIVRD', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1351 +INSERT INTO `tool_codegen_column` VALUES (441, 36, 'api_receive_msg', 'varchar(255)', 'API 接收结果的说明', b'1', b'0', '0', 24, 'String', 'apiReceiveMsg', '', 'DELIVRD', b'0', b'0', b'0', '=', b'1', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1352 +INSERT INTO `tool_codegen_column` VALUES (442, 36, 'creator', 'varchar(64)', '创建者', b'1', b'0', '0', 25, 'String', 'creator', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1353 +INSERT INTO `tool_codegen_column` VALUES (443, 36, 'create_time', 'datetime', '创建时间', b'0', b'0', '0', 26, 'Date', 'createTime', '', NULL, b'0', b'0', b'0', 'BETWEEN', b'1', 'datetime', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1354 +INSERT INTO `tool_codegen_column` VALUES (444, 36, 'updater', 'varchar(64)', '更新者', b'1', b'0', '0', 27, 'String', 'updater', '', NULL, b'0', b'0', b'0', '=', b'0', 'input', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1355 +INSERT INTO `tool_codegen_column` VALUES (445, 36, 'update_time', 'datetime', 'æ›´æ–°æ—¶é—´', b'0', b'0', '0', 28, 'Date', 'updateTime', '', NULL, b'0', b'0', b'0', 'BETWEEN', b'0', 'datetime', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
  1356 +INSERT INTO `tool_codegen_column` VALUES (446, 36, 'deleted', 'bit(1)', '是å¦åˆ é™¤', b'0', b'0', '0', 29, 'Boolean', 'deleted', '', NULL, b'0', b'0', b'0', '=', b'0', 'radio', '1', '2021-04-11 09:13:48', '1', '2021-04-11 20:33:54', b'0');
1129 1357 COMMIT;
1130 1358  
1131 1359 -- ----------------------------
... ... @@ -1151,7 +1379,7 @@ CREATE TABLE `tool_codegen_table` (
1151 1379 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ›´æ–°æ—¶é—´',
1152 1380 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤',
1153 1381 PRIMARY KEY (`id`) USING BTREE
1154   -) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8mb4 COMMENT='代ç ç”Ÿæˆè¡¨å®šä¹‰';
  1382 +) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4 COMMENT='代ç ç”Ÿæˆè¡¨å®šä¹‰';
1155 1383  
1156 1384 -- ----------------------------
1157 1385 -- Records of tool_codegen_table
... ... @@ -1169,6 +1397,9 @@ INSERT INTO `tool_codegen_table` VALUES (28, 1, &#39;sys_dict_type&#39;, &#39;字典类型è¡
1169 1397 INSERT INTO `tool_codegen_table` VALUES (29, 1, 'sys_dict_type', '字典类型表', NULL, 'system', 'dict', 'SysDictType', '字典类型', '芋艿', 1, NULL, '', '2021-03-06 03:52:57', '', '2021-03-06 04:03:52', b'0');
1170 1398 INSERT INTO `tool_codegen_table` VALUES (30, 1, 'sys_dict_data', '字典数æ®è¡¨', NULL, 'system', 'type', 'SysDictData', '字典数æ®', 'èŠ‹é“æºç ', 1, NULL, '', '2021-03-06 06:48:28', '', '2021-03-06 06:50:47', b'0');
1171 1399 INSERT INTO `tool_codegen_table` VALUES (33, 1, 'inf_file', '文件表', NULL, 'infra', 'file', 'InfFile', '文件', '芋艿', 1, 2, '1', '2021-03-13 09:43:20', '1', '2021-03-13 11:27:12', b'0');
  1400 +INSERT INTO `tool_codegen_table` VALUES (34, 1, 'sys_sms_channel', '短信渠é“', NULL, 'system', 'sms', 'SysSmsChannel', '短信渠é“', 'èŠ‹é“æºç ', 1, 1093, '1', '2021-04-03 13:39:06', '1', '2021-04-05 20:52:09', b'0');
  1401 +INSERT INTO `tool_codegen_table` VALUES (35, 1, 'sys_sms_template', '短信模æ¿', NULL, 'system', 'sms', 'SysSmsTemplate', '短信模æ¿', 'èŠ‹é“æºç ', 1, 1093, '1', '2021-04-03 13:58:55', '1', '2021-04-05 22:23:38', b'0');
  1402 +INSERT INTO `tool_codegen_table` VALUES (36, 1, 'sys_sms_log', '短信日志', NULL, 'system', 'sms', 'SysSmsLog', '短信日志', 'èŠ‹é“æºç ', 1, 1093, '1', '2021-04-11 01:12:57', '1', '2021-04-11 20:33:54', b'0');
1172 1403 COMMIT;
1173 1404  
1174 1405 -- ----------------------------
... ...
src/main/java/cn/iocoder/dashboard/common/core/KeyValue.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.core;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +/**
  8 + * Key Value 的键值对
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
  15 +public class KeyValue<K, V> {
  16 +
  17 + private K key;
  18 + private V value;
  19 +
  20 +}
... ...
src/main/java/cn/iocoder/dashboard/common/enums/DefaultBitFieldEnum.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.enums;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 通用状态枚举
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Getter
  12 +@AllArgsConstructor
  13 +public enum DefaultBitFieldEnum {
  14 +
  15 + NO(0, "否"),
  16 + YES(1, "是");
  17 +
  18 + /**
  19 + * 状态值
  20 + */
  21 + private final Integer val;
  22 + /**
  23 + * 状态名
  24 + */
  25 + private final String name;
  26 +
  27 +}
... ...
src/main/java/cn/iocoder/dashboard/common/exception/ErrorCode.java
1 1 package cn.iocoder.dashboard.common.exception;
2 2  
  3 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
3 4 import cn.iocoder.dashboard.common.exception.enums.ServiceErrorCodeRange;
4 5 import lombok.Data;
5 6  
6 7 /**
7 8 * 错误码对象
8 9 *
9   - * 全局错误码,占用 [0, 999],参见 {@link GlobalException}
  10 + * 全局错误码,占用 [0, 999], 参见 {@link GlobalErrorCodeConstants}
10 11 * 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange}
11 12 *
12 13 * TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
... ... @@ -21,11 +22,11 @@ public class ErrorCode {
21 22 /**
22 23 * 错误提示
23 24 */
24   - private final String message;
  25 + private final String msg;
25 26  
26 27 public ErrorCode(Integer code, String message) {
27 28 this.code = code;
28   - this.message = message;
  29 + this.msg = message;
29 30 }
30 31  
31 32 }
... ...
src/main/java/cn/iocoder/dashboard/common/exception/GlobalException.java deleted
1   -package cn.iocoder.dashboard.common.exception;
2   -
3   -import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
4   -import lombok.Data;
5   -import lombok.EqualsAndHashCode;
6   -
7   -/**
8   - * 全局异常 Exception
9   - */
10   -@Data
11   -@EqualsAndHashCode(callSuper = true)
12   -public class GlobalException extends RuntimeException {
13   -
14   - /**
15   - * 全局错误码
16   - *
17   - * @see GlobalErrorCodeConstants
18   - */
19   - private Integer code;
20   - /**
21   - * 错误提示
22   - */
23   - private String message;
24   -
25   - /**
26   - * 空构造方法,避免反序列化问题
27   - */
28   - public GlobalException() {
29   - }
30   -
31   - public GlobalException(ErrorCode errorCode) {
32   - this.code = errorCode.getCode();
33   - this.message = errorCode.getMessage();
34   - }
35   -
36   - public GlobalException(Integer code, String message) {
37   - this.code = code;
38   - this.message = message;
39   - }
40   -
41   -}
src/main/java/cn/iocoder/dashboard/common/exception/ServiceException.java
... ... @@ -30,7 +30,7 @@ public final class ServiceException extends RuntimeException {
30 30  
31 31 public ServiceException(ErrorCode errorCode) {
32 32 this.code = errorCode.getCode();
33   - this.message = errorCode.getMessage();
  33 + this.message = errorCode.getMsg();
34 34 }
35 35  
36 36 public ServiceException(Integer code, String message) {
... ...
src/main/java/cn/iocoder/dashboard/common/exception/enums/GlobalErrorCodeConstants.java
... ... @@ -32,6 +32,7 @@ public interface GlobalErrorCodeConstants {
32 32  
33 33 // ========== 自定义错误段 ==========
34 34 ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
  35 + ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作");
35 36  
36 37 ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
37 38  
... ...
src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java
... ... @@ -47,12 +47,12 @@ public class ServiceExceptionUtil {
47 47 // ========== 和 ServiceException 的集成 ==========
48 48  
49 49 public static ServiceException exception(ErrorCode errorCode) {
50   - String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMessage());
  50 + String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg());
51 51 return exception0(errorCode.getCode(), messagePattern);
52 52 }
53 53  
54 54 public static ServiceException exception(ErrorCode errorCode, Object... params) {
55   - String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMessage());
  55 + String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg());
56 56 return exception0(errorCode.getCode(), messagePattern, params);
57 57 }
58 58  
... ...
src/main/java/cn/iocoder/dashboard/common/pojo/CommonResult.java
1 1 package cn.iocoder.dashboard.common.pojo;
2 2  
3 3 import cn.iocoder.dashboard.common.exception.ErrorCode;
4   -import cn.iocoder.dashboard.common.exception.GlobalException;
5 4 import cn.iocoder.dashboard.common.exception.ServiceException;
6 5 import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
7 6 import com.fasterxml.jackson.annotation.JsonIgnore;
... ... @@ -9,6 +8,7 @@ import lombok.Data;
9 8 import org.springframework.util.Assert;
10 9  
11 10 import java.io.Serializable;
  11 +import java.util.Objects;
12 12  
13 13 /**
14 14 * 通用返回
... ... @@ -16,7 +16,7 @@ import java.io.Serializable;
16 16 * @param <T> 数据泛型
17 17 */
18 18 @Data
19   -public final class CommonResult<T> implements Serializable {
  19 +public class CommonResult<T> implements Serializable {
20 20  
21 21 /**
22 22 * 错误码
... ... @@ -31,7 +31,7 @@ public final class CommonResult&lt;T&gt; implements Serializable {
31 31 /**
32 32 * 错误提示,用户可阅读
33 33 *
34   - * @see ErrorCode#getMessage() ()
  34 + * @see ErrorCode#getMsg() ()
35 35 */
36 36 private String msg;
37 37  
... ... @@ -57,7 +57,7 @@ public final class CommonResult&lt;T&gt; implements Serializable {
57 57 }
58 58  
59 59 public static <T> CommonResult<T> error(ErrorCode errorCode) {
60   - return error(errorCode.getCode(), errorCode.getMessage());
  60 + return error(errorCode.getCode(), errorCode.getMsg());
61 61 }
62 62  
63 63 public static <T> CommonResult<T> success(T data) {
... ... @@ -68,9 +68,13 @@ public final class CommonResult&lt;T&gt; implements Serializable {
68 68 return result;
69 69 }
70 70  
  71 + public static boolean isSuccess(Integer code) {
  72 + return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
  73 + }
  74 +
71 75 @JsonIgnore // 避免 jackson 序列化
72 76 public boolean isSuccess() {
73   - return GlobalErrorCodeConstants.SUCCESS.getCode().equals(code);
  77 + return isSuccess(code);
74 78 }
75 79  
76 80 @JsonIgnore // 避免 jackson 序列化
... ... @@ -81,16 +85,12 @@ public final class CommonResult&lt;T&gt; implements Serializable {
81 85 // ========= 和 Exception 异常体系集成 =========
82 86  
83 87 /**
84   - * 判断是否有异常。如果有,则抛出 {@link GlobalException} 或 {@link ServiceException} 异常
  88 + * 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
85 89 */
86   - public void checkError() throws GlobalException, ServiceException {
  90 + public void checkError() throws ServiceException {
87 91 if (isSuccess()) {
88 92 return;
89 93 }
90   - // 全局异常
91   - if (GlobalErrorCodeConstants.isMatch(code)) {
92   - throw new GlobalException(code, msg);
93   - }
94 94 // 业务异常
95 95 throw new ServiceException(code, msg);
96 96 }
... ... @@ -99,8 +99,4 @@ public final class CommonResult&lt;T&gt; implements Serializable {
99 99 return error(serviceException.getCode(), serviceException.getMessage());
100 100 }
101 101  
102   - public static <T> CommonResult<T> error(GlobalException globalException) {
103   - return error(globalException.getCode(), globalException.getMessage());
104   - }
105   -
106 102 }
... ...
src/main/java/cn/iocoder/dashboard/framework/excel/core/convert/DictConvert.java
... ... @@ -13,6 +13,11 @@ import com.alibaba.excel.metadata.GlobalConfiguration;
13 13 import com.alibaba.excel.metadata.property.ExcelContentProperty;
14 14 import lombok.extern.slf4j.Slf4j;
15 15  
  16 +/**
  17 + * Excel {@link SysDictDataDO} 数据字典转换器
  18 + *
  19 + * @author 芋道源码
  20 + */
16 21 @Slf4j
17 22 public class DictConvert implements Converter<Object> {
18 23  
... ...
src/main/java/cn/iocoder/dashboard/framework/excel/core/convert/JsonConvert.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.excel.core.convert;
  2 +
  3 +import cn.iocoder.dashboard.util.json.JsonUtils;
  4 +import com.alibaba.excel.converters.Converter;
  5 +import com.alibaba.excel.enums.CellDataTypeEnum;
  6 +import com.alibaba.excel.metadata.CellData;
  7 +import com.alibaba.excel.metadata.GlobalConfiguration;
  8 +import com.alibaba.excel.metadata.property.ExcelContentProperty;
  9 +
  10 +/**
  11 + * Excel Json 转换器
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public class JsonConvert implements Converter<Object> {
  16 +
  17 + @Override
  18 + public Class<?> supportJavaTypeKey() {
  19 + throw new UnsupportedOperationException("暂不支持,也不需要");
  20 + }
  21 +
  22 + @Override
  23 + public CellDataTypeEnum supportExcelTypeKey() {
  24 + throw new UnsupportedOperationException("暂不支持,也不需要");
  25 + }
  26 +
  27 + @Override
  28 + public Object convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
  29 + throw new UnsupportedOperationException("暂不支持,也不需要");
  30 + }
  31 +
  32 + @Override
  33 + public CellData<String> convertToExcelData(Object value, ExcelContentProperty contentProperty,
  34 + GlobalConfiguration globalConfiguration) {
  35 + // 生成 Excel 小表格
  36 + return new CellData<>(JsonUtils.toJsonString(value));
  37 + }
  38 +
  39 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/redis/config/RedisConfig.java
... ... @@ -48,8 +48,8 @@ public class RedisConfig {
48 48 * 创建 Redis Pub/Sub 广播消费的容器
49 49 */
50 50 @Bean
51   - public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory,
52   - List<AbstractChannelMessageListener<?>> listeners) {
  51 + public RedisMessageListenerContainer redisMessageListenerContainer(
  52 + RedisConnectionFactory factory, List<AbstractChannelMessageListener<?>> listeners) {
53 53 // 创建 RedisMessageListenerContainer 对象
54 54 RedisMessageListenerContainer container = new RedisMessageListenerContainer();
55 55 // 设置 RedisConnection 工厂。
... ... @@ -69,8 +69,8 @@ public class RedisConfig {
69 69 * Redis Stream 的 xreadgroup 命令:https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html
70 70 */
71 71 @Bean(initMethod = "start", destroyMethod = "stop")
72   - public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(RedisTemplate<String, Object> redisTemplate,
73   - List<AbstractStreamMessageListener<?>> listeners) {
  72 + public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
  73 + RedisTemplate<String, Object> redisTemplate, List<AbstractStreamMessageListener<?>> listeners) {
74 74 // 第一步,创建 StreamMessageListenerContainer 容器
75 75 // 创建 options 配置
76 76 StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> containerOptions =
... ...
src/main/java/cn/iocoder/dashboard/framework/security/config/SecurityConfiguration.java
... ... @@ -128,13 +128,13 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
128 128 // 设置每个请求的权限
129 129 .authorizeRequests()
130 130 // 登陆的接口,可匿名访问
131   - .antMatchers(webProperties.getApiPrefix() + "/login").anonymous()
  131 + .antMatchers(api("/login")).anonymous()
132 132 // 通用的接口,可匿名访问
133   - .antMatchers( webProperties.getApiPrefix() + "/system/captcha/**").anonymous()
  133 + .antMatchers(api("/system/captcha/**")).anonymous()
134 134 // 静态资源,可匿名访问
135 135 .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
136 136 // 文件的获取接口,可匿名访问
137   - .antMatchers(webProperties.getApiPrefix() + "/infra/file/get/**").anonymous()
  137 + .antMatchers(api("/infra/file/get/**")).anonymous()
138 138 // Swagger 接口文档
139 139 .antMatchers("/swagger-ui.html").anonymous()
140 140 .antMatchers("/swagger-resources/**").anonymous()
... ... @@ -148,13 +148,19 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
148 148 .antMatchers("/actuator/**").anonymous()
149 149 // Druid 监控
150 150 .antMatchers("/druid/**").anonymous()
  151 + // 短信回调 API
  152 + .antMatchers(api("/system/sms/callback/**")).anonymous()
151 153 // 除上面外的所有请求全部需要鉴权认证
152 154 .anyRequest().authenticated()
153 155 .and()
154 156 .headers().frameOptions().disable();
155   - httpSecurity.logout().logoutUrl(webProperties.getApiPrefix() + "/logout").logoutSuccessHandler(logoutSuccessHandler);
  157 + httpSecurity.logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler);
156 158 // 添加 JWT Filter
157 159 httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
158 160 }
159 161  
  162 + private String api(String url) {
  163 + return webProperties.getApiPrefix() + url;
  164 + }
  165 +
160 166 }
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/config/SmsConfiguration.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.config;
  2 +
  3 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
  4 +import cn.iocoder.dashboard.framework.sms.core.client.impl.SmsClientFactoryImpl;
  5 +import org.springframework.context.annotation.Bean;
  6 +import org.springframework.context.annotation.Configuration;
  7 +
  8 +/**
  9 + * 短信配置类
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Configuration
  14 +public class SmsConfiguration {
  15 +
  16 + @Bean
  17 + public SmsClientFactory smsClientFactory() {
  18 + return new SmsClientFactoryImpl();
  19 + }
  20 +
  21 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/SmsClient.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client;
  2 +
  3 +import cn.iocoder.dashboard.common.core.KeyValue;
  4 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  5 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  6 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * 短信客户端接口
  12 + *
  13 + * @author zzf
  14 + * @date 2021/1/25 14:14
  15 + */
  16 +public interface SmsClient {
  17 +
  18 + /**
  19 + * 获得渠道编号
  20 + *
  21 + * @return 渠道编号
  22 + */
  23 + Long getId();
  24 +
  25 + /**
  26 + * 发送消息
  27 + *
  28 + * @param logId 日志编号
  29 + * @param mobile 手机号
  30 + * @param apiTemplateId 短信 API 的模板编号
  31 + * @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序
  32 + * @return 短信发送结果
  33 + */
  34 + SmsCommonResult<SmsSendRespDTO> sendSms(Long logId, String mobile, String apiTemplateId,
  35 + List<KeyValue<String, Object>> templateParams);
  36 +
  37 + /**
  38 + * 解析接收短信的接收结果
  39 + *
  40 + * @param text 结果
  41 + * @return 结果内容
  42 + * @throws Throwable 当解析 text 发生异常时,则会抛出异常
  43 + */
  44 + List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable;
  45 +
  46 + /**
  47 + * 查询指定的短信模板
  48 + *
  49 + * @param apiTemplateId 短信 API 的模板编号
  50 + * @return 短信模板
  51 + */
  52 + SmsCommonResult<SmsTemplateRespDTO> getSmsTemplate(String apiTemplateId);
  53 +
  54 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/SmsClientFactory.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client;
  2 +
  3 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  4 +
  5 +/**
  6 + * 短信客户端工厂接口
  7 + *
  8 + * @author zzf
  9 + * @date 2021/1/28 14:01
  10 + */
  11 +public interface SmsClientFactory {
  12 +
  13 + /**
  14 + * 获得短信 Client
  15 + *
  16 + * @param channelId 渠道编号
  17 + * @return 短信 Client
  18 + */
  19 + SmsClient getSmsClient(Long channelId);
  20 +
  21 + /**
  22 + * 获得短信 Client
  23 + *
  24 + * @param channelCode 渠道编码
  25 + * @return 短信 Client
  26 + */
  27 + SmsClient getSmsClient(String channelCode);
  28 +
  29 + /**
  30 + * 创建短信 Client
  31 + *
  32 + * @param properties 配置对象
  33 + */
  34 + void createOrUpdateSmsClient(SmsChannelProperties properties);
  35 +
  36 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/SmsCodeMapping.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  4 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
  5 +
  6 +import java.util.function.Function;
  7 +
  8 +/**
  9 + * 将 API 的错误码,转换为通用的错误码
  10 + *
  11 + * @see SmsCommonResult
  12 + * @see SmsFrameworkErrorCodeConstants
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public interface SmsCodeMapping extends Function<String, ErrorCode> {
  17 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/SmsCommonResult.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client;
  2 +
  3 +import cn.hutool.core.exceptions.ExceptionUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  6 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  7 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
  8 +import lombok.Data;
  9 +import lombok.EqualsAndHashCode;
  10 +import lombok.ToString;
  11 +
  12 +/**
  13 + * 短信的 CommonResult 拓展类
  14 + *
  15 + * 考虑到不同的平台,返回的 code 和 msg 是不同的,所以统一额外返回 {@link #apiCode} 和 {@link #apiMsg} 字段
  16 + *
  17 + * 另外,一些短信平台(例如说阿里云、腾讯云)会返回一个请求编号,用于排查请求失败的问题,我们设置到 {@link #apiRequestId} 字段
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +@Data
  22 +@EqualsAndHashCode(callSuper = true)
  23 +@ToString(callSuper = true)
  24 +public class SmsCommonResult<T> extends CommonResult<T> {
  25 +
  26 + /**
  27 + * API 返回错误码
  28 + *
  29 + * 由于第三方的错误码可能是字符串,所以使用 String 类型
  30 + */
  31 + private String apiCode;
  32 + /**
  33 + * API 返回提示
  34 + */
  35 + private String apiMsg;
  36 +
  37 + /**
  38 + * API 请求编号
  39 + */
  40 + private String apiRequestId;
  41 +
  42 + private SmsCommonResult() {
  43 + }
  44 +
  45 + public static <T> SmsCommonResult<T> build(String apiCode, String apiMsg, String apiRequestId,
  46 + T data, SmsCodeMapping codeMapping) {
  47 + Assert.notNull(codeMapping, "参数 codeMapping 不能为空");
  48 + SmsCommonResult<T> result = new SmsCommonResult<T>().setApiCode(apiCode).setApiMsg(apiMsg).setApiRequestId(apiRequestId);
  49 + result.setData(data);
  50 + // 翻译错误码
  51 + if (codeMapping != null) {
  52 + ErrorCode errorCode = codeMapping.apply(apiCode);
  53 + if (errorCode == null) {
  54 + errorCode = SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
  55 + }
  56 + result.setCode(errorCode.getCode()).setMsg(errorCode.getMsg());
  57 + }
  58 + return result;
  59 + }
  60 +
  61 + public static <T> SmsCommonResult<T> error(Throwable ex) {
  62 + SmsCommonResult<T> result = new SmsCommonResult<>();
  63 + result.setCode(SmsFrameworkErrorCodeConstants.EXCEPTION.getCode());
  64 + result.setMsg(ExceptionUtil.getRootCauseMessage(ex));
  65 + return result;
  66 + }
  67 +
  68 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/dto/SmsReceiveRespDTO.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.Date;
  6 +
  7 +/**
  8 + * 消息接收 Response DTO
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class SmsReceiveRespDTO {
  14 +
  15 + /**
  16 + * 是否接收成功
  17 + */
  18 + private Boolean success;
  19 + /**
  20 + * API 接收结果的编码
  21 + */
  22 + private String errorCode;
  23 + /**
  24 + * API 接收结果的说明
  25 + */
  26 + private String errorMsg;
  27 +
  28 + /**
  29 + * 手机号
  30 + */
  31 + private String mobile;
  32 + /**
  33 + * 用户接收时间
  34 + */
  35 + private Date receiveTime;
  36 +
  37 + /**
  38 + * 短信 API 发送返回的序号
  39 + */
  40 + private String serialNo;
  41 + /**
  42 + * 短信日志编号
  43 + *
  44 + * 对应 SysSmsLogDO 的编号
  45 + */
  46 + private Long logId;
  47 +
  48 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/dto/SmsSendRespDTO.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 短信发送 Response DTO
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +@Data
  11 +public class SmsSendRespDTO {
  12 +
  13 + /**
  14 + * 短信 API 发送返回的序号
  15 + */
  16 + private String serialNo;
  17 +
  18 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/dto/SmsTemplateRespDTO.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.dto;
  2 +
  3 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 短信模板 Response DTO
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Data
  12 +public class SmsTemplateRespDTO {
  13 +
  14 + /**
  15 + * 模板编号
  16 + */
  17 + private String id;
  18 + /**
  19 + * 短信内容
  20 + */
  21 + private String content;
  22 + /**
  23 + * 审核状态
  24 + *
  25 + * 枚举 {@link SmsTemplateAuditStatusEnum}
  26 + */
  27 + private Integer auditStatus;
  28 + /**
  29 + * 审核未通过的理由
  30 + */
  31 + private String auditReason;
  32 +
  33 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/AbstractSmsClient.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl;
  2 +
  3 +import cn.iocoder.dashboard.common.core.KeyValue;
  4 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
  5 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCodeMapping;
  6 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  7 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  8 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  9 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  10 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +
  13 +import java.util.List;
  14 +
  15 +/**
  16 + * 短信客户端抽象类
  17 + *
  18 + * @author zzf
  19 + * @date 2021/2/1 9:28
  20 + */
  21 +@Slf4j
  22 +public abstract class AbstractSmsClient implements SmsClient {
  23 +
  24 + /**
  25 + * 短信渠道配置
  26 + */
  27 + protected volatile SmsChannelProperties properties;
  28 + /**
  29 + * 错误码枚举类
  30 + */
  31 + protected final SmsCodeMapping codeMapping;
  32 +
  33 + /**
  34 + * 短信客户端有参构造函数
  35 + *
  36 + * @param properties 短信配置
  37 + */
  38 + public AbstractSmsClient(SmsChannelProperties properties, SmsCodeMapping codeMapping) {
  39 + this.properties = properties;
  40 + this.codeMapping = codeMapping;
  41 + }
  42 +
  43 + /**
  44 + * 初始化
  45 + */
  46 + public final void init() {
  47 + doInit();
  48 + log.info("[init][配置({}) 初始化完成]", properties);
  49 + }
  50 +
  51 + public final void refresh(SmsChannelProperties properties) {
  52 + // 判断是否更新
  53 + if (properties.equals(this.properties)) {
  54 + return;
  55 + }
  56 + log.info("[refresh][配置({})发生变化,重新初始化]", properties);
  57 + this.properties = properties;
  58 + // 初始化
  59 + this.init();
  60 + }
  61 +
  62 + /**
  63 + * 自定义初始化
  64 + */
  65 + protected abstract void doInit();
  66 +
  67 + @Override
  68 + public Long getId() {
  69 + return properties.getId();
  70 + }
  71 +
  72 + @Override
  73 + public final SmsCommonResult<SmsSendRespDTO> sendSms(Long logId, String mobile,
  74 + String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
  75 + // 执行短信发送
  76 + SmsCommonResult<SmsSendRespDTO> result;
  77 + try {
  78 + result = doSendSms(logId, mobile, apiTemplateId, templateParams);
  79 + } catch (Throwable ex) {
  80 + // 打印异常日志
  81 + log.error("[sendSms][发送短信异常,sendLogId({}) mobile({}) apiTemplateId({}) templateParams({})]",
  82 + logId, mobile, apiTemplateId, templateParams, ex);
  83 + // 封装返回
  84 + return SmsCommonResult.error(ex);
  85 + }
  86 + return result;
  87 + }
  88 +
  89 + protected abstract SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
  90 + String apiTemplateId, List<KeyValue<String, Object>> templateParams)
  91 + throws Throwable;
  92 +
  93 + @Override
  94 + public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable {
  95 + try {
  96 + return doParseSmsReceiveStatus(text);
  97 + } catch (Throwable ex) {
  98 + log.error("[parseSmsReceiveStatus][text({}) 解析发生异常]", text, ex);
  99 + throw ex;
  100 + }
  101 + }
  102 +
  103 + protected abstract List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable;
  104 +
  105 + @Override
  106 + public SmsCommonResult<SmsTemplateRespDTO> getSmsTemplate(String apiTemplateId) {
  107 + // 执行短信发送
  108 + SmsCommonResult<SmsTemplateRespDTO> result;
  109 + try {
  110 + result = doGetSmsTemplate(apiTemplateId);
  111 + } catch (Throwable ex) {
  112 + // 打印异常日志
  113 + log.error("[getSmsTemplate][获得短信模板({}) 发生异常]", apiTemplateId, ex);
  114 + // 封装返回
  115 + return SmsCommonResult.error(ex);
  116 + }
  117 + return result;
  118 + }
  119 +
  120 + protected abstract SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) throws Throwable;
  121 +
  122 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/SmsClientFactoryImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl;
  2 +
  3 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
  4 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
  5 +import cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun.AliyunSmsClient;
  6 +import cn.iocoder.dashboard.framework.sms.core.client.impl.debug.DebugDingTalkSmsClient;
  7 +import cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian.YunpianSmsClient;
  8 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
  9 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.springframework.util.Assert;
  12 +import org.springframework.validation.annotation.Validated;
  13 +
  14 +import java.util.Arrays;
  15 +import java.util.concurrent.ConcurrentHashMap;
  16 +import java.util.concurrent.ConcurrentMap;
  17 +
  18 +/**
  19 + * 短信客户端工厂接口
  20 + *
  21 + * @author zzf
  22 + */
  23 +@Validated
  24 +@Slf4j
  25 +public class SmsClientFactoryImpl implements SmsClientFactory {
  26 +
  27 + /**
  28 + * 短信客户端 Map
  29 + * key:渠道编号,使用 {@link SmsChannelProperties#getId()}
  30 + */
  31 + private final ConcurrentMap<Long, AbstractSmsClient> channelIdClients = new ConcurrentHashMap<>();
  32 +
  33 + /**
  34 + * 短信客户端 Map
  35 + * key:渠道编码,使用 {@link SmsChannelProperties#getCode()} ()}
  36 + *
  37 + * 注意,一些场景下,需要获得某个渠道类型的客户端,所以需要使用它。
  38 + * 例如说,解析短信接收结果,是相对通用的,不需要使用某个渠道编号的 {@link #channelIdClients}
  39 + */
  40 + private final ConcurrentMap<String, AbstractSmsClient> channelCodeClients = new ConcurrentHashMap<>();
  41 +
  42 + public SmsClientFactoryImpl() {
  43 + // 初始化 channelCodeClients 集合
  44 + Arrays.stream(SmsChannelEnum.values()).forEach(channel -> {
  45 + // 创建一个空的 SmsChannelProperties 对象
  46 + SmsChannelProperties properties = new SmsChannelProperties().setCode(channel.getCode())
  47 + .setApiKey("default").setApiSecret("default");
  48 + // 创建 Sms 客户端
  49 + AbstractSmsClient smsClient = createSmsClient(properties);
  50 + channelCodeClients.put(channel.getCode(), smsClient);
  51 + });
  52 + }
  53 +
  54 + @Override
  55 + public SmsClient getSmsClient(Long channelId) {
  56 + return channelIdClients.get(channelId);
  57 + }
  58 +
  59 + @Override
  60 + public SmsClient getSmsClient(String channelCode) {
  61 + return channelCodeClients.get(channelCode);
  62 + }
  63 +
  64 + @Override
  65 + public void createOrUpdateSmsClient(SmsChannelProperties properties) {
  66 + AbstractSmsClient client = channelIdClients.get(properties.getId());
  67 + if (client == null) {
  68 + client = this.createSmsClient(properties);
  69 + client.init();
  70 + channelIdClients.put(client.getId(), client);
  71 + } else {
  72 + client.refresh(properties);
  73 + }
  74 + }
  75 +
  76 + private AbstractSmsClient createSmsClient(SmsChannelProperties properties) {
  77 + SmsChannelEnum channelEnum = SmsChannelEnum.getByCode(properties.getCode());
  78 + Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum));
  79 + // 创建客户端
  80 + switch (channelEnum) {
  81 + case ALIYUN: return new AliyunSmsClient(properties);
  82 + case YUN_PIAN: return new YunpianSmsClient(properties);
  83 + case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties);
  84 + }
  85 + // 创建失败,错误日志 + 抛出异常
  86 + log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
  87 + throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", properties));
  88 + }
  89 +
  90 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClient.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.ReflectUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.dashboard.common.core.KeyValue;
  7 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  8 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  9 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  10 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  11 +import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient;
  12 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
  13 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  14 +import cn.iocoder.dashboard.util.collection.MapUtils;
  15 +import cn.iocoder.dashboard.util.json.JsonUtils;
  16 +import com.aliyuncs.AcsRequest;
  17 +import com.aliyuncs.AcsResponse;
  18 +import com.aliyuncs.DefaultAcsClient;
  19 +import com.aliyuncs.IAcsClient;
  20 +import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
  21 +import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
  22 +import com.aliyuncs.exceptions.ClientException;
  23 +import com.aliyuncs.profile.DefaultProfile;
  24 +import com.aliyuncs.profile.IClientProfile;
  25 +import com.fasterxml.jackson.annotation.JsonFormat;
  26 +import com.fasterxml.jackson.annotation.JsonProperty;
  27 +import com.google.common.annotations.VisibleForTesting;
  28 +import lombok.Data;
  29 +import lombok.extern.slf4j.Slf4j;
  30 +
  31 +import java.util.Date;
  32 +import java.util.List;
  33 +import java.util.Objects;
  34 +import java.util.function.Function;
  35 +import java.util.stream.Collectors;
  36 +
  37 +import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  38 +import static cn.iocoder.dashboard.util.date.DateUtils.TIME_ZONE_DEFAULT;
  39 +
  40 +/**
  41 + * 阿里短信客户端的实现类
  42 + *
  43 + * @author zzf
  44 + * @date 2021/1/25 14:17
  45 + */
  46 +@Slf4j
  47 +public class AliyunSmsClient extends AbstractSmsClient {
  48 +
  49 + /**
  50 + * REGION, 使用杭州
  51 + */
  52 + private static final String ENDPOINT = "cn-hangzhou";
  53 +
  54 + /**
  55 + * 阿里云客户端
  56 + */
  57 + private volatile IAcsClient client;
  58 +
  59 + public AliyunSmsClient(SmsChannelProperties properties) {
  60 + super(properties, new AliyunSmsCodeMapping());
  61 + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
  62 + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
  63 + }
  64 +
  65 + @Override
  66 + protected void doInit() {
  67 + IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret());
  68 + client = new DefaultAcsClient(profile);
  69 + }
  70 +
  71 + @Override
  72 + protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
  73 + String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
  74 + // 构建参数
  75 + SendSmsRequest request = new SendSmsRequest();
  76 + request.setPhoneNumbers(mobile);
  77 + request.setSignName(properties.getSignature());
  78 + request.setTemplateCode(apiTemplateId);
  79 + request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
  80 + request.setOutId(String.valueOf(sendLogId));
  81 + // 执行请求
  82 + return invoke(request, response -> new SmsSendRespDTO().setSerialNo(response.getBizId()));
  83 + }
  84 +
  85 + @Override
  86 + protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
  87 + List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
  88 + return statuses.stream().map(status -> {
  89 + SmsReceiveRespDTO resp = new SmsReceiveRespDTO();
  90 + resp.setSuccess(status.getSuccess());
  91 + resp.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg());
  92 + resp.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime());
  93 + resp.setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId()));
  94 + return resp;
  95 + }).collect(Collectors.toList());
  96 + }
  97 +
  98 + @Override
  99 + protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) {
  100 + // 构建参数
  101 + QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
  102 + request.setTemplateCode(apiTemplateId);
  103 + // 执行请求
  104 + return invoke(request, response -> {
  105 + SmsTemplateRespDTO data = new SmsTemplateRespDTO();
  106 + data.setId(response.getTemplateCode()).setContent(response.getTemplateContent());
  107 + data.setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason());
  108 + return data;
  109 + });
  110 + }
  111 +
  112 + @VisibleForTesting
  113 + Integer convertSmsTemplateAuditStatus(Integer templateStatus) {
  114 + switch (templateStatus) {
  115 + case 0: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
  116 + case 1: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
  117 + case 2: return SmsTemplateAuditStatusEnum.FAIL.getStatus();
  118 + default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
  119 + }
  120 + }
  121 +
  122 + @VisibleForTesting
  123 + <T extends AcsResponse, R> SmsCommonResult<R> invoke(AcsRequest<T> request, Function<T, R> responseConsumer) {
  124 + try {
  125 + // 执行发送. 由于阿里云 sms 短信没有统一的 Response,但是有统一的 code、message、requestId 属性,所以只好反射
  126 + T sendResult = client.getAcsResponse(request);
  127 + String code = (String) ReflectUtil.getFieldValue(sendResult, "code");
  128 + String message = (String) ReflectUtil.getFieldValue(sendResult, "message");
  129 + String requestId = (String) ReflectUtil.getFieldValue(sendResult, "requestId");
  130 + // 解析结果
  131 + R data = null;
  132 + if (Objects.equals(code, "OK")) { // 请求成功的情况下
  133 + data = responseConsumer.apply(sendResult);
  134 + }
  135 + // 拼接结果
  136 + return SmsCommonResult.build(code, message, requestId, data, codeMapping);
  137 + } catch (ClientException ex) {
  138 + return SmsCommonResult.build(ex.getErrCode(), formatResultMsg(ex), ex.getRequestId(), null, codeMapping);
  139 + }
  140 + }
  141 +
  142 + private static String formatResultMsg(ClientException ex) {
  143 + if (StrUtil.isEmpty(ex.getErrorDescription())) {
  144 + return ex.getErrMsg();
  145 + }
  146 + return ex.getErrMsg() + " => " + ex.getErrorDescription();
  147 + }
  148 +
  149 + /**
  150 + * 短信接收状态
  151 + *
  152 + * 参见 https://help.aliyun.com/document_detail/101867.html 文档
  153 + *
  154 + * @author 芋道源码
  155 + */
  156 + @Data
  157 + public static class SmsReceiveStatus {
  158 +
  159 + /**
  160 + * 手机号
  161 + */
  162 + @JsonProperty("phone_number")
  163 + private String phoneNumber;
  164 + /**
  165 + * 发送时间
  166 + */
  167 + @JsonProperty("send_time")
  168 + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
  169 + private Date sendTime;
  170 + /**
  171 + * 状态报告时间
  172 + */
  173 + @JsonProperty("report_time")
  174 + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
  175 + private Date reportTime;
  176 + /**
  177 + * 是否接收成功
  178 + */
  179 + private Boolean success;
  180 + /**
  181 + * 状态报告说明
  182 + */
  183 + @JsonProperty("err_msg")
  184 + private String errMsg;
  185 + /**
  186 + * 状态报告编码
  187 + */
  188 + @JsonProperty("err_code")
  189 + private String errCode;
  190 + /**
  191 + * 发送序列号
  192 + */
  193 + @JsonProperty("biz_id")
  194 + private String bizId;
  195 + /**
  196 + * 用户序列号
  197 + *
  198 + * 这里我们传递的是 SysSmsLogDO 的日志编号
  199 + */
  200 + @JsonProperty("out_id")
  201 + private String outId;
  202 + /**
  203 + * 短信长度,例如说 1、2、3
  204 + *
  205 + * 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送
  206 + */
  207 + @JsonProperty("sms_size")
  208 + private Integer smsSize;
  209 +
  210 + }
  211 +
  212 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsCodeMapping.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  4 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  5 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCodeMapping;
  6 +
  7 +import static cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants.*;
  8 +
  9 +/**
  10 + * 阿里云的 SmsCodeMapping 实现类
  11 + *
  12 + * 参见 https://help.aliyun.com/document_detail/101346.htm 文档
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public class AliyunSmsCodeMapping implements SmsCodeMapping {
  17 +
  18 + @Override
  19 + public ErrorCode apply(String apiCode) {
  20 + switch (apiCode) {
  21 + case "OK": return GlobalErrorCodeConstants.SUCCESS;
  22 + case "isv.ACCOUNT_NOT_EXISTS":
  23 + case "isv.ACCOUNT_ABNORMAL":
  24 + case "MissingAccessKeyId": return SMS_ACCOUNT_INVALID;
  25 + case "isp.RAM_PERMISSION_DENY": return SMS_PERMISSION_DENY;
  26 + case "isv.INVALID_JSON_PARAM":
  27 + case "isv.INVALID_PARAMETERS": return SMS_API_PARAM_ERROR;
  28 + case "isv.BUSINESS_LIMIT_CONTROL": return SMS_SEND_BUSINESS_LIMIT_CONTROL;
  29 + case "isv.DAY_LIMIT_CONTROL": return SMS_SEND_DAY_LIMIT_CONTROL;
  30 + case "isv.SMS_CONTENT_ILLEGAL": return SMS_SEND_CONTENT_INVALID;
  31 + case "isv.SMS_TEMPLATE_ILLEGAL": return SMS_TEMPLATE_INVALID;
  32 + case "isv.SMS_SIGNATURE_ILLEGAL":
  33 + case "isv.SIGN_NAME_ILLEGAL":
  34 + case "isv.SMS_SIGN_ILLEGAL": return SMS_SIGN_INVALID;
  35 + case "isv.AMOUNT_NOT_ENOUGH":
  36 + case "isv.OUT_OF_SERVICE": return SMS_ACCOUNT_MONEY_NOT_ENOUGH;
  37 + case "isv.MOBILE_NUMBER_ILLEGAL": return SMS_MOBILE_INVALID;
  38 + case "isv.TEMPLATE_MISSING_PARAMETERS": return SMS_TEMPLATE_PARAM_ERROR;
  39 + }
  40 + return SMS_UNKNOWN;
  41 + }
  42 +
  43 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkCodeMapping.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.debug;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  4 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  5 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCodeMapping;
  6 +
  7 +import java.util.Objects;
  8 +
  9 +import static cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
  10 +
  11 +/**
  12 + * 钉钉的 SmsCodeMapping 实现类
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public class DebugDingTalkCodeMapping implements SmsCodeMapping {
  17 +
  18 + @Override
  19 + public ErrorCode apply(String apiCode) {
  20 + return Objects.equals(apiCode, "0") ? GlobalErrorCodeConstants.SUCCESS : SMS_UNKNOWN;
  21 + }
  22 +
  23 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClient.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.debug;
  2 +
  3 +import cn.hutool.core.codec.Base64;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.map.MapUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.hutool.crypto.digest.DigestUtil;
  8 +import cn.hutool.crypto.digest.HmacAlgorithm;
  9 +import cn.hutool.http.HttpUtil;
  10 +import cn.iocoder.dashboard.common.core.KeyValue;
  11 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  12 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  13 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  14 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  15 +import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient;
  16 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
  17 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  18 +import cn.iocoder.dashboard.util.collection.MapUtils;
  19 +import cn.iocoder.dashboard.util.json.JsonUtils;
  20 +
  21 +import java.util.HashMap;
  22 +import java.util.List;
  23 +import java.util.Map;
  24 +
  25 +/**
  26 + * 基于钉钉 WebHook 实现的调试的短信客户端实现类
  27 + *
  28 + * 考虑到省钱,我们使用钉钉 WebHook 模拟发送短信,方便调试。
  29 + *
  30 + * @author 芋道源码
  31 + */
  32 +public class DebugDingTalkSmsClient extends AbstractSmsClient {
  33 +
  34 + public DebugDingTalkSmsClient(SmsChannelProperties properties) {
  35 + super(properties, new DebugDingTalkCodeMapping());
  36 + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
  37 + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
  38 + }
  39 +
  40 + @Override
  41 + protected void doInit() {
  42 + }
  43 +
  44 + @Override
  45 + protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
  46 + String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
  47 + // 构建请求
  48 + String url = buildUrl("robot/send");
  49 + Map<String, Object> params = new HashMap<>();
  50 + params.put("msgtype", "text");
  51 + String content = String.format("【模拟短信】\n手机号:%s\n短信日志编号:%d\n模板参数:%s",
  52 + mobile, sendLogId, MapUtils.convertMap(templateParams));
  53 + params.put("text", MapUtil.builder().put("content", content).build());
  54 + // 执行请求
  55 + String responseText = HttpUtil.post(url, JsonUtils.toJsonString(params));
  56 + // 解析结果
  57 + Map<?, ?> responseObj = JsonUtils.parseObject(responseText, Map.class);
  58 + return SmsCommonResult.build(MapUtil.getStr(responseObj, "errcode"), MapUtil.getStr(responseObj, "errorMsg"),
  59 + null, new SmsSendRespDTO().setSerialNo(StrUtil.uuid()), codeMapping);
  60 + }
  61 +
  62 + /**
  63 + * 构建请求地址
  64 + *
  65 + * 参见 https://developers.dingtalk.com/document/app/custom-robot-access/title-nfv-794-g71 文档
  66 + *
  67 + * @param path 请求路径
  68 + * @return 请求地址
  69 + */
  70 + @SuppressWarnings("SameParameterValue")
  71 + private String buildUrl(String path) {
  72 + // 生成 timestamp
  73 + long timestamp = System.currentTimeMillis();
  74 + // 生成 sign
  75 + String secret = properties.getApiSecret();
  76 + String stringToSign = timestamp + "\n" + secret;
  77 + byte[] signData = DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.bytes(secret)).digest(stringToSign);
  78 + String sign = Base64.encode(signData);
  79 + // 构建最终 URL
  80 + return String.format("https://oapi.dingtalk.com/%s?access_token=%s&timestamp=%d&sign=%s",
  81 + path, properties.getApiKey(), timestamp, sign);
  82 + }
  83 +
  84 + @Override
  85 + protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
  86 + throw new UnsupportedOperationException("模拟短信客户端,暂时无需解析回调");
  87 + }
  88 +
  89 + @Override
  90 + protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) {
  91 + SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
  92 + .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
  93 + return SmsCommonResult.build("0", "success", null, data, codeMapping);
  94 + }
  95 +
  96 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClient.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.core.util.URLUtil;
  7 +import cn.iocoder.dashboard.common.core.KeyValue;
  8 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  9 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  10 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  11 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  12 +import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient;
  13 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
  14 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  15 +import cn.iocoder.dashboard.util.json.JsonUtils;
  16 +import com.fasterxml.jackson.annotation.JsonFormat;
  17 +import com.fasterxml.jackson.annotation.JsonProperty;
  18 +import com.google.common.annotations.VisibleForTesting;
  19 +import com.yunpian.sdk.YunpianClient;
  20 +import com.yunpian.sdk.constant.YunpianConstant;
  21 +import com.yunpian.sdk.model.Result;
  22 +import com.yunpian.sdk.model.Template;
  23 +import lombok.Data;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +
  26 +import java.util.*;
  27 +import java.util.function.Function;
  28 +import java.util.function.Supplier;
  29 +import java.util.stream.Collectors;
  30 +
  31 +import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  32 +import static cn.iocoder.dashboard.util.date.DateUtils.TIME_ZONE_DEFAULT;
  33 +
  34 +/**
  35 + * 云片短信客户端的实现类
  36 + *
  37 + * @author zzf
  38 + * @date 9:48 2021/3/5
  39 + */
  40 +@Slf4j
  41 +public class YunpianSmsClient extends AbstractSmsClient {
  42 +
  43 + /**
  44 + * 云信短信客户端
  45 + */
  46 + private volatile YunpianClient client;
  47 +
  48 + public YunpianSmsClient(SmsChannelProperties properties) {
  49 + super(properties, new YunpianSmsCodeMapping());
  50 + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
  51 + }
  52 +
  53 + @Override
  54 + public void doInit() {
  55 + YunpianClient oldClient = client;
  56 + // 初始化新的客户端
  57 + YunpianClient newClient = new YunpianClient(properties.getApiKey());
  58 + newClient.init();
  59 + this.client = newClient;
  60 + // 销毁老的客户端
  61 + if (oldClient != null) {
  62 + oldClient.close();
  63 + }
  64 + }
  65 +
  66 + @Override
  67 + protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
  68 + String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
  69 + return invoke(() -> {
  70 + Map<String, String> request = new HashMap<>();
  71 + request.put(YunpianConstant.MOBILE, mobile);
  72 + request.put(YunpianConstant.TPL_ID, apiTemplateId);
  73 + request.put(YunpianConstant.TPL_VALUE, formatTplValue(templateParams));
  74 + request.put(YunpianConstant.UID, String.valueOf(sendLogId));
  75 + request.put(YunpianConstant.CALLBACK_URL, properties.getCallbackUrl());
  76 + return client.sms().tpl_single_send(request);
  77 + }, response -> new SmsSendRespDTO().setSerialNo(String.valueOf(response.getSid())));
  78 + }
  79 +
  80 + private static String formatTplValue(List<KeyValue<String, Object>> templateParams) {
  81 + if (CollUtil.isEmpty(templateParams)) {
  82 + return "";
  83 + }
  84 + // 参考 https://www.yunpian.com/official/document/sms/zh_cn/introduction_demos_encode_sample 格式化
  85 + StringJoiner joiner = new StringJoiner("&");
  86 + templateParams.forEach(param -> joiner.add(String.format("#%s#=%s", param.getKey(), URLUtil.encode(String.valueOf(param.getValue())))));
  87 + return joiner.toString();
  88 + }
  89 +
  90 + @Override
  91 + protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
  92 + List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
  93 + return statuses.stream().map(status -> {
  94 + SmsReceiveRespDTO resp = new SmsReceiveRespDTO();
  95 + resp.setSuccess(Objects.equals(status.getReportStatus(), "SUCCESS"));
  96 + resp.setErrorCode(status.getErrorMsg()).setErrorMsg(status.getErrorDetail());
  97 + resp.setMobile(status.getMobile()).setReceiveTime(status.getUserReceiveTime());
  98 + resp.setSerialNo(String.valueOf(status.getSid())).setLogId(status.getUid());
  99 + return resp;
  100 + }).collect(Collectors.toList());
  101 + }
  102 +
  103 + @Override
  104 + protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) throws Throwable {
  105 + return invoke(() -> {
  106 + Map<String, String> request = new HashMap<>();
  107 + request.put(YunpianConstant.APIKEY, properties.getApiKey());
  108 + request.put(YunpianConstant.TPL_ID, apiTemplateId);
  109 + return client.tpl().get(request);
  110 + }, response -> {
  111 + Template template = response.get(0);
  112 + return new SmsTemplateRespDTO().setId(String.valueOf(template.getTpl_id())).setContent(template.getTpl_content())
  113 + .setAuditStatus(convertSmsTemplateAuditStatus(template.getCheck_status())).setAuditReason(template.getReason());
  114 + });
  115 + }
  116 +
  117 + @VisibleForTesting
  118 + Integer convertSmsTemplateAuditStatus(String checkStatus) {
  119 + switch (checkStatus) {
  120 + case "CHECKING": return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
  121 + case "SUCCESS": return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
  122 + case "FAIL": return SmsTemplateAuditStatusEnum.FAIL.getStatus();
  123 + default: throw new IllegalArgumentException(String.format("未知审核状态(%s)", checkStatus));
  124 + }
  125 + }
  126 +
  127 + @VisibleForTesting
  128 + <T, R> SmsCommonResult<R> invoke(Supplier<Result<T>> requestConsumer, Function<T, R> responseConsumer) throws Throwable {
  129 + // 执行请求
  130 + Result<T> result = requestConsumer.get();
  131 + if (result.getThrowable() != null) {
  132 + throw result.getThrowable();
  133 + }
  134 + // 解析结果
  135 + R data = null;
  136 + if (result.getData() != null) {
  137 + data = responseConsumer.apply(result.getData());
  138 + }
  139 + // 拼接结果
  140 + return SmsCommonResult.build(String.valueOf(result.getCode()), formatResultMsg(result), null, data, codeMapping);
  141 + }
  142 +
  143 + private static String formatResultMsg(Result<?> sendResult) {
  144 + if (StrUtil.isEmpty(sendResult.getDetail())) {
  145 + return sendResult.getMsg();
  146 + }
  147 + return sendResult.getMsg() + " => " + sendResult.getDetail();
  148 + }
  149 +
  150 + /**
  151 + * 短信接收状态
  152 + *
  153 + * 参见 https://www.yunpian.com/official/document/sms/zh_cn/domestic_push_report 文档
  154 + *
  155 + * @author 芋道源码
  156 + */
  157 + @Data
  158 + public static class SmsReceiveStatus {
  159 +
  160 + /**
  161 + * 接收状态
  162 + *
  163 + * 目前仅有 SUCCESS / FAIL,所以使用 Boolean 接收
  164 + */
  165 + @JsonProperty("report_status")
  166 + private String reportStatus;
  167 + /**
  168 + * 接收手机号
  169 + */
  170 + private String mobile;
  171 + /**
  172 + * 运营商返回的代码,如:"DB:0103"
  173 + *
  174 + * 由于不同运营商信息不同,此字段仅供参考;
  175 + */
  176 + @JsonProperty("error_msg")
  177 + private String errorMsg;
  178 + /**
  179 + * 运营商反馈代码的中文解释
  180 + *
  181 + * 默认不推送此字段,如需推送,请联系客服
  182 + */
  183 + @JsonProperty("error_detail")
  184 + private String errorDetail;
  185 + /**
  186 + * 短信编号
  187 + */
  188 + private Long sid;
  189 + /**
  190 + * 用户自定义 id
  191 + *
  192 + * 这里我们传递的是 SysSmsLogDO 的日志编号
  193 + */
  194 + private Long uid;
  195 + /**
  196 + * 用户接收时间
  197 + */
  198 + @JsonProperty("user_receive_time")
  199 + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
  200 + private Date userReceiveTime;
  201 +
  202 + }
  203 +
  204 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsCodeMapping.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  4 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCodeMapping;
  5 +
  6 +import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.SUCCESS;
  7 +import static cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants.*;
  8 +import static com.yunpian.sdk.constant.Code.*;
  9 +
  10 +/**
  11 + * 云片的 SmsCodeMapping 实现类
  12 + *
  13 + * 参见 https://www.yunpian.com/official/document/sms/zh_CN/returnvalue_common 文档
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +public class YunpianSmsCodeMapping implements SmsCodeMapping {
  18 +
  19 + @Override
  20 + public ErrorCode apply(String apiCode) {
  21 + int code = Integer.parseInt(apiCode);
  22 + switch (code) {
  23 + case OK: return SUCCESS;
  24 + case ARGUMENT_MISSING: return SMS_API_PARAM_ERROR;
  25 + case BAD_ARGUMENT_FORMAT: return SMS_TEMPLATE_PARAM_ERROR;
  26 + case TPL_NOT_FOUND:
  27 + case TPL_NOT_VALID: return SMS_TEMPLATE_INVALID;
  28 + case MONEY_NOT_ENOUGH: return SMS_ACCOUNT_MONEY_NOT_ENOUGH;
  29 + case BLACK_WORD: return SMS_SEND_CONTENT_INVALID;
  30 + case DUP_IN_SHORT_TIME:
  31 + case TOO_MANY_TIME_IN_5:
  32 + case DAY_LIMIT_PER_MOBILE:
  33 + case HOUR_LIMIT_PER_MOBILE: return SMS_SEND_BUSINESS_LIMIT_CONTROL;
  34 + case BLACK_PHONE_FILTER: return SMS_MOBILE_BLACK;
  35 + case SIGN_NOT_MATCH:
  36 + case BAD_SIGN_FORMAT:
  37 + case SIGN_NOT_VALID: return SMS_SIGN_INVALID;
  38 + case BAD_API_KEY: return SMS_ACCOUNT_INVALID;
  39 + case API_NOT_ALLOWED: return SMS_PERMISSION_DENY;
  40 + case IP_NOT_ALLOWED: return SMS_IP_DENY;
  41 + }
  42 + return SMS_UNKNOWN;
  43 + }
  44 +
  45 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsChannelEnum.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.enums;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +/**
  8 + * 短信渠道枚举
  9 + *
  10 + * @author zzf
  11 + * @date 2021/1/25 10:56
  12 + */
  13 +@Getter
  14 +@AllArgsConstructor
  15 +public enum SmsChannelEnum {
  16 +
  17 + DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
  18 + YUN_PIAN("YUN_PIAN", "云片"),
  19 + ALIYUN("ALIYUN", "阿里云"),
  20 +// TENCENT("TENCENT", "腾讯云"),
  21 +// HUA_WEI("HUA_WEI", "华为云"),
  22 + ;
  23 +
  24 + /**
  25 + * 编码
  26 + */
  27 + private final String code;
  28 + /**
  29 + * 名字
  30 + */
  31 + private final String name;
  32 +
  33 + public static SmsChannelEnum getByCode(String code) {
  34 + return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values());
  35 + }
  36 +
  37 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsFrameworkErrorCodeConstants.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.enums;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  4 +
  5 +/**
  6 + * 短信框架的错误码枚举
  7 + *
  8 + * 短信框架,使用 2-001-000-000 段
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface SmsFrameworkErrorCodeConstants {
  13 +
  14 + ErrorCode SMS_UNKNOWN = new ErrorCode(2001000000, "未知错误,需要解析");
  15 +
  16 + // ========== 权限 / 限流等相关 2001000100 ==========
  17 +
  18 + ErrorCode SMS_PERMISSION_DENY = new ErrorCode(2001000100, "没有发送短信的权限");
  19 + // 云片:可以配置 IP 白名单,只有在白名单中才可以发送短信
  20 + ErrorCode SMS_IP_DENY = new ErrorCode(2001000100, "IP 不允许发送短信");
  21 +
  22 + // 阿里云:将短信发送频率限制在正常的业务限流范围内。默认短信验证码:使用同一签名,对同一个手机号验证码,支持 1 条 / 分钟,5 条 / 小时,累计 10 条 / 天。
  23 + ErrorCode SMS_SEND_BUSINESS_LIMIT_CONTROL = new ErrorCode(2001000102, "指定手机的发送限流");
  24 + // 阿里云:已经达到您在控制台设置的短信日发送量限额值。在国内消息设置 > 安全设置,修改发送总量阈值。
  25 + ErrorCode SMS_SEND_DAY_LIMIT_CONTROL = new ErrorCode(2001000103, "每天的发送限流");
  26 +
  27 + ErrorCode SMS_SEND_CONTENT_INVALID = new ErrorCode(2001000104, "短信内容有敏感词");
  28 +
  29 + // ========== 模板相关 2001000200 ==========
  30 + ErrorCode SMS_TEMPLATE_INVALID = new ErrorCode(2001000200, "短信模板不合法"); // 包括短信模板不存在
  31 + ErrorCode SMS_TEMPLATE_PARAM_ERROR = new ErrorCode(2001000201, "模板参数不正确");
  32 +
  33 + // ========== 签名相关 2001000300 ==========
  34 + ErrorCode SMS_SIGN_INVALID = new ErrorCode(2001000300, "短信签名不可用");
  35 +
  36 + // ========== 账户相关 2001000400 ==========
  37 + ErrorCode SMS_ACCOUNT_MONEY_NOT_ENOUGH = new ErrorCode(2001000400, "账户余额不足");
  38 + ErrorCode SMS_ACCOUNT_INVALID = new ErrorCode(2001000401, "apiKey 不存在");
  39 +
  40 + // ========== 其它相关 2001000900 开头 ==========
  41 + ErrorCode SMS_API_PARAM_ERROR = new ErrorCode(2001000900, "请求参数缺失");
  42 + ErrorCode SMS_MOBILE_INVALID = new ErrorCode(2001000901, "手机格式不正确");
  43 + ErrorCode SMS_MOBILE_BLACK = new ErrorCode(2001000902, "手机号在黑名单中");
  44 +
  45 + ErrorCode EXCEPTION = new ErrorCode(2001000999, "调用异常");
  46 +
  47 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsTemplateAuditStatusEnum.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.enums;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 短信模板的审核状态枚举
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@AllArgsConstructor
  12 +@Getter
  13 +public enum SmsTemplateAuditStatusEnum {
  14 +
  15 + CHECKING(1),
  16 + SUCCESS(2),
  17 + FAIL(3);
  18 +
  19 + private final Integer status;
  20 +
  21 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/property/SmsChannelProperties.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.property;
  2 +
  3 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
  4 +import lombok.Data;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +import javax.validation.constraints.NotEmpty;
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +/**
  11 + * 短信渠道配置类
  12 + *
  13 + * @author zzf
  14 + * @date 2021/1/25 17:01
  15 + */
  16 +@Data
  17 +@Validated
  18 +public class SmsChannelProperties {
  19 +
  20 + /**
  21 + * 渠道编号
  22 + */
  23 + @NotNull(message = "短信渠道 ID 不能为空")
  24 + private Long id;
  25 + /**
  26 + * 短信签名
  27 + */
  28 + @NotEmpty(message = "短信签名不能为空")
  29 + private String signature;
  30 + /**
  31 + * 渠道编码
  32 + *
  33 + * 枚举 {@link SmsChannelEnum}
  34 + */
  35 + @NotEmpty(message = "渠道编码不能为空")
  36 + private String code;
  37 + /**
  38 + * 短信 API 的账号
  39 + */
  40 + @NotEmpty(message = "短信 API 的账号不能为空")
  41 + private String apiKey;
  42 + /**
  43 + * 短信 API 的秘钥
  44 + */
  45 + @NotEmpty(message = "短信 API 的秘钥不能为空")
  46 + private String apiSecret;
  47 + /**
  48 + * 短信发送回调 URL
  49 + */
  50 + private String callbackUrl;
  51 +
  52 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/web/config/WebConfiguration.java
... ... @@ -2,7 +2,9 @@ package cn.iocoder.dashboard.framework.web.config;
2 2  
3 3 import cn.iocoder.dashboard.framework.web.core.enums.FilterOrderEnum;
4 4 import cn.iocoder.dashboard.framework.web.core.filter.CacheRequestBodyFilter;
  5 +import cn.iocoder.dashboard.framework.web.core.filter.DemoFilter;
5 6 import cn.iocoder.dashboard.framework.web.core.filter.XssFilter;
  7 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
6 8 import org.springframework.boot.context.properties.EnableConfigurationProperties;
7 9 import org.springframework.boot.web.servlet.FilterRegistrationBean;
8 10 import org.springframework.context.annotation.Bean;
... ... @@ -68,6 +70,15 @@ public class WebConfiguration implements WebMvcConfigurer {
68 70 return createFilterBean(new XssFilter(properties, pathMatcher), FilterOrderEnum.XSS_FILTER);
69 71 }
70 72  
  73 + /**
  74 + * 创建 DemoFilter Bean,演示模式
  75 + */
  76 + @Bean
  77 + @ConditionalOnProperty(value = "yudao.demo", havingValue = "true")
  78 + public FilterRegistrationBean<DemoFilter> demoFilter() {
  79 + return createFilterBean(new DemoFilter(), FilterOrderEnum.DEMO_FILTER);
  80 + }
  81 +
71 82 private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
72 83 FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
73 84 bean.setOrder(order);
... ...
src/main/java/cn/iocoder/dashboard/framework/web/core/enums/FilterOrderEnum.java
... ... @@ -9,6 +9,7 @@ public interface FilterOrderEnum {
9 9  
10 10 int CORS_FILTER = Integer.MIN_VALUE;
11 11  
  12 +
12 13 int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
13 14  
14 15 // OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
... ... @@ -19,4 +20,6 @@ public interface FilterOrderEnum {
19 20  
20 21 // Spring Security Filter 默认为 -100,可见 SecurityProperties 配置属性类
21 22  
  23 + int DEMO_FILTER = Integer.MAX_VALUE;
  24 +
22 25 }
... ...
src/main/java/cn/iocoder/dashboard/framework/web/core/filter/DemoFilter.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.web.core.filter;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  5 +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
  6 +import cn.iocoder.dashboard.util.servlet.ServletUtils;
  7 +import org.springframework.web.filter.OncePerRequestFilter;
  8 +
  9 +import javax.servlet.FilterChain;
  10 +import javax.servlet.http.HttpServletRequest;
  11 +import javax.servlet.http.HttpServletResponse;
  12 +
  13 +import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.DEMO_DENY;
  14 +
  15 +/**
  16 + * 演示 Filter,禁止用户发起写操作,避免影响测试数据
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +public class DemoFilter extends OncePerRequestFilter {
  21 +
  22 + @Override
  23 + protected boolean shouldNotFilter(HttpServletRequest request) {
  24 + String method = request.getMethod();
  25 + return !StrUtil.equalsAnyIgnoreCase(method, "POST", "PUT", "DELETE") // 写操作时,不进行过滤率
  26 + || SecurityFrameworkUtils.getLoginUser() == null; // 非登陆用户时,不进行过滤
  27 + }
  28 +
  29 + @Override
  30 + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
  31 + // 直接返回 DEMO_DENY 的结果。即,请求不继续
  32 + ServletUtils.writeJSON(response, CommonResult.error(DEMO_DENY));
  33 + }
  34 +
  35 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.dashboard.framework.web.core.handler;
3 3 import cn.hutool.core.exceptions.ExceptionUtil;
4 4 import cn.hutool.core.map.MapUtil;
5 5 import cn.hutool.extra.servlet.ServletUtil;
6   -import cn.iocoder.dashboard.common.exception.GlobalException;
7 6 import cn.iocoder.dashboard.common.exception.ServiceException;
8 7 import cn.iocoder.dashboard.common.pojo.CommonResult;
9 8 import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiErrorLogFrameworkService;
... ... @@ -96,9 +95,6 @@ public class GlobalExceptionHandler {
96 95 if (ex instanceof AccessDeniedException) {
97 96 return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
98 97 }
99   - if (ex instanceof GlobalException) {
100   - return globalExceptionHandler(request, (GlobalException) ex);
101   - }
102 98 return defaultExceptionHandler(request, ex);
103 99 }
104 100  
... ... @@ -223,25 +219,6 @@ public class GlobalExceptionHandler {
223 219 }
224 220  
225 221 /**
226   - * 处理全局异常 ServiceException
227   - *
228   - * 例如说,Dubbo 请求超时,调用的 Dubbo 服务系统异常
229   - */
230   - @ExceptionHandler(value = GlobalException.class)
231   - public CommonResult<?> globalExceptionHandler(HttpServletRequest req, GlobalException ex) {
232   - // 系统异常时,才打印异常日志
233   - if (INTERNAL_SERVER_ERROR.getCode().equals(ex.getCode())) {
234   - // 插入异常日志
235   - this.createExceptionLog(req, ex);
236   - // 普通全局异常,打印 info 日志即可
237   - } else {
238   - log.info("[globalExceptionHandler]", ex);
239   - }
240   - // 返回 ERROR CommonResult
241   - return CommonResult.error(ex);
242   - }
243   -
244   - /**
245 222 * 处理系统异常,兜底处理所有的一切
246 223 */
247 224 @ExceptionHandler(value = Exception.class)
... ... @@ -250,7 +227,7 @@ public class GlobalExceptionHandler {
250 227 // 插入异常日志
251 228 this.createExceptionLog(req, ex);
252 229 // 返回 ERROR CommonResult
253   - return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMessage());
  230 + return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
254 231 }
255 232  
256 233 private void createExceptionLog(HttpServletRequest req, Throwable e) {
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/SysDictDataController.java
... ... @@ -61,7 +61,7 @@ public class SysDictDataController {
61 61 @GetMapping("/list-all-simple")
62 62 @ApiOperation(value = "获得全部字典数据列表", notes = "一般用于管理后台缓存字典数据在本地")
63 63 // 无需添加权限认证,因为前端全局都需要
64   - public CommonResult<List<SysDictDataSimpleVO>> getSimpleDictDatas() {
  64 + public CommonResult<List<SysDictDataSimpleRespVO>> getSimpleDictDatas() {
65 65 List<SysDictDataDO> list = dictDataService.getDictDatas();
66 66 return success(SysDictDataConvert.INSTANCE.convertList(list));
67 67 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/SysDictTypeController.java
... ... @@ -41,7 +41,7 @@ public class SysDictTypeController {
41 41 return success(dictTypeId);
42 42 }
43 43  
44   - @PostMapping("update")
  44 + @PutMapping("/update")
45 45 @ApiOperation("修改字典类型")
46 46 @PreAuthorize("@ss.hasPermission('system:dict:update')")
47 47 public CommonResult<Boolean> updateDictType(@Valid @RequestBody SysDictTypeUpdateReqVO reqVO) {
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/vo/data/SysDictDataSimpleVO.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/vo/data/SysDictDataSimpleRespVO.java
... ... @@ -4,9 +4,9 @@ import io.swagger.annotations.ApiModel;
4 4 import io.swagger.annotations.ApiModelProperty;
5 5 import lombok.Data;
6 6  
7   -@ApiModel("数据字典精简 VO")
  7 +@ApiModel("数据字典精简 Response VO")
8 8 @Data
9   -public class SysDictDataSimpleVO {
  9 +public class SysDictDataSimpleRespVO {
10 10  
11 11 @ApiModelProperty(value = "字典类型", required = true, example = "gender")
12 12 private String dictType;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsCallbackController.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms;
  2 +
  3 +import cn.hutool.core.util.URLUtil;
  4 +import cn.hutool.extra.servlet.ServletUtil;
  5 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  6 +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
  7 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
  8 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
  9 +import io.swagger.annotations.Api;
  10 +import io.swagger.annotations.ApiImplicitParam;
  11 +import io.swagger.annotations.ApiOperation;
  12 +import org.springframework.web.bind.annotation.PostMapping;
  13 +import org.springframework.web.bind.annotation.RequestMapping;
  14 +import org.springframework.web.bind.annotation.RequestParam;
  15 +import org.springframework.web.bind.annotation.RestController;
  16 +
  17 +import javax.annotation.Resource;
  18 +import javax.servlet.http.HttpServletRequest;
  19 +
  20 +import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
  21 +
  22 +@Api(tags = "短信回调")
  23 +@RestController
  24 +@RequestMapping("/system/sms/callback")
  25 +public class SmsCallbackController {
  26 +
  27 + @Resource
  28 + private SysSmsService smsService;
  29 +
  30 + @PostMapping("/sms/yunpian")
  31 + @ApiOperation(value = "云片短信的回调", notes = "参见 https://www.yunpian.com/official/document/sms/zh_cn/domestic_push_report 文档")
  32 + @ApiImplicitParam(name = "sms_status", value = "发送状态", required = true, example = "[{具体内容}]", dataTypeClass = Long.class)
  33 + @OperateLog(enable = false)
  34 + public String receiveYunpianSmsStatus(@RequestParam("sms_status") String smsStatus) throws Throwable {
  35 + String text = URLUtil.decode(smsStatus); // decode 解码参数,因为它被 encode
  36 + smsService.receiveSmsStatus(SmsChannelEnum.YUN_PIAN.getCode(), text);
  37 + return "SUCCESS"; // 约定返回 SUCCESS 为成功
  38 + }
  39 +
  40 + @PostMapping("/sms/aliyun")
  41 + @ApiOperation(value = "阿里云短信的回调", notes = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
  42 + @OperateLog(enable = false)
  43 + public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {
  44 + String text = ServletUtil.getBody(request);
  45 + smsService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text);
  46 + return success(true);
  47 + }
  48 +
  49 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SysSmsChannelController.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  4 +import cn.iocoder.dashboard.common.pojo.PageResult;
  5 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.*;
  6 +import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsChannelConvert;
  7 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
  8 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
  9 +import io.swagger.annotations.Api;
  10 +import io.swagger.annotations.ApiImplicitParam;
  11 +import io.swagger.annotations.ApiOperation;
  12 +import org.springframework.security.access.prepost.PreAuthorize;
  13 +import org.springframework.web.bind.annotation.*;
  14 +
  15 +import javax.annotation.Resource;
  16 +import javax.validation.Valid;
  17 +import java.util.Comparator;
  18 +import java.util.List;
  19 +
  20 +import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
  21 +
  22 +@Api(tags = "短信渠道")
  23 +@RestController
  24 +@RequestMapping("system/sms-channel")
  25 +public class SysSmsChannelController {
  26 +
  27 + @Resource
  28 + private SysSmsChannelService smsChannelService;
  29 +
  30 + @PostMapping("/create")
  31 + @ApiOperation("创建短信渠道")
  32 + @PreAuthorize("@ss.hasPermission('system:sms-channel:create')")
  33 + public CommonResult<Long> createSmsChannel(@Valid @RequestBody SysSmsChannelCreateReqVO createReqVO) {
  34 + return success(smsChannelService.createSmsChannel(createReqVO));
  35 + }
  36 +
  37 + @PutMapping("/update")
  38 + @ApiOperation("更新短信渠道")
  39 + @PreAuthorize("@ss.hasPermission('system:sms-channel:update')")
  40 + public CommonResult<Boolean> updateSmsChannel(@Valid @RequestBody SysSmsChannelUpdateReqVO updateReqVO) {
  41 + smsChannelService.updateSmsChannel(updateReqVO);
  42 + return success(true);
  43 + }
  44 +
  45 + @DeleteMapping("/delete")
  46 + @ApiOperation("删除短信渠道")
  47 + @ApiImplicitParam(name = "id", value = "编号", required = true)
  48 + @PreAuthorize("@ss.hasPermission('system:sms-channel:delete')")
  49 + public CommonResult<Boolean> deleteSmsChannel(@RequestParam("id") Long id) {
  50 + smsChannelService.deleteSmsChannel(id);
  51 + return success(true);
  52 + }
  53 +
  54 + @GetMapping("/get")
  55 + @ApiOperation("获得短信渠道")
  56 + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
  57 + @PreAuthorize("@ss.hasPermission('system:sms-channel:query')")
  58 + public CommonResult<SysSmsChannelRespVO> getSmsChannel(@RequestParam("id") Long id) {
  59 + SysSmsChannelDO smsChannel = smsChannelService.getSmsChannel(id);
  60 + return success(SysSmsChannelConvert.INSTANCE.convert(smsChannel));
  61 + }
  62 +
  63 + @GetMapping("/page")
  64 + @ApiOperation("获得短信渠道分页")
  65 + @PreAuthorize("@ss.hasPermission('system:sms-channel:query')")
  66 + public CommonResult<PageResult<SysSmsChannelRespVO>> getSmsChannelPage(@Valid SysSmsChannelPageReqVO pageVO) {
  67 + PageResult<SysSmsChannelDO> pageResult = smsChannelService.getSmsChannelPage(pageVO);
  68 + return success(SysSmsChannelConvert.INSTANCE.convertPage(pageResult));
  69 + }
  70 +
  71 + @GetMapping("/list-all-simple")
  72 + @ApiOperation(value = "获得短信渠道精简列表", notes = "包含被禁用的短信渠道")
  73 + public CommonResult<List<SysSmsChannelSimpleRespVO>> getSimpleSmsChannels() {
  74 + List<SysSmsChannelDO> list = smsChannelService.getSmsChannelList();
  75 + // 排序后,返回给前端
  76 + list.sort(Comparator.comparing(SysSmsChannelDO::getId));
  77 + return success(SysSmsChannelConvert.INSTANCE.convertList03(list));
  78 + }
  79 +
  80 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SysSmsLogController.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  4 +import cn.iocoder.dashboard.common.pojo.PageResult;
  5 +import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
  6 +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
  7 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogExcelVO;
  8 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogExportReqVO;
  9 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogPageReqVO;
  10 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogRespVO;
  11 +import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsLogConvert;
  12 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsLogDO;
  13 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsLogService;
  14 +import io.swagger.annotations.Api;
  15 +import io.swagger.annotations.ApiOperation;
  16 +import org.springframework.security.access.prepost.PreAuthorize;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.GetMapping;
  19 +import org.springframework.web.bind.annotation.RequestMapping;
  20 +import org.springframework.web.bind.annotation.RestController;
  21 +
  22 +import javax.annotation.Resource;
  23 +import javax.servlet.http.HttpServletResponse;
  24 +import javax.validation.Valid;
  25 +import java.io.IOException;
  26 +import java.util.List;
  27 +
  28 +import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
  29 +import static cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateTypeEnum.EXPORT;
  30 +
  31 +@Api(tags = "短信日志")
  32 +@RestController
  33 +@RequestMapping("/system/sms-log")
  34 +@Validated
  35 +public class SysSmsLogController {
  36 +
  37 + @Resource
  38 + private SysSmsLogService smsLogService;
  39 +
  40 + @GetMapping("/page")
  41 + @ApiOperation("获得短信日志分页")
  42 + @PreAuthorize("@ss.hasPermission('system:sms-log:query')")
  43 + public CommonResult<PageResult<SysSmsLogRespVO>> getSmsLogPage(@Valid SysSmsLogPageReqVO pageVO) {
  44 + PageResult<SysSmsLogDO> pageResult = smsLogService.getSmsLogPage(pageVO);
  45 + return success(SysSmsLogConvert.INSTANCE.convertPage(pageResult));
  46 + }
  47 +
  48 + @GetMapping("/export-excel")
  49 + @ApiOperation("导出短信日志 Excel")
  50 + @PreAuthorize("@ss.hasPermission('system:sms-log:export')")
  51 + @OperateLog(type = EXPORT)
  52 + public void exportSmsLogExcel(@Valid SysSmsLogExportReqVO exportReqVO,
  53 + HttpServletResponse response) throws IOException {
  54 + List<SysSmsLogDO> list = smsLogService.getSmsLogList(exportReqVO);
  55 + // 导出 Excel
  56 + List<SysSmsLogExcelVO> datas = SysSmsLogConvert.INSTANCE.convertList02(list);
  57 + ExcelUtils.write(response, "短信日志.xls", "数据", SysSmsLogExcelVO.class, datas);
  58 + }
  59 +
  60 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SysSmsTemplateController.http 0 → 100644
  1 +### 请求 /menu/list 接口 => 成功
  2 +POST {{baseUrl}}/system/sms-template/send-sms
  3 +Authorization: Bearer {{token}}
  4 +Content-Type: application/json
  5 +
  6 +{
  7 + "code": "test_01",
  8 + "params": {
  9 + "key01": "value01",
  10 + "key02": "value02"
  11 + }
  12 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SysSmsTemplateController.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  4 +import cn.iocoder.dashboard.common.pojo.PageResult;
  5 +import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
  6 +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
  7 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.*;
  8 +import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsTemplateConvert;
  9 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  10 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
  11 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
  12 +import io.swagger.annotations.Api;
  13 +import io.swagger.annotations.ApiImplicitParam;
  14 +import io.swagger.annotations.ApiOperation;
  15 +import org.springframework.security.access.prepost.PreAuthorize;
  16 +import org.springframework.web.bind.annotation.*;
  17 +
  18 +import javax.annotation.Resource;
  19 +import javax.servlet.http.HttpServletResponse;
  20 +import javax.validation.Valid;
  21 +import java.io.IOException;
  22 +import java.util.List;
  23 +
  24 +import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
  25 +import static cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateTypeEnum.EXPORT;
  26 +
  27 +@Api("短信模板")
  28 +@RestController
  29 +@RequestMapping("/system/sms-template")
  30 +public class SysSmsTemplateController {
  31 +
  32 + @Resource
  33 + private SysSmsTemplateService smsTemplateService;
  34 + @Resource
  35 + private SysSmsService smsService;
  36 +
  37 + @PostMapping("/create")
  38 + @ApiOperation("创建短信模板")
  39 + @PreAuthorize("@ss.hasPermission('system:sms-template:create')")
  40 + public CommonResult<Long> createSmsTemplate(@Valid @RequestBody SysSmsTemplateCreateReqVO createReqVO) {
  41 + return success(smsTemplateService.createSmsTemplate(createReqVO));
  42 + }
  43 +
  44 + @PutMapping("/update")
  45 + @ApiOperation("更新短信模板")
  46 + @PreAuthorize("@ss.hasPermission('system:sms-template:update')")
  47 + public CommonResult<Boolean> updateSmsTemplate(@Valid @RequestBody SysSmsTemplateUpdateReqVO updateReqVO) {
  48 + smsTemplateService.updateSmsTemplate(updateReqVO);
  49 + return success(true);
  50 + }
  51 +
  52 + @DeleteMapping("/delete")
  53 + @ApiOperation("删除短信模板")
  54 + @ApiImplicitParam(name = "id", value = "编号", required = true)
  55 + @PreAuthorize("@ss.hasPermission('system:sms-template:delete')")
  56 + public CommonResult<Boolean> deleteSmsTemplate(@RequestParam("id") Long id) {
  57 + smsTemplateService.deleteSmsTemplate(id);
  58 + return success(true);
  59 + }
  60 +
  61 + @GetMapping("/get")
  62 + @ApiOperation("获得短信模板")
  63 + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
  64 + @PreAuthorize("@ss.hasPermission('system:sms-template:query')")
  65 + public CommonResult<SysSmsTemplateRespVO> getSmsTemplate(@RequestParam("id") Long id) {
  66 + SysSmsTemplateDO smsTemplate = smsTemplateService.getSmsTemplate(id);
  67 + return success(SysSmsTemplateConvert.INSTANCE.convert(smsTemplate));
  68 + }
  69 +
  70 + @GetMapping("/page")
  71 + @ApiOperation("获得短信模板分页")
  72 + @PreAuthorize("@ss.hasPermission('system:sms-template:query')")
  73 + public CommonResult<PageResult<SysSmsTemplateRespVO>> getSmsTemplatePage(@Valid SysSmsTemplatePageReqVO pageVO) {
  74 + PageResult<SysSmsTemplateDO> pageResult = smsTemplateService.getSmsTemplatePage(pageVO);
  75 + return success(SysSmsTemplateConvert.INSTANCE.convertPage(pageResult));
  76 + }
  77 +
  78 + @GetMapping("/export-excel")
  79 + @ApiOperation("导出短信模板 Excel")
  80 + @PreAuthorize("@ss.hasPermission('system:sms-template:export')")
  81 + @OperateLog(type = EXPORT)
  82 + public void exportSmsTemplateExcel(@Valid SysSmsTemplateExportReqVO exportReqVO,
  83 + HttpServletResponse response) throws IOException {
  84 + List<SysSmsTemplateDO> list = smsTemplateService.getSmsTemplateList(exportReqVO);
  85 + // 导出 Excel
  86 + List<SysSmsTemplateExcelVO> datas = SysSmsTemplateConvert.INSTANCE.convertList02(list);
  87 + ExcelUtils.write(response, "短信模板.xls", "数据", SysSmsTemplateExcelVO.class, datas);
  88 + }
  89 +
  90 + @PostMapping("/send-sms")
  91 + @ApiOperation("发送短信")
  92 + @PreAuthorize("@ss.hasPermission('system:sms-template:send-sms')")
  93 + public CommonResult<Long> sendSms(@Valid @RequestBody SysSmsTemplateSendReqVO sendReqVO) {
  94 + return success(smsService.sendSingleSms(sendReqVO.getMobile(), null, null,
  95 + sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));
  96 + }
  97 +
  98 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/channel/SysSmsChannelBaseVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.channel;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +import org.hibernate.validator.constraints.URL;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +/**
  10 +* 短信渠道 Base VO,提供给添加、修改、详细的子 VO 使用
  11 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  12 +*/
  13 +@Data
  14 +public class SysSmsChannelBaseVO {
  15 +
  16 + @ApiModelProperty(value = "短信签名", required = true, example = "芋道源码")
  17 + @NotNull(message = "短信签名不能为空")
  18 + private String signature;
  19 +
  20 + @ApiModelProperty(value = "启用状态", required = true, example = "1")
  21 + @NotNull(message = "启用状态不能为空")
  22 + private Integer status;
  23 +
  24 + @ApiModelProperty(value = "备注", example = "好吃!")
  25 + private String remark;
  26 +
  27 + @ApiModelProperty(value = "短信 API 的账号", required = true, example = "yudao")
  28 + @NotNull(message = "短信 API 的账号不能为空")
  29 + private String apiKey;
  30 +
  31 + @ApiModelProperty(value = "短信 API 的秘钥", example = "yuanma")
  32 + private String apiSecret;
  33 +
  34 + @ApiModelProperty(value = "短信发送回调 URL", example = "http://www.iocoder.cn")
  35 + @URL(message = "回调 URL 格式不正确")
  36 + private String callbackUrl;
  37 +
  38 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/channel/SysSmsChannelCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.channel;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import javax.validation.constraints.NotNull;
  10 +
  11 +@ApiModel("短信渠道创建 Request VO")
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
  15 +public class SysSmsChannelCreateReqVO extends SysSmsChannelBaseVO {
  16 +
  17 + @ApiModelProperty(value = "渠道编码", required = true, example = "YUN_PIAN", notes = "参见 SmsChannelEnum 枚举类")
  18 + @NotNull(message = "渠道编码不能为空")
  19 + private String code;
  20 +
  21 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/channel/SysSmsChannelPageReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.channel;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageParam;
  4 +import io.swagger.annotations.ApiModel;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +import lombok.ToString;
  9 +import org.springframework.format.annotation.DateTimeFormat;
  10 +
  11 +import java.util.Date;
  12 +
  13 +import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  14 +
  15 +@ApiModel("短信渠道分页 Request VO")
  16 +@Data
  17 +@EqualsAndHashCode(callSuper = true)
  18 +@ToString(callSuper = true)
  19 +public class SysSmsChannelPageReqVO extends PageParam {
  20 +
  21 + @ApiModelProperty(value = "任务状态", example = "1")
  22 + private Integer status;
  23 +
  24 + @ApiModelProperty(value = "短信签名", example = "芋道源码", notes = "模糊匹配")
  25 + private String signature;
  26 +
  27 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  28 + @ApiModelProperty(value = "开始创建时间")
  29 + private Date beginCreateTime;
  30 +
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + @ApiModelProperty(value = "结束创建时间")
  33 + private Date endCreateTime;
  34 +
  35 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/channel/SysSmsChannelRespVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.channel;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import java.util.Date;
  10 +
  11 +@ApiModel("短信渠道 Response VO")
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
  15 +public class SysSmsChannelRespVO extends SysSmsChannelBaseVO {
  16 +
  17 + @ApiModelProperty(value = "编号", required = true, example = "1024")
  18 + private Long id;
  19 +
  20 + @ApiModelProperty(value = "渠道编码", required = true, example = "YUN_PIAN", notes = "参见 SmsChannelEnum 枚举类")
  21 + private String code;
  22 +
  23 + @ApiModelProperty(value = "创建时间", required = true)
  24 + private Date createTime;
  25 +
  26 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/channel/SysSmsChannelSimpleRespVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.channel;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@ApiModel("短信渠道精简 Response VO")
  10 +@Data
  11 +public class SysSmsChannelSimpleRespVO {
  12 +
  13 + @ApiModelProperty(value = "编号", required = true, example = "1024")
  14 + @NotNull(message = "编号不能为空")
  15 + private Long id;
  16 +
  17 + @ApiModelProperty(value = "短信签名", required = true, example = "芋道源码")
  18 + @NotNull(message = "短信签名不能为空")
  19 + private String signature;
  20 +
  21 + @ApiModelProperty(value = "渠道编码", required = true, example = "YUN_PIAN", notes = "参见 SmsChannelEnum 枚举类")
  22 + private String code;
  23 +
  24 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/channel/SysSmsChannelUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.channel;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import javax.validation.constraints.NotNull;
  10 +
  11 +@ApiModel("短信渠道更新 Request VO")
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
  15 +public class SysSmsChannelUpdateReqVO extends SysSmsChannelBaseVO {
  16 +
  17 + @ApiModelProperty(value = "编号", required = true, example = "1024")
  18 + @NotNull(message = "编号不能为空")
  19 + private Long id;
  20 +
  21 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/log/SysSmsLogExcelVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.log;
  2 +
  3 +import cn.iocoder.dashboard.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.dashboard.framework.excel.core.convert.DictConvert;
  5 +import cn.iocoder.dashboard.framework.excel.core.convert.JsonConvert;
  6 +import com.alibaba.excel.annotation.ExcelProperty;
  7 +import lombok.Data;
  8 +
  9 +import java.util.Date;
  10 +import java.util.Map;
  11 +
  12 +import static cn.iocoder.dashboard.modules.system.enums.dict.SysDictTypeEnum.*;
  13 +
  14 +/**
  15 + * 短信日志 Excel VO
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +@Data
  20 +public class SysSmsLogExcelVO {
  21 +
  22 + @ExcelProperty("编号")
  23 + private Long id;
  24 +
  25 + @ExcelProperty("短信渠道编号")
  26 + private Long channelId;
  27 +
  28 + @ExcelProperty("短信渠道编码")
  29 + private String channelCode;
  30 +
  31 + @ExcelProperty("模板编号")
  32 + private Long templateId;
  33 +
  34 + @ExcelProperty("模板编码")
  35 + private String templateCode;
  36 +
  37 + @ExcelProperty(value = "短信类型", converter = DictConvert.class)
  38 + @DictFormat(SYS_SMS_TEMPLATE_TYPE)
  39 + private Integer templateType;
  40 +
  41 + @ExcelProperty("短信内容")
  42 + private String templateContent;
  43 +
  44 + @ExcelProperty(value = "短信参数", converter = JsonConvert.class)
  45 + private Map<String, Object> templateParams;
  46 +
  47 + @ExcelProperty("短信 API 的模板编号")
  48 + private String apiTemplateId;
  49 +
  50 + @ExcelProperty("手机号")
  51 + private String mobile;
  52 +
  53 + @ExcelProperty("用户编号")
  54 + private Long userId;
  55 +
  56 + @ExcelProperty(value = "用户类型", converter = DictConvert.class)
  57 + @DictFormat(USER_TYPE)
  58 + private Integer userType;
  59 +
  60 + @ExcelProperty(value = "发送状态", converter = DictConvert.class)
  61 + @DictFormat(SYS_SMS_SEND_STATUS)
  62 + private Integer sendStatus;
  63 +
  64 + @ExcelProperty("发送时间")
  65 + private Date sendTime;
  66 +
  67 + @ExcelProperty("发送结果的编码")
  68 + private Integer sendCode;
  69 +
  70 + @ExcelProperty("发送结果的提示")
  71 + private String sendMsg;
  72 +
  73 + @ExcelProperty("短信 API 发送结果的编码")
  74 + private String apiSendCode;
  75 +
  76 + @ExcelProperty("短信 API 发送失败的提示")
  77 + private String apiSendMsg;
  78 +
  79 + @ExcelProperty("短信 API 发送返回的唯一请求 ID")
  80 + private String apiRequestId;
  81 +
  82 + @ExcelProperty("短信 API 发送返回的序号")
  83 + private String apiSerialNo;
  84 +
  85 + @ExcelProperty(value = "接收状态", converter = DictConvert.class)
  86 + @DictFormat(SYS_SMS_RECEIVE_STATUS)
  87 + private Integer receiveStatus;
  88 +
  89 + @ExcelProperty("接收时间")
  90 + private Date receiveTime;
  91 +
  92 + @ExcelProperty("API 接收结果的编码")
  93 + private String apiReceiveCode;
  94 +
  95 + @ExcelProperty("API 接收结果的说明")
  96 + private String apiReceiveMsg;
  97 +
  98 + @ExcelProperty("创建时间")
  99 + private Date createTime;
  100 +
  101 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/log/SysSmsLogExportReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.log;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +
  8 +import java.util.Date;
  9 +
  10 +import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +@ApiModel(value = "短信日志 Excel 导出 Request VO", description = "参数和 SysSmsLogPageReqVO 是一致的")
  13 +@Data
  14 +public class SysSmsLogExportReqVO {
  15 +
  16 + @ApiModelProperty(value = "短信渠道编号", example = "10")
  17 + private Long channelId;
  18 +
  19 + @ApiModelProperty(value = "模板编号", example = "20")
  20 + private Long templateId;
  21 +
  22 + @ApiModelProperty(value = "手机号", example = "15601691300")
  23 + private String mobile;
  24 +
  25 + @ApiModelProperty(value = "发送状态", example = "1")
  26 + private Integer sendStatus;
  27 +
  28 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  29 + @ApiModelProperty(value = "开始发送时间")
  30 + private Date beginSendTime;
  31 +
  32 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  33 + @ApiModelProperty(value = "结束发送时间")
  34 + private Date endSendTime;
  35 +
  36 + @ApiModelProperty(value = "接收状态", example = "0")
  37 + private Integer receiveStatus;
  38 +
  39 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  40 + @ApiModelProperty(value = "开始接收时间")
  41 + private Date beginReceiveTime;
  42 +
  43 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  44 + @ApiModelProperty(value = "结束接收时间")
  45 + private Date endReceiveTime;
  46 +
  47 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/log/SysSmsLogPageReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.log;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageParam;
  4 +import io.swagger.annotations.ApiModel;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +import lombok.ToString;
  9 +import org.springframework.format.annotation.DateTimeFormat;
  10 +
  11 +import java.util.Date;
  12 +
  13 +import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  14 +
  15 +@ApiModel("短信日志分页 Request VO")
  16 +@Data
  17 +@EqualsAndHashCode(callSuper = true)
  18 +@ToString(callSuper = true)
  19 +public class SysSmsLogPageReqVO extends PageParam {
  20 +
  21 + @ApiModelProperty(value = "短信渠道编号", example = "10")
  22 + private Long channelId;
  23 +
  24 + @ApiModelProperty(value = "模板编号", example = "20")
  25 + private Long templateId;
  26 +
  27 + @ApiModelProperty(value = "手机号", example = "15601691300")
  28 + private String mobile;
  29 +
  30 + @ApiModelProperty(value = "发送状态", example = "1", notes = "参见 SysSmsSendStatusEnum 枚举类")
  31 + private Integer sendStatus;
  32 +
  33 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  34 + @ApiModelProperty(value = "开始发送时间")
  35 + private Date beginSendTime;
  36 +
  37 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  38 + @ApiModelProperty(value = "结束发送时间")
  39 + private Date endSendTime;
  40 +
  41 + @ApiModelProperty(value = "接收状态", example = "0", notes = "参见 SysSmsReceiveStatusEnum 枚举类")
  42 + private Integer receiveStatus;
  43 +
  44 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  45 + @ApiModelProperty(value = "开始接收时间")
  46 + private Date beginReceiveTime;
  47 +
  48 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  49 + @ApiModelProperty(value = "结束接收时间")
  50 + private Date endReceiveTime;
  51 +
  52 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/log/SysSmsLogRespVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.log;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import java.util.Date;
  8 +import java.util.Map;
  9 +
  10 +@ApiModel("短信日志 Response VO")
  11 +@Data
  12 +public class SysSmsLogRespVO {
  13 +
  14 + @ApiModelProperty(value = "编号", required = true, example = "1024")
  15 + private Long id;
  16 +
  17 + @ApiModelProperty(value = "短信渠道编号", required = true, example = "10")
  18 + private Long channelId;
  19 +
  20 + @ApiModelProperty(value = "短信渠道编码", required = true, example = "ALIYUN")
  21 + private String channelCode;
  22 +
  23 + @ApiModelProperty(value = "模板编号", required = true, example = "20")
  24 + private Long templateId;
  25 +
  26 + @ApiModelProperty(value = "模板编码", required = true, example = "test-01")
  27 + private String templateCode;
  28 +
  29 + @ApiModelProperty(value = "短信类型", required = true, example = "1")
  30 + private Integer templateType;
  31 +
  32 + @ApiModelProperty(value = "短信内容", required = true, example = "你好,你的验证码是 1024")
  33 + private String templateContent;
  34 +
  35 + @ApiModelProperty(value = "短信参数", required = true, example = "name,code")
  36 + private Map<String, Object> templateParams;
  37 +
  38 + @ApiModelProperty(value = "短信 API 的模板编号", required = true, example = "SMS_207945135")
  39 + private String apiTemplateId;
  40 +
  41 + @ApiModelProperty(value = "手机号", required = true, example = "15601691300")
  42 + private String mobile;
  43 +
  44 + @ApiModelProperty(value = "用户编号", example = "10")
  45 + private Long userId;
  46 +
  47 + @ApiModelProperty(value = "用户类型", example = "1")
  48 + private Integer userType;
  49 +
  50 + @ApiModelProperty(value = "发送状态", required = true, example = "1")
  51 + private Integer sendStatus;
  52 +
  53 + @ApiModelProperty(value = "发送时间")
  54 + private Date sendTime;
  55 +
  56 + @ApiModelProperty(value = "发送结果的编码", example = "0")
  57 + private Integer sendCode;
  58 +
  59 + @ApiModelProperty(value = "发送结果的提示", example = "成功")
  60 + private String sendMsg;
  61 +
  62 + @ApiModelProperty(value = "短信 API 发送结果的编码", example = "SUCCESS")
  63 + private String apiSendCode;
  64 +
  65 + @ApiModelProperty(value = "短信 API 发送失败的提示", example = "成功")
  66 + private String apiSendMsg;
  67 +
  68 + @ApiModelProperty(value = "短信 API 发送返回的唯一请求 ID", example = "3837C6D3-B96F-428C-BBB2-86135D4B5B99")
  69 + private String apiRequestId;
  70 +
  71 + @ApiModelProperty(value = "短信 API 发送返回的序号", example = "62923244790")
  72 + private String apiSerialNo;
  73 +
  74 + @ApiModelProperty(value = "接收状态", required = true, example = "0")
  75 + private Integer receiveStatus;
  76 +
  77 + @ApiModelProperty(value = "接收时间")
  78 + private Date receiveTime;
  79 +
  80 + @ApiModelProperty(value = "API 接收结果的编码", example = "DELIVRD")
  81 + private String apiReceiveCode;
  82 +
  83 + @ApiModelProperty(value = "API 接收结果的说明", example = "用户接收成功")
  84 + private String apiReceiveMsg;
  85 +
  86 + @ApiModelProperty(value = "创建时间", required = true)
  87 + private Date createTime;
  88 +
  89 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/template/SysSmsTemplateBaseVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.template;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotNull;
  7 +
  8 +/**
  9 +* 短信模板 Base VO,提供给添加、修改、详细的子 VO 使用
  10 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  11 +*/
  12 +@Data
  13 +public class SysSmsTemplateBaseVO {
  14 +
  15 + @ApiModelProperty(value = "短信类型", required = true, example = "1", notes = "参见 SysSmsTemplateTypeEnum 枚举类")
  16 + @NotNull(message = "短信类型不能为空")
  17 + private Integer type;
  18 +
  19 + @ApiModelProperty(value = "开启状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举类")
  20 + @NotNull(message = "开启状态不能为空")
  21 + private Integer status;
  22 +
  23 + @ApiModelProperty(value = "模板编码", required = true, example = "test_01")
  24 + @NotNull(message = "模板编码不能为空")
  25 + private String code;
  26 +
  27 + @ApiModelProperty(value = "模板名称", required = true, example = "yudao")
  28 + @NotNull(message = "模板名称不能为空")
  29 + private String name;
  30 +
  31 + @ApiModelProperty(value = "模板内容", required = true, example = "你好,{name}。你长的太{like}啦!")
  32 + @NotNull(message = "模板内容不能为空")
  33 + private String content;
  34 +
  35 + @ApiModelProperty(value = "备注", example = "哈哈哈")
  36 + private String remark;
  37 +
  38 + @ApiModelProperty(value = "短信 API 的模板编号", required = true, example = "4383920")
  39 + @NotNull(message = "短信 API 的模板编号不能为空")
  40 + private String apiTemplateId;
  41 +
  42 + @ApiModelProperty(value = "短信渠道编号", required = true, example = "10")
  43 + @NotNull(message = "短信渠道编号不能为空")
  44 + private Long channelId;
  45 +
  46 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/template/SysSmsTemplateCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.template;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +@ApiModel("短信模板创建 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class SysSmsTemplateCreateReqVO extends SysSmsTemplateBaseVO {
  13 +
  14 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/template/SysSmsTemplateExcelVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.template;
  2 +
  3 +import cn.iocoder.dashboard.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.dashboard.framework.excel.core.convert.DictConvert;
  5 +import com.alibaba.excel.annotation.ExcelProperty;
  6 +import lombok.Data;
  7 +
  8 +import java.util.Date;
  9 +
  10 +import static cn.iocoder.dashboard.modules.system.enums.dict.SysDictTypeEnum.*;
  11 +
  12 +/**
  13 + * 短信模板 Excel VO
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Data
  18 +public class SysSmsTemplateExcelVO {
  19 +
  20 + @ExcelProperty("编号")
  21 + private Long id;
  22 +
  23 + @ExcelProperty(value = "短信签名", converter = DictConvert.class)
  24 + @DictFormat(SYS_SMS_TEMPLATE_TYPE)
  25 + private Integer type;
  26 +
  27 + @ExcelProperty(value = "开启状态", converter = DictConvert.class)
  28 + @DictFormat(SYS_COMMON_STATUS)
  29 + private Integer status;
  30 +
  31 + @ExcelProperty("模板编码")
  32 + private String code;
  33 +
  34 + @ExcelProperty("模板名称")
  35 + private String name;
  36 +
  37 + @ExcelProperty("模板内容")
  38 + private String content;
  39 +
  40 + @ExcelProperty("备注")
  41 + private String remark;
  42 +
  43 + @ExcelProperty("短信 API 的模板编号")
  44 + private String apiTemplateId;
  45 +
  46 + @ExcelProperty("短信渠道编号")
  47 + private Long channelId;
  48 +
  49 + @ExcelProperty(value = "短信渠道编码", converter = DictConvert.class)
  50 + @DictFormat(SYS_SMS_CHANNEL_CODE)
  51 + private String channelCode;
  52 +
  53 + @ExcelProperty("创建时间")
  54 + private Date createTime;
  55 +
  56 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/template/SysSmsTemplateExportReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.template;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +
  8 +import java.util.Date;
  9 +
  10 +import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +@ApiModel(value = "短信模板 Excel 导出 Request VO", description = "参数和 SysSmsTemplatePageReqVO 是一致的")
  13 +@Data
  14 +public class SysSmsTemplateExportReqVO {
  15 +
  16 + @ApiModelProperty(value = "短信签名", example = "1")
  17 + private Integer type;
  18 +
  19 + @ApiModelProperty(value = "开启状态", example = "1")
  20 + private Integer status;
  21 +
  22 + @ApiModelProperty(value = "模板编码", example = "test_01", notes = "模糊匹配")
  23 + private String code;
  24 +
  25 + @ApiModelProperty(value = "模板内容", example = "你好,{name}。你长的太{like}啦!", notes = "模糊匹配")
  26 + private String content;
  27 +
  28 + @ApiModelProperty(value = "短信 API 的模板编号", example = "4383920", notes = "模糊匹配")
  29 + private String apiTemplateId;
  30 +
  31 + @ApiModelProperty(value = "短信渠道编号", example = "10")
  32 + private Long channelId;
  33 +
  34 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  35 + @ApiModelProperty(value = "开始创建时间")
  36 + private Date beginCreateTime;
  37 +
  38 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  39 + @ApiModelProperty(value = "结束创建时间")
  40 + private Date endCreateTime;
  41 +
  42 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/template/SysSmsTemplatePageReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.template;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageParam;
  4 +import io.swagger.annotations.ApiModel;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +import lombok.ToString;
  9 +import org.springframework.format.annotation.DateTimeFormat;
  10 +
  11 +import java.util.Date;
  12 +
  13 +import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  14 +
  15 +@ApiModel("短信模板分页 Request VO")
  16 +@Data
  17 +@EqualsAndHashCode(callSuper = true)
  18 +@ToString(callSuper = true)
  19 +public class SysSmsTemplatePageReqVO extends PageParam {
  20 +
  21 + @ApiModelProperty(value = "短信签名", example = "1")
  22 + private Integer type;
  23 +
  24 + @ApiModelProperty(value = "开启状态", example = "1")
  25 + private Integer status;
  26 +
  27 + @ApiModelProperty(value = "模板编码", example = "test_01", notes = "模糊匹配")
  28 + private String code;
  29 +
  30 + @ApiModelProperty(value = "模板内容", example = "你好,{name}。你长的太{like}啦!", notes = "模糊匹配")
  31 + private String content;
  32 +
  33 + @ApiModelProperty(value = "短信 API 的模板编号", example = "4383920", notes = "模糊匹配")
  34 + private String apiTemplateId;
  35 +
  36 + @ApiModelProperty(value = "短信渠道编号", example = "10")
  37 + private Long channelId;
  38 +
  39 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  40 + @ApiModelProperty(value = "开始创建时间")
  41 + private Date beginCreateTime;
  42 +
  43 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  44 + @ApiModelProperty(value = "结束创建时间")
  45 + private Date endCreateTime;
  46 +
  47 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/template/SysSmsTemplateRespVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.template;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import java.util.Date;
  10 +import java.util.List;
  11 +
  12 +@ApiModel("短信模板 Response VO")
  13 +@Data
  14 +@EqualsAndHashCode(callSuper = true)
  15 +@ToString(callSuper = true)
  16 +public class SysSmsTemplateRespVO extends SysSmsTemplateBaseVO {
  17 +
  18 + @ApiModelProperty(value = "编号", required = true, example = "1024")
  19 + private Long id;
  20 +
  21 + @ApiModelProperty(value = "短信渠道编码", required = true, example = "ALIYUN")
  22 + private String channelCode;
  23 +
  24 + @ApiModelProperty(value = "参数数组", example = "name,code")
  25 + private List<String> params;
  26 +
  27 + @ApiModelProperty(value = "创建时间", required = true)
  28 + private Date createTime;
  29 +
  30 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/template/SysSmsTemplateSendReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.template;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.util.Map;
  9 +
  10 +@ApiModel("短信模板的发送 Request VO")
  11 +@Data
  12 +public class SysSmsTemplateSendReqVO {
  13 +
  14 + @ApiModelProperty(value = "手机号", required = true, example = "15601691300")
  15 + @NotNull(message = "手机号不能为空")
  16 + private String mobile;
  17 +
  18 + @ApiModelProperty(value = "模板编码", required = true, example = "test_01")
  19 + @NotNull(message = "模板编码不能为空")
  20 + private String templateCode;
  21 +
  22 + @ApiModelProperty(value = "模板参数")
  23 + private Map<String, Object> templateParams;
  24 +
  25 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/vo/template/SysSmsTemplateUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.sms.vo.template;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import javax.validation.constraints.NotNull;
  10 +
  11 +@ApiModel("短信模板更新 Request VO")
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
  15 +public class SysSmsTemplateUpdateReqVO extends SysSmsTemplateBaseVO {
  16 +
  17 + @ApiModelProperty(value = "编号", required = true, example = "1024")
  18 + @NotNull(message = "编号不能为空")
  19 + private Long id;
  20 +
  21 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java
... ... @@ -149,9 +149,9 @@ public class SysUserController {
149 149 // 手动创建导出 demo
150 150 List<SysUserImportExcelVO> list = Arrays.asList(
151 151 SysUserImportExcelVO.builder().username("yudao").deptId(1L).email("yudao@iocoder.cn").mobile("15601691300")
152   - .nickname("芋道").status(CommonStatusEnum.ENABLE.getStatus()).sex(SysSexEnum.MALE.getSEX()).build(),
  152 + .nickname("芋道").status(CommonStatusEnum.ENABLE.getStatus()).sex(SysSexEnum.MALE.getSex()).build(),
153 153 SysUserImportExcelVO.builder().username("yuanma").deptId(2L).email("yuanma@iocoder.cn").mobile("15601701300")
154   - .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SysSexEnum.FEMALE.getSEX()).build()
  154 + .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SysSexEnum.FEMALE.getSex()).build()
155 155 );
156 156  
157 157 // 输出
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/profile/SysUserProfileUpdateReqVO.java
... ... @@ -3,9 +3,9 @@ package cn.iocoder.dashboard.modules.system.controller.user.vo.profile;
3 3 import io.swagger.annotations.ApiModel;
4 4 import io.swagger.annotations.ApiModelProperty;
5 5 import lombok.Data;
  6 +import org.hibernate.validator.constraints.Length;
6 7  
7 8 import javax.validation.constraints.Email;
8   -import javax.validation.constraints.Pattern;
9 9 import javax.validation.constraints.Size;
10 10  
11 11 @ApiModel("用户个人信息更新 Request VO")
... ... @@ -22,7 +22,7 @@ public class SysUserProfileUpdateReqVO {
22 22 private String email;
23 23  
24 24 @ApiModelProperty(value = "手机号码", example = "15601691300")
25   - @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")
  25 + @Length(min = 11, max = 11, message = "手机号长度必须 11 位")
26 26 private String mobile;
27 27  
28 28 @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SysSexEnum 枚举类")
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserBaseVO.java
... ... @@ -2,10 +2,10 @@ package cn.iocoder.dashboard.modules.system.controller.user.vo.user;
2 2  
3 3 import io.swagger.annotations.ApiModelProperty;
4 4 import lombok.Data;
  5 +import org.hibernate.validator.constraints.Length;
5 6  
6 7 import javax.validation.constraints.Email;
7 8 import javax.validation.constraints.NotBlank;
8   -import javax.validation.constraints.Pattern;
9 9 import javax.validation.constraints.Size;
10 10 import java.util.Set;
11 11  
... ... @@ -40,7 +40,7 @@ public class SysUserBaseVO {
40 40 private String email;
41 41  
42 42 @ApiModelProperty(value = "手机号码", example = "15601691300")
43   - @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")
  43 + @Length(min = 11, max = 11, message = "手机号长度必须 11 位")
44 44 private String mobile;
45 45  
46 46 @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SysSexEnum 枚举类")
... ...
src/main/java/cn/iocoder/dashboard/modules/system/convert/dict/SysDictDataConvert.java
... ... @@ -13,7 +13,7 @@ public interface SysDictDataConvert {
13 13  
14 14 SysDictDataConvert INSTANCE = Mappers.getMapper(SysDictDataConvert.class);
15 15  
16   - List<SysDictDataSimpleVO> convertList(List<SysDictDataDO> list);
  16 + List<SysDictDataSimpleRespVO> convertList(List<SysDictDataDO> list);
17 17  
18 18 SysDictDataRespVO convert(SysDictDataDO bean);
19 19  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SysSmsChannelConvert.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.convert.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageResult;
  4 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  5 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelCreateReqVO;
  6 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelRespVO;
  7 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelSimpleRespVO;
  8 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelUpdateReqVO;
  9 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
  10 +import org.mapstruct.Mapper;
  11 +import org.mapstruct.factory.Mappers;
  12 +
  13 +import java.util.List;
  14 +
  15 +/**
  16 + * 短信渠道 Convert
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@Mapper
  21 +public interface SysSmsChannelConvert {
  22 +
  23 + SysSmsChannelConvert INSTANCE = Mappers.getMapper(SysSmsChannelConvert.class);
  24 +
  25 + SysSmsChannelDO convert(SysSmsChannelCreateReqVO bean);
  26 +
  27 + SysSmsChannelDO convert(SysSmsChannelUpdateReqVO bean);
  28 +
  29 + SysSmsChannelRespVO convert(SysSmsChannelDO bean);
  30 +
  31 + List<SysSmsChannelRespVO> convertList(List<SysSmsChannelDO> list);
  32 +
  33 + PageResult<SysSmsChannelRespVO> convertPage(PageResult<SysSmsChannelDO> page);
  34 +
  35 + List<SmsChannelProperties> convertList02(List<SysSmsChannelDO> list);
  36 +
  37 + List<SysSmsChannelSimpleRespVO> convertList03(List<SysSmsChannelDO> list);
  38 +
  39 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SysSmsLogConvert.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.convert.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageResult;
  4 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogExcelVO;
  5 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogRespVO;
  6 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsLogDO;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.factory.Mappers;
  9 +
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * 短信日志 Convert
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Mapper
  18 +public interface SysSmsLogConvert {
  19 +
  20 + SysSmsLogConvert INSTANCE = Mappers.getMapper(SysSmsLogConvert.class);
  21 +
  22 + SysSmsLogRespVO convert(SysSmsLogDO bean);
  23 +
  24 + List<SysSmsLogRespVO> convertList(List<SysSmsLogDO> list);
  25 +
  26 + PageResult<SysSmsLogRespVO> convertPage(PageResult<SysSmsLogDO> page);
  27 +
  28 + List<SysSmsLogExcelVO> convertList02(List<SysSmsLogDO> list);
  29 +
  30 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/convert/sms/SysSmsTemplateConvert.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.convert.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageResult;
  4 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateCreateReqVO;
  5 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateExcelVO;
  6 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateRespVO;
  7 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateUpdateReqVO;
  8 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  9 +import org.mapstruct.Mapper;
  10 +import org.mapstruct.factory.Mappers;
  11 +
  12 +import java.util.List;
  13 +
  14 +@Mapper
  15 +public interface SysSmsTemplateConvert {
  16 +
  17 + SysSmsTemplateConvert INSTANCE = Mappers.getMapper(SysSmsTemplateConvert.class);
  18 +
  19 + SysSmsTemplateDO convert(SysSmsTemplateCreateReqVO bean);
  20 +
  21 + SysSmsTemplateDO convert(SysSmsTemplateUpdateReqVO bean);
  22 +
  23 + SysSmsTemplateRespVO convert(SysSmsTemplateDO bean);
  24 +
  25 + List<SysSmsTemplateRespVO> convertList(List<SysSmsTemplateDO> list);
  26 +
  27 + PageResult<SysSmsTemplateRespVO> convertPage(PageResult<SysSmsTemplateDO> page);
  28 +
  29 + List<SysSmsTemplateExcelVO> convertList02(List<SysSmsTemplateDO> list);
  30 +
  31 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/sms/SysSmsChannelDO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.dataobject.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
  6 +import com.baomidou.mybatisplus.annotation.TableName;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +import lombok.ToString;
  10 +
  11 +/**
  12 + * 短信渠道 DO
  13 + *
  14 + * @author zzf
  15 + * @since 2021-01-25
  16 + */
  17 +@TableName(value = "sys_sms_channel", autoResultMap = true)
  18 +@Data
  19 +@EqualsAndHashCode(callSuper = true)
  20 +@ToString(callSuper = true)
  21 +public class SysSmsChannelDO extends BaseDO {
  22 +
  23 + /**
  24 + * 渠道编号
  25 + */
  26 + private Long id;
  27 + /**
  28 + * 短信签名
  29 + */
  30 + private String signature;
  31 + /**
  32 + * 渠道编码
  33 + *
  34 + * 枚举 {@link SmsChannelEnum}
  35 + */
  36 + private String code;
  37 + /**
  38 + * 启用状态
  39 + *
  40 + * 枚举 {@link CommonStatusEnum}
  41 + */
  42 + private Integer status;
  43 + /**
  44 + * 备注
  45 + */
  46 + private String remark;
  47 + /**
  48 + * 短信 API 的账号
  49 + */
  50 + private String apiKey;
  51 + /**
  52 + * 短信 API 的秘钥
  53 + */
  54 + private String apiSecret;
  55 + /**
  56 + * 短信发送回调 URL
  57 + */
  58 + private String callbackUrl;
  59 +
  60 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/sms/SysSmsLogDO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.dataobject.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.enums.UserTypeEnum;
  4 +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
  6 +import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsReceiveStatusEnum;
  7 +import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
  8 +import com.baomidou.mybatisplus.annotation.TableField;
  9 +import com.baomidou.mybatisplus.annotation.TableName;
  10 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  11 +import lombok.*;
  12 +
  13 +import java.util.Date;
  14 +import java.util.Map;
  15 +
  16 +/**
  17 + * 短信日志 DO
  18 + *
  19 + * @author zzf
  20 + * @since 2021-01-25
  21 + */
  22 +@TableName(value = "sys_sms_log", autoResultMap = true)
  23 +@Data
  24 +@EqualsAndHashCode(callSuper = true)
  25 +@ToString(callSuper = true)
  26 +@Builder
  27 +@AllArgsConstructor
  28 +@NoArgsConstructor
  29 +public class SysSmsLogDO extends BaseDO {
  30 +
  31 + /**
  32 + * 自增编号
  33 + */
  34 + private Long id;
  35 +
  36 + // ========= 渠道相关字段 =========
  37 +
  38 + /**
  39 + * 短信渠道编号
  40 + *
  41 + * 关联 {@link SysSmsChannelDO#getId()}
  42 + */
  43 + private Long channelId;
  44 + /**
  45 + * 短信渠道编码
  46 + *
  47 + * 冗余 {@link SysSmsChannelDO#getCode()}
  48 + */
  49 + private String channelCode;
  50 +
  51 + // ========= 模板相关字段 =========
  52 +
  53 + /**
  54 + * 模板编号
  55 + *
  56 + * 关联 {@link SysSmsTemplateDO#getId()}
  57 + */
  58 + private Long templateId;
  59 + /**
  60 + * 模板编码
  61 + *
  62 + * 冗余 {@link SysSmsTemplateDO#getCode()}
  63 + */
  64 + private String templateCode;
  65 + /**
  66 + * 短信类型
  67 + *
  68 + * 冗余 {@link SysSmsTemplateDO#getType()}
  69 + */
  70 + private Integer templateType;
  71 + /**
  72 + * 基于 {@link SysSmsTemplateDO#getContent()} 格式化后的内容
  73 + */
  74 + private String templateContent;
  75 + /**
  76 + * 基于 {@link SysSmsTemplateDO#getParams()} 输入后的参数
  77 + */
  78 + @TableField(typeHandler = JacksonTypeHandler.class)
  79 + private Map<String, Object> templateParams;
  80 + /**
  81 + * 短信 API 的模板编号
  82 + *
  83 + * 冗余 {@link SysSmsTemplateDO#getApiTemplateId()}
  84 + */
  85 + private String apiTemplateId;
  86 +
  87 + // ========= 手机相关字段 =========
  88 +
  89 + /**
  90 + * 手机号
  91 + */
  92 + private String mobile;
  93 + /**
  94 + * 用户编号
  95 + */
  96 + private Long userId;
  97 + /**
  98 + * 用户类型
  99 + *
  100 + * 枚举 {@link UserTypeEnum}
  101 + */
  102 + private Integer userType;
  103 +
  104 + // ========= 发送相关字段 =========
  105 +
  106 + /**
  107 + * 发送状态
  108 + *
  109 + * 枚举 {@link SysSmsSendStatusEnum}
  110 + */
  111 + private Integer sendStatus;
  112 + /**
  113 + * 发送时间
  114 + */
  115 + private Date sendTime;
  116 + /**
  117 + * 发送结果的编码
  118 + *
  119 + * 枚举 {@link SmsFrameworkErrorCodeConstants}
  120 + */
  121 + private Integer sendCode;
  122 + /**
  123 + * 发送结果的提示
  124 + *
  125 + * 一般情况下,使用 {@link SmsFrameworkErrorCodeConstants}
  126 + * 异常情况下,通过格式化 Exception 的提示存储
  127 + */
  128 + private String sendMsg;
  129 + /**
  130 + * 短信 API 发送结果的编码
  131 + *
  132 + * 由于第三方的错误码可能是字符串,所以使用 String 类型
  133 + */
  134 + private String apiSendCode;
  135 + /**
  136 + * 短信 API 发送失败的提示
  137 + */
  138 + private String apiSendMsg;
  139 + /**
  140 + * 短信 API 发送返回的唯一请求 ID
  141 + *
  142 + * 用于和短信 API 进行定位于排错
  143 + */
  144 + private String apiRequestId;
  145 + /**
  146 + * 短信 API 发送返回的序号
  147 + *
  148 + * 用于和短信 API 平台的发送记录关联
  149 + */
  150 + private String apiSerialNo;
  151 +
  152 + // ========= 接收相关字段 =========
  153 +
  154 + /**
  155 + * 接收状态
  156 + *
  157 + * 枚举 {@link SysSmsReceiveStatusEnum}
  158 + */
  159 + private Integer receiveStatus;
  160 + /**
  161 + * 接收时间
  162 + */
  163 + private Date receiveTime;
  164 + /**
  165 + * 短信 API 接收结果的编码
  166 + */
  167 + private String apiReceiveCode;
  168 + /**
  169 + * 短信 API 接收结果的提示
  170 + */
  171 + private String apiReceiveMsg;
  172 +
  173 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/dataobject/sms/SysSmsTemplateDO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.dataobject.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsTemplateTypeEnum;
  6 +import com.baomidou.mybatisplus.annotation.TableField;
  7 +import com.baomidou.mybatisplus.annotation.TableName;
  8 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  9 +import lombok.Data;
  10 +import lombok.EqualsAndHashCode;
  11 +import lombok.ToString;
  12 +
  13 +import java.util.List;
  14 +
  15 +/**
  16 + * 短信模板 DO
  17 + *
  18 + * @author zzf
  19 + * @since 2021-01-25
  20 + */
  21 +@TableName(value = "sys_sms_template", autoResultMap = true)
  22 +@Data
  23 +@EqualsAndHashCode(callSuper = true)
  24 +@ToString(callSuper = true)
  25 +public class SysSmsTemplateDO extends BaseDO {
  26 +
  27 + /**
  28 + * 自增编号
  29 + */
  30 + private Long id;
  31 +
  32 + // ========= 模板相关字段 =========
  33 +
  34 + /**
  35 + * 短信类型
  36 + *
  37 + * 枚举 {@link SysSmsTemplateTypeEnum}
  38 + */
  39 + private Integer type;
  40 + /**
  41 + * 启用状态
  42 + *
  43 + * 枚举 {@link CommonStatusEnum}
  44 + */
  45 + private Integer status;
  46 + /**
  47 + * 模板编码,保证唯一
  48 + */
  49 + private String code;
  50 + /**
  51 + * 模板名称
  52 + */
  53 + private String name;
  54 + /**
  55 + * 模板内容
  56 + *
  57 + * 内容的参数,使用 {} 包括,例如说 {name}
  58 + */
  59 + private String content;
  60 + /**
  61 + * 参数数组(自动根据内容生成)
  62 + */
  63 + @TableField(typeHandler = JacksonTypeHandler.class)
  64 + private List<String> params;
  65 + /**
  66 + * 备注
  67 + */
  68 + private String remark;
  69 + /**
  70 + * 短信 API 的模板编号
  71 + */
  72 + private String apiTemplateId;
  73 +
  74 + // ========= 渠道相关字段 =========
  75 +
  76 + /**
  77 + * 短信渠道编号
  78 + *
  79 + * 关联 {@link SysSmsChannelDO#getId()}
  80 + */
  81 + private Long channelId;
  82 + /**
  83 + * 短信渠道编码
  84 + *
  85 + * 冗余 {@link SysSmsChannelDO#getCode()}
  86 + */
  87 + private String channelCode;
  88 +
  89 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsChannelMapper.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.mysql.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageResult;
  4 +import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
  6 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelPageReqVO;
  7 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
  8 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  9 +import org.apache.ibatis.annotations.Mapper;
  10 +import org.apache.ibatis.annotations.Select;
  11 +
  12 +import java.util.Date;
  13 +import java.util.List;
  14 +
  15 +@Mapper
  16 +public interface SysSmsChannelMapper extends BaseMapperX<SysSmsChannelDO> {
  17 +
  18 + default PageResult<SysSmsChannelDO> selectPage(SysSmsChannelPageReqVO reqVO) {
  19 + return selectPage(reqVO, new QueryWrapperX<SysSmsChannelDO>()
  20 + .likeIfPresent("signature", reqVO.getSignature())
  21 + .eqIfPresent("status", reqVO.getStatus())
  22 + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
  23 + .orderByDesc("id"));
  24 + }
  25 +
  26 + @Select("SELECT id FROM sys_sms_channel WHERE update_time > #{maxUpdateTime} LIMIT 1")
  27 + Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
  28 +
  29 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsLogMapper.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.mysql.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageResult;
  4 +import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
  6 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogExportReqVO;
  7 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogPageReqVO;
  8 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsLogDO;
  9 +import org.apache.ibatis.annotations.Mapper;
  10 +
  11 +import java.util.List;
  12 +
  13 +@Mapper
  14 +public interface SysSmsLogMapper extends BaseMapperX<SysSmsLogDO> {
  15 +
  16 + default PageResult<SysSmsLogDO> selectPage(SysSmsLogPageReqVO reqVO) {
  17 + return selectPage(reqVO, new QueryWrapperX<SysSmsLogDO>()
  18 + .eqIfPresent("channel_id", reqVO.getChannelId())
  19 + .eqIfPresent("template_id", reqVO.getTemplateId())
  20 + .likeIfPresent("mobile", reqVO.getMobile())
  21 + .eqIfPresent("send_status", reqVO.getSendStatus())
  22 + .betweenIfPresent("send_time", reqVO.getBeginSendTime(), reqVO.getEndSendTime())
  23 + .eqIfPresent("receive_status", reqVO.getReceiveStatus())
  24 + .betweenIfPresent("receive_time", reqVO.getBeginReceiveTime(), reqVO.getEndReceiveTime())
  25 + .orderByDesc("id"));
  26 + }
  27 +
  28 + default List<SysSmsLogDO> selectList(SysSmsLogExportReqVO reqVO) {
  29 + return selectList(new QueryWrapperX<SysSmsLogDO>()
  30 + .eqIfPresent("channel_id", reqVO.getChannelId())
  31 + .eqIfPresent("template_id", reqVO.getTemplateId())
  32 + .likeIfPresent("mobile", reqVO.getMobile())
  33 + .eqIfPresent("send_status", reqVO.getSendStatus())
  34 + .betweenIfPresent("send_time", reqVO.getBeginSendTime(), reqVO.getEndSendTime())
  35 + .eqIfPresent("receive_status", reqVO.getReceiveStatus())
  36 + .betweenIfPresent("receive_time", reqVO.getBeginReceiveTime(), reqVO.getEndReceiveTime())
  37 + .orderByDesc("id"));
  38 + }
  39 +
  40 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/sms/SysSmsTemplateMapper.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.mysql.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageResult;
  4 +import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
  6 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateExportReqVO;
  7 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplatePageReqVO;
  8 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  9 +import org.apache.ibatis.annotations.Mapper;
  10 +import org.apache.ibatis.annotations.Select;
  11 +
  12 +import java.util.Date;
  13 +import java.util.List;
  14 +
  15 +@Mapper
  16 +public interface SysSmsTemplateMapper extends BaseMapperX<SysSmsTemplateDO> {
  17 +
  18 + default SysSmsTemplateDO selectByCode(String code) {
  19 + return selectOne("code", code);
  20 + }
  21 +
  22 + default PageResult<SysSmsTemplateDO> selectPage(SysSmsTemplatePageReqVO reqVO) {
  23 + return selectPage(reqVO, new QueryWrapperX<SysSmsTemplateDO>()
  24 + .eqIfPresent("type", reqVO.getType())
  25 + .eqIfPresent("status", reqVO.getStatus())
  26 + .likeIfPresent("code", reqVO.getCode())
  27 + .likeIfPresent("content", reqVO.getContent())
  28 + .likeIfPresent("api_template_id", reqVO.getApiTemplateId())
  29 + .eqIfPresent("channel_id", reqVO.getChannelId())
  30 + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
  31 + .orderByDesc("id"));
  32 + }
  33 +
  34 + default List<SysSmsTemplateDO> selectList(SysSmsTemplateExportReqVO reqVO) {
  35 + return selectList(new QueryWrapperX<SysSmsTemplateDO>()
  36 + .eqIfPresent("type", reqVO.getType())
  37 + .eqIfPresent("status", reqVO.getStatus())
  38 + .likeIfPresent("code", reqVO.getCode())
  39 + .likeIfPresent("content", reqVO.getContent())
  40 + .likeIfPresent("api_template_id", reqVO.getApiTemplateId())
  41 + .eqIfPresent("channel_id", reqVO.getChannelId())
  42 + .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
  43 + .orderByDesc("id"));
  44 + }
  45 +
  46 + default Integer selectCountByChannelId(Long channelId) {
  47 + return selectCount("channel_id", channelId);
  48 + }
  49 +
  50 + @Select("SELECT id FROM sys_sms_template WHERE update_time > #{maxUpdateTime} LIMIT 1")
  51 + Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
  52 +
  53 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/RedisKeyConstants.java 0 → 100644
src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java
... ... @@ -78,4 +78,18 @@ public interface SysErrorCodeConstants {
78 78 ErrorCode FILE_UPLOAD_FAILED = new ErrorCode(1002009002, "文件上传失败");
79 79 ErrorCode FILE_IS_EMPTY= new ErrorCode(1002009003, "文件为空");
80 80  
  81 + // ========== 短信渠道 1002011000 ==========
  82 + ErrorCode SMS_CHANNEL_NOT_EXISTS = new ErrorCode(1002011000, "短信渠道不存在");
  83 + ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1002011001, "短信渠道不处于开启状态,不允许选择");
  84 + ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1002011002, "无法删除,该短信渠道还有短信模板");
  85 +
  86 + // ========== 短信模板 1002011000 ==========
  87 + ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1002011000, "短信模板不存在");
  88 + ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1002011001, "已经存在编码为【{}】的短信模板");
  89 +
  90 + // ========== 短信发送 1002012000 ==========
  91 + ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1002012000, "手机号不存在");
  92 + ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1002012001, "模板参数({})缺失");
  93 +
  94 +
81 95 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/enums/common/SysSexEnum.java
... ... @@ -3,6 +3,11 @@ package cn.iocoder.dashboard.modules.system.enums.common;
3 3 import lombok.AllArgsConstructor;
4 4 import lombok.Getter;
5 5  
  6 +/**
  7 + * 性别的枚举值
  8 + *
  9 + * @author 芋道源码
  10 + */
6 11 @Getter
7 12 @AllArgsConstructor
8 13 public enum SysSexEnum {
... ... @@ -10,6 +15,9 @@ public enum SysSexEnum {
10 15 MALE(1), // 男
11 16 FEMALE(2); // 女
12 17  
13   - private final Integer SEX;
  18 + /**
  19 + * 性别
  20 + */
  21 + private final Integer sex;
14 22  
15 23 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/enums/dict/SysDictTypeEnum.java
... ... @@ -18,6 +18,10 @@ public enum SysDictTypeEnum {
18 18 SYS_LOGIN_RESULT("sys_login_result"), // 登陆结果
19 19 SYS_CONFIG_TYPE("sys_config_type"), // 参数配置类型
20 20 SYS_BOOLEAN_STRING("sys_boolean_string"), // Boolean 是否类型
  21 + SYS_SMS_CHANNEL_CODE("sys_sms_channel_code"), // 短信渠道编码
  22 + SYS_SMS_TEMPLATE_TYPE("sys_sms_template_type"), // 短信模板类型
  23 + SYS_SMS_SEND_STATUS("sys_sms_send_status"), // 短信发送状态
  24 + SYS_SMS_RECEIVE_STATUS("sys_sms_receive_status"), // 短信接收状态
21 25  
22 26 INF_REDIS_TIMEOUT_TYPE("inf_redis_timeout_type"), // Redis 超时类型
23 27 INF_JOB_STATUS("inf_job_status"), // 定时任务状态的枚举
... ...
src/main/java/cn/iocoder/dashboard/modules/system/enums/sms/SysSmsReceiveStatusEnum.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.enums.sms;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 短信的接收状态枚举
  8 + *
  9 + * @author 芋道源码
  10 + * @date 2021/2/1 13:39
  11 + */
  12 +@Getter
  13 +@AllArgsConstructor
  14 +public enum SysSmsReceiveStatusEnum {
  15 +
  16 + INIT(0), // 初始化
  17 + SUCCESS(10), // 接收成功
  18 + FAILURE(20), // 接收失败
  19 + ;
  20 +
  21 + private final int status;
  22 +
  23 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/enums/sms/SysSmsSendStatusEnum.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.enums.sms;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 短信的发送状态枚举
  8 + *
  9 + * @author zzf
  10 + * @date 2021/2/1 13:39
  11 + */
  12 +@Getter
  13 +@AllArgsConstructor
  14 +public enum SysSmsSendStatusEnum {
  15 +
  16 + INIT(0), // 初始化
  17 + SUCCESS(10), // 发送成功
  18 + FAILURE(20), // 发送失败
  19 + IGNORE(30), // 忽略,即不发送
  20 + ;
  21 +
  22 + private final int status;
  23 +
  24 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/enums/sms/SysSmsTemplateTypeEnum.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.enums.sms;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 短信的模板类型枚举
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Getter
  12 +@AllArgsConstructor
  13 +public enum SysSmsTemplateTypeEnum {
  14 +
  15 + VERIFICATION_CODE(1), // 验证码
  16 + NOTICE(2), // 通知
  17 + PROMOTION(3), // 营销
  18 + ;
  19 +
  20 + /**
  21 + * 类型
  22 + */
  23 + private final int type;
  24 +
  25 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsChannelRefreshConsumer.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsChannelRefreshMessage;
  5 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link SysSmsChannelRefreshMessage} 的消费者
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class SysSmsChannelRefreshConsumer extends AbstractChannelMessageListener<SysSmsChannelRefreshMessage> {
  19 +
  20 + @Resource
  21 + private SysSmsChannelService smsChannelService;
  22 +
  23 + @Override
  24 + public void onMessage(SysSmsChannelRefreshMessage message) {
  25 + log.info("[onMessage][收到 SmsChannel 刷新消息]");
  26 + smsChannelService.initSmsClients();
  27 + }
  28 +
  29 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsSendConsumer.java
... ... @@ -2,16 +2,29 @@ package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener;
4 4 import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
  5 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
5 6 import lombok.extern.slf4j.Slf4j;
6 7 import org.springframework.stereotype.Component;
7 8  
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link SysSmsSendMessage} 的消费者
  13 + *
  14 + * @author zzf
  15 + * @date 2021/3/9 16:35
  16 + */
8 17 @Component
9 18 @Slf4j
10 19 public class SysSmsSendConsumer extends AbstractStreamMessageListener<SysSmsSendMessage> {
11 20  
  21 + @Resource
  22 + private SysSmsService smsService;
  23 +
12 24 @Override
13 25 public void onMessage(SysSmsSendMessage message) {
14 26 log.info("[onMessage][消息内容({})]", message);
  27 + smsService.doSendSms(message);
15 28 }
16 29  
17 30 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsTemplateRefreshConsumer.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsTemplateRefreshMessage;
  5 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link SysSmsTemplateRefreshMessage} 的消费者
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class SysSmsTemplateRefreshConsumer extends AbstractChannelMessageListener<SysSmsTemplateRefreshMessage> {
  19 +
  20 + @Resource
  21 + private SysSmsTemplateService smsTemplateService;
  22 +
  23 + @Override
  24 + public void onMessage(SysSmsTemplateRefreshMessage message) {
  25 + log.info("[onMessage][收到 SmsTemplate 刷新消息]");
  26 + smsTemplateService.initLocalCache();
  27 + }
  28 +
  29 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsChannelRefreshMessage.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.message.sms;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 短信渠道的数据刷新 Message
  8 + */
  9 +@Data
  10 +public class SysSmsChannelRefreshMessage implements ChannelMessage {
  11 +
  12 + @Override
  13 + public String getChannel() {
  14 + return "system.sms-channel.refresh";
  15 + }
  16 +
  17 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsSendMessage.java
1 1 package cn.iocoder.dashboard.modules.system.mq.message.sms;
2 2  
  3 +import cn.iocoder.dashboard.common.core.KeyValue;
3 4 import cn.iocoder.dashboard.framework.redis.core.stream.StreamMessage;
4 5 import lombok.Data;
5 6  
6 7 import javax.validation.constraints.NotNull;
7   -import java.util.Map;
  8 +import java.util.List;
8 9  
9 10 /**
10 11 * 短信发送消息
... ... @@ -15,28 +16,29 @@ import java.util.Map;
15 16 public class SysSmsSendMessage implements StreamMessage {
16 17  
17 18 /**
  19 + * 短信日志编号
  20 + */
  21 + @NotNull(message = "短信日志编号不能为空")
  22 + private Long logId;
  23 + /**
18 24 * 手机号
19 25 */
20 26 @NotNull(message = "手机号不能为空")
21 27 private String mobile;
22 28 /**
23   - * 短信模板编号
  29 + * 短信渠道编号
24 30 */
25   - @NotNull(message = "短信模板编号不能为空")
26   - private String templateCode;
  31 + @NotNull(message = "短信渠道编号不能为空")
  32 + private Long channelId;
27 33 /**
28   - * 短信模板参数
  34 + * 短信 API 的模板编号
29 35 */
30   - private Map<String, Object> templateParams;
31   -
  36 + @NotNull(message = "短信 API 的模板编号不能为空")
  37 + private String apiTemplateId;
32 38 /**
33   - * 用户编号,允许空
34   - */
35   - private Integer userId;
36   - /**
37   - * 用户类型,允许空
  39 + * 短信模板参数
38 40 */
39   - private Integer userType;
  41 + private List<KeyValue<String, Object>> templateParams;
40 42  
41 43 @Override
42 44 public String getStreamKey() {
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsTemplateRefreshMessage.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.message.sms;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 短信模板的数据刷新 Message
  8 + */
  9 +@Data
  10 +public class SysSmsTemplateRefreshMessage implements ChannelMessage {
  11 +
  12 + @Override
  13 + public String getChannel() {
  14 + return "system.sms-template.refresh";
  15 + }
  16 +
  17 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysRoleProducer.java
... ... @@ -9,6 +9,8 @@ import javax.annotation.Resource;
9 9  
10 10 /**
11 11 * Role 角色相关消息的 Producer
  12 + *
  13 + * @author 芋道源码
12 14 */
13 15 @Component
14 16 public class SysRoleProducer {
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/sms/SysSmsProducer.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.producer.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.core.KeyValue;
  4 +import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
  5 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsChannelRefreshMessage;
  6 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
  7 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsTemplateRefreshMessage;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.springframework.data.redis.core.StringRedisTemplate;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import javax.annotation.Resource;
  13 +import java.util.List;
  14 +
  15 +/**
  16 + * Sms 短信相关消息的 Producer
  17 + *
  18 + * @author zzf
  19 + * @date 2021/3/9 16:35
  20 + */
  21 +@Slf4j
  22 +@Component
  23 +public class SysSmsProducer {
  24 +
  25 + @Resource
  26 + private StringRedisTemplate stringRedisTemplate;
  27 +
  28 + /**
  29 + * 发送 {@link SysSmsSendMessage} 消息
  30 + *
  31 + * @param logId 短信日志编号
  32 + * @param mobile 手机号
  33 + * @param channelId 渠道编号
  34 + * @param apiTemplateId 短信模板编号
  35 + * @param templateParams 短信模板参数
  36 + */
  37 + public void sendSmsSendMessage(Long logId, String mobile,
  38 + Long channelId, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
  39 + SysSmsSendMessage message = new SysSmsSendMessage().setLogId(logId).setMobile(mobile);
  40 + message.setChannelId(channelId).setApiTemplateId(apiTemplateId).setTemplateParams(templateParams);
  41 + RedisMessageUtils.sendStreamMessage(stringRedisTemplate, message);
  42 + }
  43 +
  44 + /**
  45 + * 发送 {@link SysSmsChannelRefreshMessage} 消息
  46 + */
  47 + public void sendSmsChannelRefreshMessage() {
  48 + SysSmsChannelRefreshMessage message = new SysSmsChannelRefreshMessage();
  49 + RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
  50 + }
  51 +
  52 + /**
  53 + * 发送 {@link SysSmsTemplateRefreshMessage} 消息
  54 + */
  55 + public void sendSmsTemplateRefreshMessage() {
  56 + SysSmsTemplateRefreshMessage message = new SysSmsTemplateRefreshMessage();
  57 + RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
  58 + }
  59 +
  60 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java
... ... @@ -18,7 +18,7 @@ import java.util.Set;
18 18 public interface SysPermissionService extends SecurityPermissionFrameworkService {
19 19  
20 20 /**
21   - * 初始化
  21 + * 初始化权限的本地缓存
22 22 */
23 23 void initLocalCache();
24 24  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.dashboard.modules.system.service.permission.impl;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
5 5 import cn.hutool.core.util.ArrayUtil;
6   -import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
7 6 import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
8 7 import cn.iocoder.dashboard.modules.system.dal.mysql.permission.SysRoleMenuMapper;
9 8 import cn.iocoder.dashboard.modules.system.dal.mysql.permission.SysUserRoleMapper;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java
... ... @@ -59,7 +59,7 @@ public class SysRoleServiceImpl implements SysRoleService {
59 59 */
60 60 private volatile Map<Long, SysRoleDO> roleCache;
61 61 /**
62   - * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
  62 + * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
63 63 */
64 64 private volatile Date maxUpdateTime;
65 65  
... ... @@ -78,7 +78,7 @@ public class SysRoleServiceImpl implements SysRoleService {
78 78 @Override
79 79 @PostConstruct
80 80 public void initLocalCache() {
81   - // 获取菜单列表,如果有更新
  81 + // 获取角色列表,如果有更新
82 82 List<SysRoleDO> roleList = this.loadRoleIfUpdate(maxUpdateTime);
83 83 if (CollUtil.isEmpty(roleList)) {
84 84 return;
... ... @@ -99,23 +99,23 @@ public class SysRoleServiceImpl implements SysRoleService {
99 99 }
100 100  
101 101 /**
102   - * 如果菜单发生变化,从数据库中获取最新的全量菜单。
  102 + * 如果角色发生变化,从数据库中获取最新的全量角色。
103 103 * 如果未发生变化,则返回空
104 104 *
105   - * @param maxUpdateTime 当前菜单的最大更新时间
106   - * @return 菜单列表
  105 + * @param maxUpdateTime 当前角色的最大更新时间
  106 + * @return 角色列表
107 107 */
108 108 private List<SysRoleDO> loadRoleIfUpdate(Date maxUpdateTime) {
109 109 // 第一步,判断是否要更新。
110 110 if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
111   - log.info("[loadRoleIfUpdate][首次加载全量菜单]");
112   - } else { // 判断数据库中是否有更新的菜单
  111 + log.info("[loadRoleIfUpdate][首次加载全量角色]");
  112 + } else { // 判断数据库中是否有更新的角色
113 113 if (!roleMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
114 114 return null;
115 115 }
116   - log.info("[loadRoleIfUpdate][增量加载全量菜单]");
  116 + log.info("[loadRoleIfUpdate][增量加载全量角色]");
117 117 }
118   - // 第二步,如果有更新,则从数据库加载所有菜单
  118 + // 第二步,如果有更新,则从数据库加载所有角色
119 119 return roleMapper.selectList();
120 120 }
121 121  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsChannelService.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageResult;
  4 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelCreateReqVO;
  5 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelPageReqVO;
  6 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelUpdateReqVO;
  7 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
  8 +
  9 +import javax.validation.Valid;
  10 +import java.util.Collection;
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * 短信渠道Service接口
  15 + *
  16 + * @author zzf
  17 + * @date 2021/1/25 9:24
  18 + */
  19 +public interface SysSmsChannelService {
  20 +
  21 + /**
  22 + * 初始化短信客户端
  23 + */
  24 + void initSmsClients();
  25 +
  26 + /**
  27 + * 创建短信渠道
  28 + *
  29 + * @param createReqVO 创建信息
  30 + * @return 编号
  31 + */
  32 + Long createSmsChannel(@Valid SysSmsChannelCreateReqVO createReqVO);
  33 +
  34 + /**
  35 + * 更新短信渠道
  36 + *
  37 + * @param updateReqVO 更新信息
  38 + */
  39 + void updateSmsChannel(@Valid SysSmsChannelUpdateReqVO updateReqVO);
  40 +
  41 + /**
  42 + * 删除短信渠道
  43 + *
  44 + * @param id 编号
  45 + */
  46 + void deleteSmsChannel(Long id);
  47 +
  48 + /**
  49 + * 获得短信渠道
  50 + *
  51 + * @param id 编号
  52 + * @return 短信渠道
  53 + */
  54 + SysSmsChannelDO getSmsChannel(Long id);
  55 +
  56 + /**
  57 + * 获得短信渠道列表
  58 + *
  59 + * @param ids 编号
  60 + * @return 短信渠道列表
  61 + */
  62 + List<SysSmsChannelDO> getSmsChannelList(Collection<Long> ids);
  63 +
  64 + /**
  65 + * 获得所有短信渠道列表
  66 + *
  67 + * @return 短信渠道列表
  68 + */
  69 + List<SysSmsChannelDO> getSmsChannelList();
  70 +
  71 + /**
  72 + * 获得短信渠道分页
  73 + *
  74 + * @param pageReqVO 分页查询
  75 + * @return 短信渠道分页
  76 + */
  77 + PageResult<SysSmsChannelDO> getSmsChannelPage(SysSmsChannelPageReqVO pageReqVO);
  78 +
  79 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsLogService.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageResult;
  4 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogExportReqVO;
  5 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogPageReqVO;
  6 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsLogDO;
  7 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  8 +
  9 +import java.util.Date;
  10 +import java.util.List;
  11 +import java.util.Map;
  12 +
  13 +/**
  14 + * 短信日志 Service 实现类
  15 + *
  16 + * @author zzf
  17 + * @date 13:48 2021/3/2
  18 + */
  19 +public interface SysSmsLogService {
  20 +
  21 + /**
  22 + * 创建短信日志
  23 + *
  24 + * @param mobile 手机号
  25 + * @param userId 用户编号
  26 + * @param userType 用户类型
  27 + * @param isSend 是否发送
  28 + * @param template 短信模板
  29 + * @param templateContent 短信内容
  30 + * @param templateParams 短信参数
  31 + * @return 发送日志编号
  32 + */
  33 + Long createSmsLog(String mobile, Long userId, Integer userType, Boolean isSend,
  34 + SysSmsTemplateDO template, String templateContent, Map<String, Object> templateParams);
  35 +
  36 + /**
  37 + * 更新日志的发送结果
  38 + *
  39 + * @param id 日志编号
  40 + * @param sendCode 发送结果的编码
  41 + * @param sendMsg 发送结果的提示
  42 + * @param apiSendCode 短信 API 发送结果的编码
  43 + * @param apiSendMsg 短信 API 发送失败的提示
  44 + * @param apiRequestId 短信 API 发送返回的唯一请求 ID
  45 + * @param apiSerialNo 短信 API 发送返回的序号
  46 + */
  47 + void updateSmsSendResult(Long id, Integer sendCode, String sendMsg,
  48 + String apiSendCode, String apiSendMsg, String apiRequestId, String apiSerialNo);
  49 +
  50 + /**
  51 + * 更新日志的接收结果
  52 + *
  53 + * @param id 日志编号
  54 + * @param success 是否接收成功
  55 + * @param receiveTime 用户接收时间
  56 + * @param apiReceiveCode API 接收结果的编码
  57 + * @param apiReceiveMsg API 接收结果的说明
  58 + */
  59 + void updateSmsReceiveResult(Long id, Boolean success, Date receiveTime, String apiReceiveCode, String apiReceiveMsg);
  60 +
  61 + /**
  62 + * 获得短信日志分页
  63 + *
  64 + * @param pageReqVO 分页查询
  65 + * @return 短信日志分页
  66 + */
  67 + PageResult<SysSmsLogDO> getSmsLogPage(SysSmsLogPageReqVO pageReqVO);
  68 +
  69 + /**
  70 + * 获得短信日志列表, 用于 Excel 导出
  71 + *
  72 + * @param exportReqVO 查询条件
  73 + * @return 短信日志列表
  74 + */
  75 + List<SysSmsLogDO> getSmsLogList(SysSmsLogExportReqVO exportReqVO);
  76 +
  77 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsService.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
  4 +
  5 +import java.util.List;
  6 +import java.util.Map;
  7 +
  8 +/**
  9 + * 短信Service接口
  10 + * 只支持异步,因此没有返回值
  11 + *
  12 + * @author zzf
  13 + * @date 2021/1/25 9:24
  14 + */
  15 +public interface SysSmsService {
  16 +
  17 + /**
  18 + * 发送单条短信给用户(管理员)
  19 + *
  20 + * 在 mobile 为空时,使用 userId 加载对应管理员的手机号
  21 + *
  22 + * @param mobile 手机号
  23 + * @param userId 用户编号
  24 + * @param templateCode 短信模板编号
  25 + * @param templateParams 短信模板参数
  26 + * @return 发送日志编号
  27 + */
  28 + Long sendSingleSmsToAdmin(String mobile, Long userId,
  29 + String templateCode, Map<String, Object> templateParams);
  30 +
  31 + /**
  32 + * 发送单条短信给用户(会员)
  33 + *
  34 + * 在 mobile 为空时,使用 userId 加载对应会员的手机号
  35 + *
  36 + * @param mobile 手机号
  37 + * @param userId 用户编号
  38 + * @param templateCode 短信模板编号
  39 + * @param templateParams 短信模板参数
  40 + * @return 发送日志编号
  41 + */
  42 + Long sendSingleSmsToMember(String mobile, Long userId,
  43 + String templateCode, Map<String, Object> templateParams);
  44 +
  45 + Long sendSingleSms(String mobile, Long userId, Integer userType,
  46 + String templateCode, Map<String, Object> templateParams);
  47 +
  48 + void sendBatchSms(List<String> mobiles, List<Long> userIds, Integer userType,
  49 + String templateCode, Map<String, Object> templateParams);
  50 +
  51 + void doSendSms(SysSmsSendMessage message);
  52 +
  53 + /**
  54 + * 接收短信的接收结果
  55 + *
  56 + * @param channelCode 渠道编码
  57 + * @param text 结果内容
  58 + * @throws Throwable 处理失败时,抛出异常
  59 + */
  60 + void receiveSmsStatus(String channelCode, String text) throws Throwable;
  61 +
  62 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsTemplateService.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.PageResult;
  4 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateCreateReqVO;
  5 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateExportReqVO;
  6 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplatePageReqVO;
  7 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateUpdateReqVO;
  8 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  9 +
  10 +import javax.validation.Valid;
  11 +import java.util.Collection;
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +/**
  16 + * 短信模板 Service 接口
  17 + *
  18 + * @author zzf
  19 + * @date 2021/1/25 9:24
  20 + */
  21 +public interface SysSmsTemplateService {
  22 +
  23 + /**
  24 + * 初始化短信模板的本地缓存
  25 + */
  26 + void initLocalCache();
  27 +
  28 + /**
  29 + * 获得短信模板
  30 + *
  31 + * @param code 模板编码
  32 + * @return 短信模板
  33 + */
  34 + SysSmsTemplateDO getSmsTemplateByCode(String code);
  35 +
  36 + /**
  37 + * 获得短信模板,从缓存中
  38 + *
  39 + * @param code 模板编码
  40 + * @return 短信模板
  41 + */
  42 + SysSmsTemplateDO getSmsTemplateByCodeFromCache(String code);
  43 +
  44 + /**
  45 + * 格式化短信内容
  46 + *
  47 + * @param content 短信模板的内容
  48 + * @param params 内容的参数
  49 + * @return 格式化后的内容
  50 + */
  51 + String formatSmsTemplateContent(String content, Map<String, Object> params);
  52 +
  53 + /**
  54 + * 创建短信模板
  55 + *
  56 + * @param createReqVO 创建信息
  57 + * @return 编号
  58 + */
  59 + Long createSmsTemplate(@Valid SysSmsTemplateCreateReqVO createReqVO);
  60 +
  61 + /**
  62 + * 更新短信模板
  63 + *
  64 + * @param updateReqVO 更新信息
  65 + */
  66 + void updateSmsTemplate(@Valid SysSmsTemplateUpdateReqVO updateReqVO);
  67 +
  68 + /**
  69 + * 删除短信模板
  70 + *
  71 + * @param id 编号
  72 + */
  73 + void deleteSmsTemplate(Long id);
  74 +
  75 + /**
  76 + * 获得短信模板
  77 + *
  78 + * @param id 编号
  79 + * @return 短信模板
  80 + */
  81 + SysSmsTemplateDO getSmsTemplate(Long id);
  82 +
  83 + /**
  84 + * 获得短信模板列表
  85 + *
  86 + * @param ids 编号
  87 + * @return 短信模板列表
  88 + */
  89 + List<SysSmsTemplateDO> getSmsTemplateList(Collection<Long> ids);
  90 +
  91 + /**
  92 + * 获得短信模板分页
  93 + *
  94 + * @param pageReqVO 分页查询
  95 + * @return 短信模板分页
  96 + */
  97 + PageResult<SysSmsTemplateDO> getSmsTemplatePage(SysSmsTemplatePageReqVO pageReqVO);
  98 +
  99 + /**
  100 + * 获得短信模板列表, 用于 Excel 导出
  101 + *
  102 + * @param exportReqVO 查询条件
  103 + * @return 短信模板分页
  104 + */
  105 + List<SysSmsTemplateDO> getSmsTemplateList(SysSmsTemplateExportReqVO exportReqVO);
  106 +
  107 + /**
  108 + * 获得指定短信渠道下的短信模板数量
  109 + *
  110 + * @param channelId 短信渠道编号
  111 + * @return 数量
  112 + */
  113 + Integer countByChannelId(Long channelId);
  114 +
  115 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsChannelServiceImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms.impl;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.dashboard.common.pojo.PageResult;
  5 +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
  6 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
  7 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  8 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelCreateReqVO;
  9 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelPageReqVO;
  10 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelUpdateReqVO;
  11 +import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsChannelConvert;
  12 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
  13 +import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsChannelMapper;
  14 +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
  15 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
  16 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +import org.springframework.scheduling.annotation.Scheduled;
  19 +import org.springframework.stereotype.Service;
  20 +
  21 +import javax.annotation.PostConstruct;
  22 +import javax.annotation.Resource;
  23 +import java.util.Collection;
  24 +import java.util.Comparator;
  25 +import java.util.Date;
  26 +import java.util.List;
  27 +
  28 +import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
  29 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
  30 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
  31 +
  32 +/**
  33 + * 短信渠道Service实现类
  34 + *
  35 + * @author zzf
  36 + * @date 2021/1/25 9:25
  37 + */
  38 +@Service
  39 +@Slf4j
  40 +public class SysSmsChannelServiceImpl implements SysSmsChannelService {
  41 +
  42 + /**
  43 + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
  44 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
  45 + */
  46 + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
  47 +
  48 + /**
  49 + * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
  50 + */
  51 + private volatile Date maxUpdateTime;
  52 +
  53 + @Resource
  54 + private SmsClientFactory smsClientFactory;
  55 +
  56 + @Resource
  57 + private SysSmsChannelMapper smsChannelMapper;
  58 +
  59 + @Resource
  60 + private SysSmsTemplateService smsTemplateService;
  61 +
  62 + @Resource
  63 + private SysSmsProducer smsProducer;
  64 +
  65 + @Override
  66 + @PostConstruct
  67 + public void initSmsClients() {
  68 + // 获取短信渠道,如果有更新
  69 + List<SysSmsChannelDO> smsChannels = this.loadSmsChannelIfUpdate(maxUpdateTime);
  70 + if (CollUtil.isEmpty(smsChannels)) {
  71 + return;
  72 + }
  73 +
  74 + // 创建或更新短信 Client
  75 + List<SmsChannelProperties> propertiesList = SysSmsChannelConvert.INSTANCE.convertList02(smsChannels);
  76 + propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
  77 +
  78 + // 写入缓存
  79 + assert smsChannels.size() > 0; // 断言,避免告警
  80 + maxUpdateTime = smsChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
  81 + log.info("[initSmsClients][初始化 SmsChannel 数量为 {}]", smsChannels.size());
  82 + }
  83 +
  84 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  85 + public void schedulePeriodicRefresh() {
  86 + initSmsClients();
  87 + }
  88 +
  89 + /**
  90 + * 如果短信渠道发生变化,从数据库中获取最新的全量短信渠道。
  91 + * 如果未发生变化,则返回空
  92 + *
  93 + * @param maxUpdateTime 当前短信渠道的最大更新时间
  94 + * @return 短信渠道列表
  95 + */
  96 + private List<SysSmsChannelDO> loadSmsChannelIfUpdate(Date maxUpdateTime) {
  97 + // 第一步,判断是否要更新。
  98 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
  99 + log.info("[loadSmsChannelIfUpdate][首次加载全量短信渠道]");
  100 + } else { // 判断数据库中是否有更新的短信渠道
  101 + if (smsChannelMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
  102 + return null;
  103 + }
  104 + log.info("[loadSmsChannelIfUpdate][增量加载全量短信渠道]");
  105 + }
  106 + // 第二步,如果有更新,则从数据库加载所有短信渠道
  107 + return smsChannelMapper.selectList();
  108 + }
  109 +
  110 + @Override
  111 + public Long createSmsChannel(SysSmsChannelCreateReqVO createReqVO) {
  112 + // 插入
  113 + SysSmsChannelDO smsChannel = SysSmsChannelConvert.INSTANCE.convert(createReqVO);
  114 + smsChannelMapper.insert(smsChannel);
  115 + // 发送刷新消息
  116 + smsProducer.sendSmsChannelRefreshMessage();
  117 + // 返回
  118 + return smsChannel.getId();
  119 + }
  120 +
  121 + @Override
  122 + public void updateSmsChannel(SysSmsChannelUpdateReqVO updateReqVO) {
  123 + // 校验存在
  124 + this.validateSmsChannelExists(updateReqVO.getId());
  125 + // 更新
  126 + SysSmsChannelDO updateObj = SysSmsChannelConvert.INSTANCE.convert(updateReqVO);
  127 + smsChannelMapper.updateById(updateObj);
  128 + // 发送刷新消息
  129 + smsProducer.sendSmsChannelRefreshMessage();
  130 + }
  131 +
  132 + @Override
  133 + public void deleteSmsChannel(Long id) {
  134 + // 校验存在
  135 + this.validateSmsChannelExists(id);
  136 + // 校验是否有字典数据
  137 + if (smsTemplateService.countByChannelId(id) > 0) {
  138 + throw exception(SMS_CHANNEL_HAS_CHILDREN);
  139 + }
  140 + // 删除
  141 + smsChannelMapper.deleteById(id);
  142 + // 发送刷新消息
  143 + smsProducer.sendSmsChannelRefreshMessage();
  144 + }
  145 +
  146 + private void validateSmsChannelExists(Long id) {
  147 + if (smsChannelMapper.selectById(id) == null) {
  148 + throw exception(SMS_CHANNEL_NOT_EXISTS);
  149 + }
  150 + }
  151 +
  152 + @Override
  153 + public SysSmsChannelDO getSmsChannel(Long id) {
  154 + return smsChannelMapper.selectById(id);
  155 + }
  156 +
  157 + @Override
  158 + public List<SysSmsChannelDO> getSmsChannelList(Collection<Long> ids) {
  159 + return smsChannelMapper.selectBatchIds(ids);
  160 + }
  161 +
  162 + @Override
  163 + public List<SysSmsChannelDO> getSmsChannelList() {
  164 + return smsChannelMapper.selectList();
  165 + }
  166 +
  167 + @Override
  168 + public PageResult<SysSmsChannelDO> getSmsChannelPage(SysSmsChannelPageReqVO pageReqVO) {
  169 + return smsChannelMapper.selectPage(pageReqVO);
  170 + }
  171 +
  172 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsLogServiceImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms.impl;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  4 +import cn.iocoder.dashboard.common.pojo.PageResult;
  5 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogExportReqVO;
  6 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogPageReqVO;
  7 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsLogDO;
  8 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  9 +import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsLogMapper;
  10 +import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsReceiveStatusEnum;
  11 +import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
  12 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsLogService;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +import org.springframework.stereotype.Service;
  15 +
  16 +import javax.annotation.Resource;
  17 +import java.util.Date;
  18 +import java.util.List;
  19 +import java.util.Map;
  20 +import java.util.Objects;
  21 +
  22 +/**
  23 + * 短信日志 Service 实现类
  24 + *
  25 + * @author zzf
  26 + * @date 2021/1/25 9:25
  27 + */
  28 +@Slf4j
  29 +@Service
  30 +public class SysSmsLogServiceImpl implements SysSmsLogService {
  31 +
  32 + @Resource
  33 + private SysSmsLogMapper smsLogMapper;
  34 +
  35 + @Override
  36 + public Long createSmsLog(String mobile, Long userId, Integer userType, Boolean isSend,
  37 + SysSmsTemplateDO template, String templateContent, Map<String, Object> templateParams) {
  38 + SysSmsLogDO.SysSmsLogDOBuilder logBuilder = SysSmsLogDO.builder();
  39 + // 根据是否要发送,设置状态
  40 + logBuilder.sendStatus(Objects.equals(isSend, true) ? SysSmsSendStatusEnum.INIT.getStatus()
  41 + : SysSmsSendStatusEnum.IGNORE.getStatus());
  42 + // 设置手机相关字段
  43 + logBuilder.mobile(mobile).userId(userId).userType(userType);
  44 + // 设置模板相关字段
  45 + logBuilder.templateId(template.getId()).templateCode(template.getCode()).templateType(template.getType());
  46 + logBuilder.templateContent(templateContent).templateParams(templateParams).apiTemplateId(template.getApiTemplateId());
  47 + // 设置渠道相关字段
  48 + logBuilder.channelId(template.getChannelId()).channelCode(template.getChannelCode());
  49 + // 设置接收相关字段
  50 + logBuilder.receiveStatus(SysSmsReceiveStatusEnum.INIT.getStatus());
  51 +
  52 + // 插入数据库
  53 + SysSmsLogDO logDO = logBuilder.build();
  54 + smsLogMapper.insert(logDO);
  55 + return logDO.getId();
  56 + }
  57 +
  58 + @Override
  59 + public void updateSmsSendResult(Long id, Integer sendCode, String sendMsg,
  60 + String apiSendCode, String apiSendMsg, String apiRequestId, String apiSerialNo) {
  61 + SysSmsSendStatusEnum sendStatus = CommonResult.isSuccess(sendCode) ? SysSmsSendStatusEnum.SUCCESS
  62 + : SysSmsSendStatusEnum.FAILURE;
  63 + smsLogMapper.updateById(SysSmsLogDO.builder().id(id).sendStatus(sendStatus.getStatus()).sendTime(new Date())
  64 + .sendCode(sendCode).sendMsg(sendMsg).apiSendCode(apiSendCode).apiSendMsg(apiSendMsg)
  65 + .apiRequestId(apiRequestId).apiSerialNo(apiSerialNo).build());
  66 + }
  67 +
  68 + @Override
  69 + public void updateSmsReceiveResult(Long id, Boolean success, Date receiveTime, String apiReceiveCode, String apiReceiveMsg) {
  70 + SysSmsReceiveStatusEnum receiveStatus = Objects.equals(success, true) ? SysSmsReceiveStatusEnum.SUCCESS
  71 + : SysSmsReceiveStatusEnum.FAILURE;
  72 + smsLogMapper.updateById(SysSmsLogDO.builder().id(id).receiveStatus(receiveStatus.getStatus()).receiveTime(receiveTime)
  73 + .apiReceiveCode(apiReceiveCode).apiReceiveMsg(apiReceiveMsg).build());
  74 + }
  75 +
  76 + @Override
  77 + public PageResult<SysSmsLogDO> getSmsLogPage(SysSmsLogPageReqVO pageReqVO) {
  78 + return smsLogMapper.selectPage(pageReqVO);
  79 + }
  80 +
  81 + @Override
  82 + public List<SysSmsLogDO> getSmsLogList(SysSmsLogExportReqVO exportReqVO) {
  83 + return smsLogMapper.selectList(exportReqVO);
  84 + }
  85 +
  86 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms.impl;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.dashboard.common.core.KeyValue;
  6 +import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
  7 +import cn.iocoder.dashboard.common.enums.UserTypeEnum;
  8 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
  9 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
  10 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  11 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  12 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  13 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  14 +import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
  15 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
  16 +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
  17 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsLogService;
  18 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
  19 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
  20 +import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
  21 +import com.google.common.annotations.VisibleForTesting;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.springframework.stereotype.Service;
  24 +import org.springframework.util.Assert;
  25 +
  26 +import javax.annotation.Resource;
  27 +import java.util.List;
  28 +import java.util.Map;
  29 +import java.util.stream.Collectors;
  30 +
  31 +import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
  32 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
  33 +
  34 +/**
  35 + * 短信日志Service实现类
  36 + *
  37 + * @author zzf
  38 + * @date 2021/1/25 9:25
  39 + */
  40 +@Service
  41 +@Slf4j
  42 +public class SysSmsServiceImpl implements SysSmsService {
  43 +
  44 + @Resource
  45 + private SysSmsTemplateService smsTemplateService;
  46 + @Resource
  47 + private SysSmsLogService smsLogService;
  48 + @Resource
  49 + private SysSmsProducer smsProducer;
  50 + @Resource
  51 + private SmsClientFactory smsClientFactory;
  52 +
  53 + @Resource
  54 + private SysUserService userService;
  55 +
  56 + @Override
  57 + public Long sendSingleSmsToAdmin(String mobile, Long userId, String templateCode, Map<String, Object> templateParams) {
  58 + // 如果 mobile 为空,则加载用户编号对应的手机号
  59 + if (StrUtil.isEmpty(mobile)) {
  60 + SysUserDO user = userService.getUser(userId);
  61 + if (user != null) {
  62 + mobile = user.getMobile();
  63 + }
  64 + }
  65 + // 执行发送
  66 + return this.sendSingleSms(mobile, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);
  67 + }
  68 +
  69 + @Override
  70 + public Long sendSingleSmsToMember(String mobile, Long userId, String templateCode, Map<String, Object> templateParams) {
  71 + throw new UnsupportedOperationException("暂时不支持该操作,感兴趣可以实现该功能哟!");
  72 + }
  73 +
  74 + @Override
  75 + public Long sendSingleSms(String mobile, Long userId, Integer userType,
  76 + String templateCode, Map<String, Object> templateParams) {
  77 + // 校验短信模板是否合法
  78 + SysSmsTemplateDO template = this.checkSmsTemplateValid(templateCode);
  79 + // 校验手机号码是否存在
  80 + mobile = this.checkMobile(mobile);
  81 + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志
  82 + List<KeyValue<String, Object>> newTemplateParams = this.buildTemplateParams(template, templateParams);
  83 +
  84 + // 创建发送日志
  85 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()); // 如果模板被禁用,则不发送短信,只记录日志
  86 + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams);
  87 + Long sendLogId = smsLogService.createSmsLog(mobile, userId, userType, isSend, template, content, templateParams);
  88 +
  89 + // 发送 MQ 消息,异步执行发送短信
  90 + if (isSend) {
  91 + smsProducer.sendSmsSendMessage(sendLogId, mobile, template.getChannelId(), template.getApiTemplateId(), newTemplateParams);
  92 + }
  93 + return sendLogId;
  94 + }
  95 +
  96 + @Override
  97 + public void sendBatchSms(List<String> mobiles, List<Long> userIds, Integer userType,
  98 + String templateCode, Map<String, Object> templateParams) {
  99 + throw new UnsupportedOperationException("暂时不支持该操作,感兴趣可以实现该功能哟!");
  100 + }
  101 +
  102 + @VisibleForTesting
  103 + public SysSmsTemplateDO checkSmsTemplateValid(String templateCode) {
  104 + // 获得短信模板。考虑到效率,从缓存中获取
  105 + SysSmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode);
  106 + // 短信模板不存在
  107 + if (template == null) {
  108 + throw exception(SMS_TEMPLATE_NOT_EXISTS);
  109 + }
  110 + return template;
  111 + }
  112 +
  113 + /**
  114 + * 将参数模板,处理成有序的 KeyValue 数组
  115 + *
  116 + * 原因是,部分短信平台并不是使用 key 作为参数,而是数组下标,例如说腾讯云 https://cloud.tencent.com/document/product/382/39023
  117 + *
  118 + * @param template 短信模板
  119 + * @param templateParams 原始参数
  120 + * @return 处理后的参数
  121 + */
  122 + @VisibleForTesting
  123 + public List<KeyValue<String, Object>> buildTemplateParams(SysSmsTemplateDO template, Map<String, Object> templateParams) {
  124 + return template.getParams().stream().map(key -> {
  125 + Object value = templateParams.get(key);
  126 + if (value == null) {
  127 + throw exception(SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, key);
  128 + }
  129 + return new KeyValue<>(key, value);
  130 + }).collect(Collectors.toList());
  131 + }
  132 +
  133 + @VisibleForTesting
  134 + public String checkMobile(String mobile) {
  135 + if (StrUtil.isEmpty(mobile)) {
  136 + throw exception(SMS_SEND_MOBILE_NOT_EXISTS);
  137 + }
  138 + return mobile;
  139 + }
  140 +
  141 + @Override
  142 + public void doSendSms(SysSmsSendMessage message) {
  143 + // 获得渠道对应的 SmsClient 客户端
  144 + SmsClient smsClient = smsClientFactory.getSmsClient(message.getChannelId());
  145 + Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", message.getChannelId()));
  146 + // 发送短信
  147 + SmsCommonResult<SmsSendRespDTO> sendResult = smsClient.sendSms(message.getLogId(), message.getMobile(),
  148 + message.getApiTemplateId(), message.getTemplateParams());
  149 + smsLogService.updateSmsSendResult(message.getLogId(), sendResult.getCode(), sendResult.getMsg(),
  150 + sendResult.getApiCode(), sendResult.getApiMsg(), sendResult.getApiRequestId(),
  151 + sendResult.getData() != null ? sendResult.getData().getSerialNo() : null);
  152 + }
  153 +
  154 + @Override
  155 + public void receiveSmsStatus(String channelCode, String text) throws Throwable {
  156 + // 获得渠道对应的 SmsClient 客户端
  157 + SmsClient smsClient = smsClientFactory.getSmsClient(channelCode);
  158 + Assert.notNull(smsClient, String.format("短信客户端(%s) 不存在", channelCode));
  159 + // 解析内容
  160 + List<SmsReceiveRespDTO> receiveResults = smsClient.parseSmsReceiveStatus(text);
  161 + if (CollUtil.isEmpty(receiveResults)) {
  162 + return;
  163 + }
  164 + // 更新短信日志的接收结果. 因为量一般不打,所以先使用 for 循环更新
  165 + receiveResults.forEach(result -> {
  166 + smsLogService.updateSmsReceiveResult(result.getLogId(), result.getSuccess(), result.getReceiveTime(),
  167 + result.getErrorCode(), result.getErrorCode());
  168 + });
  169 + }
  170 +
  171 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsTemplateServiceImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms.impl;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ReUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
  7 +import cn.iocoder.dashboard.common.pojo.PageResult;
  8 +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
  9 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
  10 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
  11 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  12 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  13 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateCreateReqVO;
  14 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateExportReqVO;
  15 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplatePageReqVO;
  16 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateUpdateReqVO;
  17 +import cn.iocoder.dashboard.modules.system.convert.sms.SysSmsTemplateConvert;
  18 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
  19 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  20 +import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsTemplateMapper;
  21 +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
  22 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
  23 +import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
  24 +import com.google.common.annotations.VisibleForTesting;
  25 +import com.google.common.collect.ImmutableMap;
  26 +import lombok.extern.slf4j.Slf4j;
  27 +import org.springframework.scheduling.annotation.Scheduled;
  28 +import org.springframework.stereotype.Service;
  29 +import org.springframework.util.Assert;
  30 +
  31 +import javax.annotation.PostConstruct;
  32 +import javax.annotation.Resource;
  33 +import java.util.*;
  34 +import java.util.regex.Pattern;
  35 +
  36 +import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
  37 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
  38 +
  39 +/**
  40 + * 短信模板Service实现类
  41 + *
  42 + * @author zzf
  43 + * @date 2021/1/25 9:25
  44 + */
  45 +@Service
  46 +@Slf4j
  47 +public class SysSmsTemplateServiceImpl implements SysSmsTemplateService {
  48 +
  49 + /**
  50 + * 正则表达式,匹配 {} 中的变量
  51 + */
  52 + private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}");
  53 +
  54 + /**
  55 + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
  56 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
  57 + */
  58 + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
  59 +
  60 + /**
  61 + * 短信模板缓存
  62 + * key:短信模板编码 {@link SysSmsTemplateDO#getCode()}
  63 + *
  64 + * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
  65 + */
  66 + private volatile Map<String, SysSmsTemplateDO> smsTemplateCache;
  67 + /**
  68 + * 缓存短信模板的最大更新时间,用于后续的增量轮询,判断是否有更新
  69 + */
  70 + private volatile Date maxUpdateTime;
  71 +
  72 + @Resource
  73 + private SysSmsTemplateMapper smsTemplateMapper;
  74 +
  75 + @Resource
  76 + private SysSmsChannelService smsChannelService;
  77 +
  78 + @Resource
  79 + private SmsClientFactory smsClientFactory;
  80 +
  81 + @Resource
  82 + private SysSmsProducer smsProducer;
  83 +
  84 + /**
  85 + * 初始化 {@link #smsTemplateCache} 缓存
  86 + */
  87 + @Override
  88 + @PostConstruct
  89 + public void initLocalCache() {
  90 + // 获取短信模板列表,如果有更新
  91 + List<SysSmsTemplateDO> smsTemplateList = this.loadSmsTemplateIfUpdate(maxUpdateTime);
  92 + if (CollUtil.isEmpty(smsTemplateList)) {
  93 + return;
  94 + }
  95 +
  96 + // 写入缓存
  97 + ImmutableMap.Builder<String, SysSmsTemplateDO> builder = ImmutableMap.builder();
  98 + smsTemplateList.forEach(sysSmsTemplateDO -> builder.put(sysSmsTemplateDO.getCode(), sysSmsTemplateDO));
  99 + smsTemplateCache = builder.build();
  100 + assert smsTemplateList.size() > 0; // 断言,避免告警
  101 + maxUpdateTime = smsTemplateList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
  102 + log.info("[initLocalCache][初始化 SmsTemplate 数量为 {}]", smsTemplateList.size());
  103 + }
  104 +
  105 + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
  106 + public void schedulePeriodicRefresh() {
  107 + initLocalCache();
  108 + }
  109 +
  110 + /**
  111 + * 如果短信模板发生变化,从数据库中获取最新的全量短信模板。
  112 + * 如果未发生变化,则返回空
  113 + *
  114 + * @param maxUpdateTime 当前短信模板的最大更新时间
  115 + * @return 短信模板列表
  116 + */
  117 + private List<SysSmsTemplateDO> loadSmsTemplateIfUpdate(Date maxUpdateTime) {
  118 + // 第一步,判断是否要更新。
  119 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
  120 + log.info("[loadSmsTemplateIfUpdate][首次加载全量短信模板]");
  121 + } else { // 判断数据库中是否有更新的短信模板
  122 + if (smsTemplateMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
  123 + return null;
  124 + }
  125 + log.info("[loadSmsTemplateIfUpdate][增量加载全量短信模板]");
  126 + }
  127 + // 第二步,如果有更新,则从数据库加载所有短信模板
  128 + return smsTemplateMapper.selectList();
  129 + }
  130 +
  131 + @Override
  132 + public SysSmsTemplateDO getSmsTemplateByCode(String code) {
  133 + return smsTemplateMapper.selectByCode(code);
  134 + }
  135 +
  136 + @Override
  137 + public SysSmsTemplateDO getSmsTemplateByCodeFromCache(String code) {
  138 + return smsTemplateCache.get(code);
  139 + }
  140 +
  141 + @Override
  142 + public String formatSmsTemplateContent(String content, Map<String, Object> params) {
  143 + return StrUtil.format(content, params);
  144 + }
  145 +
  146 + @VisibleForTesting
  147 + public List<String> parseTemplateContentParams(String content) {
  148 + return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
  149 + }
  150 +
  151 + @Override
  152 + public Long createSmsTemplate(SysSmsTemplateCreateReqVO createReqVO) {
  153 + // 校验短信渠道
  154 + SysSmsChannelDO channelDO = checkSmsChannel(createReqVO.getChannelId());
  155 + // 校验短信编码是否重复
  156 + checkSmsTemplateCodeDuplicate(null, createReqVO.getCode());
  157 + // 校验短信模板
  158 + checkApiTemplate(createReqVO.getChannelId(), createReqVO.getApiTemplateId());
  159 +
  160 + // 插入
  161 + SysSmsTemplateDO template = SysSmsTemplateConvert.INSTANCE.convert(createReqVO);
  162 + template.setParams(parseTemplateContentParams(template.getContent()));
  163 + template.setChannelCode(channelDO.getCode());
  164 + smsTemplateMapper.insert(template);
  165 + // 发送刷新消息
  166 + smsProducer.sendSmsTemplateRefreshMessage();
  167 + // 返回
  168 + return template.getId();
  169 + }
  170 +
  171 + @Override
  172 + public void updateSmsTemplate(SysSmsTemplateUpdateReqVO updateReqVO) {
  173 + // 校验存在
  174 + this.validateSmsTemplateExists(updateReqVO.getId());
  175 + // 校验短信渠道
  176 + SysSmsChannelDO channelDO = checkSmsChannel(updateReqVO.getChannelId());
  177 + // 校验短信编码是否重复
  178 + checkSmsTemplateCodeDuplicate(updateReqVO.getId(), updateReqVO.getCode());
  179 + // 校验短信模板
  180 + checkApiTemplate(updateReqVO.getChannelId(), updateReqVO.getApiTemplateId());
  181 +
  182 + // 更新
  183 + SysSmsTemplateDO updateObj = SysSmsTemplateConvert.INSTANCE.convert(updateReqVO);
  184 + updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
  185 + updateObj.setChannelCode(channelDO.getCode());
  186 + smsTemplateMapper.updateById(updateObj);
  187 + // 发送刷新消息
  188 + smsProducer.sendSmsTemplateRefreshMessage();
  189 + }
  190 +
  191 + @Override
  192 + public void deleteSmsTemplate(Long id) {
  193 + // 校验存在
  194 + this.validateSmsTemplateExists(id);
  195 + // 更新
  196 + smsTemplateMapper.deleteById(id);
  197 + // 发送刷新消息
  198 + smsProducer.sendSmsTemplateRefreshMessage();
  199 + }
  200 +
  201 + private void validateSmsTemplateExists(Long id) {
  202 + if (smsTemplateMapper.selectById(id) == null) {
  203 + throw exception(SMS_TEMPLATE_NOT_EXISTS);
  204 + }
  205 + }
  206 +
  207 + @Override
  208 + public SysSmsTemplateDO getSmsTemplate(Long id) {
  209 + return smsTemplateMapper.selectById(id);
  210 + }
  211 +
  212 + @Override
  213 + public List<SysSmsTemplateDO> getSmsTemplateList(Collection<Long> ids) {
  214 + return smsTemplateMapper.selectBatchIds(ids);
  215 + }
  216 +
  217 + @Override
  218 + public PageResult<SysSmsTemplateDO> getSmsTemplatePage(SysSmsTemplatePageReqVO pageReqVO) {
  219 + return smsTemplateMapper.selectPage(pageReqVO);
  220 + }
  221 +
  222 + @Override
  223 + public List<SysSmsTemplateDO> getSmsTemplateList(SysSmsTemplateExportReqVO exportReqVO) {
  224 + return smsTemplateMapper.selectList(exportReqVO);
  225 + }
  226 +
  227 + @Override
  228 + public Integer countByChannelId(Long channelId) {
  229 + return smsTemplateMapper.selectCountByChannelId(channelId);
  230 + }
  231 +
  232 + @VisibleForTesting
  233 + public SysSmsChannelDO checkSmsChannel(Long channelId) {
  234 + SysSmsChannelDO channelDO = smsChannelService.getSmsChannel(channelId);
  235 + if (channelDO == null) {
  236 + throw exception(SMS_CHANNEL_NOT_EXISTS);
  237 + }
  238 + if (!Objects.equals(channelDO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
  239 + throw exception(SMS_CHANNEL_DISABLE);
  240 + }
  241 + return channelDO;
  242 + }
  243 +
  244 + @VisibleForTesting
  245 + public void checkSmsTemplateCodeDuplicate(Long id, String code) {
  246 + SysSmsTemplateDO template = smsTemplateMapper.selectByCode(code);
  247 + if (template == null) {
  248 + return;
  249 + }
  250 + // 如果 id 为空,说明不用比较是否为相同 id 的字典类型
  251 + if (id == null) {
  252 + throw exception(SMS_TEMPLATE_CODE_DUPLICATE, code);
  253 + }
  254 + if (!template.getId().equals(id)) {
  255 + throw exception(SMS_TEMPLATE_CODE_DUPLICATE, code);
  256 + }
  257 + }
  258 +
  259 + /**
  260 + * 校验 API 短信平台的模板是否有效
  261 + *
  262 + * @param channelId 渠道编号
  263 + * @param apiTemplateId API 模板编号
  264 + */
  265 + @VisibleForTesting
  266 + public void checkApiTemplate(Long channelId, String apiTemplateId) {
  267 + // 获得短信模板
  268 + SmsClient smsClient = smsClientFactory.getSmsClient(channelId);
  269 + Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", channelId));
  270 + SmsCommonResult<SmsTemplateRespDTO> templateResult = smsClient.getSmsTemplate(apiTemplateId);
  271 + // 校验短信模板是否正确
  272 + templateResult.checkError();
  273 + }
  274 +
  275 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java
... ... @@ -6,17 +6,11 @@ import cn.hutool.core.util.IdUtil;
6 6 import cn.hutool.core.util.StrUtil;
7 7 import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
8 8 import cn.iocoder.dashboard.common.exception.ServiceException;
9   -import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
10 9 import cn.iocoder.dashboard.common.pojo.PageResult;
11 10 import cn.iocoder.dashboard.modules.infra.service.file.InfFileService;
12 11 import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdatePasswordReqVO;
13 12 import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdateReqVO;
14   -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserCreateReqVO;
15   -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserExportReqVO;
16   -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserImportExcelVO;
17   -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserImportRespVO;
18   -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserPageReqVO;
19   -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO;
  13 +import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*;
20 14 import cn.iocoder.dashboard.modules.system.convert.user.SysUserConvert;
21 15 import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysDeptDO;
22 16 import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysPostDO;
... ... @@ -26,21 +20,18 @@ import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
26 20 import cn.iocoder.dashboard.modules.system.service.dept.SysPostService;
27 21 import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
28 22 import cn.iocoder.dashboard.util.collection.CollectionUtils;
  23 +import com.google.common.annotations.VisibleForTesting;
29 24 import lombok.extern.slf4j.Slf4j;
  25 +import org.springframework.beans.factory.annotation.Value;
30 26 import org.springframework.security.crypto.password.PasswordEncoder;
31 27 import org.springframework.stereotype.Service;
32 28 import org.springframework.transaction.annotation.Transactional;
33 29  
34 30 import javax.annotation.Resource;
35 31 import java.io.InputStream;
36   -import java.util.ArrayList;
37   -import java.util.Collection;
38   -import java.util.Collections;
39   -import java.util.LinkedHashMap;
40   -import java.util.List;
41   -import java.util.Map;
42   -import java.util.Set;
  32 +import java.util.*;
43 33  
  34 +import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
44 35 import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
45 36  
46 37  
... ... @@ -53,6 +44,9 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
53 44 @Slf4j
54 45 public class SysUserServiceImpl implements SysUserService {
55 46  
  47 + @Value("${sys.user.init-password:yudaoyuanma}")
  48 + private String userInitPassword;
  49 +
56 50 @Resource
57 51 private SysUserMapper userMapper;
58 52  
... ... @@ -142,8 +136,6 @@ public class SysUserServiceImpl implements SysUserService {
142 136 updateObj.setId(id);
143 137 updateObj.setStatus(status);
144 138 userMapper.updateById(updateObj);
145   - // 删除用户关联数据
146   - permissionService.processUserDeleted(id);
147 139 }
148 140  
149 141 @Override
... ... @@ -152,6 +144,8 @@ public class SysUserServiceImpl implements SysUserService {
152 144 this.checkUserExists(id);
153 145 // 删除用户
154 146 userMapper.deleteById(id);
  147 + // 删除用户关联数据
  148 + permissionService.processUserDeleted(id);
155 149 }
156 150  
157 151 @Override
... ... @@ -221,18 +215,20 @@ public class SysUserServiceImpl implements SysUserService {
221 215 this.checkPostEnable(postIds);
222 216 }
223 217  
224   - private void checkUserExists(Long id) {
  218 + @VisibleForTesting
  219 + void checkUserExists(Long id) {
225 220 if (id == null) {
226 221 return;
227 222 }
228 223 SysUserDO user = userMapper.selectById(id);
229 224 if (user == null) {
230   - throw ServiceExceptionUtil.exception(USER_NOT_EXISTS);
  225 + throw exception(USER_NOT_EXISTS);
231 226 }
232 227 }
233 228  
234   - private void checkUsernameUnique(Long id, String username) {
235   - if (StrUtil.isNotBlank(username)) {
  229 + @VisibleForTesting
  230 + void checkUsernameUnique(Long id, String username) {
  231 + if (StrUtil.isBlank(username)) {
236 232 return;
237 233 }
238 234 SysUserDO user = userMapper.selectByUsername(username);
... ... @@ -241,15 +237,16 @@ public class SysUserServiceImpl implements SysUserService {
241 237 }
242 238 // 如果 id 为空,说明不用比较是否为相同 id 的用户
243 239 if (id == null) {
244   - throw ServiceExceptionUtil.exception(USER_USERNAME_EXISTS);
  240 + throw exception(USER_USERNAME_EXISTS);
245 241 }
246 242 if (!user.getId().equals(id)) {
247   - throw ServiceExceptionUtil.exception(USER_USERNAME_EXISTS);
  243 + throw exception(USER_USERNAME_EXISTS);
248 244 }
249 245 }
250 246  
251   - private void checkEmailUnique(Long id, String email) {
252   - if (StrUtil.isNotBlank(email)) {
  247 + @VisibleForTesting
  248 + void checkEmailUnique(Long id, String email) {
  249 + if (StrUtil.isBlank(email)) {
253 250 return;
254 251 }
255 252 SysUserDO user = userMapper.selectByEmail(email);
... ... @@ -258,59 +255,62 @@ public class SysUserServiceImpl implements SysUserService {
258 255 }
259 256 // 如果 id 为空,说明不用比较是否为相同 id 的用户
260 257 if (id == null) {
261   - throw ServiceExceptionUtil.exception(USER_EMAIL_EXISTS);
  258 + throw exception(USER_EMAIL_EXISTS);
262 259 }
263 260 if (!user.getId().equals(id)) {
264   - throw ServiceExceptionUtil.exception(USER_EMAIL_EXISTS);
  261 + throw exception(USER_EMAIL_EXISTS);
265 262 }
266 263 }
267 264  
268   - private void checkMobileUnique(Long id, String email) {
269   - if (StrUtil.isNotBlank(email)) {
  265 + @VisibleForTesting
  266 + void checkMobileUnique(Long id, String mobile) {
  267 + if (StrUtil.isBlank(mobile)) {
270 268 return;
271 269 }
272   - SysUserDO user = userMapper.selectByMobile(email);
  270 + SysUserDO user = userMapper.selectByMobile(mobile);
273 271 if (user == null) {
274 272 return;
275 273 }
276 274 // 如果 id 为空,说明不用比较是否为相同 id 的用户
277 275 if (id == null) {
278   - throw ServiceExceptionUtil.exception(USER_MOBILE_EXISTS);
  276 + throw exception(USER_MOBILE_EXISTS);
279 277 }
280 278 if (!user.getId().equals(id)) {
281   - throw ServiceExceptionUtil.exception(USER_MOBILE_EXISTS);
  279 + throw exception(USER_MOBILE_EXISTS);
282 280 }
283 281 }
284 282  
285   - private void checkDeptEnable(Long deptId) {
  283 + @VisibleForTesting
  284 + void checkDeptEnable(Long deptId) {
286 285 if (deptId == null) { // 允许不选择
287 286 return;
288 287 }
289 288 SysDeptDO dept = deptService.getDept(deptId);
290 289 if (dept == null) {
291   - throw ServiceExceptionUtil.exception(DEPT_NOT_FOUND);
  290 + throw exception(DEPT_NOT_FOUND);
292 291 }
293 292 if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) {
294   - throw ServiceExceptionUtil.exception(DEPT_NOT_ENABLE);
  293 + throw exception(DEPT_NOT_ENABLE);
295 294 }
296 295 }
297 296  
298   - private void checkPostEnable(Set<Long> postIds) {
  297 + @VisibleForTesting
  298 + void checkPostEnable(Set<Long> postIds) {
299 299 if (CollUtil.isEmpty(postIds)) { // 允许不选择
300 300 return;
301 301 }
302 302 List<SysPostDO> posts = postService.getPosts(postIds, null);
303 303 if (CollUtil.isEmpty(posts)) {
304   - throw ServiceExceptionUtil.exception(POST_NOT_FOUND);
  304 + throw exception(POST_NOT_FOUND);
305 305 }
306 306 Map<Long, SysPostDO> postMap = CollectionUtils.convertMap(posts, SysPostDO::getId);
307 307 postIds.forEach(postId -> {
308 308 SysPostDO post = postMap.get(postId);
309 309 if (post == null) {
310   - throw ServiceExceptionUtil.exception(POST_NOT_FOUND);
  310 + throw exception(POST_NOT_FOUND);
311 311 }
312 312 if (!CommonStatusEnum.ENABLE.getStatus().equals(post.getStatus())) {
313   - throw ServiceExceptionUtil.exception(POST_NOT_ENABLE, post.getName());
  313 + throw exception(POST_NOT_ENABLE, post.getName());
314 314 }
315 315 });
316 316 }
... ... @@ -321,13 +321,14 @@ public class SysUserServiceImpl implements SysUserService {
321 321 * @param id 用户 id
322 322 * @param oldPassword 旧密码
323 323 */
324   - private void checkOldPassword(Long id, String oldPassword) {
  324 + @VisibleForTesting
  325 + void checkOldPassword(Long id, String oldPassword) {
325 326 SysUserDO user = userMapper.selectById(id);
326 327 if (user == null) {
327   - throw ServiceExceptionUtil.exception(USER_NOT_EXISTS);
  328 + throw exception(USER_NOT_EXISTS);
328 329 }
329 330 if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
330   - throw ServiceExceptionUtil.exception(USER_PASSWORD_FAILED);
  331 + throw exception(USER_PASSWORD_FAILED);
331 332 }
332 333 }
333 334  
... ... @@ -335,7 +336,7 @@ public class SysUserServiceImpl implements SysUserService {
335 336 @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
336 337 public SysUserImportRespVO importUsers(List<SysUserImportExcelVO> importUsers, boolean isUpdateSupport) {
337 338 if (CollUtil.isEmpty(importUsers)) {
338   - throw ServiceExceptionUtil.exception(USER_IMPORT_LIST_IS_EMPTY);
  339 + throw exception(USER_IMPORT_LIST_IS_EMPTY);
339 340 }
340 341 SysUserImportRespVO respVO = SysUserImportRespVO.builder().createUsernames(new ArrayList<>())
341 342 .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();
... ... @@ -351,14 +352,14 @@ public class SysUserServiceImpl implements SysUserService {
351 352 // 判断如果不存在,在进行插入
352 353 SysUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
353 354 if (existUser == null) {
354   - // TODO 芋艿:初始密码
355   - userMapper.insert(SysUserConvert.INSTANCE.convert(importUser));
  355 + userMapper.insert(SysUserConvert.INSTANCE.convert(importUser)
  356 + .setPassword(passwordEncoder.encode(userInitPassword))); // 设置默认密码
356 357 respVO.getCreateUsernames().add(importUser.getUsername());
357 358 return;
358 359 }
359 360 // 如果存在,判断是否允许更新
360 361 if (!isUpdateSupport) {
361   - respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMessage());
  362 + respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg());
362 363 return;
363 364 }
364 365 SysUserDO updateUser = SysUserConvert.INSTANCE.convert(importUser);
... ...
src/main/java/cn/iocoder/dashboard/modules/tool/dal/mysql/codegen/ToolSchemaColumnMapper.java
... ... @@ -10,9 +10,10 @@ import java.util.List;
10 10 @Mapper
11 11 public interface ToolSchemaColumnMapper extends BaseMapperX<ToolSchemaColumnDO> {
12 12  
13   - default List<ToolSchemaColumnDO> selectListByTableName(String tableName) {
  13 + default List<ToolSchemaColumnDO> selectListByTableName(String tableSchema, String tableName) {
14 14 return selectList(new QueryWrapper<ToolSchemaColumnDO>().eq("table_name", tableName)
15   - .orderByAsc("ordinal_position"));
  15 + .eq("table_schema", tableSchema)
  16 + .orderByAsc("ordinal_position"));
16 17 }
17 18  
18 19 }
... ...
src/main/java/cn/iocoder/dashboard/modules/tool/dal/mysql/codegen/ToolSchemaTableMapper.java
... ... @@ -22,8 +22,9 @@ public interface ToolSchemaTableMapper extends BaseMapperX&lt;ToolSchemaTableDO&gt; {
22 22 return selectList(new QueryWrapper<ToolSchemaTableDO>().eq("table_schema", tableSchema));
23 23 }
24 24  
25   - default ToolSchemaTableDO selectByTableName(String tableName) {
26   - return selectOne(new QueryWrapper<ToolSchemaTableDO>().eq("table_name", tableName));
  25 + default ToolSchemaTableDO selectByTableName1(String tableSchema, String tableName) {
  26 + return selectOne(new QueryWrapper<ToolSchemaTableDO>().eq("table_schema",tableSchema)
  27 + .eq("table_name", tableName));
27 28 }
28 29  
29 30 }
... ...
src/main/java/cn/iocoder/dashboard/modules/tool/service/codegen/impl/ToolCodegenServiceImpl.java
... ... @@ -101,9 +101,11 @@ public class ToolCodegenServiceImpl implements ToolCodegenService {
101 101  
102 102 @Override
103 103 public Long createCodegen(String tableName) {
  104 + //获取当前schema
  105 + String tableSchema = codegenProperties.getDbSchemas().iterator().next();
104 106 // 从数据库中,获得数据库表结构
105   - ToolSchemaTableDO schemaTable = schemaTableMapper.selectByTableName(tableName);
106   - List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(tableName);
  107 + ToolSchemaTableDO schemaTable = schemaTableMapper.selectByTableName1(tableSchema, tableName);
  108 + List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(tableSchema, tableName);
107 109 // 导入
108 110 return this.createCodegen0(ToolCodegenImportTypeEnum.DB, schemaTable, schemaColumns);
109 111 }
... ... @@ -141,8 +143,9 @@ public class ToolCodegenServiceImpl implements ToolCodegenService {
141 143 if (table == null) {
142 144 throw exception(CODEGEN_TABLE_NOT_EXISTS);
143 145 }
  146 + String tableSchema = codegenProperties.getDbSchemas().iterator().next();
144 147 // 从数据库中,获得数据库表结构
145   - List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(table.getTableName());
  148 + List<ToolSchemaColumnDO> schemaColumns = schemaColumnMapper.selectListByTableName(tableSchema, table.getTableName());
146 149  
147 150 // 执行同步
148 151 this.syncCodegen0(tableId, schemaColumns);
... ...
src/main/java/cn/iocoder/dashboard/util/collection/MapUtils.java
... ... @@ -2,6 +2,8 @@ package cn.iocoder.dashboard.util.collection;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
  5 +import cn.iocoder.dashboard.common.core.KeyValue;
  6 +import com.google.common.collect.Maps;
5 7 import com.google.common.collect.Multimap;
6 8  
7 9 import java.util.ArrayList;
... ... @@ -55,4 +57,10 @@ public class MapUtils {
55 57 consumer.accept(value);
56 58 }
57 59  
  60 + public static <K, V> Map<K, V> convertMap(List<KeyValue<K, V>> keyValues) {
  61 + Map<K, V> map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size());
  62 + keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue()));
  63 + return map;
  64 + }
  65 +
58 66 }
... ...
src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java
... ... @@ -9,6 +9,11 @@ import java.util.Date;
9 9 */
10 10 public class DateUtils {
11 11  
  12 + /**
  13 + * 时区 - 默认
  14 + */
  15 + public static final String TIME_ZONE_DEFAULT = "GMT+8";
  16 +
12 17 public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
13 18  
14 19 public static Date addTime(Duration duration) {
... ...
src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java
... ... @@ -7,7 +7,8 @@ import com.fasterxml.jackson.core.type.TypeReference;
7 7 import com.fasterxml.jackson.databind.ObjectMapper;
8 8  
9 9 import java.io.IOException;
10   -import java.util.Set;
  10 +import java.util.ArrayList;
  11 +import java.util.List;
11 12  
12 13 /**
13 14 * JSON 工具类
... ... @@ -20,7 +21,7 @@ public class JsonUtils {
20 21  
21 22 /**
22 23 * 初始化 objectMapper 属性
23   - *
  24 + * <p>
24 25 * 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
25 26 *
26 27 * @param objectMapper ObjectMapper 对象
... ... @@ -59,7 +60,7 @@ public class JsonUtils {
59 60 }
60 61 }
61 62  
62   - public static Object parseObject(String text, TypeReference<Set<Long>> typeReference) {
  63 + public static <T> T parseObject(String text, TypeReference<T> typeReference) {
63 64 try {
64 65 return objectMapper.readValue(text, typeReference);
65 66 } catch (IOException e) {
... ... @@ -67,4 +68,15 @@ public class JsonUtils {
67 68 }
68 69 }
69 70  
  71 + public static <T> List<T> parseArray(String text, Class<T> clazz) {
  72 + if (StrUtil.isEmpty(text)) {
  73 + return new ArrayList<>();
  74 + }
  75 + try {
  76 + return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
  77 + } catch (IOException e) {
  78 + throw new RuntimeException(e);
  79 + }
  80 + }
  81 +
70 82 }
... ...
src/main/java/cn/iocoder/dashboard/util/string/StrUtils.java
1 1 package cn.iocoder.dashboard.util.string;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
3 4 import cn.hutool.core.util.StrUtil;
4 5  
  6 +import java.util.Map;
  7 +
5 8 /**
6 9 * 字符串工具类
7 10 *
... ... @@ -13,4 +16,22 @@ public class StrUtils {
13 16 return StrUtil.maxLength(str, maxLength - 3); // -3 的原因,是该方法会补充 ... 恰好
14 17 }
15 18  
  19 + /**
  20 + * 指定字符串的
  21 + * @param str
  22 + * @param replaceMap
  23 + * @return
  24 + */
  25 + public static String replace(String str, Map<String, String> replaceMap) {
  26 + assert StrUtil.isNotBlank(str);
  27 + if (ObjectUtil.isEmpty(replaceMap)) {
  28 + return str;
  29 + }
  30 + String result = null;
  31 + for (String key : replaceMap.keySet()) {
  32 + result = str.replace(key, replaceMap.get(key));
  33 + }
  34 + return result;
  35 + }
  36 +
16 37 }
... ...
src/main/resources/application-dev.yaml
... ... @@ -187,3 +187,4 @@ yudao:
187 187 exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
188 188 - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
189 189 - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
  190 + demo: true # 开启演示模式
... ...
src/main/resources/application-local.yaml
... ... @@ -185,6 +185,7 @@ yudao:
185 185 exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
186 186 - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
187 187 - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
  188 + demo: false # 关闭演示模式
188 189  
189 190  
190 191  
... ...
src/main/resources/codegen/java/service/service.vm
... ... @@ -63,7 +63,7 @@ public interface ${table.className}Service {
63 63 * 获得${table.classComment}列表, 用于 Excel 导出
64 64 *
65 65 * @param exportReqVO 查询条件
66   - * @return ${table.classComment}分页
  66 + * @return ${table.classComment}列表
67 67 */
68 68 List<${table.className}DO> get${simpleClassName}List(${table.className}ExportReqVO exportReqVO);
69 69  
... ...
src/main/resources/codegen/vue/views/index.vue.vm
... ... @@ -76,7 +76,7 @@
76 76 <template slot-scope="scope">
77 77 <span>{{ getDictDataLabel(DICT_TYPE.$dictType.toUpperCase(), scope.row.${column.javaField}) }}</span>
78 78 </template>
79   - </el-table-column>>
  79 + </el-table-column>
80 80 #else
81 81 <el-table-column label="${comment}" align="center" prop="${javaField}" />
82 82 #end
... ... @@ -137,7 +137,7 @@
137 137 </el-select>
138 138 </el-form-item>
139 139 #elseif($column.htmlType == "checkbox")## 多选框
140   - <el-form-item label="${comment}">
  140 + <el-form-item label="${comment}" prop="${javaField}">
141 141 <el-checkbox-group v-model="form.${javaField}">
142 142 #if ("" != $dictType)## 有数据字典
143 143 <el-checkbox v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
... ... @@ -148,7 +148,7 @@
148 148 </el-checkbox-group>
149 149 </el-form-item>
150 150 #elseif($column.htmlType == "radio")## 单选框
151   - <el-form-item label="${comment}">
  151 + <el-form-item label="${comment}" prop="${javaField}">
152 152 <el-radio-group v-model="form.${javaField}">
153 153 #if ("" != $dictType)## 有数据字典
154 154 <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
... ...
src/test-integration/java/cn/iocoder/dashboard/BaseDbAndRedisIntegrationTest.java 0 → 100644
  1 +package cn.iocoder.dashboard;
  2 +
  3 +import cn.iocoder.dashboard.framework.datasource.config.DataSourceConfiguration;
  4 +import cn.iocoder.dashboard.framework.mybatis.config.MybatisConfiguration;
  5 +import cn.iocoder.dashboard.framework.redis.config.RedisConfig;
  6 +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
  7 +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
  8 +import org.redisson.spring.starter.RedissonAutoConfiguration;
  9 +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
  10 +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
  11 +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
  12 +import org.springframework.boot.test.context.SpringBootTest;
  13 +import org.springframework.context.annotation.Import;
  14 +import org.springframework.test.context.ActiveProfiles;
  15 +
  16 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisIntegrationTest.Application.class)
  17 +@ActiveProfiles("integration-test") // 设置使用 application-integration-test 配置文件
  18 +public class BaseDbAndRedisIntegrationTest {
  19 +
  20 + @Import({
  21 + // DB 配置类
  22 + DataSourceConfiguration.class, // 自己的 DB 配置类
  23 + DataSourceAutoConfiguration.class, // Spring DB 自动配置类
  24 + DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
  25 + DruidDataSourceAutoConfigure.class, // Druid 自动配置类
  26 + // MyBatis 配置类
  27 + MybatisConfiguration.class, // 自己的 MyBatis 配置类
  28 + MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
  29 + // Redis 配置类
  30 + RedisAutoConfiguration.class, // Spring Redis 自动配置类
  31 + RedisConfig.class, // 自己的 Redis 配置类
  32 + RedissonAutoConfiguration.class, // Redisson 自动高配置类
  33 + })
  34 + public static class Application {
  35 + }
  36 +
  37 +}
... ...
src/test-integration/java/cn/iocoder/dashboard/framework/redis/core/stream/RedisStreamTest.java
... ... @@ -43,7 +43,7 @@ public class RedisStreamTest {
43 43 for (int i = 0; i < 100; i++) {
44 44 // 创建消息
45 45 SysSmsSendMessage message = new SysSmsSendMessage();
46   - message.setMobile("15601691300").setTemplateCode("test:" + i);
  46 + message.setMobile("15601691300").setApiTemplateId("test:" + i);
47 47 // 发送消息
48 48 RedisMessageUtils.sendStreamMessage(stringRedisTemplate, message);
49 49 }
... ...
src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClientIntegrationTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
  2 +
  3 +import cn.iocoder.dashboard.common.core.KeyValue;
  4 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  5 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  6 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  7 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
  8 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  9 +import org.junit.jupiter.api.BeforeAll;
  10 +import org.junit.jupiter.api.Test;
  11 +
  12 +import java.util.ArrayList;
  13 +import java.util.List;
  14 +
  15 +/**
  16 + * {@link AliyunSmsClient} 的集成测试
  17 + */
  18 +public class AliyunSmsClientIntegrationTest {
  19 +
  20 + private static AliyunSmsClient smsClient;
  21 +
  22 + @BeforeAll
  23 + public static void before() {
  24 + // 创建配置类
  25 + SmsChannelProperties properties = new SmsChannelProperties();
  26 + properties.setId(1L);
  27 + properties.setSignature("Ballcat");
  28 + properties.setCode(SmsChannelEnum.ALIYUN.getCode());
  29 + properties.setApiKey(System.getenv("ALIYUN_ACCESS_KEY"));
  30 + properties.setApiSecret(System.getenv("ALIYUN_SECRET_KEY"));
  31 + // 创建客户端
  32 + smsClient = new AliyunSmsClient(properties);
  33 + smsClient.init();
  34 + }
  35 +
  36 + @Test
  37 + public void testSendSms() {
  38 + List<KeyValue<String, Object>> templateParams = new ArrayList<>();
  39 + templateParams.add(new KeyValue<>("code", "1024"));
  40 +// templateParams.put("operation", "嘿嘿");
  41 +// SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams);
  42 + SmsCommonResult<SmsSendRespDTO> result = smsClient.sendSms(1L, "15601691399",
  43 + "SMS_207945135", templateParams);
  44 + System.out.println(result);
  45 + }
  46 +
  47 + @Test
  48 + public void testGetSmsTemplate() {
  49 + String apiTemplateId = "SMS_2079451351";
  50 + SmsCommonResult<SmsTemplateRespDTO> result = smsClient.getSmsTemplate(apiTemplateId);
  51 + System.out.println(result);
  52 + }
  53 +
  54 +}
... ...
src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/debug/DebugDingTalkSmsClientIntegrationTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.debug;
  2 +
  3 +import cn.iocoder.dashboard.common.core.KeyValue;
  4 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  5 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  6 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
  7 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  8 +import org.junit.jupiter.api.BeforeAll;
  9 +import org.junit.jupiter.api.Test;
  10 +
  11 +import java.util.ArrayList;
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * {@link DebugDingTalkSmsClient} 的集成测试
  16 + */
  17 +public class DebugDingTalkSmsClientIntegrationTest {
  18 +
  19 + private static DebugDingTalkSmsClient smsClient;
  20 +
  21 + @BeforeAll
  22 + public static void init() {
  23 + // 创建配置类
  24 + SmsChannelProperties properties = new SmsChannelProperties();
  25 + properties.setId(1L);
  26 + properties.setSignature("芋道");
  27 + properties.setCode(SmsChannelEnum.DEBUG_DING_TALK.getCode());
  28 + properties.setApiKey("696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859");
  29 + properties.setApiSecret("SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67");
  30 + // 创建客户端
  31 + smsClient = new DebugDingTalkSmsClient(properties);
  32 + smsClient.init();
  33 + }
  34 +
  35 + @Test
  36 + public void testSendSms() {
  37 + List<KeyValue<String, Object>> templateParams = new ArrayList<>();
  38 + templateParams.add(new KeyValue<>("code", "1024"));
  39 + templateParams.add(new KeyValue<>("operation", "嘿嘿"));
  40 +// SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams);
  41 + SmsCommonResult<SmsSendRespDTO> result = smsClient.sendSms(1L, "15601691399", "4383920", templateParams);
  42 + System.out.println(result);
  43 + }
  44 +
  45 +}
... ...
src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientIntegrationTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
  2 +
  3 +import cn.iocoder.dashboard.common.core.KeyValue;
  4 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  5 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  6 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  7 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
  8 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  9 +import org.junit.jupiter.api.BeforeAll;
  10 +import org.junit.jupiter.api.Test;
  11 +
  12 +import java.util.ArrayList;
  13 +import java.util.List;
  14 +
  15 +/**
  16 + * {@link YunpianSmsClient} 的集成测试
  17 + */
  18 +public class YunpianSmsClientIntegrationTest {
  19 +
  20 + private static YunpianSmsClient smsClient;
  21 +
  22 + @BeforeAll
  23 + public static void init() {
  24 + // 创建配置类
  25 + SmsChannelProperties properties = new SmsChannelProperties();
  26 + properties.setId(1L);
  27 + properties.setSignature("芋道");
  28 + properties.setCode(SmsChannelEnum.YUN_PIAN.getCode());
  29 + properties.setApiKey("1555a14277cb8a608cf45a9e6a80d510");
  30 + // 创建客户端
  31 + smsClient = new YunpianSmsClient(properties);
  32 + smsClient.init();
  33 + }
  34 +
  35 + @Test
  36 + public void testSendSms() {
  37 + List<KeyValue<String, Object>> templateParams = new ArrayList<>();
  38 + templateParams.add(new KeyValue<>("code", "1024"));
  39 + templateParams.add(new KeyValue<>("operation", "嘿嘿"));
  40 +// SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams);
  41 + SmsCommonResult<SmsSendRespDTO> result = smsClient.sendSms(1L, "15601691399", "4383920", templateParams);
  42 + System.out.println(result);
  43 + }
  44 +
  45 + @Test
  46 + public void testGetSmsTemplate() {
  47 + String apiTemplateId = "4383920";
  48 + SmsCommonResult<SmsTemplateRespDTO> result = smsClient.getSmsTemplate(apiTemplateId);
  49 + System.out.println(result);
  50 + }
  51 +
  52 +}
... ...
src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/package-info.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/package-info.java renamed to src/test-integration/java/cn/iocoder/dashboard/modules/system/service/package-info.java
src/test-integration/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsServiceIntegrationTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.thread.ThreadUtil;
  5 +import cn.iocoder.dashboard.BaseDbAndRedisIntegrationTest;
  6 +import cn.iocoder.dashboard.common.enums.UserTypeEnum;
  7 +import cn.iocoder.dashboard.framework.sms.config.SmsConfiguration;
  8 +import cn.iocoder.dashboard.modules.system.mq.consumer.sms.SysSmsSendConsumer;
  9 +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
  10 +import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsChannelServiceImpl;
  11 +import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsLogServiceImpl;
  12 +import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsServiceImpl;
  13 +import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsTemplateServiceImpl;
  14 +import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
  15 +import org.junit.jupiter.api.Test;
  16 +import org.springframework.boot.test.mock.mockito.MockBean;
  17 +import org.springframework.context.annotation.Import;
  18 +
  19 +import javax.annotation.Resource;
  20 +import java.util.Map;
  21 +import java.util.concurrent.TimeUnit;
  22 +
  23 +@Import({SmsConfiguration.class,
  24 + SysSmsChannelServiceImpl.class, SysSmsServiceImpl.class, SysSmsTemplateServiceImpl.class, SysSmsLogServiceImpl.class,
  25 + SysSmsProducer.class, SysSmsSendConsumer.class})
  26 +public class SysSmsServiceIntegrationTest extends BaseDbAndRedisIntegrationTest {
  27 +
  28 + @Resource
  29 + private SysSmsServiceImpl smsService;
  30 + @Resource
  31 + private SysSmsChannelServiceImpl smsChannelService;
  32 +
  33 + @MockBean
  34 + private SysUserService userService;
  35 +
  36 + @Test
  37 + public void testSendSingleSms_yunpianSuccess() {
  38 + // 参数准备
  39 + String mobile = "15601691399";
  40 + Long userId = 1L;
  41 + Integer userType = UserTypeEnum.ADMIN.getValue();
  42 + String templateCode = "test_01";
  43 + Map<String, Object> templateParams = MapUtil.<String, Object>builder()
  44 + .put("operation", "登陆").put("code", "1234").build();
  45 + // 调用
  46 + smsService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
  47 +
  48 + // 等待 MQ 消费
  49 + ThreadUtil.sleep(1, TimeUnit.HOURS);
  50 + }
  51 +
  52 + @Test
  53 + public void testSendSingleSms_aliyunSuccess() {
  54 + // 参数准备
  55 + String mobile = "15601691399";
  56 + Long userId = 1L;
  57 + Integer userType = UserTypeEnum.ADMIN.getValue();
  58 + String templateCode = "test_02";
  59 + Map<String, Object> templateParams = MapUtil.<String, Object>builder()
  60 + .put("code", "1234").build();
  61 + // 调用
  62 + smsService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
  63 +
  64 + // 等待 MQ 消费
  65 + ThreadUtil.sleep(1, TimeUnit.HOURS);
  66 + }
  67 +
  68 +// @Test
  69 +// public void testDoSendSms() {
  70 +// // 等待 MQ 消费
  71 +// ThreadUtil.sleep(1, TimeUnit.HOURS);
  72 +// }
  73 +
  74 +}
... ...
src/test-integration/java/cn/iocoder/dashboard/modules/tool/dal/mysql/codegen/ToolInformationSchemaColumnMapperTest.java
... ... @@ -17,7 +17,7 @@ public class ToolInformationSchemaColumnMapperTest extends BaseDbUnitTest {
17 17 @Test
18 18 public void testSelectListByTableName() {
19 19 List<ToolSchemaColumnDO> columns = toolInformationSchemaColumnMapper
20   - .selectListByTableName("inf_config");
  20 + .selectListByTableName("", "inf_config");
21 21 assertTrue(columns.size() > 0);
22 22 }
23 23  
... ...
src/test-integration/resources/application-integration-test.yaml
... ... @@ -9,19 +9,15 @@ spring:
9 9 # 数据源配置项
10 10 datasource:
11 11 name: ruoyi-vue-pro
12   - url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
13   - driver-class-name: org.h2.Driver
14   - username: sa
15   - password:
16   - schema: classpath:sql/create_tables.sql # MySQL 转 H2 的语句,使用 https://www.jooq.org/translate/ 工具
17   - druid:
18   - async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
19   - initial-size: 1 # 单元测试,配置为 1,提升启动速度
  12 + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.name}?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
  13 + driver-class-name: com.mysql.jdbc.Driver
  14 + username: root
  15 + password: 123456
20 16  
21 17 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
22 18 redis:
23 19 host: 127.0.0.1 # 地址
24   - port: 6379 # 端口(单元测试,使用 16379 端口)
  20 + port: 6379 # 端口
25 21 database: 0 # 数据库索引
26 22  
27 23 mybatis:
... ...
src/test/java/cn/iocoder/dashboard/BaseMockitoUnitTest.java 0 → 100644
  1 +package cn.iocoder.dashboard;
  2 +
  3 +import org.junit.jupiter.api.extension.ExtendWith;
  4 +import org.mockito.junit.jupiter.MockitoExtension;
  5 +
  6 +/**
  7 + * 纯 Mockito 的单元测试
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@ExtendWith(MockitoExtension.class)
  12 +public class BaseMockitoUnitTest {
  13 +}
... ...
src/test/java/cn/iocoder/dashboard/framework/package-info.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework;
... ...
src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClientTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
  2 +
  3 +import cn.hutool.core.util.ReflectUtil;
  4 +import cn.iocoder.dashboard.BaseMockitoUnitTest;
  5 +import cn.iocoder.dashboard.common.core.KeyValue;
  6 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  7 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  8 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  9 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  10 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  11 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
  12 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  13 +import cn.iocoder.dashboard.util.collection.MapUtils;
  14 +import cn.iocoder.dashboard.util.date.DateUtils;
  15 +import com.aliyuncs.AcsRequest;
  16 +import com.aliyuncs.IAcsClient;
  17 +import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
  18 +import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;
  19 +import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
  20 +import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
  21 +import com.aliyuncs.exceptions.ClientException;
  22 +import com.google.common.collect.Lists;
  23 +import org.junit.jupiter.api.Test;
  24 +import org.mockito.ArgumentMatcher;
  25 +import org.mockito.InjectMocks;
  26 +import org.mockito.Mock;
  27 +
  28 +import java.util.List;
  29 +import java.util.function.Function;
  30 +
  31 +import static cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR;
  32 +import static cn.iocoder.dashboard.util.RandomUtils.*;
  33 +import static cn.iocoder.dashboard.util.json.JsonUtils.toJsonString;
  34 +import static org.junit.jupiter.api.Assertions.*;
  35 +import static org.mockito.ArgumentMatchers.any;
  36 +import static org.mockito.ArgumentMatchers.argThat;
  37 +import static org.mockito.Mockito.when;
  38 +
  39 +/**
  40 + * {@link AliyunSmsClient} 的单元测试
  41 + *
  42 + * @author 芋道源码
  43 + */
  44 +public class AliyunSmsClientTest extends BaseMockitoUnitTest {
  45 +
  46 + private final SmsChannelProperties properties = new SmsChannelProperties()
  47 + .setApiKey(randomString()) // 随机一个 apiKey,避免构建报错
  48 + .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
  49 + .setSignature("芋道源码");
  50 +
  51 + @InjectMocks
  52 + private final AliyunSmsClient smsClient = new AliyunSmsClient(properties);
  53 +
  54 + @Mock
  55 + private IAcsClient client;
  56 +
  57 + @Test
  58 + public void testDoInit() {
  59 + // 准备参数
  60 + // mock 方法
  61 +
  62 + // 调用
  63 + smsClient.doInit();
  64 + // 断言
  65 + assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "acsClient"));
  66 + }
  67 +
  68 + @Test
  69 + @SuppressWarnings("unchecked")
  70 + public void testDoSendSms() throws ClientException {
  71 + // 准备参数
  72 + Long sendLogId = randomLongId();
  73 + String mobile = randomString();
  74 + String apiTemplateId = randomString();
  75 + List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
  76 + new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
  77 + // mock 方法
  78 + SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("OK"));
  79 + when(client.getAcsResponse(argThat((ArgumentMatcher<SendSmsRequest>) acsRequest -> {
  80 + assertEquals(mobile, acsRequest.getPhoneNumbers());
  81 + assertEquals(properties.getSignature(), acsRequest.getSignName());
  82 + assertEquals(apiTemplateId, acsRequest.getTemplateCode());
  83 + assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam());
  84 + assertEquals(sendLogId.toString(), acsRequest.getOutId());
  85 + return true;
  86 + }))).thenReturn(response);
  87 +
  88 + // 调用
  89 + SmsCommonResult<SmsSendRespDTO> result = smsClient.doSendSms(sendLogId, mobile,
  90 + apiTemplateId, templateParams);
  91 + // 断言
  92 + assertEquals(response.getCode(), result.getApiCode());
  93 + assertEquals(response.getMessage(), result.getApiMsg());
  94 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
  95 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
  96 + assertEquals(response.getRequestId(), result.getApiRequestId());
  97 + // 断言结果
  98 + assertEquals(response.getBizId(), result.getData().getSerialNo());
  99 + }
  100 +
  101 + @Test
  102 + public void testDoTParseSmsReceiveStatus() throws Throwable {
  103 + // 准备参数
  104 + String text = "[\n" +
  105 + " {\n" +
  106 + " \"phone_number\" : \"13900000001\",\n" +
  107 + " \"send_time\" : \"2017-01-01 11:12:13\",\n" +
  108 + " \"report_time\" : \"2017-02-02 22:23:24\",\n" +
  109 + " \"success\" : true,\n" +
  110 + " \"err_code\" : \"DELIVERED\",\n" +
  111 + " \"err_msg\" : \"用户接收成功\",\n" +
  112 + " \"sms_size\" : \"1\",\n" +
  113 + " \"biz_id\" : \"12345\",\n" +
  114 + " \"out_id\" : \"67890\"\n" +
  115 + " }\n" +
  116 + "]";
  117 + // mock 方法
  118 +
  119 + // 调用
  120 + List<SmsReceiveRespDTO> statuses = smsClient.doParseSmsReceiveStatus(text);
  121 + // 断言
  122 + assertEquals(1, statuses.size());
  123 + assertTrue(statuses.get(0).getSuccess());
  124 + assertEquals("DELIVERED", statuses.get(0).getErrorCode());
  125 + assertEquals("用户接收成功", statuses.get(0).getErrorMsg());
  126 + assertEquals("13900000001", statuses.get(0).getMobile());
  127 + assertEquals(DateUtils.buildTime(2017, 2, 2, 22, 23, 24), statuses.get(0).getReceiveTime());
  128 + assertEquals("12345", statuses.get(0).getSerialNo());
  129 + assertEquals(67890L, statuses.get(0).getLogId());
  130 + }
  131 +
  132 + @Test
  133 + public void testDoGetSmsTemplate() throws ClientException {
  134 + // 准备参数
  135 + String apiTemplateId = randomString();
  136 + // mock 方法
  137 + QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> {
  138 + o.setCode("OK");
  139 + o.setTemplateStatus(1); // 设置模板通过
  140 + });
  141 + when(client.getAcsResponse(argThat((ArgumentMatcher<QuerySmsTemplateRequest>) acsRequest -> {
  142 + assertEquals(apiTemplateId, acsRequest.getTemplateCode());
  143 + return true;
  144 + }))).thenReturn(response);
  145 +
  146 + // 调用
  147 + SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId);
  148 + // 断言
  149 + assertEquals(response.getCode(), result.getApiCode());
  150 + assertEquals(response.getMessage(), result.getApiMsg());
  151 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
  152 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
  153 + assertEquals(response.getRequestId(), result.getApiRequestId());
  154 + // 断言结果
  155 + assertEquals(response.getTemplateCode(), result.getData().getId());
  156 + assertEquals(response.getTemplateContent(), result.getData().getContent());
  157 + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
  158 + assertEquals(response.getReason(), result.getData().getAuditReason());
  159 + }
  160 +
  161 + @Test
  162 + public void testConvertSmsTemplateAuditStatus() {
  163 + assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(),
  164 + smsClient.convertSmsTemplateAuditStatus(0));
  165 + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),
  166 + smsClient.convertSmsTemplateAuditStatus(1));
  167 + assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(),
  168 + smsClient.convertSmsTemplateAuditStatus(2));
  169 + assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus(3),
  170 + "未知审核状态(3)");
  171 + }
  172 +
  173 + @Test
  174 + @SuppressWarnings("unchecked")
  175 + public void testInvoke_throwable() throws ClientException {
  176 + // 准备参数
  177 + QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
  178 + // mock 方法
  179 + ClientException ex = new ClientException("isv.INVALID_PARAMETERS", "参数不正确", randomString());
  180 + when(client.getAcsResponse(any(AcsRequest.class))).thenThrow(ex);
  181 +
  182 + // 调用,并断言异常
  183 + SmsCommonResult<?> result = smsClient.invoke(request,null);
  184 + // 断言
  185 + assertEquals(ex.getErrCode(), result.getApiCode());
  186 + assertEquals(ex.getErrMsg(), result.getApiMsg());
  187 + assertEquals(SMS_API_PARAM_ERROR.getCode(), result.getCode());
  188 + assertEquals(SMS_API_PARAM_ERROR.getMsg(), result.getMsg());
  189 + assertEquals(ex.getRequestId(), result.getApiRequestId());
  190 + }
  191 +
  192 + @Test
  193 + public void testInvoke_success() throws ClientException {
  194 + // 准备参数
  195 + QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
  196 + Function<QuerySmsTemplateResponse, SmsTemplateRespDTO> responseConsumer = response -> {
  197 + SmsTemplateRespDTO data = new SmsTemplateRespDTO();
  198 + data.setId(response.getTemplateCode()).setContent(response.getTemplateContent());
  199 + data.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(response.getReason());
  200 + return data;
  201 + };
  202 + // mock 方法
  203 + QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> {
  204 + o.setCode("OK");
  205 + o.setTemplateStatus(1); // 设置模板通过
  206 + });
  207 + when(client.getAcsResponse(any(AcsRequest.class))).thenReturn(response);
  208 +
  209 + // 调用
  210 + SmsCommonResult<SmsTemplateRespDTO> result = smsClient.invoke(request, responseConsumer);
  211 + // 断言
  212 + assertEquals(response.getCode(), result.getApiCode());
  213 + assertEquals(response.getMessage(), result.getApiMsg());
  214 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
  215 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
  216 + assertEquals(response.getRequestId(), result.getApiRequestId());
  217 + // 断言结果
  218 + assertEquals(response.getTemplateCode(), result.getData().getId());
  219 + assertEquals(response.getTemplateContent(), result.getData().getContent());
  220 + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
  221 + assertEquals(response.getReason(), result.getData().getAuditReason());
  222 + }
  223 +
  224 +}
... ...
src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsCodeMappingTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
  2 +
  3 +import cn.iocoder.dashboard.BaseMockitoUnitTest;
  4 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  5 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
  6 +import org.junit.jupiter.api.Test;
  7 +import org.mockito.InjectMocks;
  8 +
  9 +import static org.junit.jupiter.api.Assertions.*;
  10 +
  11 +/**
  12 + * {@link AliyunSmsCodeMapping} 的单元测试
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public class AliyunSmsCodeMappingTest extends BaseMockitoUnitTest {
  17 +
  18 + @InjectMocks
  19 + private AliyunSmsCodeMapping codeMapping;
  20 +
  21 + @Test
  22 + public void testApply() {
  23 + assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply("OK"));
  24 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("MissingAccessKeyId"));
  25 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("isv.ACCOUNT_NOT_EXISTS"));
  26 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("isv.ACCOUNT_ABNORMAL"));
  27 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_DAY_LIMIT_CONTROL, codeMapping.apply("isv.DAY_LIMIT_CONTROL"));
  28 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("isv.SMS_CONTENT_ILLEGAL"));
  29 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("isv.SMS_SIGN_ILLEGAL"));
  30 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("isv.SIGN_NAME_ILLEGAL"));
  31 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("isp.RAM_PERMISSION_DENY"));
  32 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH, codeMapping.apply("isv.OUT_OF_SERVICE"));
  33 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH, codeMapping.apply("isv.AMOUNT_NOT_ENOUGH"));
  34 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply("isv.SMS_TEMPLATE_ILLEGAL"));
  35 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("isv.SMS_SIGNATURE_ILLEGAL"));
  36 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR, codeMapping.apply("isv.INVALID_PARAMETERS"));
  37 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR, codeMapping.apply("isv.INVALID_JSON_PARAM"));
  38 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID, codeMapping.apply("isv.MOBILE_NUMBER_ILLEGAL"));
  39 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR, codeMapping.apply("isv.TEMPLATE_MISSING_PARAMETERS"));
  40 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply("isv.BUSINESS_LIMIT_CONTROL"));
  41 + }
  42 +
  43 +}
... ...
src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
  2 +
  3 +import cn.hutool.core.util.ReflectUtil;
  4 +import cn.iocoder.dashboard.BaseMockitoUnitTest;
  5 +import cn.iocoder.dashboard.common.core.KeyValue;
  6 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  7 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  8 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  9 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  10 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  11 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
  12 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
  13 +import cn.iocoder.dashboard.util.date.DateUtils;
  14 +import com.google.common.collect.Lists;
  15 +import com.yunpian.sdk.YunpianClient;
  16 +import com.yunpian.sdk.api.SmsApi;
  17 +import com.yunpian.sdk.api.TplApi;
  18 +import com.yunpian.sdk.constant.YunpianConstant;
  19 +import com.yunpian.sdk.model.Result;
  20 +import com.yunpian.sdk.model.SmsSingleSend;
  21 +import com.yunpian.sdk.model.Template;
  22 +import org.junit.jupiter.api.Test;
  23 +import org.mockito.InjectMocks;
  24 +import org.mockito.Mock;
  25 +
  26 +import java.util.HashMap;
  27 +import java.util.List;
  28 +import java.util.Map;
  29 +import java.util.function.Function;
  30 +import java.util.function.Supplier;
  31 +
  32 +import static cn.iocoder.dashboard.util.RandomUtils.*;
  33 +import static com.yunpian.sdk.constant.Code.OK;
  34 +import static org.junit.jupiter.api.Assertions.*;
  35 +import static org.mockito.Mockito.*;
  36 +
  37 +/**
  38 + * 对 {@link YunpianSmsClient} 的单元测试
  39 + *
  40 + * @author 芋道源码
  41 + */
  42 +public class YunpianSmsClientTest extends BaseMockitoUnitTest {
  43 +
  44 + private final SmsChannelProperties properties = new SmsChannelProperties()
  45 + .setApiKey(randomString()); // 随机一个 apiKey,避免构建报错
  46 +
  47 + @InjectMocks
  48 + private final YunpianSmsClient smsClient = new YunpianSmsClient(properties);
  49 +
  50 + @Mock
  51 + private YunpianClient client;
  52 +
  53 + @Test
  54 + public void testDoInit() {
  55 + // 准备参数
  56 + // mock 方法
  57 +
  58 + // 调用
  59 + smsClient.doInit();
  60 + // 断言
  61 + assertNotEquals(client, ReflectUtil.getFieldValue(smsClient, "client"));
  62 + verify(client, times(1)).close();
  63 + }
  64 +
  65 + @Test
  66 + @SuppressWarnings("unchecked")
  67 + public void testDoSendSms() throws Throwable {
  68 + // 准备参数
  69 + Long sendLogId = randomLongId();
  70 + String mobile = randomString();
  71 + String apiTemplateId = randomString();
  72 + List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
  73 + new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
  74 + // mock sms 方法
  75 + SmsApi smsApi = mock(SmsApi.class);
  76 + when(client.sms()).thenReturn(smsApi);
  77 + // mock tpl_single_send 方法
  78 + Map<String, String> request = new HashMap<>();
  79 + request.put(YunpianConstant.MOBILE, mobile);
  80 + request.put(YunpianConstant.TPL_ID, apiTemplateId);
  81 + request.put(YunpianConstant.TPL_VALUE, "#code#=1234&#op#=login");
  82 + request.put(YunpianConstant.UID, String.valueOf(sendLogId));
  83 + request.put(YunpianConstant.CALLBACK_URL, properties.getCallbackUrl());
  84 + Result<SmsSingleSend> responseResult = randomPojo(Result.class, SmsSingleSend.class,
  85 + o -> o.setCode(OK)); // API 发送成功的 code
  86 + when(smsApi.tpl_single_send(eq(request))).thenReturn(responseResult);
  87 +
  88 + // 调用
  89 + SmsCommonResult<SmsSendRespDTO> result = smsClient.doSendSms(sendLogId, mobile,
  90 + apiTemplateId, templateParams);
  91 + // 断言
  92 + assertEquals(String.valueOf(responseResult.getCode()), result.getApiCode());
  93 + assertEquals(responseResult.getMsg() + " => " + responseResult.getDetail(), result.getApiMsg());
  94 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
  95 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
  96 + assertNull(result.getApiRequestId());
  97 + // 断言结果
  98 + assertEquals(String.valueOf(responseResult.getData().getSid()), result.getData().getSerialNo());
  99 + }
  100 +
  101 + @Test
  102 + public void testDoParseSmsReceiveStatus() throws Throwable {
  103 + // 准备参数
  104 + String text = "[{\"sid\":9527,\"uid\":1024,\"user_receive_time\":\"2014-03-17 22:55:21\",\"error_msg\":\"\",\"mobile\":\"15205201314\",\"report_status\":\"SUCCESS\"}]";
  105 + // mock 方法
  106 +
  107 + // 调用
  108 +
  109 + // 断言
  110 + // 调用
  111 + List<SmsReceiveRespDTO> statuses = smsClient.doParseSmsReceiveStatus(text);
  112 + // 断言
  113 + assertEquals(1, statuses.size());
  114 + assertTrue(statuses.get(0).getSuccess());
  115 + assertEquals("", statuses.get(0).getErrorCode());
  116 + assertNull(statuses.get(0).getErrorMsg());
  117 + assertEquals("15205201314", statuses.get(0).getMobile());
  118 + assertEquals(DateUtils.buildTime(2014, 3, 17, 22, 55, 21), statuses.get(0).getReceiveTime());
  119 + assertEquals("9527", statuses.get(0).getSerialNo());
  120 + assertEquals(1024L, statuses.get(0).getLogId());
  121 + }
  122 +
  123 + @Test
  124 + @SuppressWarnings("unchecked")
  125 + public void testDoGetSmsTemplate() throws Throwable {
  126 + // 准备参数
  127 + String apiTemplateId = randomString();
  128 + // mock tpl 方法
  129 + TplApi tplApi = mock(TplApi.class);
  130 + when(client.tpl()).thenReturn(tplApi);
  131 + // mock get 方法
  132 + Map<String, String> request = new HashMap<>();
  133 + request.put(YunpianConstant.APIKEY, properties.getApiKey());
  134 + request.put(YunpianConstant.TPL_ID, apiTemplateId);
  135 + Result<List<Template>> responseResult = randomPojo(Result.class, List.class, o -> {
  136 + o.setCode(OK); // API 发送成功的 code
  137 + o.setData(randomPojoList(Template.class, t -> t.setCheck_status("SUCCESS")));
  138 + });
  139 + when(tplApi.get(eq(request))).thenReturn(responseResult);
  140 +
  141 + // 调用
  142 + SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId);
  143 + // 断言
  144 + assertEquals(String.valueOf(responseResult.getCode()), result.getApiCode());
  145 + assertEquals(responseResult.getMsg() + " => " + responseResult.getDetail(), result.getApiMsg());
  146 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
  147 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
  148 + assertNull(result.getApiRequestId());
  149 + // 断言结果
  150 + Template template = responseResult.getData().get(0);
  151 + assertEquals(template.getTpl_id().toString(), result.getData().getId());
  152 + assertEquals(template.getTpl_content(), result.getData().getContent());
  153 + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
  154 + assertEquals(template.getReason(), result.getData().getAuditReason());
  155 + }
  156 +
  157 + @Test
  158 + public void testConvertSmsTemplateAuditStatus() {
  159 + assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(),
  160 + smsClient.convertSmsTemplateAuditStatus("CHECKING"));
  161 + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),
  162 + smsClient.convertSmsTemplateAuditStatus("SUCCESS"));
  163 + assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(),
  164 + smsClient.convertSmsTemplateAuditStatus("FAIL"));
  165 + assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus("test"),
  166 + "未知审核状态(test)");
  167 + }
  168 +
  169 + @Test
  170 + public void testInvoke_throwable() {
  171 + // 准备参数
  172 + Supplier<Result<Object>> requestConsumer =
  173 + () -> new Result<>().setThrowable(new NullPointerException());
  174 + // mock 方法
  175 +
  176 + // 调用,并断言异常
  177 + assertThrows(NullPointerException.class,
  178 + () -> smsClient.invoke(requestConsumer, null));
  179 + }
  180 +
  181 + @Test
  182 + @SuppressWarnings("unchecked")
  183 + public void testInvoke_success() throws Throwable {
  184 + // 准备参数
  185 + Result<SmsSingleSend> responseResult = randomPojo(Result.class, SmsSingleSend.class, o -> o.setCode(OK));
  186 + Supplier<Result<SmsSingleSend>> requestConsumer = () -> responseResult;
  187 + Function<SmsSingleSend, SmsSendRespDTO> responseConsumer =
  188 + smsSingleSend -> new SmsSendRespDTO().setSerialNo(String.valueOf(responseResult.getData().getSid()));
  189 + // mock 方法
  190 +
  191 + // 调用
  192 + SmsCommonResult<SmsSendRespDTO> result = smsClient.invoke(requestConsumer, responseConsumer);
  193 + // 断言
  194 + assertEquals(String.valueOf(responseResult.getCode()), result.getApiCode());
  195 + assertEquals(responseResult.getMsg() + " => " + responseResult.getDetail(), result.getApiMsg());
  196 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
  197 + assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
  198 + assertNull(result.getApiRequestId());
  199 + assertEquals(String.valueOf(responseResult.getData().getSid()), result.getData().getSerialNo());
  200 + }
  201 +
  202 +}
... ...
src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsCodeMappingTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
  2 +
  3 +import cn.iocoder.dashboard.BaseMockitoUnitTest;
  4 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  5 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
  6 +import org.junit.jupiter.api.Test;
  7 +import org.mockito.InjectMocks;
  8 +
  9 +import static com.yunpian.sdk.constant.Code.*;
  10 +import static org.junit.jupiter.api.Assertions.assertEquals;
  11 +
  12 +/**
  13 + * {@link YunpianSmsCodeMapping} 的单元测试
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +class YunpianSmsCodeMappingTest extends BaseMockitoUnitTest {
  18 +
  19 + @InjectMocks
  20 + private YunpianSmsCodeMapping codeMapping;
  21 +
  22 + @Test
  23 + public void testApply() {
  24 + assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply(String.valueOf(OK)));
  25 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_API_PARAM_ERROR, codeMapping.apply(String.valueOf(ARGUMENT_MISSING)));
  26 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR, codeMapping.apply(String.valueOf(BAD_ARGUMENT_FORMAT)));
  27 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH, codeMapping.apply(String.valueOf(MONEY_NOT_ENOUGH)));
  28 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply(String.valueOf(TPL_NOT_FOUND)));
  29 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply(String.valueOf(TPL_NOT_VALID)));
  30 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply(String.valueOf(DUP_IN_SHORT_TIME)));
  31 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply(String.valueOf(TOO_MANY_TIME_IN_5)));
  32 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply(String.valueOf(DAY_LIMIT_PER_MOBILE)));
  33 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply(String.valueOf(HOUR_LIMIT_PER_MOBILE)));
  34 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_MOBILE_BLACK, codeMapping.apply(String.valueOf(BLACK_PHONE_FILTER)));
  35 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply(String.valueOf(SIGN_NOT_MATCH)));
  36 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply(String.valueOf(SIGN_NOT_VALID)));
  37 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply(String.valueOf(BAD_SIGN_FORMAT)));
  38 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply(String.valueOf(BAD_API_KEY)));
  39 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply(String.valueOf(API_NOT_ALLOWED)));
  40 + assertEquals(SmsFrameworkErrorCodeConstants.SMS_IP_DENY, codeMapping.apply(String.valueOf(IP_NOT_ALLOWED)));
  41 + }
  42 +
  43 +}
... ...
src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java
1 1 package cn.iocoder.dashboard.modules.system.service.auth;
2 2  
3 3 import static cn.hutool.core.util.RandomUtil.randomEle;
4   -import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER;
5 4 import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
6 5 import static cn.iocoder.dashboard.util.RandomUtils.randomDate;
7 6 import static cn.iocoder.dashboard.util.RandomUtils.randomLongId;
... ... @@ -25,7 +24,6 @@ import javax.annotation.Resource;
25 24 import org.junit.jupiter.api.Test;
26 25 import org.springframework.boot.test.mock.mockito.MockBean;
27 26 import org.springframework.context.annotation.Import;
28   -import org.springframework.data.redis.core.StringRedisTemplate;
29 27  
30 28 import cn.hutool.core.date.DateUtil;
31 29 import cn.iocoder.dashboard.BaseDbAndRedisUnitTest;
... ... @@ -33,10 +31,6 @@ import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
33 31 import cn.iocoder.dashboard.common.pojo.PageResult;
34 32 import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
35 33 import cn.iocoder.dashboard.framework.security.core.LoginUser;
36   -import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobPageReqVO;
37   -import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobDO;
38   -import cn.iocoder.dashboard.modules.infra.enums.config.InfConfigTypeEnum;
39   -import cn.iocoder.dashboard.modules.infra.enums.job.InfJobStatusEnum;
40 34 import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO;
41 35 import cn.iocoder.dashboard.modules.system.dal.dataobject.auth.SysUserSessionDO;
42 36 import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
... ... @@ -50,7 +44,6 @@ import cn.iocoder.dashboard.modules.system.service.logger.impl.SysLoginLogServic
50 44 import cn.iocoder.dashboard.modules.system.service.user.SysUserServiceImpl;
51 45 import cn.iocoder.dashboard.util.AssertUtils;
52 46 import cn.iocoder.dashboard.util.RandomUtils;
53   -import cn.iocoder.dashboard.util.json.JsonUtils;
54 47 import cn.iocoder.dashboard.util.object.ObjectUtils;
55 48  
56 49 /**
... ... @@ -167,12 +160,12 @@ public class SysUserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
167 160 String userIp = randomString();
168 161 SysUserDO dbUser1 = randomPojo(SysUserDO.class, o -> {
169 162 o.setUsername("testUsername1");
170   - o.setSex(randomEle(SysSexEnum.values()).getSEX());
  163 + o.setSex(randomEle(SysSexEnum.values()).getSex());
171 164 o.setStatus(CommonStatusEnum.ENABLE.getStatus());
172 165 });
173 166 SysUserDO dbUser2 = randomPojo(SysUserDO.class, o -> {
174 167 o.setUsername("testUsername2");
175   - o.setSex(randomEle(SysSexEnum.values()).getSEX());
  168 + o.setSex(randomEle(SysSexEnum.values()).getSex());
176 169 o.setStatus(CommonStatusEnum.ENABLE.getStatus());
177 170 });
178 171 SysUserSessionDO dbSession = randomPojo(SysUserSessionDO.class, o -> {
... ...
src/test/java/cn/iocoder/dashboard/modules/system/service/dept/SysDeptServiceTest.java
... ... @@ -270,4 +270,5 @@ class SysDeptServiceTest extends BaseDbUnitTest {
270 270 };
271 271 return randomPojo(SysDeptDO.class, ArrayUtils.append(consumer, consumers));
272 272 }
  273 +
273 274 }
... ...
src/test/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogServiceImplTest.java
... ... @@ -68,7 +68,7 @@ public class SysOperateLogServiceImplTest extends BaseDbUnitTest {
68 68 // 先构造用户
69 69 SysUserDO user = RandomUtils.randomPojo(SysUserDO.class, o -> {
70 70 o.setNickname("wangkai");
71   - o.setSex(SysSexEnum.MALE.getSEX());
  71 + o.setSex(SysSexEnum.MALE.getSex());
72 72 o.setStatus(CommonStatusEnum.ENABLE.getStatus());
73 73 });
74 74 when(userService.getUsersByNickname("wangkai")).thenReturn(Collections.singletonList(user));
... ... @@ -119,7 +119,7 @@ public class SysOperateLogServiceImplTest extends BaseDbUnitTest {
119 119 // 先构造用户
120 120 SysUserDO user = RandomUtils.randomPojo(SysUserDO.class, o -> {
121 121 o.setNickname("wangkai");
122   - o.setSex(SysSexEnum.MALE.getSEX());
  122 + o.setSex(SysSexEnum.MALE.getSex());
123 123 o.setStatus(CommonStatusEnum.ENABLE.getStatus());
124 124 });
125 125 when(userService.getUsersByNickname("wangkai")).thenReturn(Collections.singletonList(user));
... ...
src/test/java/cn/iocoder/dashboard/modules/system/service/permission/SysMenuServiceTest.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.service.permission;
3 3 import cn.hutool.core.bean.BeanUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.iocoder.dashboard.BaseDbUnitTest;
  6 +import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
6 7 import cn.iocoder.dashboard.modules.system.controller.permission.vo.menu.SysMenuCreateReqVO;
7 8 import cn.iocoder.dashboard.modules.system.controller.permission.vo.menu.SysMenuListReqVO;
8 9 import cn.iocoder.dashboard.modules.system.controller.permission.vo.menu.SysMenuUpdateReqVO;
... ... @@ -12,7 +13,6 @@ import cn.iocoder.dashboard.modules.system.enums.permission.MenuTypeEnum;
12 13 import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysMenuProducer;
13 14 import cn.iocoder.dashboard.modules.system.service.permission.impl.SysMenuServiceImpl;
14 15 import cn.iocoder.dashboard.util.AopTargetUtils;
15   -import cn.iocoder.dashboard.util.AssertUtils;
16 16 import cn.iocoder.dashboard.util.RandomUtils;
17 17 import cn.iocoder.dashboard.util.object.ObjectUtils;
18 18 import com.google.common.collect.Multimap;
... ... @@ -22,7 +22,6 @@ import org.springframework.boot.test.mock.mockito.MockBean;
22 22 import org.springframework.context.annotation.Import;
23 23  
24 24 import javax.annotation.Resource;
25   -
26 25 import java.util.*;
27 26  
28 27 import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
... ... @@ -54,18 +53,20 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
54 53 SysMenuDO menuDO2 = createMenuDO(MenuTypeEnum.MENU, "xxxx", 0L);
55 54 menuMapper.insert(menuDO2);
56 55  
57   - //调用
  56 + // 调用
58 57 sysMenuService.initLocalCache();
59 58  
60 59 // 获取代理对象
61 60 SysMenuServiceImpl target = (SysMenuServiceImpl) AopTargetUtils.getTarget(sysMenuService);
62 61  
63   - Map<Long, SysMenuDO> menuCache = (Map<Long, SysMenuDO>) BeanUtil.getFieldValue(target, "menuCache");
  62 + Map<Long, SysMenuDO> menuCache =
  63 + (Map<Long, SysMenuDO>) BeanUtil.getFieldValue(target, "menuCache");
64 64 Assert.isTrue(menuCache.size() == 2);
65 65 assertPojoEquals(menuDO1, menuCache.get(menuDO1.getId()));
66 66 assertPojoEquals(menuDO2, menuCache.get(menuDO2.getId()));
67 67  
68   - Multimap<String, SysMenuDO> permissionMenuCache = (Multimap<String, SysMenuDO>) BeanUtil.getFieldValue(target, "permissionMenuCache");
  68 + Multimap<String, SysMenuDO> permissionMenuCache =
  69 + (Multimap<String, SysMenuDO>) BeanUtil.getFieldValue(target, "permissionMenuCache");
69 70 Assert.isTrue(permissionMenuCache.size() == 2);
70 71 assertPojoEquals(menuDO1, permissionMenuCache.get(menuDO1.getPermission()));
71 72 assertPojoEquals(menuDO2, permissionMenuCache.get(menuDO2.getPermission()));
... ... @@ -186,10 +187,9 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
186 187  
187 188 //断言
188 189 Assert.isTrue(menuDOS.size() == idMenuMap.size());
189   - menuDOS.stream().forEach(m -> assertPojoEquals(idMenuMap.get(m.getId()), m));
  190 + menuDOS.forEach(m -> assertPojoEquals(idMenuMap.get(m.getId()), m));
190 191 }
191 192  
192   -
193 193 @Test
194 194 public void testGetMenusReqVo_success() {
195 195 Map<Long, SysMenuDO> idMenuMap = new HashMap<>();
... ... @@ -220,7 +220,7 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
220 220  
221 221 //断言
222 222 Assert.isTrue(menuDOS.size() == idMenuMap.size());
223   - menuDOS.stream().forEach(m -> assertPojoEquals(idMenuMap.get(m.getId()), m));
  223 + menuDOS.forEach(m -> assertPojoEquals(idMenuMap.get(m.getId()), m));
224 224 }
225 225  
226 226 @Test
... ... @@ -246,12 +246,12 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
246 246 menuDO = createMenuDO(4L, MenuTypeEnum.MENU, "name", 0L, 2);
247 247 mockCacheMap.put(menuDO.getId(), menuDO);
248 248  
249   - List<SysMenuDO> menuDOS = sysMenuService.listMenusFromCache(Arrays.asList(MenuTypeEnum.MENU.getType()), Arrays.asList(1));
  249 + List<SysMenuDO> menuDOS = sysMenuService.listMenusFromCache(Collections.singletonList(MenuTypeEnum.MENU.getType()),
  250 + Collections.singletonList(CommonStatusEnum.DISABLE.getStatus()));
250 251 Assert.isTrue(menuDOS.size() == idMenuMap.size());
251   - menuDOS.stream().forEach(m -> assertPojoEquals(idMenuMap.get(m.getId()), m));
  252 + menuDOS.forEach(m -> assertPojoEquals(idMenuMap.get(m.getId()), m));
252 253 }
253 254  
254   -
255 255 @Test
256 256 public void testListMenusFromCache2_success() throws Exception {
257 257 Map<Long, SysMenuDO> mockCacheMap = new HashMap<>();
... ... @@ -273,10 +273,10 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
273 273 menuDO = createMenuDO(4L, MenuTypeEnum.MENU, "name", 0L, 2);
274 274 mockCacheMap.put(menuDO.getId(), menuDO);
275 275  
276   - List<SysMenuDO> menuDOS = sysMenuService.listMenusFromCache(Arrays.asList(1L),
277   - Arrays.asList(MenuTypeEnum.MENU.getType()), Arrays.asList(1));
  276 + List<SysMenuDO> menuDOS = sysMenuService.listMenusFromCache(Collections.singletonList(1L),
  277 + Collections.singletonList(MenuTypeEnum.MENU.getType()), Collections.singletonList(1));
278 278 Assert.isTrue(menuDOS.size() == idMenuMap.size());
279   - menuDOS.stream().forEach(menu -> assertPojoEquals(idMenuMap.get(menu.getId()), menu));
  279 + menuDOS.forEach(menu -> assertPojoEquals(idMenuMap.get(menu.getId()), menu));
280 280 }
281 281  
282 282 @Test
... ... @@ -308,14 +308,14 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
308 308 }
309 309  
310 310 @Test
311   - public void testCheckResource_success(){
312   - SysMenuDO sonMenu=initParentAndSonMenuDO();
313   - Long parentId=sonMenu.getParentId();
  311 + public void testCheckResource_success() {
  312 + SysMenuDO sonMenu = initParentAndSonMenuDO();
  313 + Long parentId = sonMenu.getParentId();
314 314  
315   - Long otherSonMenuId=randomLongId();
316   - String otherSonMenuName=randomString();
  315 + Long otherSonMenuId = randomLongId();
  316 + String otherSonMenuName = randomString();
317 317  
318   - sysMenuService.checkResource(parentId,otherSonMenuName,otherSonMenuId);
  318 + sysMenuService.checkResource(parentId, otherSonMenuName, otherSonMenuId);
319 319 }
320 320  
321 321 @Test
... ... @@ -326,7 +326,7 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
326 326 Long otherSonMenuId=randomLongId();
327 327 String otherSonMenuName=sonMenu.getName(); //相同名称
328 328  
329   - assertServiceException(()->sysMenuService.checkResource(parentId,otherSonMenuName,otherSonMenuId), MENU_NAME_DUPLICATE);
  329 + assertServiceException(() -> sysMenuService.checkResource(parentId, otherSonMenuName, otherSonMenuId), MENU_NAME_DUPLICATE);
330 330 }
331 331  
332 332 /**
... ... @@ -354,15 +354,13 @@ public class SysMenuServiceTest extends BaseDbUnitTest {
354 354 }
355 355  
356 356 private SysMenuDO createMenuDO(Long id, MenuTypeEnum typeEnum, String menuName, Long parentId, Integer status) {
357   - SysMenuDO sonMenuDO = RandomUtils.randomPojo(SysMenuDO.class, o -> {
  357 + return RandomUtils.randomPojo(SysMenuDO.class, o -> {
358 358 o.setId(id);
359 359 o.setParentId(parentId);
360 360 o.setType(typeEnum.getType());
361 361 o.setStatus(status);
362 362 o.setName(menuName);
363 363 });
364   - return sonMenuDO;
365 364 }
366 365  
367   -
368 366 }
... ...
src/test/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsChannelServiceTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import cn.iocoder.dashboard.BaseDbUnitTest;
  5 +import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
  6 +import cn.iocoder.dashboard.common.pojo.PageResult;
  7 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
  8 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelCreateReqVO;
  9 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelPageReqVO;
  10 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.channel.SysSmsChannelUpdateReqVO;
  11 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
  12 +import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsChannelMapper;
  13 +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
  14 +import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsChannelServiceImpl;
  15 +import cn.iocoder.dashboard.util.collection.ArrayUtils;
  16 +import cn.iocoder.dashboard.util.object.ObjectUtils;
  17 +import org.junit.jupiter.api.Test;
  18 +import org.springframework.boot.test.mock.mockito.MockBean;
  19 +import org.springframework.context.annotation.Import;
  20 +
  21 +import javax.annotation.Resource;
  22 +import java.util.Date;
  23 +import java.util.function.Consumer;
  24 +
  25 +import static cn.hutool.core.util.RandomUtil.randomEle;
  26 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
  27 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
  28 +import static cn.iocoder.dashboard.util.AssertUtils.*;
  29 +import static cn.iocoder.dashboard.util.RandomUtils.*;
  30 +import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
  31 +import static cn.iocoder.dashboard.util.object.ObjectUtils.max;
  32 +import static org.junit.jupiter.api.Assertions.*;
  33 +import static org.mockito.ArgumentMatchers.eq;
  34 +import static org.mockito.Mockito.*;
  35 +
  36 +/**
  37 +* {@link SysSmsChannelServiceImpl} 的单元测试类
  38 +*
  39 +* @author 芋道源码
  40 +*/
  41 +@Import(SysSmsChannelServiceImpl.class)
  42 +public class SysSmsChannelServiceTest extends BaseDbUnitTest {
  43 +
  44 + @Resource
  45 + private SysSmsChannelServiceImpl smsChannelService;
  46 +
  47 + @Resource
  48 + private SysSmsChannelMapper smsChannelMapper;
  49 +
  50 + @MockBean
  51 + private SmsClientFactory smsClientFactory;
  52 + @MockBean
  53 + private SysSmsTemplateService smsTemplateService;
  54 + @MockBean
  55 + private SysSmsProducer smsProducer;
  56 +
  57 + @Test
  58 + public void testInitLocalCache_success() {
  59 + // mock 数据
  60 + SysSmsChannelDO smsChannelDO01 = randomSmsChannelDO();
  61 + smsChannelMapper.insert(smsChannelDO01);
  62 + SysSmsChannelDO smsChannelDO02 = randomSmsChannelDO();
  63 + smsChannelMapper.insert(smsChannelDO02);
  64 +
  65 + // 调用
  66 + smsChannelService.initSmsClients();
  67 + // 校验 maxUpdateTime 属性
  68 + Date maxUpdateTime = (Date) BeanUtil.getFieldValue(smsChannelService, "maxUpdateTime");
  69 + assertEquals(max(smsChannelDO01.getUpdateTime(), smsChannelDO02.getUpdateTime()), maxUpdateTime);
  70 + // 校验调用
  71 + verify(smsClientFactory, times(1)).createOrUpdateSmsClient(
  72 + argThat(properties -> isPojoEquals(smsChannelDO01, properties)));
  73 + verify(smsClientFactory, times(1)).createOrUpdateSmsClient(
  74 + argThat(properties -> isPojoEquals(smsChannelDO02, properties)));
  75 + }
  76 +
  77 + @Test
  78 + public void testCreateSmsChannel_success() {
  79 + // 准备参数
  80 + SysSmsChannelCreateReqVO reqVO = randomPojo(SysSmsChannelCreateReqVO.class, o -> o.setStatus(randomCommonStatus()));
  81 +
  82 + // 调用
  83 + Long smsChannelId = smsChannelService.createSmsChannel(reqVO);
  84 + // 断言
  85 + assertNotNull(smsChannelId);
  86 + // 校验记录的属性是否正确
  87 + SysSmsChannelDO smsChannel = smsChannelMapper.selectById(smsChannelId);
  88 + assertPojoEquals(reqVO, smsChannel);
  89 + // 校验调用
  90 + verify(smsProducer, times(1)).sendSmsChannelRefreshMessage();
  91 + }
  92 +
  93 + @Test
  94 + public void testUpdateSmsChannel_success() {
  95 + // mock 数据
  96 + SysSmsChannelDO dbSmsChannel = randomSmsChannelDO();
  97 + smsChannelMapper.insert(dbSmsChannel);// @Sql: 先插入出一条存在的数据
  98 + // 准备参数
  99 + SysSmsChannelUpdateReqVO reqVO = randomPojo(SysSmsChannelUpdateReqVO.class, o -> {
  100 + o.setId(dbSmsChannel.getId()); // 设置更新的 ID
  101 + o.setStatus(randomCommonStatus());
  102 + o.setCallbackUrl(randomString());
  103 + });
  104 +
  105 + // 调用
  106 + smsChannelService.updateSmsChannel(reqVO);
  107 + // 校验是否更新正确
  108 + SysSmsChannelDO smsChannel = smsChannelMapper.selectById(reqVO.getId()); // 获取最新的
  109 + assertPojoEquals(reqVO, smsChannel);
  110 + // 校验调用
  111 + verify(smsProducer, times(1)).sendSmsChannelRefreshMessage();
  112 + }
  113 +
  114 + @Test
  115 + public void testUpdateSmsChannel_notExists() {
  116 + // 准备参数
  117 + SysSmsChannelUpdateReqVO reqVO = randomPojo(SysSmsChannelUpdateReqVO.class);
  118 +
  119 + // 调用, 并断言异常
  120 + assertServiceException(() -> smsChannelService.updateSmsChannel(reqVO), SMS_CHANNEL_NOT_EXISTS);
  121 + }
  122 +
  123 + @Test
  124 + public void testDeleteSmsChannel_success() {
  125 + // mock 数据
  126 + SysSmsChannelDO dbSmsChannel = randomSmsChannelDO();
  127 + smsChannelMapper.insert(dbSmsChannel);// @Sql: 先插入出一条存在的数据
  128 + // 准备参数
  129 + Long id = dbSmsChannel.getId();
  130 +
  131 + // 调用
  132 + smsChannelService.deleteSmsChannel(id);
  133 + // 校验数据不存在了
  134 + assertNull(smsChannelMapper.selectById(id));
  135 + // 校验调用
  136 + verify(smsProducer, times(1)).sendSmsChannelRefreshMessage();
  137 + }
  138 +
  139 + @Test
  140 + public void testDeleteSmsChannel_notExists() {
  141 + // 准备参数
  142 + Long id = randomLongId();
  143 +
  144 + // 调用, 并断言异常
  145 + assertServiceException(() -> smsChannelService.deleteSmsChannel(id), SMS_CHANNEL_NOT_EXISTS);
  146 + }
  147 +
  148 + @Test
  149 + public void testDeleteSmsChannel_hasChildren() {
  150 + // mock 数据
  151 + SysSmsChannelDO dbSmsChannel = randomSmsChannelDO();
  152 + smsChannelMapper.insert(dbSmsChannel);// @Sql: 先插入出一条存在的数据
  153 + // 准备参数
  154 + Long id = dbSmsChannel.getId();
  155 + // mock 方法
  156 + when(smsTemplateService.countByChannelId(eq(id))).thenReturn(10);
  157 +
  158 + // 调用, 并断言异常
  159 + assertServiceException(() -> smsChannelService.deleteSmsChannel(id), SMS_CHANNEL_HAS_CHILDREN);
  160 + }
  161 +
  162 + @Test
  163 + public void testGetSmsChannelPage() {
  164 + // mock 数据
  165 + SysSmsChannelDO dbSmsChannel = randomPojo(SysSmsChannelDO.class, o -> { // 等会查询到
  166 + o.setSignature("芋道源码");
  167 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  168 + o.setCreateTime(buildTime(2020, 12, 12));
  169 + });
  170 + smsChannelMapper.insert(dbSmsChannel);
  171 + // 测试 signature 不匹配
  172 + smsChannelMapper.insert(ObjectUtils.clone(dbSmsChannel, o -> o.setSignature("源码")));
  173 + // 测试 status 不匹配
  174 + smsChannelMapper.insert(ObjectUtils.clone(dbSmsChannel, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
  175 + // 测试 createTime 不匹配
  176 + smsChannelMapper.insert(ObjectUtils.clone(dbSmsChannel, o -> o.setCreateTime(buildTime(2020, 11, 11))));
  177 + // 准备参数
  178 + SysSmsChannelPageReqVO reqVO = new SysSmsChannelPageReqVO();
  179 + reqVO.setSignature("芋道");
  180 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
  181 + reqVO.setBeginCreateTime(buildTime(2020, 12, 1));
  182 + reqVO.setEndCreateTime(buildTime(2020, 12, 24));
  183 +
  184 + // 调用
  185 + PageResult<SysSmsChannelDO> pageResult = smsChannelService.getSmsChannelPage(reqVO);
  186 + // 断言
  187 + assertEquals(1, pageResult.getTotal());
  188 + assertEquals(1, pageResult.getList().size());
  189 + assertPojoEquals(dbSmsChannel, pageResult.getList().get(0));
  190 + }
  191 +
  192 + // ========== 随机对象 ==========
  193 +
  194 + @SafeVarargs
  195 + private static SysSmsChannelDO randomSmsChannelDO(Consumer<SysSmsChannelDO>... consumers) {
  196 + Consumer<SysSmsChannelDO> consumer = (o) -> {
  197 + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  198 + };
  199 + return randomPojo(SysSmsChannelDO.class, ArrayUtils.append(consumer, consumers));
  200 + }
  201 +
  202 +}
... ...
src/test/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsLogServiceTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.dashboard.BaseDbUnitTest;
  5 +import cn.iocoder.dashboard.common.enums.UserTypeEnum;
  6 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  7 +import cn.iocoder.dashboard.common.pojo.PageResult;
  8 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogExportReqVO;
  9 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.log.SysSmsLogPageReqVO;
  10 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsLogDO;
  11 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  12 +import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsLogMapper;
  13 +import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsReceiveStatusEnum;
  14 +import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
  15 +import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsTemplateTypeEnum;
  16 +import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsLogServiceImpl;
  17 +import cn.iocoder.dashboard.util.collection.ArrayUtils;
  18 +import cn.iocoder.dashboard.util.object.ObjectUtils;
  19 +import org.junit.jupiter.api.Test;
  20 +import org.springframework.context.annotation.Import;
  21 +
  22 +import javax.annotation.Resource;
  23 +import java.util.Date;
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +import java.util.function.Consumer;
  27 +
  28 +import static cn.hutool.core.util.RandomUtil.randomBoolean;
  29 +import static cn.hutool.core.util.RandomUtil.randomEle;
  30 +import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
  31 +import static cn.iocoder.dashboard.util.RandomUtils.*;
  32 +import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
  33 +import static org.junit.jupiter.api.Assertions.*;
  34 +
  35 +/**
  36 +* {@link SysSmsLogServiceImpl} 的单元测试类
  37 +*
  38 +* @author 芋道源码
  39 +*/
  40 +@Import(SysSmsLogServiceImpl.class)
  41 +public class SysSmsLogServiceTest extends BaseDbUnitTest {
  42 +
  43 + @Resource
  44 + private SysSmsLogServiceImpl smsLogService;
  45 +
  46 + @Resource
  47 + private SysSmsLogMapper smsLogMapper;
  48 +
  49 + @Test
  50 + public void testCreateSmsLog() {
  51 + // 准备参数
  52 + String mobile = randomString();
  53 + Long userId = randomLongId();
  54 + Integer userType = randomEle(UserTypeEnum.values()).getValue();
  55 + Boolean isSend = randomBoolean();
  56 + SysSmsTemplateDO templateDO = randomPojo(SysSmsTemplateDO.class,
  57 + o -> o.setType(randomEle(SysSmsTemplateTypeEnum.values()).getType()));
  58 + String templateContent = randomString();
  59 + Map<String, Object> templateParams = randomTemplateParams();
  60 + // mock 方法
  61 +
  62 + // 调用
  63 + Long logId = smsLogService.createSmsLog(mobile, userId, userType, isSend,
  64 + templateDO, templateContent, templateParams);
  65 + // 断言
  66 + SysSmsLogDO logDO = smsLogMapper.selectById(logId);
  67 + assertEquals(isSend ? SysSmsSendStatusEnum.INIT.getStatus() : SysSmsSendStatusEnum.IGNORE.getStatus(),
  68 + logDO.getSendStatus());
  69 + assertEquals(mobile, logDO.getMobile());
  70 + assertEquals(userType, logDO.getUserType());
  71 + assertEquals(userId, logDO.getUserId());
  72 + assertEquals(templateDO.getId(), logDO.getTemplateId());
  73 + assertEquals(templateDO.getCode(), logDO.getTemplateCode());
  74 + assertEquals(templateDO.getType(), logDO.getTemplateType());
  75 + assertEquals(templateDO.getChannelId(), logDO.getChannelId());
  76 + assertEquals(templateDO.getChannelCode(), logDO.getChannelCode());
  77 + assertEquals(templateContent, logDO.getTemplateContent());
  78 + assertEquals(templateParams, logDO.getTemplateParams());
  79 + assertEquals(SysSmsReceiveStatusEnum.INIT.getStatus(), logDO.getReceiveStatus());
  80 + }
  81 +
  82 + @Test
  83 + public void testUpdateSmsSendResult() {
  84 + // mock 数据
  85 + SysSmsLogDO dbSmsLog = randomSmsLogDO(
  86 + o -> o.setSendStatus(SysSmsSendStatusEnum.IGNORE.getStatus()));
  87 + smsLogMapper.insert(dbSmsLog);
  88 + // 准备参数
  89 + Long id = dbSmsLog.getId();
  90 + Integer sendCode = randomInteger();
  91 + String sendMsg = randomString();
  92 + String apiSendCode = randomString();
  93 + String apiSendMsg = randomString();
  94 + String apiRequestId = randomString();
  95 + String apiSerialNo = randomString();
  96 +
  97 + // 调用
  98 + smsLogService.updateSmsSendResult(id, sendCode, sendMsg,
  99 + apiSendCode, apiSendMsg, apiRequestId, apiSerialNo);
  100 + // 断言
  101 + dbSmsLog = smsLogMapper.selectById(id);
  102 + assertEquals(CommonResult.isSuccess(sendCode) ? SysSmsSendStatusEnum.SUCCESS.getStatus()
  103 + : SysSmsSendStatusEnum.FAILURE.getStatus(), dbSmsLog.getSendStatus());
  104 + assertNotNull(dbSmsLog.getSendTime());
  105 + assertEquals(sendMsg, dbSmsLog.getSendMsg());
  106 + assertEquals(apiSendCode, dbSmsLog.getApiSendCode());
  107 + assertEquals(apiSendMsg, dbSmsLog.getApiSendMsg());
  108 + assertEquals(apiRequestId, dbSmsLog.getApiRequestId());
  109 + assertEquals(apiSerialNo, dbSmsLog.getApiSerialNo());
  110 + }
  111 +
  112 + @Test
  113 + public void testUpdateSmsReceiveResult() {
  114 + // mock 数据
  115 + SysSmsLogDO dbSmsLog = randomSmsLogDO(
  116 + o -> o.setReceiveStatus(SysSmsReceiveStatusEnum.INIT.getStatus()));
  117 + smsLogMapper.insert(dbSmsLog);
  118 + // 准备参数
  119 + Long id = dbSmsLog.getId();
  120 + Boolean success = randomBoolean();
  121 + Date receiveTime = randomDate();
  122 + String apiReceiveCode = randomString();
  123 + String apiReceiveMsg = randomString();
  124 +
  125 + // 调用
  126 + smsLogService.updateSmsReceiveResult(id, success, receiveTime, apiReceiveCode, apiReceiveMsg);
  127 + // 断言
  128 + dbSmsLog = smsLogMapper.selectById(id);
  129 + assertEquals(success ? SysSmsReceiveStatusEnum.SUCCESS.getStatus()
  130 + : SysSmsReceiveStatusEnum.FAILURE.getStatus(), dbSmsLog.getReceiveStatus());
  131 + assertEquals(receiveTime, dbSmsLog.getReceiveTime());
  132 + assertEquals(apiReceiveCode, dbSmsLog.getApiReceiveCode());
  133 + assertEquals(apiReceiveMsg, dbSmsLog.getApiReceiveMsg());
  134 + }
  135 +
  136 + @Test
  137 + public void testGetSmsLogPage() {
  138 + // mock 数据
  139 + SysSmsLogDO dbSmsLog = randomSmsLogDO(o -> { // 等会查询到
  140 + o.setChannelId(1L);
  141 + o.setTemplateId(10L);
  142 + o.setMobile("15601691300");
  143 + o.setSendStatus(SysSmsSendStatusEnum.INIT.getStatus());
  144 + o.setSendTime(buildTime(2020, 11, 11));
  145 + o.setReceiveStatus(SysSmsReceiveStatusEnum.INIT.getStatus());
  146 + o.setReceiveTime(buildTime(2021, 11, 11));
  147 + });
  148 + smsLogMapper.insert(dbSmsLog);
  149 + // 测试 channelId 不匹配
  150 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setChannelId(2L)));
  151 + // 测试 templateId 不匹配
  152 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setTemplateId(20L)));
  153 + // 测试 mobile 不匹配
  154 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setMobile("18818260999")));
  155 + // 测试 sendStatus 不匹配
  156 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setSendStatus(SysSmsSendStatusEnum.IGNORE.getStatus())));
  157 + // 测试 sendTime 不匹配
  158 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setSendTime(buildTime(2020, 12, 12))));
  159 + // 测试 receiveStatus 不匹配
  160 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setReceiveStatus(SysSmsReceiveStatusEnum.SUCCESS.getStatus())));
  161 + // 测试 receiveTime 不匹配
  162 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setReceiveTime(buildTime(2021, 12, 12))));
  163 + // 准备参数
  164 + SysSmsLogPageReqVO reqVO = new SysSmsLogPageReqVO();
  165 + reqVO.setChannelId(1L);
  166 + reqVO.setTemplateId(10L);
  167 + reqVO.setMobile("156");
  168 + reqVO.setSendStatus(SysSmsSendStatusEnum.INIT.getStatus());
  169 + reqVO.setBeginSendTime(buildTime(2020, 11, 1));
  170 + reqVO.setEndSendTime(buildTime(2020, 11, 30));
  171 + reqVO.setReceiveStatus(SysSmsReceiveStatusEnum.INIT.getStatus());
  172 + reqVO.setBeginReceiveTime(buildTime(2021, 11, 1));
  173 + reqVO.setEndReceiveTime(buildTime(2021, 11, 30));
  174 +
  175 + // 调用
  176 + PageResult<SysSmsLogDO> pageResult = smsLogService.getSmsLogPage(reqVO);
  177 + // 断言
  178 + assertEquals(1, pageResult.getTotal());
  179 + assertEquals(1, pageResult.getList().size());
  180 + assertPojoEquals(dbSmsLog, pageResult.getList().get(0));
  181 + }
  182 +
  183 + @Test
  184 + public void testGetSmsLogList() {
  185 + // mock 数据
  186 + SysSmsLogDO dbSmsLog = randomSmsLogDO(o -> { // 等会查询到
  187 + o.setChannelId(1L);
  188 + o.setTemplateId(10L);
  189 + o.setMobile("15601691300");
  190 + o.setSendStatus(SysSmsSendStatusEnum.INIT.getStatus());
  191 + o.setSendTime(buildTime(2020, 11, 11));
  192 + o.setReceiveStatus(SysSmsReceiveStatusEnum.INIT.getStatus());
  193 + o.setReceiveTime(buildTime(2021, 11, 11));
  194 + });
  195 + smsLogMapper.insert(dbSmsLog);
  196 + // 测试 channelId 不匹配
  197 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setChannelId(2L)));
  198 + // 测试 templateId 不匹配
  199 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setTemplateId(20L)));
  200 + // 测试 mobile 不匹配
  201 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setMobile("18818260999")));
  202 + // 测试 sendStatus 不匹配
  203 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setSendStatus(SysSmsSendStatusEnum.IGNORE.getStatus())));
  204 + // 测试 sendTime 不匹配
  205 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setSendTime(buildTime(2020, 12, 12))));
  206 + // 测试 receiveStatus 不匹配
  207 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setReceiveStatus(SysSmsReceiveStatusEnum.SUCCESS.getStatus())));
  208 + // 测试 receiveTime 不匹配
  209 + smsLogMapper.insert(ObjectUtils.clone(dbSmsLog, o -> o.setReceiveTime(buildTime(2021, 12, 12))));
  210 + // 准备参数
  211 + SysSmsLogExportReqVO reqVO = new SysSmsLogExportReqVO();
  212 + reqVO.setChannelId(1L);
  213 + reqVO.setTemplateId(10L);
  214 + reqVO.setMobile("156");
  215 + reqVO.setSendStatus(SysSmsSendStatusEnum.INIT.getStatus());
  216 + reqVO.setBeginSendTime(buildTime(2020, 11, 1));
  217 + reqVO.setEndSendTime(buildTime(2020, 11, 30));
  218 + reqVO.setReceiveStatus(SysSmsReceiveStatusEnum.INIT.getStatus());
  219 + reqVO.setBeginReceiveTime(buildTime(2021, 11, 1));
  220 + reqVO.setEndReceiveTime(buildTime(2021, 11, 30));
  221 +
  222 + // 调用
  223 + List<SysSmsLogDO> list = smsLogService.getSmsLogList(reqVO);
  224 + // 断言
  225 + assertEquals(1, list.size());
  226 + assertPojoEquals(dbSmsLog, list.get(0));
  227 + }
  228 +
  229 + // ========== 随机对象 ==========
  230 +
  231 + @SafeVarargs
  232 + private static SysSmsLogDO randomSmsLogDO(Consumer<SysSmsLogDO>... consumers) {
  233 + Consumer<SysSmsLogDO> consumer = (o) -> {
  234 + o.setTemplateParams(randomTemplateParams());
  235 + o.setTemplateType(randomEle(SysSmsTemplateTypeEnum.values()).getType()); // 保证 templateType 的范围
  236 + o.setUserType(randomEle(UserTypeEnum.values()).getValue()); // 保证 userType 的范围
  237 + o.setSendStatus(randomEle(SysSmsSendStatusEnum.values()).getStatus()); // 保证 sendStatus 的范围
  238 + o.setReceiveStatus(randomEle(SysSmsReceiveStatusEnum.values()).getStatus()); // 保证 receiveStatus 的范围
  239 + };
  240 + return randomPojo(SysSmsLogDO.class, ArrayUtils.append(consumer, consumers));
  241 + }
  242 +
  243 +
  244 + private static Map<String, Object> randomTemplateParams() {
  245 + return MapUtil.<String, Object>builder().put(randomString(), randomString())
  246 + .put(randomString(), randomString()).build();
  247 + }
  248 +}
... ...
src/test/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsServiceTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.dashboard.BaseMockitoUnitTest;
  5 +import cn.iocoder.dashboard.common.core.KeyValue;
  6 +import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
  7 +import cn.iocoder.dashboard.common.enums.UserTypeEnum;
  8 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
  9 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
  10 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  11 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  12 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  13 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  14 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
  15 +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
  16 +import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsServiceImpl;
  17 +import org.assertj.core.util.Lists;
  18 +import org.junit.jupiter.api.Test;
  19 +import org.mockito.InjectMocks;
  20 +import org.mockito.Mock;
  21 +
  22 +import java.util.HashMap;
  23 +import java.util.List;
  24 +import java.util.Map;
  25 +
  26 +import static cn.hutool.core.util.RandomUtil.randomEle;
  27 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
  28 +import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
  29 +import static cn.iocoder.dashboard.util.RandomUtils.*;
  30 +import static org.junit.jupiter.api.Assertions.assertEquals;
  31 +import static org.mockito.ArgumentMatchers.eq;
  32 +import static org.mockito.Mockito.*;
  33 +
  34 +/**
  35 + * {@link SysSmsServiceImpl} 的单元测试类
  36 + *
  37 + * @author 芋道源码
  38 + */
  39 +public class SysSmsServiceTest extends BaseMockitoUnitTest {
  40 +
  41 + @InjectMocks
  42 + private SysSmsServiceImpl smsService;
  43 +
  44 + @Mock
  45 + private SysSmsTemplateService smsTemplateService;
  46 + @Mock
  47 + private SysSmsLogService smsLogService;
  48 + @Mock
  49 + private SysSmsProducer smsProducer;
  50 + @Mock
  51 + private SmsClientFactory smsClientFactory;
  52 +
  53 + /**
  54 + * 发送成功,当短信模板开启时
  55 + */
  56 + @Test
  57 + public void testSendSingleSms_successWhenSmsTemplateEnable() {
  58 + // 准备参数
  59 + String mobile = randomString();
  60 + Long userId = randomLongId();
  61 + Integer userType = randomEle(UserTypeEnum.values()).getValue();
  62 + String templateCode = randomString();
  63 + Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234")
  64 + .put("op", "login").build();
  65 + // mock SmsTemplateService 的方法
  66 + SysSmsTemplateDO template = randomPojo(SysSmsTemplateDO.class, o -> {
  67 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  68 + o.setContent("验证码为{code}, 操作为{op}");
  69 + o.setParams(Lists.newArrayList("code", "op"));
  70 + });
  71 + when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);
  72 + String content = randomString();
  73 + when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams)))
  74 + .thenReturn(content);
  75 + // mock SmsLogService 的方法
  76 + Long smsLogId = randomLongId();
  77 + when(smsLogService.createSmsLog(eq(mobile), eq(userId), eq(userType), eq(Boolean.TRUE), eq(template),
  78 + eq(content), eq(templateParams))).thenReturn(smsLogId);
  79 +
  80 + // 调用
  81 + Long resultSmsLogId = smsService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
  82 + // 断言
  83 + assertEquals(smsLogId, resultSmsLogId);
  84 + // 断言调用
  85 + verify(smsProducer, times(1)).sendSmsSendMessage(eq(smsLogId), eq(mobile),
  86 + eq(template.getChannelId()), eq(template.getApiTemplateId()),
  87 + eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login"))));
  88 + }
  89 +
  90 + /**
  91 + * 发送成功,当短信模板关闭时
  92 + */
  93 + @Test
  94 + public void testSendSingleSms_successWhenSmsTemplateDisable() {
  95 + // 准备参数
  96 + String mobile = randomString();
  97 + Long userId = randomLongId();
  98 + Integer userType = randomEle(UserTypeEnum.values()).getValue();
  99 + String templateCode = randomString();
  100 + Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234")
  101 + .put("op", "login").build();
  102 + // mock SmsTemplateService 的方法
  103 + SysSmsTemplateDO template = randomPojo(SysSmsTemplateDO.class, o -> {
  104 + o.setStatus(CommonStatusEnum.DISABLE.getStatus());
  105 + o.setContent("验证码为{code}, 操作为{op}");
  106 + o.setParams(Lists.newArrayList("code", "op"));
  107 + });
  108 + when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);
  109 + String content = randomString();
  110 + when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams)))
  111 + .thenReturn(content);
  112 + // mock SmsLogService 的方法
  113 + Long smsLogId = randomLongId();
  114 + when(smsLogService.createSmsLog(eq(mobile), eq(userId), eq(userType), eq(Boolean.FALSE), eq(template),
  115 + eq(content), eq(templateParams))).thenReturn(smsLogId);
  116 +
  117 + // 调用
  118 + Long resultSmsLogId = smsService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
  119 + // 断言
  120 + assertEquals(smsLogId, resultSmsLogId);
  121 + // 断言调用
  122 + verify(smsProducer, times(0)).sendSmsSendMessage(anyLong(), anyString(),
  123 + anyLong(), any(), anyList());
  124 + }
  125 +
  126 + @Test
  127 + public void testCheckSmsTemplateValid_notExists() {
  128 + // 准备参数
  129 + String templateCode = randomString();
  130 + // mock 方法
  131 +
  132 + // 调用,并断言异常
  133 + assertServiceException(() -> smsService.checkSmsTemplateValid(templateCode),
  134 + SMS_TEMPLATE_NOT_EXISTS);
  135 + }
  136 +
  137 + @Test
  138 + public void testBuildTemplateParams_paramMiss() {
  139 + // 准备参数
  140 + SysSmsTemplateDO template = randomPojo(SysSmsTemplateDO.class,
  141 + o -> o.setParams(Lists.newArrayList("code")));
  142 + Map<String, Object> templateParams = new HashMap<>();
  143 + // mock 方法
  144 +
  145 + // 调用,并断言异常
  146 + assertServiceException(() -> smsService.buildTemplateParams(template, templateParams),
  147 + SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, "code");
  148 + }
  149 +
  150 + @Test
  151 + public void testCheckMobile_notExists() {
  152 + // 准备参数
  153 + // mock 方法
  154 +
  155 + // 调用,并断言异常
  156 + assertServiceException(() -> smsService.checkMobile(null),
  157 + SMS_SEND_MOBILE_NOT_EXISTS);
  158 + }
  159 +
  160 + @Test
  161 + @SuppressWarnings("unchecked")
  162 + public void testDoSendSms() {
  163 + // 准备参数
  164 + SysSmsSendMessage message = randomPojo(SysSmsSendMessage.class);
  165 + // mock SmsClientFactory 的方法
  166 + SmsClient smsClient = spy(SmsClient.class);
  167 + when(smsClientFactory.getSmsClient(eq(message.getChannelId()))).thenReturn(smsClient);
  168 + // mock SmsClient 的方法
  169 + SmsCommonResult<SmsSendRespDTO> sendResult = randomPojo(SmsCommonResult.class, SmsSendRespDTO.class);
  170 + when(smsClient.sendSms(eq(message.getLogId()), eq(message.getMobile()), eq(message.getApiTemplateId()),
  171 + eq(message.getTemplateParams()))).thenReturn(sendResult);
  172 +
  173 + // 调用
  174 + smsService.doSendSms(message);
  175 + // 断言
  176 + verify(smsLogService, times(1)).updateSmsSendResult(eq(message.getLogId()),
  177 + eq(sendResult.getCode()), eq(sendResult.getMsg()), eq(sendResult.getApiCode()),
  178 + eq(sendResult.getApiMsg()), eq(sendResult.getApiRequestId()), eq(sendResult.getData().getSerialNo()));
  179 + }
  180 +
  181 + @Test
  182 + public void testReceiveSmsStatus() throws Throwable {
  183 + // 准备参数
  184 + String channelCode = randomString();
  185 + String text = randomString();
  186 + // mock SmsClientFactory 的方法
  187 + SmsClient smsClient = spy(SmsClient.class);
  188 + when(smsClientFactory.getSmsClient(eq(channelCode))).thenReturn(smsClient);
  189 + // mock SmsClient 的方法
  190 + List<SmsReceiveRespDTO> receiveResults = randomPojoList(SmsReceiveRespDTO.class);
  191 +
  192 + // 调用
  193 + smsService.receiveSmsStatus(channelCode, text);
  194 + // 断言
  195 + receiveResults.forEach(result -> {
  196 + smsLogService.updateSmsReceiveResult(eq(result.getLogId()), eq(result.getSuccess()),
  197 + eq(result.getReceiveTime()), eq(result.getErrorCode()), eq(result.getErrorCode()));
  198 + });
  199 + }
  200 +
  201 +}
... ...
src/test/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsTemplateServiceTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +import cn.iocoder.dashboard.BaseDbUnitTest;
  4 +import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
  5 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  6 +import cn.iocoder.dashboard.common.pojo.PageResult;
  7 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
  8 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
  9 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  10 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsTemplateRespDTO;
  11 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateCreateReqVO;
  12 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateExportReqVO;
  13 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplatePageReqVO;
  14 +import cn.iocoder.dashboard.modules.system.controller.sms.vo.template.SysSmsTemplateUpdateReqVO;
  15 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsChannelDO;
  16 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  17 +import cn.iocoder.dashboard.modules.system.dal.mysql.sms.SysSmsTemplateMapper;
  18 +import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsTemplateTypeEnum;
  19 +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
  20 +import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsTemplateServiceImpl;
  21 +import cn.iocoder.dashboard.util.collection.ArrayUtils;
  22 +import cn.iocoder.dashboard.util.object.ObjectUtils;
  23 +import com.google.common.collect.Lists;
  24 +import org.junit.jupiter.api.Test;
  25 +import org.springframework.boot.test.mock.mockito.MockBean;
  26 +import org.springframework.context.annotation.Import;
  27 +
  28 +import javax.annotation.Resource;
  29 +import java.util.Date;
  30 +import java.util.List;
  31 +import java.util.Map;
  32 +import java.util.function.Consumer;
  33 +
  34 +import static cn.hutool.core.bean.BeanUtil.getFieldValue;
  35 +import static cn.hutool.core.util.RandomUtil.randomEle;
  36 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
  37 +import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
  38 +import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
  39 +import static cn.iocoder.dashboard.util.RandomUtils.*;
  40 +import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
  41 +import static cn.iocoder.dashboard.util.object.ObjectUtils.max;
  42 +import static org.junit.jupiter.api.Assertions.*;
  43 +import static org.mockito.ArgumentMatchers.eq;
  44 +import static org.mockito.Mockito.*;
  45 +
  46 +/**
  47 +* {@link SysSmsTemplateServiceImpl} 的单元测试类
  48 +*
  49 +* @author 芋道源码
  50 +*/
  51 +@Import(SysSmsTemplateServiceImpl.class)
  52 +public class SysSmsTemplateServiceTest extends BaseDbUnitTest {
  53 +
  54 + @Resource
  55 + private SysSmsTemplateServiceImpl smsTemplateService;
  56 +
  57 + @Resource
  58 + private SysSmsTemplateMapper smsTemplateMapper;
  59 +
  60 + @MockBean
  61 + private SysSmsChannelService smsChannelService;
  62 + @MockBean
  63 + private SmsClientFactory smsClientFactory;
  64 + @MockBean
  65 + private SmsClient smsClient;
  66 + @MockBean
  67 + private SysSmsProducer smsProducer;
  68 +
  69 + @Test
  70 + @SuppressWarnings("unchecked")
  71 + void testInitLocalCache() {
  72 + // mock 数据
  73 + SysSmsTemplateDO smsTemplate01 = randomSmsTemplateDO();
  74 + smsTemplateMapper.insert(smsTemplate01);
  75 + SysSmsTemplateDO smsTemplate02 = randomSmsTemplateDO();
  76 + smsTemplateMapper.insert(smsTemplate02);
  77 +
  78 + // 调用
  79 + smsTemplateService.initLocalCache();
  80 + // 断言 deptCache 缓存
  81 + Map<String, SysSmsTemplateDO> smsTemplateCache = (Map<String, SysSmsTemplateDO>) getFieldValue(smsTemplateService, "smsTemplateCache");
  82 + assertEquals(2, smsTemplateCache.size());
  83 + assertPojoEquals(smsTemplate01, smsTemplateCache.get(smsTemplate01.getCode()));
  84 + assertPojoEquals(smsTemplate02, smsTemplateCache.get(smsTemplate02.getCode()));
  85 + // 断言 maxUpdateTime 缓存
  86 + Date maxUpdateTime = (Date) getFieldValue(smsTemplateService, "maxUpdateTime");
  87 + assertEquals(max(smsTemplate01.getUpdateTime(), smsTemplate02.getUpdateTime()), maxUpdateTime);
  88 + }
  89 +
  90 + @Test
  91 + public void testParseTemplateContentParams() {
  92 + // 准备参数
  93 + String content = "正在进行登录操作{operation},您的验证码是{code}";
  94 + // mock 方法
  95 +
  96 + // 调用
  97 + List<String> params = smsTemplateService.parseTemplateContentParams(content);
  98 + // 断言
  99 + assertEquals(Lists.newArrayList("operation", "code"), params);
  100 + }
  101 +
  102 + @Test
  103 + @SuppressWarnings("unchecked")
  104 + public void testCreateSmsTemplate_success() {
  105 + // 准备参数
  106 + SysSmsTemplateCreateReqVO reqVO = randomPojo(SysSmsTemplateCreateReqVO.class, o -> {
  107 + o.setContent("正在进行登录操作{operation},您的验证码是{code}");
  108 + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  109 + o.setType(randomEle(SysSmsTemplateTypeEnum.values()).getType()); // 保证 type 的 范围
  110 + });
  111 + // mock Channel 的方法
  112 + SysSmsChannelDO channelDO = randomPojo(SysSmsChannelDO.class, o -> {
  113 + o.setId(reqVO.getChannelId());
  114 + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 保证 status 开启,创建必须处于这个状态
  115 + });
  116 + when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO);
  117 + // mock 获得 API 短信模板成功
  118 + when(smsClientFactory.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient);
  119 + when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(randomPojo(SmsCommonResult.class, SmsTemplateRespDTO.class,
  120 + o -> o.setCode(GlobalErrorCodeConstants.SUCCESS.getCode())));
  121 +
  122 + // 调用
  123 + Long smsTemplateId = smsTemplateService.createSmsTemplate(reqVO);
  124 + // 断言
  125 + assertNotNull(smsTemplateId);
  126 + // 校验记录的属性是否正确
  127 + SysSmsTemplateDO smsTemplate = smsTemplateMapper.selectById(smsTemplateId);
  128 + assertPojoEquals(reqVO, smsTemplate);
  129 + assertEquals(Lists.newArrayList("operation", "code"), smsTemplate.getParams());
  130 + assertEquals(channelDO.getCode(), smsTemplate.getChannelCode());
  131 + // 校验调用
  132 + verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
  133 + }
  134 +
  135 + @Test
  136 + @SuppressWarnings("unchecked")
  137 + public void testUpdateSmsTemplate_success() {
  138 + // mock 数据
  139 + SysSmsTemplateDO dbSmsTemplate = randomSmsTemplateDO();
  140 + smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据
  141 + // 准备参数
  142 + SysSmsTemplateUpdateReqVO reqVO = randomPojo(SysSmsTemplateUpdateReqVO.class, o -> {
  143 + o.setId(dbSmsTemplate.getId()); // 设置更新的 ID
  144 + o.setContent("正在进行登录操作{operation},您的验证码是{code}");
  145 + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  146 + o.setType(randomEle(SysSmsTemplateTypeEnum.values()).getType()); // 保证 type 的 范围
  147 + });
  148 + // mock 方法
  149 + SysSmsChannelDO channelDO = randomPojo(SysSmsChannelDO.class, o -> {
  150 + o.setId(reqVO.getChannelId());
  151 + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 保证 status 开启,创建必须处于这个状态
  152 + });
  153 + when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO);
  154 + // mock 获得 API 短信模板成功
  155 + when(smsClientFactory.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient);
  156 + when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(randomPojo(SmsCommonResult.class, SmsTemplateRespDTO.class,
  157 + o -> o.setCode(GlobalErrorCodeConstants.SUCCESS.getCode())));
  158 +
  159 + // 调用
  160 + smsTemplateService.updateSmsTemplate(reqVO);
  161 + // 校验是否更新正确
  162 + SysSmsTemplateDO smsTemplate = smsTemplateMapper.selectById(reqVO.getId()); // 获取最新的
  163 + assertPojoEquals(reqVO, smsTemplate);
  164 + assertEquals(Lists.newArrayList("operation", "code"), smsTemplate.getParams());
  165 + assertEquals(channelDO.getCode(), smsTemplate.getChannelCode());
  166 + // 校验调用
  167 + verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
  168 + }
  169 +
  170 + @Test
  171 + public void testUpdateSmsTemplate_notExists() {
  172 + // 准备参数
  173 + SysSmsTemplateUpdateReqVO reqVO = randomPojo(SysSmsTemplateUpdateReqVO.class);
  174 +
  175 + // 调用, 并断言异常
  176 + assertServiceException(() -> smsTemplateService.updateSmsTemplate(reqVO), SMS_TEMPLATE_NOT_EXISTS);
  177 + }
  178 +
  179 + @Test
  180 + public void testDeleteSmsTemplate_success() {
  181 + // mock 数据
  182 + SysSmsTemplateDO dbSmsTemplate = randomSmsTemplateDO();
  183 + smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据
  184 + // 准备参数
  185 + Long id = dbSmsTemplate.getId();
  186 +
  187 + // 调用
  188 + smsTemplateService.deleteSmsTemplate(id);
  189 + // 校验数据不存在了
  190 + assertNull(smsTemplateMapper.selectById(id));
  191 + // 校验调用
  192 + verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
  193 + }
  194 +
  195 + @Test
  196 + public void testDeleteSmsTemplate_notExists() {
  197 + // 准备参数
  198 + Long id = randomLongId();
  199 +
  200 + // 调用, 并断言异常
  201 + assertServiceException(() -> smsTemplateService.deleteSmsTemplate(id), SMS_TEMPLATE_NOT_EXISTS);
  202 + }
  203 +
  204 + @Test
  205 + public void testGetSmsTemplatePage() {
  206 + // mock 数据
  207 + SysSmsTemplateDO dbSmsTemplate = randomPojo(SysSmsTemplateDO.class, o -> { // 等会查询到
  208 + o.setType(SysSmsTemplateTypeEnum.PROMOTION.getType());
  209 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  210 + o.setCode("yudaoyuanma");
  211 + o.setContent("芋道源码");
  212 + o.setApiTemplateId("yunai");
  213 + o.setChannelId(1L);
  214 + o.setCreateTime(buildTime(2021, 11, 11));
  215 + });
  216 + smsTemplateMapper.insert(dbSmsTemplate);
  217 + // 测试 type 不匹配
  218 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setType(SysSmsTemplateTypeEnum.VERIFICATION_CODE.getType())));
  219 + // 测试 status 不匹配
  220 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
  221 + // 测试 code 不匹配
  222 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setCode("yuanma")));
  223 + // 测试 content 不匹配
  224 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setContent("源码")));
  225 + // 测试 apiTemplateId 不匹配
  226 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setApiTemplateId("nai")));
  227 + // 测试 channelId 不匹配
  228 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setChannelId(2L)));
  229 + // 测试 createTime 不匹配
  230 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setCreateTime(buildTime(2021, 12, 12))));
  231 + // 准备参数
  232 + SysSmsTemplatePageReqVO reqVO = new SysSmsTemplatePageReqVO();
  233 + reqVO.setType(SysSmsTemplateTypeEnum.PROMOTION.getType());
  234 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
  235 + reqVO.setCode("yudao");
  236 + reqVO.setContent("芋道");
  237 + reqVO.setApiTemplateId("yu");
  238 + reqVO.setChannelId(1L);
  239 + reqVO.setBeginCreateTime(buildTime(2021, 11, 1));
  240 + reqVO.setEndCreateTime(buildTime(2021, 12, 1));
  241 +
  242 + // 调用
  243 + PageResult<SysSmsTemplateDO> pageResult = smsTemplateService.getSmsTemplatePage(reqVO);
  244 + // 断言
  245 + assertEquals(1, pageResult.getTotal());
  246 + assertEquals(1, pageResult.getList().size());
  247 + assertPojoEquals(dbSmsTemplate, pageResult.getList().get(0));
  248 + }
  249 +
  250 + @Test
  251 + public void testGetSmsTemplateList() {
  252 + // mock 数据
  253 + SysSmsTemplateDO dbSmsTemplate = randomPojo(SysSmsTemplateDO.class, o -> { // 等会查询到
  254 + o.setType(SysSmsTemplateTypeEnum.PROMOTION.getType());
  255 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  256 + o.setCode("yudaoyuanma");
  257 + o.setContent("芋道源码");
  258 + o.setApiTemplateId("yunai");
  259 + o.setChannelId(1L);
  260 + o.setCreateTime(buildTime(2021, 11, 11));
  261 + });
  262 + smsTemplateMapper.insert(dbSmsTemplate);
  263 + // 测试 type 不匹配
  264 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setType(SysSmsTemplateTypeEnum.VERIFICATION_CODE.getType())));
  265 + // 测试 status 不匹配
  266 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
  267 + // 测试 code 不匹配
  268 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setCode("yuanma")));
  269 + // 测试 content 不匹配
  270 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setContent("源码")));
  271 + // 测试 apiTemplateId 不匹配
  272 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setApiTemplateId("nai")));
  273 + // 测试 channelId 不匹配
  274 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setChannelId(2L)));
  275 + // 测试 createTime 不匹配
  276 + smsTemplateMapper.insert(ObjectUtils.clone(dbSmsTemplate, o -> o.setCreateTime(buildTime(2021, 12, 12))));
  277 + // 准备参数
  278 + SysSmsTemplateExportReqVO reqVO = new SysSmsTemplateExportReqVO();
  279 + reqVO.setType(SysSmsTemplateTypeEnum.PROMOTION.getType());
  280 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
  281 + reqVO.setCode("yudao");
  282 + reqVO.setContent("芋道");
  283 + reqVO.setApiTemplateId("yu");
  284 + reqVO.setChannelId(1L);
  285 + reqVO.setBeginCreateTime(buildTime(2021, 11, 1));
  286 + reqVO.setEndCreateTime(buildTime(2021, 12, 1));
  287 +
  288 + // 调用
  289 + List<SysSmsTemplateDO> list = smsTemplateService.getSmsTemplateList(reqVO);
  290 + // 断言
  291 + assertEquals(1, list.size());
  292 + assertPojoEquals(dbSmsTemplate, list.get(0));
  293 + }
  294 +
  295 + @Test
  296 + public void testCheckSmsChannel_success() {
  297 + // 准备参数
  298 + Long channelId = randomLongId();
  299 + // mock 方法
  300 + SysSmsChannelDO channelDO = randomPojo(SysSmsChannelDO.class, o -> {
  301 + o.setId(channelId);
  302 + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 保证 status 开启,创建必须处于这个状态
  303 + });
  304 + when(smsChannelService.getSmsChannel(eq(channelId))).thenReturn(channelDO);
  305 +
  306 + // 调用
  307 + SysSmsChannelDO returnChannelDO = smsTemplateService.checkSmsChannel(channelId);
  308 + // 断言
  309 + assertPojoEquals(returnChannelDO, channelDO);
  310 + }
  311 +
  312 + @Test
  313 + public void testCheckSmsChannel_notExists() {
  314 + // 准备参数
  315 + Long channelId = randomLongId();
  316 +
  317 + // 调用,校验异常
  318 + assertServiceException(() -> smsTemplateService.checkSmsChannel(channelId),
  319 + SMS_CHANNEL_NOT_EXISTS);
  320 + }
  321 +
  322 + @Test
  323 + public void testCheckSmsChannel_disable() {
  324 + // 准备参数
  325 + Long channelId = randomLongId();
  326 + // mock 方法
  327 + SysSmsChannelDO channelDO = randomPojo(SysSmsChannelDO.class, o -> {
  328 + o.setId(channelId);
  329 + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); // 保证 status 禁用,触发失败
  330 + });
  331 + when(smsChannelService.getSmsChannel(eq(channelId))).thenReturn(channelDO);
  332 +
  333 + // 调用,校验异常
  334 + assertServiceException(() -> smsTemplateService.checkSmsChannel(channelId),
  335 + SMS_CHANNEL_DISABLE);
  336 + }
  337 +
  338 + @Test
  339 + public void testCheckDictDataValueUnique_success() {
  340 + // 调用,成功
  341 + smsTemplateService.checkSmsTemplateCodeDuplicate(randomLongId(), randomString());
  342 + }
  343 +
  344 + @Test
  345 + public void testCheckSmsTemplateCodeDuplicate_valueDuplicateForCreate() {
  346 + // 准备参数
  347 + String code = randomString();
  348 + // mock 数据
  349 + smsTemplateMapper.insert(randomSmsTemplateDO(o -> o.setCode(code)));
  350 +
  351 + // 调用,校验异常
  352 + assertServiceException(() -> smsTemplateService.checkSmsTemplateCodeDuplicate(null, code),
  353 + SMS_TEMPLATE_CODE_DUPLICATE, code);
  354 + }
  355 +
  356 + @Test
  357 + public void testCheckDictDataValueUnique_valueDuplicateForUpdate() {
  358 + // 准备参数
  359 + Long id = randomLongId();
  360 + String code = randomString();
  361 + // mock 数据
  362 + smsTemplateMapper.insert(randomSmsTemplateDO(o -> o.setCode(code)));
  363 +
  364 + // 调用,校验异常
  365 + assertServiceException(() -> smsTemplateService.checkSmsTemplateCodeDuplicate(id, code),
  366 + SMS_TEMPLATE_CODE_DUPLICATE, code);
  367 + }
  368 +
  369 + // ========== 随机对象 ==========
  370 +
  371 + @SafeVarargs
  372 + private static SysSmsTemplateDO randomSmsTemplateDO(Consumer<SysSmsTemplateDO>... consumers) {
  373 + Consumer<SysSmsTemplateDO> consumer = (o) -> {
  374 + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  375 + o.setType(randomEle(SysSmsTemplateTypeEnum.values()).getType()); // 保证 type 的 范围
  376 + };
  377 + return randomPojo(SysSmsTemplateDO.class, ArrayUtils.append(consumer, consumers));
  378 + }
  379 +
  380 +}
... ...
src/test/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.user;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.RandomUtil;
  5 +import cn.iocoder.dashboard.BaseDbUnitTest;
  6 +import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
  7 +import cn.iocoder.dashboard.common.pojo.PageResult;
  8 +import cn.iocoder.dashboard.modules.infra.service.file.InfFileService;
  9 +import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdatePasswordReqVO;
  10 +import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdateReqVO;
  11 +import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*;
  12 +import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysDeptDO;
  13 +import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysPostDO;
  14 +import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
  15 +import cn.iocoder.dashboard.modules.system.dal.mysql.user.SysUserMapper;
  16 +import cn.iocoder.dashboard.modules.system.enums.common.SysSexEnum;
  17 +import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
  18 +import cn.iocoder.dashboard.modules.system.service.dept.SysPostService;
  19 +import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
  20 +import cn.iocoder.dashboard.util.collection.ArrayUtils;
  21 +import cn.iocoder.dashboard.util.collection.CollectionUtils;
  22 +import cn.iocoder.dashboard.util.object.ObjectUtils;
  23 +import org.junit.jupiter.api.Test;
  24 +import org.mockito.stubbing.Answer;
  25 +import org.springframework.boot.test.mock.mockito.MockBean;
  26 +import org.springframework.context.annotation.Import;
  27 +import org.springframework.security.crypto.password.PasswordEncoder;
  28 +
  29 +import javax.annotation.Resource;
  30 +import java.io.ByteArrayInputStream;
  31 +import java.util.List;
  32 +import java.util.Set;
  33 +import java.util.function.Consumer;
  34 +
  35 +import static cn.hutool.core.util.RandomUtil.randomBytes;
  36 +import static cn.hutool.core.util.RandomUtil.randomEle;
  37 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
  38 +import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
  39 +import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
  40 +import static cn.iocoder.dashboard.util.RandomUtils.*;
  41 +import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
  42 +import static org.assertj.core.util.Lists.newArrayList;
  43 +import static org.junit.jupiter.api.Assertions.assertEquals;
  44 +import static org.junit.jupiter.api.Assertions.assertNull;
  45 +import static org.mockito.ArgumentMatchers.*;
  46 +import static org.mockito.Mockito.*;
  47 +
  48 +/**
  49 + * {@link SysUserService} 的单元测试类
  50 + *
  51 + * @author zxl
  52 + */
  53 +@Import(SysUserServiceImpl.class)
  54 +public class SysUserServiceImplTest extends BaseDbUnitTest {
  55 +
  56 + @Resource
  57 + private SysUserServiceImpl userService;
  58 +
  59 + @Resource
  60 + private SysUserMapper userMapper;
  61 +
  62 + @MockBean
  63 + private SysDeptService deptService;
  64 + @MockBean
  65 + private SysPostService postService;
  66 + @MockBean
  67 + private SysPermissionService permissionService;
  68 + @MockBean
  69 + private PasswordEncoder passwordEncoder;
  70 + @MockBean
  71 + private InfFileService fileService;
  72 +
  73 + @Test
  74 + public void testCreatUser_success() {
  75 + // 准备参数
  76 + SysUserCreateReqVO reqVO = randomPojo(SysUserCreateReqVO.class, o -> {
  77 + o.setSex(RandomUtil.randomEle(SysSexEnum.values()).getSex());
  78 + o.setMobile(randomString());
  79 + });
  80 + // mock deptService 的方法
  81 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
  82 + o.setId(reqVO.getDeptId());
  83 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  84 + });
  85 + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
  86 + // mock postService 的方法
  87 + List<SysPostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->
  88 + randomPojo(SysPostDO.class, o -> {
  89 + o.setId(postId);
  90 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  91 + }));
  92 + when(postService.getPosts(eq(reqVO.getPostIds()), isNull())).thenReturn(posts);
  93 + // mock passwordEncoder 的方法
  94 + when(passwordEncoder.encode(eq(reqVO.getPassword()))).thenReturn("yudaoyuanma");
  95 +
  96 + // 调用
  97 + Long userId = userService.createUser(reqVO);
  98 + // 断言
  99 + SysUserDO user = userMapper.selectById(userId);
  100 + assertPojoEquals(reqVO, user, "password");
  101 + assertEquals("yudaoyuanma", user.getPassword());
  102 + assertEquals(CommonStatusEnum.ENABLE.getStatus(), user.getStatus());
  103 + }
  104 +
  105 + @Test
  106 + public void testUpdateUser_success() {
  107 + // mock 数据
  108 + SysUserDO dbUser = randomSysUserDO();
  109 + userMapper.insert(dbUser);
  110 + // 准备参数
  111 + SysUserUpdateReqVO reqVO = randomPojo(SysUserUpdateReqVO.class, o -> {
  112 + o.setId(dbUser.getId());
  113 + o.setSex(RandomUtil.randomEle(SysSexEnum.values()).getSex());
  114 + o.setMobile(randomString());
  115 + });
  116 + // mock deptService 的方法
  117 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
  118 + o.setId(reqVO.getDeptId());
  119 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  120 + });
  121 + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
  122 + // mock postService 的方法
  123 + List<SysPostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->
  124 + randomPojo(SysPostDO.class, o -> {
  125 + o.setId(postId);
  126 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  127 + }));
  128 + when(postService.getPosts(eq(reqVO.getPostIds()), isNull())).thenReturn(posts);
  129 +
  130 + // 调用
  131 + userService.updateUser(reqVO);
  132 + // 断言
  133 + SysUserDO user = userMapper.selectById(reqVO.getId());
  134 + assertPojoEquals(reqVO, user);
  135 + }
  136 +
  137 + @Test
  138 + public void testUpdateUserProfile_success() {
  139 + // mock 数据
  140 + SysUserDO dbUser = randomSysUserDO();
  141 + userMapper.insert(dbUser);
  142 + // 准备参数
  143 + Long userId = dbUser.getId();
  144 + SysUserProfileUpdateReqVO reqVO = randomPojo(SysUserProfileUpdateReqVO.class, o -> {
  145 + o.setMobile(randomString());
  146 + o.setSex(RandomUtil.randomEle(SysSexEnum.values()).getSex());
  147 + });
  148 +
  149 + // 调用
  150 + userService.updateUserProfile(userId, reqVO);
  151 + // 断言
  152 + SysUserDO user = userMapper.selectById(userId);
  153 + assertPojoEquals(reqVO, user);
  154 + }
  155 +
  156 + @Test
  157 + public void testUpdateUserPassword_success() {
  158 + // mock 数据
  159 + SysUserDO dbUser = randomSysUserDO(o -> o.setPassword("encode:yudao"));
  160 + userMapper.insert(dbUser);
  161 + // 准备参数
  162 + Long userId = dbUser.getId();
  163 + SysUserProfileUpdatePasswordReqVO reqVO = randomPojo(SysUserProfileUpdatePasswordReqVO.class, o -> {
  164 + o.setOldPassword("yudao");
  165 + o.setNewPassword("yuanma");
  166 + });
  167 + // mock 方法
  168 + when(passwordEncoder.encode(anyString())).then(
  169 + (Answer<String>) invocationOnMock -> "encode:" + invocationOnMock.getArgument(0));
  170 + when(passwordEncoder.matches(eq(reqVO.getOldPassword()), eq(dbUser.getPassword()))).thenReturn(true);
  171 +
  172 + // 调用
  173 + userService.updateUserPassword(userId, reqVO);
  174 + // 断言
  175 + SysUserDO user = userMapper.selectById(userId);
  176 + assertEquals("encode:yuanma", user.getPassword());
  177 + }
  178 +
  179 + @Test
  180 + public void testUpdateUserAvatar_success() {
  181 + // mock 数据
  182 + SysUserDO dbUser = randomSysUserDO();
  183 + userMapper.insert(dbUser);
  184 + // 准备参数
  185 + Long userId = dbUser.getId();
  186 + byte[] avatarFileBytes = randomBytes(10);
  187 + ByteArrayInputStream avatarFile = new ByteArrayInputStream(avatarFileBytes);
  188 + // mock 方法
  189 + String avatar = randomString();
  190 + when(fileService.createFile(anyString(), eq(avatarFileBytes))).thenReturn(avatar);
  191 +
  192 + // 调用
  193 + userService.updateUserAvatar(userId, avatarFile);
  194 + // 断言
  195 + SysUserDO user = userMapper.selectById(userId);
  196 + assertEquals(avatar, user.getAvatar());
  197 + }
  198 +
  199 + @Test
  200 + public void testUpdateUserPassword02_success() {
  201 + // mock 数据
  202 + SysUserDO dbUser = randomSysUserDO();
  203 + userMapper.insert(dbUser);
  204 + // 准备参数
  205 + Long userId = dbUser.getId();
  206 + String password = "yudao";
  207 + // mock 方法
  208 + when(passwordEncoder.encode(anyString())).then(
  209 + (Answer<String>) invocationOnMock -> "encode:" + invocationOnMock.getArgument(0));
  210 +
  211 + // 调用
  212 + userService.updateUserPassword(userId, password);
  213 + // 断言
  214 + SysUserDO user = userMapper.selectById(userId);
  215 + assertEquals("encode:" + password, user.getPassword());
  216 + }
  217 +
  218 + @Test
  219 + public void testUpdateUserStatus() {
  220 + // mock 数据
  221 + SysUserDO dbUser = randomSysUserDO();
  222 + userMapper.insert(dbUser);
  223 + // 准备参数
  224 + Long userId = dbUser.getId();
  225 + Integer status = randomCommonStatus();
  226 +
  227 + // 调用
  228 + userService.updateUserStatus(userId, status);
  229 + // 断言
  230 + SysUserDO user = userMapper.selectById(userId);
  231 + assertEquals(status, user.getStatus());
  232 + }
  233 +
  234 + @Test
  235 + public void testDeleteUser_success(){
  236 + // mock 数据
  237 + SysUserDO dbUser = randomSysUserDO();
  238 + userMapper.insert(dbUser);
  239 + // 准备参数
  240 + Long userId = dbUser.getId();
  241 +
  242 + // 调用数据
  243 + userService.deleteUser(userId);
  244 + // 校验结果
  245 + assertNull(userService.getUser(userId));
  246 + // 校验调用次数
  247 + verify(permissionService, times(1)).processUserDeleted(eq(userId));
  248 + }
  249 +
  250 + @Test
  251 + public void testGetUserPage() {
  252 + // mock 数据
  253 + SysUserDO dbUser = initGetUserPageData();
  254 + // 准备参数
  255 + SysUserPageReqVO reqVO = new SysUserPageReqVO();
  256 + reqVO.setUsername("yudao");
  257 + reqVO.setMobile("1560");
  258 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
  259 + reqVO.setBeginTime(buildTime(2020, 12, 1));
  260 + reqVO.setEndTime(buildTime(2020, 12, 24));
  261 + reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
  262 + // mock 方法
  263 + List<SysDeptDO> deptList = newArrayList(randomPojo(SysDeptDO.class, o -> o.setId(2L)));
  264 + when(deptService.getDeptsByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList);
  265 +
  266 + // 调用
  267 + PageResult<SysUserDO> pageResult = userService.getUserPage(reqVO);
  268 + // 断言
  269 + assertEquals(1, pageResult.getTotal());
  270 + assertEquals(1, pageResult.getList().size());
  271 + assertPojoEquals(dbUser, pageResult.getList().get(0));
  272 + }
  273 +
  274 + @Test
  275 + public void testGetUsers() {
  276 + // mock 数据
  277 + SysUserDO dbUser = initGetUserPageData();
  278 + // 准备参数
  279 + SysUserExportReqVO reqVO = new SysUserExportReqVO();
  280 + reqVO.setUsername("yudao");
  281 + reqVO.setMobile("1560");
  282 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
  283 + reqVO.setBeginTime(buildTime(2020, 12, 1));
  284 + reqVO.setEndTime(buildTime(2020, 12, 24));
  285 + reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
  286 + // mock 方法
  287 + List<SysDeptDO> deptList = newArrayList(randomPojo(SysDeptDO.class, o -> o.setId(2L)));
  288 + when(deptService.getDeptsByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList);
  289 +
  290 + // 调用
  291 + List<SysUserDO> list = userService.getUsers(reqVO);
  292 + // 断言
  293 + assertEquals(1, list.size());
  294 + assertPojoEquals(dbUser, list.get(0));
  295 + }
  296 +
  297 + /**
  298 + * 初始化 getUserPage 方法的测试数据
  299 + */
  300 + private SysUserDO initGetUserPageData() {
  301 + // mock 数据
  302 + SysUserDO dbUser = randomSysUserDO(o -> { // 等会查询到
  303 + o.setUsername("yudaoyuanma");
  304 + o.setMobile("15601691300");
  305 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  306 + o.setCreateTime(buildTime(2020, 12, 12));
  307 + o.setDeptId(2L);
  308 + });
  309 + userMapper.insert(dbUser);
  310 + // 测试 username 不匹配
  311 + userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setUsername("yuanma")));
  312 + // 测试 mobile 不匹配
  313 + userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setMobile("18818260888")));
  314 + // 测试 status 不匹配
  315 + userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
  316 + // 测试 createTime 不匹配
  317 + userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setCreateTime(buildTime(2020, 11, 11))));
  318 + // 测试 dept 不匹配
  319 + userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setDeptId(0L)));
  320 + return dbUser;
  321 + }
  322 +
  323 + /**
  324 + * 情况一,校验不通过,导致插入失败
  325 + */
  326 + @Test
  327 + public void testImportUsers_01() {
  328 + // 准备参数
  329 + SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class);
  330 + // mock 方法
  331 +
  332 + // 调用
  333 + SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), true);
  334 + // 断言
  335 + assertEquals(0, respVO.getCreateUsernames().size());
  336 + assertEquals(0, respVO.getUpdateUsernames().size());
  337 + assertEquals(1, respVO.getFailureUsernames().size());
  338 + assertEquals(DEPT_NOT_FOUND.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername()));
  339 + }
  340 +
  341 + /**
  342 + * 情况二,不存在,进行插入
  343 + */
  344 + @Test
  345 + public void testImportUsers_02() {
  346 + // 准备参数
  347 + SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class, o -> {
  348 + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  349 + o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围
  350 + });
  351 + // mock deptService 的方法
  352 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
  353 + o.setId(importUser.getDeptId());
  354 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  355 + });
  356 + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
  357 + // mock passwordEncoder 的方法
  358 + when(passwordEncoder.encode(eq("yudaoyuanma"))).thenReturn("java");
  359 +
  360 + // 调用
  361 + SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), true);
  362 + // 断言
  363 + assertEquals(1, respVO.getCreateUsernames().size());
  364 + SysUserDO user = userMapper.selectByUsername(respVO.getCreateUsernames().get(0));
  365 + assertPojoEquals(importUser, user);
  366 + assertEquals("java", user.getPassword());
  367 + assertEquals(0, respVO.getUpdateUsernames().size());
  368 + assertEquals(0, respVO.getFailureUsernames().size());
  369 + }
  370 +
  371 + /**
  372 + * 情况三,存在,但是不强制更新
  373 + */
  374 + @Test
  375 + public void testImportUsers_03() {
  376 + // mock 数据
  377 + SysUserDO dbUser = randomSysUserDO();
  378 + userMapper.insert(dbUser);
  379 + // 准备参数
  380 + SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class, o -> {
  381 + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  382 + o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围
  383 + o.setUsername(dbUser.getUsername());
  384 + });
  385 + // mock deptService 的方法
  386 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
  387 + o.setId(importUser.getDeptId());
  388 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  389 + });
  390 + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
  391 +
  392 + // 调用
  393 + SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), false);
  394 + // 断言
  395 + assertEquals(0, respVO.getCreateUsernames().size());
  396 + assertEquals(0, respVO.getUpdateUsernames().size());
  397 + assertEquals(1, respVO.getFailureUsernames().size());
  398 + assertEquals(USER_USERNAME_EXISTS.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername()));
  399 + }
  400 +
  401 + /**
  402 + * 情况四,存在,强制更新
  403 + */
  404 + @Test
  405 + public void testImportUsers_04() {
  406 + // mock 数据
  407 + SysUserDO dbUser = randomSysUserDO();
  408 + userMapper.insert(dbUser);
  409 + // 准备参数
  410 + SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class, o -> {
  411 + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  412 + o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围
  413 + o.setUsername(dbUser.getUsername());
  414 + });
  415 + // mock deptService 的方法
  416 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
  417 + o.setId(importUser.getDeptId());
  418 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  419 + });
  420 + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
  421 +
  422 + // 调用
  423 + SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), true);
  424 + // 断言
  425 + assertEquals(0, respVO.getCreateUsernames().size());
  426 + assertEquals(1, respVO.getUpdateUsernames().size());
  427 + SysUserDO user = userMapper.selectByUsername(respVO.getUpdateUsernames().get(0));
  428 + assertPojoEquals(importUser, user);
  429 + assertEquals(0, respVO.getFailureUsernames().size());
  430 + }
  431 +
  432 + @Test
  433 + public void testCheckUserExists_notExists() {
  434 + assertServiceException(() -> userService.checkUserExists(randomLongId()), USER_NOT_EXISTS);
  435 + }
  436 +
  437 + @Test
  438 + public void testCheckUsernameUnique_usernameExistsForCreate() {
  439 + // 准备参数
  440 + String username = randomString();
  441 + // mock 数据
  442 + userMapper.insert(randomSysUserDO(o -> o.setUsername(username)));
  443 +
  444 + // 调用,校验异常
  445 + assertServiceException(() -> userService.checkUsernameUnique(null, username),
  446 + USER_USERNAME_EXISTS);
  447 + }
  448 +
  449 + @Test
  450 + public void testCheckUsernameUnique_usernameExistsForUpdate() {
  451 + // 准备参数
  452 + Long id = randomLongId();
  453 + String username = randomString();
  454 + // mock 数据
  455 + userMapper.insert(randomSysUserDO(o -> o.setUsername(username)));
  456 +
  457 + // 调用,校验异常
  458 + assertServiceException(() -> userService.checkUsernameUnique(id, username),
  459 + USER_USERNAME_EXISTS);
  460 + }
  461 +
  462 + @Test
  463 + public void testCheckEmailUnique_emailExistsForCreate() {
  464 + // 准备参数
  465 + String email = randomString();
  466 + // mock 数据
  467 + userMapper.insert(randomSysUserDO(o -> o.setEmail(email)));
  468 +
  469 + // 调用,校验异常
  470 + assertServiceException(() -> userService.checkEmailUnique(null, email),
  471 + USER_EMAIL_EXISTS);
  472 + }
  473 +
  474 + @Test
  475 + public void testCheckEmailUnique_emailExistsForUpdate() {
  476 + // 准备参数
  477 + Long id = randomLongId();
  478 + String email = randomString();
  479 + // mock 数据
  480 + userMapper.insert(randomSysUserDO(o -> o.setEmail(email)));
  481 +
  482 + // 调用,校验异常
  483 + assertServiceException(() -> userService.checkEmailUnique(id, email),
  484 + USER_EMAIL_EXISTS);
  485 + }
  486 +
  487 + @Test
  488 + public void testCheckMobileUnique_mobileExistsForCreate() {
  489 + // 准备参数
  490 + String mobile = randomString();
  491 + // mock 数据
  492 + userMapper.insert(randomSysUserDO(o -> o.setMobile(mobile)));
  493 +
  494 + // 调用,校验异常
  495 + assertServiceException(() -> userService.checkMobileUnique(null, mobile),
  496 + USER_MOBILE_EXISTS);
  497 + }
  498 +
  499 + @Test
  500 + public void testCheckMobileUnique_mobileExistsForUpdate() {
  501 + // 准备参数
  502 + Long id = randomLongId();
  503 + String mobile = randomString();
  504 + // mock 数据
  505 + userMapper.insert(randomSysUserDO(o -> o.setMobile(mobile)));
  506 +
  507 + // 调用,校验异常
  508 + assertServiceException(() -> userService.checkMobileUnique(id, mobile),
  509 + USER_MOBILE_EXISTS);
  510 + }
  511 +
  512 + @Test
  513 + public void testCheckDeptEnable_notFound() {
  514 + assertServiceException(() -> userService.checkDeptEnable(randomLongId()),
  515 + DEPT_NOT_FOUND);
  516 + }
  517 +
  518 + @Test
  519 + public void testCheckDeptEnable_notEnable() {
  520 + // 准备参数
  521 + Long deptId = randomLongId();
  522 + // mock deptService 的方法
  523 + SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
  524 + o.setId(deptId);
  525 + o.setStatus(CommonStatusEnum.DISABLE.getStatus());
  526 + });
  527 + when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
  528 +
  529 + // 调用,校验异常
  530 + assertServiceException(() -> userService.checkDeptEnable(deptId),
  531 + DEPT_NOT_ENABLE);
  532 + }
  533 +
  534 + @Test
  535 + public void testCheckPostEnable_notFound() {
  536 + assertServiceException(() -> userService.checkPostEnable(randomSet(Long.class)),
  537 + POST_NOT_FOUND);
  538 + }
  539 +
  540 + @Test
  541 + public void testCheckPostEnable_notEnable() {
  542 + // 准备参数
  543 + Set<Long> postIds = randomSet(Long.class);
  544 + // mock postService 的方法
  545 + List<SysPostDO> posts = CollectionUtils.convertList(postIds, postId ->
  546 + randomPojo(SysPostDO.class, o -> {
  547 + o.setId(postId);
  548 + o.setStatus(CommonStatusEnum.DISABLE.getStatus());
  549 + }));
  550 + when(postService.getPosts(eq(postIds), isNull())).thenReturn(posts);
  551 +
  552 + // 调用,校验异常
  553 + assertServiceException(() -> userService.checkPostEnable(postIds),
  554 + POST_NOT_ENABLE, CollUtil.getFirst(posts).getName());
  555 + }
  556 +
  557 + @Test
  558 + public void testCheckOldPassword_notExists() {
  559 + assertServiceException(() -> userService.checkOldPassword(randomLongId(), randomString()),
  560 + USER_NOT_EXISTS);
  561 + }
  562 +
  563 + @Test
  564 + public void testCheckOldPassword_passwordFailed() {
  565 + // mock 数据
  566 + SysUserDO user = randomSysUserDO();
  567 + userMapper.insert(user);
  568 + // 准备参数
  569 + Long id = user.getId();
  570 + String oldPassword = user.getPassword();
  571 +
  572 + // 调用,校验异常
  573 + assertServiceException(() -> userService.checkOldPassword(id, oldPassword),
  574 + USER_PASSWORD_FAILED);
  575 + // 校验调用
  576 + verify(passwordEncoder, times(1)).matches(eq(oldPassword), eq(user.getPassword()));
  577 + }
  578 +
  579 + // ========== 随机对象 ==========
  580 +
  581 + @SafeVarargs
  582 + private static SysUserDO randomSysUserDO(Consumer<SysUserDO>... consumers) {
  583 + Consumer<SysUserDO> consumer = (o) -> {
  584 + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  585 + o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围
  586 + };
  587 + return randomPojo(SysUserDO.class, ArrayUtils.append(consumer, consumers));
  588 + }
  589 +
  590 +}
... ...
src/test/java/cn/iocoder/dashboard/util/AopTargetUtils.java
... ... @@ -5,43 +5,42 @@ import org.springframework.aop.framework.AdvisedSupport;
5 5 import org.springframework.aop.framework.AopProxy;
6 6 import org.springframework.aop.support.AopUtils;
7 7  
8   -import java.lang.reflect.Field;
9   -
10 8 /**
11   - * http://www.bubuko.com/infodetail-3471885.html
  9 + * Spring AOP 工具类
  10 + *
  11 + * 参考波克尔 http://www.bubuko.com/infodetail-3471885.html 实现
12 12 */
13 13 public class AopTargetUtils {
14 14  
15 15 /**
16   - * 获取 目标对象
  16 + * 获取代理的目标对象
17 17 *
18 18 * @param proxy 代理对象
19   - * @return
20   - * @throws Exception
  19 + * @return 目标对象
21 20 */
22 21 public static Object getTarget(Object proxy) throws Exception {
  22 + // 不是代理对象
23 23 if (!AopUtils.isAopProxy(proxy)) {
24   - return proxy; //不是代理对象
  24 + return proxy;
25 25 }
  26 + // Jdk 代理
26 27 if (AopUtils.isJdkDynamicProxy(proxy)) {
27 28 return getJdkDynamicProxyTargetObject(proxy);
28   - } else { //cglib
29   - return getCglibProxyTargetObject(proxy);
30 29 }
  30 + // Cglib 代理
  31 + return getCglibProxyTargetObject(proxy);
31 32 }
32 33  
33 34 private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
34 35 Object dynamicAdvisedInterceptor = BeanUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0");
35 36 AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(dynamicAdvisedInterceptor, "advised");
36   - Object target = advisedSupport.getTargetSource().getTarget();
37   - return target;
  37 + return advisedSupport.getTargetSource().getTarget();
38 38 }
39 39  
40 40 private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
41 41 AopProxy aopProxy = (AopProxy) BeanUtil.getFieldValue(proxy, "h");
42 42 AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(aopProxy, "advised");
43   - Object target = advisedSupport.getTargetSource().getTarget();
44   - return target;
  43 + return advisedSupport.getTargetSource().getTarget();
45 44 }
46 45  
47   -}
48 46 \ No newline at end of file
  47 +}
... ...
src/test/java/cn/iocoder/dashboard/util/AssertUtils.java
... ... @@ -10,6 +10,7 @@ import org.junit.jupiter.api.function.Executable;
10 10  
11 11 import java.lang.reflect.Field;
12 12 import java.util.Arrays;
  13 +import java.util.Objects;
13 14  
14 15 import static org.junit.jupiter.api.Assertions.assertThrows;
15 16  
... ... @@ -51,6 +52,33 @@ public class AssertUtils {
51 52 }
52 53  
53 54 /**
  55 + * 比对两个对象的属性是否一致
  56 + *
  57 + * 注意,如果 expected 存在的属性,actual 不存在的时候,会进行忽略
  58 + *
  59 + * @param expected 期望对象
  60 + * @param actual 实际对象
  61 + * @param ignoreFields 忽略的属性数组
  62 + * @return 是否一致
  63 + */
  64 + public static boolean isPojoEquals(Object expected, Object actual, String... ignoreFields) {
  65 + Field[] expectedFields = ReflectUtil.getFields(expected.getClass());
  66 + return Arrays.stream(expectedFields).allMatch(expectedField -> {
  67 + // 如果是忽略的属性,则不进行比对
  68 + if (ArrayUtil.contains(ignoreFields, expectedField.getName())) {
  69 + return true;
  70 + }
  71 + // 忽略不存在的属性
  72 + Field actualField = ReflectUtil.getField(actual.getClass(), expectedField.getName());
  73 + if (actualField == null) {
  74 + return true;
  75 + }
  76 + return Objects.equals(ReflectUtil.getFieldValue(expected, expectedField),
  77 + ReflectUtil.getFieldValue(actual, actualField));
  78 + });
  79 + }
  80 +
  81 + /**
54 82 * 执行方法,校验抛出的 Service 是否符合条件
55 83 *
56 84 * @param executable 业务异常
... ... @@ -62,7 +90,7 @@ public class AssertUtils {
62 90 ServiceException serviceException = assertThrows(ServiceException.class, executable);
63 91 // 校验错误码
64 92 Assertions.assertEquals(errorCode.getCode(), serviceException.getCode(), "错误码不匹配");
65   - String message = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMessage(), messageParams);
  93 + String message = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), messageParams);
66 94 Assertions.assertEquals(message, serviceException.getMessage(), "错误提示不匹配");
67 95 }
68 96  
... ...
src/test/java/cn/iocoder/dashboard/util/RandomUtils.java
... ... @@ -7,9 +7,8 @@ import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
7 7 import uk.co.jemos.podam.api.PodamFactory;
8 8 import uk.co.jemos.podam.api.PodamFactoryImpl;
9 9  
10   -import java.util.Arrays;
11   -import java.util.Date;
12   -import java.util.Set;
  10 +import java.lang.reflect.Type;
  11 +import java.util.*;
13 12 import java.util.function.Consumer;
14 13 import java.util.stream.Collectors;
15 14 import java.util.stream.Stream;
... ... @@ -87,4 +86,21 @@ public class RandomUtils {
87 86 return pojo;
88 87 }
89 88  
  89 + @SafeVarargs
  90 + public static <T> T randomPojo(Class<T> clazz, Type type, Consumer<T>... consumers) {
  91 + T pojo = PODAM_FACTORY.manufacturePojo(clazz, type);
  92 + // 非空时,回调逻辑。通过它,可以实现 Pojo 的进一步处理
  93 + if (ArrayUtil.isNotEmpty(consumers)) {
  94 + Arrays.stream(consumers).forEach(consumer -> consumer.accept(pojo));
  95 + }
  96 + return pojo;
  97 + }
  98 +
  99 + @SafeVarargs
  100 + public static <T> List<T> randomPojoList(Class<T> clazz, Consumer<T>... consumers) {
  101 + int size = RandomUtil.randomInt(0, RANDOM_COLLECTION_LENGTH);
  102 + return Stream.iterate(0, i -> i).limit(size).map(o -> randomPojo(clazz, consumers))
  103 + .collect(Collectors.toList());
  104 + }
  105 +
90 106 }
... ...
src/test/resources/sql/clean.sql
... ... @@ -19,3 +19,6 @@ DELETE FROM &quot;sys_post&quot;;
19 19 DELETE FROM "sys_login_log";
20 20 DELETE FROM "sys_operate_log";
21 21 DELETE FROM "sys_user";
  22 +DELETE FROM "sys_sms_channel";
  23 +DELETE FROM "sys_sms_template";
  24 +DELETE FROM "sys_sms_log";
... ...
src/test/resources/sql/create_tables.sql
... ... @@ -47,8 +47,7 @@ CREATE TABLE IF NOT EXISTS &quot;inf_job&quot; (
47 47 PRIMARY KEY ("id")
48 48 ) COMMENT='定时任务表';
49 49  
50   -DROP TABLE IF EXISTS "inf_job_log";
51   -CREATE TABLE "inf_job_log" (
  50 +CREATE TABLE IF NOT EXISTS "inf_job_log" (
52 51 "id" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '日志编号',
53 52 "job_id" bigint(20) NOT NULL COMMENT '任务编号',
54 53 "handler_name" varchar(64) NOT NULL COMMENT '处理器的名字',
... ... @@ -192,8 +191,7 @@ CREATE TABLE IF NOT EXISTS `sys_user_session` (
192 191 PRIMARY KEY (`id`)
193 192 ) COMMENT '用户在线 Session';
194 193  
195   -CREATE TABLE IF NOT EXISTS "sys_post"
196   -(
  194 +CREATE TABLE IF NOT EXISTS "sys_post" (
197 195 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
198 196 "code" varchar(64) NOT NULL,
199 197 "name" varchar(50) NOT NULL,
... ... @@ -208,7 +206,6 @@ CREATE TABLE IF NOT EXISTS &quot;sys_post&quot;
208 206 PRIMARY KEY ("id")
209 207 ) COMMENT '岗位信息表';
210 208  
211   -
212 209 CREATE TABLE IF NOT EXISTS "sys_notice" (
213 210 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
214 211 "title" varchar(50) NOT NULL COMMENT '公告标题',
... ... @@ -223,7 +220,6 @@ CREATE TABLE IF NOT EXISTS &quot;sys_notice&quot; (
223 220 PRIMARY KEY("id")
224 221 ) COMMENT '通知公告表';
225 222  
226   -
227 223 CREATE TABLE IF NOT EXISTS `sys_login_log` (
228 224 `id` bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
229 225 `log_type` bigint(4) NOT NULL,
... ... @@ -240,7 +236,6 @@ CREATE TABLE IF NOT EXISTS `sys_login_log` (
240 236 PRIMARY KEY (`id`)
241 237 ) COMMENT ='系统访问记录';
242 238  
243   -
244 239 CREATE TABLE IF NOT EXISTS `sys_operate_log` (
245 240 `id` bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
246 241 `trace_id` varchar(64) NOT NULL DEFAULT '',
... ... @@ -346,3 +341,73 @@ CREATE TABLE IF NOT EXISTS &quot;inf_api_error_log&quot; (
346 341 "deleted" bit not null default false,
347 342 primary key ("id")
348 343 ) COMMENT '系统异常日志';
  344 +
  345 +CREATE TABLE IF NOT EXISTS "sys_sms_channel" (
  346 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  347 + "signature" varchar(10) NOT NULL,
  348 + "code" varchar(63) NOT NULL,
  349 + "status" tinyint NOT NULL,
  350 + "remark" varchar(255) DEFAULT NULL,
  351 + "api_key" varchar(63) NOT NULL,
  352 + "api_secret" varchar(63) DEFAULT NULL,
  353 + "callback_url" varchar(255) DEFAULT NULL,
  354 + "creator" varchar(64) DEFAULT '',
  355 + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  356 + "updater" varchar(64) DEFAULT '',
  357 + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  358 + "deleted" bit NOT NULL DEFAULT FALSE,
  359 + PRIMARY KEY ("id")
  360 +) COMMENT '短信渠道';
  361 +
  362 +CREATE TABLE "sys_sms_template" (
  363 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  364 + "type" tinyint NOT NULL,
  365 + "status" tinyint NOT NULL,
  366 + "code" varchar(63) NOT NULL,
  367 + "name" varchar(63) NOT NULL,
  368 + "content" varchar(255) NOT NULL,
  369 + "params" varchar(255) NOT NULL,
  370 + "remark" varchar(255) DEFAULT NULL,
  371 + "api_template_id" varchar(63) NOT NULL,
  372 + "channel_id" bigint NOT NULL,
  373 + "channel_code" varchar(63) NOT NULL,
  374 + "creator" varchar(64) DEFAULT '',
  375 + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  376 + "updater" varchar(64) DEFAULT '',
  377 + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  378 + "deleted" bit NOT NULL DEFAULT FALSE,
  379 + PRIMARY KEY ("id")
  380 +) COMMENT '短信模板';
  381 +
  382 +CREATE TABLE "sys_sms_log" (
  383 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  384 + "channel_id" bigint NOT NULL,
  385 + "channel_code" varchar(63) NOT NULL,
  386 + "template_id" bigint NOT NULL,
  387 + "template_code" varchar(63) NOT NULL,
  388 + "template_type" tinyint NOT NULL,
  389 + "template_content" varchar(255) NOT NULL,
  390 + "template_params" varchar(255) NOT NULL,
  391 + "api_template_id" varchar(63) NOT NULL,
  392 + "mobile" varchar(11) NOT NULL,
  393 + "user_id" bigint DEFAULT '0',
  394 + "user_type" tinyint DEFAULT '0',
  395 + "send_status" tinyint NOT NULL DEFAULT '0',
  396 + "send_time" timestamp DEFAULT NULL,
  397 + "send_code" int DEFAULT NULL,
  398 + "send_msg" varchar(255) DEFAULT NULL,
  399 + "api_send_code" varchar(63) DEFAULT NULL,
  400 + "api_send_msg" varchar(255) DEFAULT NULL,
  401 + "api_request_id" varchar(255) DEFAULT NULL,
  402 + "api_serial_no" varchar(255) DEFAULT NULL,
  403 + "receive_status" tinyint NOT NULL DEFAULT '0',
  404 + "receive_time" timestamp DEFAULT NULL,
  405 + "api_receive_code" varchar(63) DEFAULT NULL,
  406 + "api_receive_msg" varchar(255) DEFAULT NULL,
  407 + "creator" varchar(64) DEFAULT '',
  408 + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  409 + "updater" varchar(64) DEFAULT '',
  410 + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  411 + "deleted" bit NOT NULL DEFAULT FALSE,
  412 + PRIMARY KEY ("id")
  413 +) COMMENT '短信日志';
... ...