Commit 56d736c3 authored by honghy's avatar honghy

短信功能实作

parent dea7eb34
-- 短信模板
ALTER TABLE system_sms_template ADD node_value varchar(100) comment '节点',add transport_id int comment '运输方式',add message_type tinyint default 1 comment '发送类型',add language varchar(100) comment '语言';
ALTER TABLE system_sms_channel MODIFY api_secret VARCHAR(200) COMMENT '短信 API 的秘钥';
-- 新增数据字典渠道类型
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (3, 'Sendchamp', 'SENDCHAMP', 'system_sms_channel_code', 0, 'primary', '', NULL, '2741', '2024-10-28 09:11:18', '2741', '2024-10-28 09:12:13', b'0', 'Sendchamp');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (4, 'Ycloud', 'YCLOUD', 'system_sms_channel_code', 0, 'primary', '', NULL, '2741', '2024-10-28 09:12:55', '2741', '2024-10-28 09:12:55', b'0', 'Ycloud');
-- 新增渠道
INSERT INTO `jiedao`.`system_sms_channel`(`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `signature_en`) VALUES (7, 'sendchamp', 'SENDCHAMP', 0, '', 'ecit@ewchina.net', 'sendchamp_live_$2a$10$vQPdaDjl96Ybc5tzFmZYg.nqGirXuJBGDqJArthZnFR8P9mM5Z/JO', '', '2741', '2024-10-26 17:06:44', '2741', '2024-10-28 15:16:39', b'0', 'sendchamp');
INSERT INTO `jiedao`.`system_sms_channel`(`id`, `signature`, `code`, `status`, `remark`, `api_key`, `api_secret`, `callback_url`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `signature_en`) VALUES (8, 'ycloud', 'YCLOUD', 0, NULL, 'ecit@ewchina.net', 'gAAAAABl5ZDpoKnKVaK0scmWo7GQeKDA1sn3OZBeMXUz211vZv5QqSEtr7_LD5ISTqSD0PnWLRWavYmJ3nQJiutT-sgp0dyHR2HIL6FYEhD3t2CNuoHJoSIOia5ffo_rjfxW_pk8co0i7UMTYANNGxsRNJLQTu_Alw==', NULL, '2741', '2024-10-26 17:12:50', '2741', '2024-10-28 15:16:18', b'0', 'ycloud');
-- 短信日志新增字段
alter table system_sms_log add node_id bigint comment '节点id',add node_template_sn int default 1 comment '节点模板序列号',add message_type tinyint comment '发送类型';
-- 短信验证码
ALTER TABLE system_sms_code CHANGE scene node_value varchar(100) not NULL comment '节点';
ALTER TABLE ecw_box_order_sms_log CHANGE scene node_value varchar(100) not NULL comment '节点';
/*==============================================================*/
/* Table: system_sms_node */
/*==============================================================*/
create table system_sms_node
(
id bigint(0) NOT NULL AUTO_INCREMENT COMMENT '编号',
node_value varchar(100) not null comment '节点',
is_transport tinyint not null comment '是否匹配运输方式(0:否,1:是)',
transport_id tinyint not null comment '运输方式ID',
is_orders tinyint not null comment '多订单(0:否,1:是)',
country_id bigint not null comment '国家区号id',
country_code varchar(30) not null comment '国家区号,和订单中国家区号保持一致',
status tinyint not null comment '启用状态(0:开启,1:关闭)',
template_id_one bigint not null comment '模板1',
template_id_two bigint comment '模板2',
template_id_three bigint comment '模板3',
template_id_four bigint comment '模板4',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
primary key (id) USING BTREE
) CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信节点表';
-- 菜单 SQL
INSERT INTO `system_menu`(
`name`, `permission`, `menu_type`, `sort`, `parent_id`,
`path`, `icon`, `component`, `status`
)
VALUES (
'短信节点管理', '', 2, 3, 1093,
'sms-node', 'phone', 'system/sms/smsNode', 0
);
-- 按钮父菜单ID
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO `system_menu`(
`name`, `permission`, `menu_type`, `sort`, `parent_id`,
`path`, `icon`, `component`, `status`
)
VALUES (
'短信节点查询', 'system:sms-node:query', 3, 1, @parentId,
'', '', '', 0
);
INSERT INTO `system_menu`(
`name`, `permission`, `menu_type`, `sort`, `parent_id`,
`path`, `icon`, `component`, `status`
)
VALUES (
'短信节点创建', 'system:sms-node:create', 3, 2, @parentId,
'', '', '', 0
);
INSERT INTO `system_menu`(
`name`, `permission`, `menu_type`, `sort`, `parent_id`,
`path`, `icon`, `component`, `status`
)
VALUES (
'短信节点更新', 'system:sms-node:update', 3, 3, @parentId,
'', '', '', 0
);
INSERT INTO `system_menu`(
`name`, `permission`, `menu_type`, `sort`, `parent_id`,
`path`, `icon`, `component`, `status`
)
VALUES (
'短信节点删除', 'system:sms-node:delete', 3, 4, @parentId,
'', '', '', 0
);
INSERT INTO `system_menu`(
`name`, `permission`, `menu_type`, `sort`, `parent_id`,
`path`, `icon`, `component`, `status`
)
VALUES (
'短信节点导出', 'system:sms-node:export', 3, 5, @parentId,
'', '', '', 0
);
INSERT INTO `jiedao`.`system_dict_type`(`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('短信节点', 'system_sms_node_node', 0, NULL, '1', '2024-10-31 10:08:50', '1', '2024-10-31 10:08:50', b'0');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (1, '会员用户 - 手机号注册', 'user-sms-reg', 'system_sms_node_node', 0, 'default', '', '0', '2740', '2024-11-05 14:22:53', '2740', '2024-11-05 14:24:33', b'0', '会员用户 - 手机号注册');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (2, '会员用户 - 手机号登陆', 'user-sms-login', 'system_sms_node_node', 0, 'default', '', '1', '2740', '2024-11-05 14:23:27', '2740', '2024-11-05 14:24:39', b'0', 'user-sms-login');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (3, '会员用户 - 解绑手机', 'user-sms-update-mobile', 'system_sms_node_node', 0, 'default', '', '2', '2740', '2024-11-05 14:23:43', '2740', '2024-11-05 14:24:41', b'0', '会员用户 - 解绑手机');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (4, '会员用户 - 忘记密码', 'user-sms-reset-password', 'system_sms_node_node', 0, 'default', '', '3', '2740', '2024-11-05 14:24:05', '2740', '2024-11-05 14:24:43', b'0', '会员用户 - 忘记密码');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (5, '订单 - 控货权转移', 'transfer-control-goods', 'system_sms_node_node', 0, 'default', '', '4', '2740', '2024-11-05 14:26:08', '2740', '2024-11-05 14:26:20', b'0', '订单 - 控货权转移');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (6, '订单 - 放货验证短信', 'delivery-verification-sms', 'system_sms_node_node', 0, 'default', '', '5', '2740', '2024-11-05 14:26:36', '2740', '2024-11-05 14:26:36', b'0', '订单 - 放货验证短信');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (7, '订单 - 放货成功通知', 'notification-successful-delivery', 'system_sms_node_node', 0, 'default', '', '6', '2740', '2024-11-05 14:32:40', '2740', '2024-11-05 14:32:44', b'0', '订单 - 放货成功通知');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (8, '订单 - 取消放货通知', 'notification-successful-cancel-delivery', 'system_sms_node_node', 0, 'default', '', '7', '2740', '2024-11-05 14:33:12', '2740', '2024-11-05 14:33:12', b'0', '订单 - 取消放货通知');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (9, '出货 - 到港通知', 'shipment-arrival', 'system_sms_node_node', 0, 'default', '', NULL, '2740', '2024-11-12 17:13:30', '2740', '2024-11-12 17:13:30', b'0', '出货 - 到港通知');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (10, '出货 - 清关通知', 'shipment-customs-clearance', 'system_sms_node_node', 0, 'default', '', NULL, '2740', '2024-11-12 17:13:52', '2740', '2024-11-12 17:13:52', b'0', '出货 - 清关通知');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (11, '出货 - 装柜通知', 'shipment-cabinet', 'system_sms_node_node', 0, 'default', '', NULL, '2740', '2024-11-12 17:20:24', '2740', '2024-11-12 17:20:24', b'0', '出货 - 装柜通知');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (12, '出货 - 封柜反审', 'shipment-close-container', 'system_sms_node_node', 0, 'default', '', NULL, '2740', '2024-11-12 17:22:02', '2740', '2024-11-12 17:22:02', b'0', '出货 - 封柜反审');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (13, '入仓控货通知', 'warehouse-in-control', 'system_sms_node_node', 0, 'default', '', NULL, '2740', '2024-11-12 17:22:30', '2740', '2024-11-12 17:22:30', b'0', '入仓控货通知');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (14, '入仓通知', 'warehouse-in', 'system_sms_node_node', 0, 'default', '', NULL, '2740', '2024-11-12 17:23:11', '2740', '2024-11-12 17:23:11', b'0', '入仓通知');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (15, '追加通知', 'warehouse-in-append', 'system_sms_node_node', 0, 'default', '', NULL, '2740', '2024-11-12 17:23:52', '2740', '2024-11-12 17:23:52', b'0', '追加通知');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (16, '入仓-非控货代收货款', 'warehouse-in-collect-of-goods', 'system_sms_node_node', 0, 'default', '', NULL, '2740', '2024-11-12 17:24:22', '2740', '2024-11-12 17:24:22', b'0', '入仓-非控货代收货款');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (17, '出仓通知', 'air-shipment-warehouse', 'system_sms_node_node', 0, 'default', '', NULL, '2740', '2024-11-14 15:15:58', '2740', '2024-11-14 17:55:52', b'0', '出仓通知');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (18, '会员用户 - 兑换礼品', 'user-sms-redeem-reward', 'system_sms_node_node', 0, 'default', '', NULL, '2740', '2024-11-20 09:52:56', '2740', '2024-11-20 09:52:56', b'0', '会员用户 - 兑换礼品');
INSERT INTO `jiedao`.`system_dict_type`(`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('发送类型', 'system_sms_template_message_type', 0, NULL, '2740', '2024-11-04 17:07:41', '2740', '2024-11-04 17:07:41', b'0');
INSERT INTO `jiedao`.`system_dict_data`( `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES ( 3, 'email', '3', 'system_sms_template_message_type', 0, 'default', '', NULL, '2740', '2024-11-04 17:20:21', '2740', '2024-11-04 17:20:21', b'0', 'email');
INSERT INTO `jiedao`.`system_dict_data`( `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES ( 2, 'whatsapp', '2', 'system_sms_template_message_type', 0, 'default', '', NULL, '2740', '2024-11-04 17:20:00', '2740', '2024-11-04 17:20:00', b'0', 'whatsapp');
INSERT INTO `jiedao`.`system_dict_data`( `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES ( 1, '短信', '1', 'system_sms_template_message_type', 0, 'default', '', NULL, '2740', '2024-11-04 17:17:48', '2740', '2024-11-04 17:18:16', b'0', 'message');
INSERT INTO `jiedao`.`system_dict_type`(`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('模板语言', 'system_sms_template_language', 0, NULL, '2740', '2024-11-04 17:38:08', '2740', '2024-11-04 17:38:08', b'0');
INSERT INTO `jiedao`.`system_dict_data`(`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `label_en`) VALUES (1, 'en', 'en', 'system_sms_template_language', 0, 'default', '', NULL, '2740', '2024-11-04 17:40:55', '2740', '2024-11-04 17:40:55', b'0', 'en');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'air-shipment-warehouse', 1, 3, 1, 0, '0', 0, 51, NULL, NULL, NULL, '2740', '2024-11-14 15:17:49', '2740', '2024-11-15 09:45:52', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'air-shipment-warehouse', 1, 3, 0, 0, '0', 0, 50, NULL, NULL, NULL, '2740', '2024-11-14 15:17:36', '2740', '2024-11-15 09:45:44', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'warehouse-in', 1, 3, 0, 0, '0', 0, 47, NULL, NULL, NULL, '2740', '2024-11-13 17:11:09', '2740', '2024-11-13 17:11:09', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'warehouse-in-control', 1, 3, 0, 0, '0', 0, 46, NULL, NULL, NULL, '2740', '2024-11-13 17:10:40', '2740', '2024-11-13 17:10:40', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'warehouse-in-append', 1, 3, 0, 0, '0', 0, 65, NULL, NULL, NULL, '2740', '2024-11-13 17:09:51', '2740', '2024-11-13 17:09:51', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'warehouse-in-collect-of-goods', 1, 1, 0, 0, '0', 0, 41, NULL, NULL, NULL, '2740', '2024-11-13 10:50:18', '2740', '2024-11-13 10:50:18', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'warehouse-in-append', 1, 1, 0, 0, '0', 0, 34, NULL, NULL, NULL, '2740', '2024-11-13 10:49:47', '2740', '2024-11-13 10:49:47', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'warehouse-in', 1, 1, 0, 0, '0', 0, 32, NULL, NULL, NULL, '2740', '2024-11-13 10:48:58', '2740', '2024-11-19 14:17:19', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'warehouse-in-control', 1, 1, 0, 0, '0', 0, 31, NULL, NULL, NULL, '2740', '2024-11-13 10:48:36', '2740', '2024-11-13 10:48:36', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-close-container', 1, 1, 0, 0, '0', 0, 39, NULL, NULL, NULL, '2740', '2024-11-13 10:48:08', '2740', '2024-11-13 10:48:08', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-cabinet', 1, 3, 1, 0, '0', 0, 64, NULL, NULL, NULL, '2740', '2024-11-13 10:29:02', '2740', '2024-11-13 10:29:02', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-cabinet', 1, 3, 0, 0, '0', 0, 63, NULL, NULL, NULL, '2740', '2024-11-13 10:28:48', '2740', '2024-11-13 10:28:48', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-cabinet', 1, 1, 1, 0, '0', 0, 21, NULL, NULL, NULL, '2740', '2024-11-13 10:09:37', '2740', '2024-11-13 10:09:37', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-cabinet', 1, 1, 0, 0, '0', 0, 45, NULL, NULL, NULL, '2740', '2024-11-13 10:09:04', '2740', '2024-11-13 10:09:04', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-customs-clearance', 1, 3, 1, 0, '0', 0, 53, NULL, NULL, NULL, '2740', '2024-11-13 10:07:59', '2740', '2024-11-13 10:07:59', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-customs-clearance', 1, 3, 0, 0, '0', 0, 52, NULL, NULL, NULL, '2740', '2024-11-13 10:07:46', '2740', '2024-11-13 10:07:46', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-customs-clearance', 1, 1, 1, 0, '0', 0, 23, NULL, NULL, NULL, '2740', '2024-11-13 10:02:56', '2740', '2024-11-13 10:02:56', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-customs-clearance', 1, 1, 0, 0, '0', 0, 43, NULL, NULL, NULL, '2740', '2024-11-13 10:02:41', '2740', '2024-11-13 10:02:41', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-arrival', 1, 3, 1, 0, '0', 0, 49, NULL, NULL, NULL, '2740', '2024-11-13 09:58:16', '2740', '2024-11-13 09:58:16', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-arrival', 1, 3, 0, 0, '0', 0, 48, NULL, NULL, NULL, '2740', '2024-11-13 09:58:02', '2740', '2024-11-13 09:58:02', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-arrival', 1, 1, 1, 0, '0', 0, 22, NULL, NULL, NULL, '2740', '2024-11-13 09:57:40', '2740', '2024-11-13 09:57:40', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ( 'shipment-arrival', 1, 1, 0, 0, '0', 0, 44, NULL, NULL, NULL, '2740', '2024-11-13 09:57:12', '2740', '2024-11-13 09:57:23', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('notification-successful-cancel-delivery', 0, 0, 0, 0, '0', 0, 42, NULL, NULL, NULL, '2740', '2024-11-12 11:13:47', '2740', '2024-11-12 17:53:38', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('notification-successful-delivery', 0, 0, 0, 0, '0', 0, 36, NULL, NULL, NULL, '2740', '2024-11-12 11:09:38', '2740', '2024-11-12 17:53:49', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('delivery-verification-sms', 0, 0, 0, 0, '0', 0, 38, NULL, NULL, NULL, '2740', '2024-11-08 16:16:21', '2740', '2024-11-08 16:16:21', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('transfer-control-goods', 0, 0, 0, 0, '0', 0, 37, NULL, NULL, NULL, '2740', '2024-11-08 15:50:53', '2740', '2024-11-08 15:50:53', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('user-sms-update-mobile', 0, 0, 0, 0, '0', 0, 16, NULL, NULL, NULL, '2740', '2024-11-08 15:00:45', '2740', '2024-11-08 15:00:45', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('user-sms-reset-password', 0, 0, 0, 0, '0', 0, 15, NULL, NULL, NULL, '2740', '2024-11-08 14:18:49', '2740', '2024-11-08 14:18:49', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('user-sms-login', 0, 0, 0, 0, '0', 0, 14, 60, NULL, NULL, '2740', '2024-11-06 14:16:31', '2740', '2024-11-11 15:11:03', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('user-sms-reg', 0, 0, 0, 0, '0', 0, 13, 59, NULL, NULL, '2740', '2024-11-05 15:00:37', '2740', '2024-11-08 13:59:18', b'0');
INSERT INTO `jiedao`.`system_sms_node`(`id`, `node_value`, `is_transport`, `transport_id`, `is_orders`, `country_id`, `country_code`, `status`, `template_id_one`, `template_id_two`, `template_id_three`, `template_id_four`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (33, 'user-sms-redeem-reward', 0, 0, 0, 0, '0', 0, 55, NULL, NULL, NULL, '2740', '2024-11-20 09:54:21', '2740', '2024-11-20 09:54:21', b'0');
......@@ -162,7 +162,7 @@ public class PhoneUtil {
try {
List<DictDataRespDTO> dtos = DictFrameworkUtils.listDictDatasFromCache("phone_number_rule");
if (CollUtil.isNotEmpty(dtos)) {
DictDataRespDTO dto = dtos.stream().filter(d -> d.getValue().trim().equals(code.trim())).findFirst().orElseGet(null);
DictDataRespDTO dto = dtos.stream().filter(d -> d.getValue().trim().equals(code.trim())).findFirst().orElse(null);
if (Objects.nonNull(dto)) {
// 优先获取中文
rule = dto.getLabel();
......
......@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.http.core.Request;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.core.client.HttpClient;
import cn.iocoder.yudao.framework.http.core.client.HttpClientFactory;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
......@@ -16,52 +17,70 @@ import java.util.Map;
**/
public class SendchampHttp {
private static final String SMS_URL="https://api.sendchamp.com/api/v1/sms/send";
private static final String SMS_URL = "https://api.sendchamp.com/api/v1/sms/send";
public void postReq(Map<String,Object> param,Map<String, String> header){
private static final String RECEIVE_URL = "https://api.sendchamp.com/api/v1/sms/status/";
public Response postReq(Map<String, Object> param, Map<String, String> header) {
HttpClient client = HttpClientFactory.get(Request.Util.OkHttp);
Request req = Request.create(SMS_URL,Request.Method.POST,param);
Request req = Request.create(SMS_URL, Request.Method.POST, param);
req.setParamFormat(Request.ParamFormat.JSON);
try {
Response res = client.execute(req,Request.Option.create(0,0,header));
System.out.println("response code ---------------------------->"+res.getCode());
System.out.println("response content is --------------------》"+res.getBody());
Response res = client.execute(req, Request.Option.create(0, 0, header));
System.out.println("response code ---------------------------->" + res.getCode());
System.out.println("response content is --------------------》" + res.getBody());
return res;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SneakyThrows
public Response getReceiveStatus(Map<String, String> header, String id) {
HttpClient client = HttpClientFactory.get(Request.Util.OkHttp);
Request req = Request.create(RECEIVE_URL + id, Request.Method.GET, "");
try {
Response res = client.execute(req, Request.Option.create(0, 0, header));
System.out.println("response code ---------------------------->" + res.getCode());
System.out.println("response content is --------------------》" + res.getBody());
return res;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@NotNull
private static Map<String , String> setHeader(String apiKey){
Map<String,String> header = new HashMap<>();
header.put("Accept","application/json,text/plain,*/*");
public static Map<String, String> setHeader(String apiKey) {
Map<String, String> header = new HashMap<>();
header.put("Accept", "application/json,text/plain,*/*");
header.put("Content-Type", "application/json");
header.put("Authorization", apiKey);
return header;
}
@NotNull
private static Map<String, Object> setParams(String to,String message,String senderName) {
Map<String,Object> param = new HashMap<>();
param.put("to",to);
param.put("message",message);
param.put("sender_name",senderName);
param.put("route","dnd");
public static Map<String, Object> setParams(String to, String message, String senderName) {
Map<String, Object> param = new HashMap<>();
param.put("to", to);
param.put("message", message);
param.put("sender_name", senderName);
param.put("route", "dnd");
return param;
}
public static void main(String[] args){
public static void main(String[] args) {
SendchampHttp test = new SendchampHttp();
String to = "2348140352000";
// String to = "2348140352000";
String to = "8618926674857";
String senderName = "Sendchamp";
//信息如果超过200字符,需要拆成多条短信,多次发送
String message = "Good day!This is E&C logistics(ship from China to Nigeria),It has been a while since we last worked together, and we would like to reestablish our partnership. Our main service: GROUPAGE/FULL CONTAINE";
Map<String, Object> param = setParams(to,message,senderName);
Map<String, Object> param = setParams(to, message, senderName);
String apiKey = "Bearer sendchamp_live_$2a$10$vQPdaDjl96Ybc5tzFmZYg.nqGirXuJBGDqJArthZnFR8P9mM5Z/JO";
Map<String,String> header = setHeader(apiKey);
test.postReq(param,header);
Map<String, String> header = setHeader(apiKey);
test.getReceiveStatus(header, "66e6e9df-b454-4df7-a968-af944a535757");
}
......
package cn.iocoder.yudao.framework.http.example;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.http.core.Request;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.core.client.HttpClient;
import cn.iocoder.yudao.framework.http.core.client.HttpClientFactory;
import lombok.SneakyThrows;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* @author wuxian
......@@ -19,32 +20,45 @@ public class YCloudWhatsappHttp {
private HttpClient client = null;
//请求地址
private static final String WA_URL="https://api.ycloud.com/v2/whatsapp/messages/sendDirectly";
private static final String WA_URL = "https://api.ycloud.com/v2/whatsapp/messages/sendDirectly";
private static final String RECEIVE_URL = "https://api.ycloud.com/v2/whatsapp/messages/";
public YCloudWhatsappHttp() {
init();
}
private void init(){
private void init() {
client = HttpClientFactory.get(Request.Util.OkHttp);
}
@SneakyThrows
public void postWhatsapp(Map<String, String> header,Map<String,Object> param){
Request req = Request.create(WA_URL,Request.Method.POST,param);
public Response postWhatsapp(Map<String, String> header, Map<String, Object> param) {
Request req = Request.create(WA_URL, Request.Method.POST, param);
req.setParamFormat(Request.ParamFormat.JSON);
Response res = client.execute(req,Request.Option.create(0,0,header));
System.out.println("response code ---------------------------->"+res.getCode());
System.out.println("response content is --------------------》"+res.getBody());
Response res = client.execute(req, Request.Option.create(0, 0, header));
System.out.println("response code ---------------------------->" + res.getCode());
System.out.println("response content is --------------------》" + res.getBody());
return res;
}
@SneakyThrows
public Response getReceiveStatus(Map<String, String> header, String id) {
Request req = Request.create(RECEIVE_URL + id, Request.Method.GET, "");
Response res = client.execute(req, Request.Option.create(0, 0, header));
System.out.println("response code ---------------------------->" + res.getCode());
System.out.println("response content is --------------------》" + res.getBody());
return res;
}
/**
*
* @param code 验证码
* @param templateName 模板名称
* @param lanCode 语言编号
* @param to 接收手机号
* @return 参数
*/
private Map<String,Object> setParams(String code,String templateName,String lanCode){
public Map<String, Object> setParams(String code, String templateName, String lanCode, String to) {
Map<String, Object> template = new HashMap<>();
Map<String, String> language = new HashMap<>();
Components components = new Components();
......@@ -69,44 +83,65 @@ public class YCloudWhatsappHttp {
list2.add(components);
list2.add(button);
language.put("code",lanCode);
language.put("policy","deterministic");
language.put("code", lanCode);
language.put("policy", "deterministic");
template.put("language",language);
template.put("name",templateName);
template.put("components",list2);
template.put("language", language);
template.put("name", templateName);
template.put("components", list2);
//构建最终requestBody的json
Map<String, Object> param = new HashMap<>();
param.put("from","8618022485824");
param.put("to","8618102810628");
param.put("type","template");
param.put("template",template);
param.put("from", "8618022485824");
param.put("to", to);
param.put("type", "template");
param.put("template", template);
return param;
}
//构建http请求的头
private Map<String,String> setHeader(String apiKey){
Map<String,String> header = new HashMap<>();
header.put("Accept","application/json");
public Map<String, String> setHeader(String apiKey) {
Map<String, String> header = new HashMap<>();
header.put("Accept", "application/json");
header.put("Content-Type", "application/json");
header.put("X-API-Key", apiKey);
return header;
}
public static void main(String[] args){
public static void main(String[] args) {
YCloudWhatsappHttp test = new YCloudWhatsappHttp();
String apiKey = "9dbd912f56c101e53b23cb7b758ffda8";
String code = "8888";
String templateName ="ec_verification";
String lanCode ="en";
test.postWhatsapp(test.setHeader(apiKey),test.setParams(code,templateName,lanCode));
// String code = "8888";
// String templateName = "ec_verification";
// String lanCode = "en";
// test.postWhatsapp(test.setHeader(apiKey), test.setParams(code, templateName, lanCode, "8618926674857"));
Response response = test.getReceiveStatus(test.setHeader(apiKey), "67370e34334f011e030dfd7b");
Map<String, Object> responseObj = JsonUtils.parseObject(response.getBody(), Map.class);
String status = (String) responseObj.get("status");
if (status != null && ("delivered".equals(status) || "read".equals(status))) {
System.out.println(status);
String deliverTime = (String) responseObj.get("deliverTime");
// 定义 ISO 8601 格式的日期时间解析器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX");
// 将字符串解析为 ZonedDateTime 对象
ZonedDateTime utcDateTime = ZonedDateTime.parse(deliverTime, formatter);
// 获取本地时区
ZoneId localZoneId = ZoneId.systemDefault();
// 将 UTC 时间转换为本地时间
ZonedDateTime localDateTime = utcDateTime.withZoneSameInstant(localZoneId);
// 将 ZonedDateTime 转换为 Date
Date localDate = Date.from(localDateTime.toInstant());
// 输出本地时间
System.out.println("本地时间: " + localDate);
} else {
System.out.println("null");
}
}
}
@lombok.Setter
@lombok.Getter
class Parameters{
class Parameters {
private String type;
private String text;
......@@ -125,7 +160,7 @@ class Button {
private String type;
private String sub_type;
private String index;
private List<Object> parameters= null;
private List<Object> parameters = null;
}
......
package cn.iocoder.yudao.framework.sms.core.client;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.*;
import java.util.List;
......@@ -34,6 +32,18 @@ public interface SmsClient {
SmsCommonResult<SmsSendRespDTO> sendSms(Long logId, String mobile, String apiTemplateId,
List<KeyValue<String, Object>> templateParams);
/**
* 发送消息
*
* @param logId 日志编号
* @param mobile 手机号
* @param smsTemplateDTO 模板
* @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序
* @return 短信发送结果
*/
SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO,
List<KeyValue<String, Object>> templateParams);
/**
* 解析接收短信的接收结果
*
......@@ -51,4 +61,11 @@ public interface SmsClient {
*/
SmsCommonResult<SmsTemplateRespDTO> getSmsTemplate(String apiTemplateId);
/**
* 查询短信发送状态
*
* @param smsLogDO
* @return
*/
SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO);
}
package cn.iocoder.yudao.framework.sms.core.client.dto;
import cn.iocoder.yudao.framework.common.util.spring.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import lombok.ToString;
import java.util.Date;
import java.util.Map;
/**
* 日志dto
*
* @author Jayden
* @date 2024/11/6
*/
@Data
@ToString(callSuper = true)
public class SmsLogDTO {
/**
* 自增编号
*/
private Long id;
// ========= 渠道相关字段 =========
/**
* 短信渠道编号
* <p>
*/
private Long channelId;
/**
* 短信渠道编码
* <p>
*/
private String channelCode;
// ========= 模板相关字段 =========
/**
* 模板编号
* <p>
*/
private Long templateId;
/**
* 模板编码
* <p>
*/
private String templateCode;
/**
* 短信类型
* <p>
*/
private Integer templateType;
/**
*
*/
private String templateContent;
/**
*
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> templateParams;
/**
* 短信 API 的模板编号
* <p>
*/
private String apiTemplateId;
// ========= 手机相关字段 =========
/**
* 手机号
*/
private String mobile;
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
* <p>
* 枚举 {@link UserTypeEnum}
*/
private Integer userType;
// ========= 发送相关字段 =========
/**
* 发送状态
* <p>
*/
private Integer sendStatus;
/**
* 发送时间
*/
private Date sendTime;
/**
* 发送结果的编码
* <p>
* 枚举 {@link SmsFrameworkErrorCodeConstants}
*/
private Integer sendCode;
/**
* 发送结果的提示
* <p>
* 一般情况下,使用 {@link SmsFrameworkErrorCodeConstants}
* 异常情况下,通过格式化 Exception 的提示存储
*/
private String sendMsg;
/**
* 短信 API 发送结果的编码
* <p>
* 由于第三方的错误码可能是字符串,所以使用 String 类型
*/
private String apiSendCode;
/**
* 短信 API 发送失败的提示
*/
private String apiSendMsg;
/**
* 短信 API 发送返回的唯一请求 ID
* <p>
* 用于和短信 API 进行定位于排错
*/
private String apiRequestId;
/**
* 短信 API 发送返回的序号
* <p>
* 用于和短信 API 平台的发送记录关联
*/
private String apiSerialNo;
// ========= 接收相关字段 =========
/**
* 接收状态
* <p>
*/
private Integer receiveStatus;
/**
* 接收时间
*/
private Date receiveTime;
/**
* 短信 API 接收结果的编码
*/
private String apiReceiveCode;
/**
* 短信 API 接收结果的提示
*/
private String apiReceiveMsg;
/**
* 重发的短信日志id
*/
private Long smsLogId;
/**
* 重发次数
*/
private Integer num;
/**
* 短信节点表id
*/
private Long nodeId;
/**
* 节点模板序列号
*/
private Integer nodeTemplateSn;
/**
* 发送类型
*/
private Integer messageType;
}
package cn.iocoder.yudao.framework.sms.core.client.dto;
import lombok.Data;
import lombok.ToString;
/**
* 模板dto
*
* @author Jayden
* @date 2024/11/6
*/
@Data
@ToString(callSuper = true)
public class SmsTemplateDTO {
/**
* 自增编号
*/
private Long id;
// ========= 模板相关字段 =========
/**
* 短信类型
*/
private Integer type;
/**
* 启用状态
*/
private Integer status;
/**
* 模板编码,保证唯一
*/
private String code;
/**
* 模板名称
*/
private String name;
/**
* 模板内容
*/
private String content;
/**
* 备注
*/
private String remark;
/**
* 短信 API 的模板编号
*/
private String apiTemplateId;
// ========= 渠道相关字段 =========
/**
* 短信渠道编号
*/
private Long channelId;
/**
* 短信渠道编码
*/
private String channelCode;
/**
* 节点
*/
private String nodeValue;
/**
* 运输方式
*/
private Long transportId;
/**
* 发送类型
*/
private Integer messageType;
/**
* 语言
*/
private String language;
}
......@@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.yudao.framework.sms.core.client.impl.aliyun.AliyunSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.debug.DebugDingTalkSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp.SendchampSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.yCloud.YCloudSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.yunpian.YunpianSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.SmsChannelEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
......@@ -33,7 +35,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
/**
* 短信客户端 Map
* key:渠道编码,使用 {@link SmsChannelProperties#getCode()} ()}
*
* <p>
* 注意,一些场景下,需要获得某个渠道类型的客户端,所以需要使用它。
* 例如说,解析短信接收结果,是相对通用的,不需要使用某个渠道编号的 {@link #channelIdClients}
*/
......@@ -78,9 +80,16 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
Assert.notNull(channelEnum, String.format("渠道类型(%s) 为空", channelEnum));
// 创建客户端
switch (channelEnum) {
case ALIYUN: return new AliyunSmsClient(properties);
case YUN_PIAN: return new YunpianSmsClient(properties);
case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties);
case ALIYUN:
return new AliyunSmsClient(properties);
case YUN_PIAN:
return new YunpianSmsClient(properties);
case DEBUG_DING_TALK:
return new DebugDingTalkSmsClient(properties);
case SENDCHAMP:
return new SendchampSmsClient(properties);
case YCLOUD:
return new YCloudSmsClient(properties);
}
// 创建失败,错误日志 + 抛出异常
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
......
......@@ -3,20 +3,21 @@ package cn.iocoder.yudao.framework.sms.core.client.impl.aliyun;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.*;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.ReceiveStatusEnum;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import com.aliyuncs.AcsRequest;
import com.aliyuncs.AcsResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.exceptions.ClientException;
......@@ -25,9 +26,11 @@ import com.aliyuncs.profile.IClientProfile;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import io.undertow.util.DateUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Objects;
......@@ -56,6 +59,8 @@ public class AliyunSmsClient extends AbstractSmsClient {
*/
private volatile IAcsClient client;
private static final String DELIVERED_STATUS = "DELIVERED";
public AliyunSmsClient(SmsChannelProperties properties) {
super(properties, new AliyunSmsCodeMapping());
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
......@@ -74,9 +79,9 @@ public class AliyunSmsClient extends AbstractSmsClient {
// 构建参数
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(mobile);
if(mobile.startsWith("0086")||mobile.startsWith("86")||mobile.startsWith("+86")||mobile.startsWith("+0086")) {
if (mobile.startsWith("0086") || mobile.startsWith("86") || mobile.startsWith("+86") || mobile.startsWith("+0086")) {
request.setSignName(properties.getSignature());
}else{
} else {
request.setSignName(properties.getSignatureEn());
}
request.setTemplateCode(apiTemplateId);
......@@ -116,10 +121,14 @@ public class AliyunSmsClient extends AbstractSmsClient {
@VisibleForTesting
Integer convertSmsTemplateAuditStatus(Integer templateStatus) {
switch (templateStatus) {
case 0: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
case 1: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
case 2: return SmsTemplateAuditStatusEnum.FAIL.getStatus();
default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
case 0:
return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
case 1:
return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
case 2:
return SmsTemplateAuditStatusEnum.FAIL.getStatus();
default:
throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
}
}
......@@ -150,9 +159,96 @@ public class AliyunSmsClient extends AbstractSmsClient {
return ex.getErrMsg() + " => " + ex.getErrorDescription();
}
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long sendLogId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
// 构建参数
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(mobile);
String apiTemplateId = smsTemplateDTO.getApiTemplateId();
request.setSignName(properties.getSignature());
request.setTemplateCode(apiTemplateId);
request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
request.setOutId(String.valueOf(sendLogId));
// 执行请求
return invoke(request, response -> new SmsSendRespDTO().setSerialNo(response.getBizId()));
}
/**
* 短信接收状态
* 获取短信日志的接收状态
* 通过调用阿里云短信服务的查询接口,更新短信日志的接收状态
*
* @param smsLogDO 短信日志实体,包含查询所需的参数,如手机号、发送时间等
* @return 更新了接收状态的短信日志实体
*/
@Override
public SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO) {
// 初始化短信日志DTO,并默认设置接收状态为未接收到
SmsLogDTO smsLogDTO = new SmsLogDTO();
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_20.getValue());
// 创建查询发送详情的请求
QuerySendDetailsRequest querySendDetailsRequest = new QuerySendDetailsRequest();
// 设置查询参数,包括手机号(去掉国际拨号)、页大小、当前页、发送日期和业务ID
querySendDetailsRequest.setPhoneNumber(removeCountryCode(smsLogDO.getMobile()));
querySendDetailsRequest.setPageSize(1L);
querySendDetailsRequest.setCurrentPage(1L);
querySendDetailsRequest.setSendDate(formatDate(smsLogDO.getSendTime()));
querySendDetailsRequest.setBizId(smsLogDO.getApiSerialNo());
// 初始化查询响应对象
QuerySendDetailsResponse acsResponse = null;
try {
// 发起查询请求并获取响应
acsResponse = client.getAcsResponse(querySendDetailsRequest);
} catch (ClientException e) {
// 如果查询出错,记录日志并返回初始化的短信日志DTO
log.error("Error querying send details: ", e);
return smsLogDTO;
}
// 如果查询成功且有查询结果,更新短信日志的接收状态
if (acsResponse != null && !acsResponse.getSmsSendDetailDTOs().isEmpty()) {
// 获取第一条查询结果
QuerySendDetailsResponse.SmsSendDetailDTO smsSendDetailDTO = acsResponse.getSmsSendDetailDTOs().get(0);
// 如果短信发送成功,更新接收状态为已接收到,并设置接收时间
if (DELIVERED_STATUS.equals(smsSendDetailDTO.getErrCode())) {
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_10.getValue());
String receiveDate = smsSendDetailDTO.getReceiveDate();
if (receiveDate != null) {
smsLogDTO.setReceiveTime(DateUtils.parseDate(receiveDate));
}
}
}
// 返回更新后的短信日志DTO
return smsLogDTO;
}
/**
* 去掉手机号开头的国家代码 "86"
*
* @param phoneNumber 包含国家代码的手机号
* @return 去掉国家代码后的手机号
*/
public static String removeCountryCode(String phoneNumber) {
// 使用 replaceFirst 方法去掉开头的 "86"
return phoneNumber.replaceFirst("^86", "");
}
/**
* 将 Date 对象格式化为指定格式的字符串
*
* @param date 需要格式化的 Date 对象
* @return 格式化后的字符串,格式为 "yyyyMMdd"
*/
public static String formatDate(Date date) {
SimpleDateFormat outputFormat = new SimpleDateFormat("yyyyMMdd");
return outputFormat.format(date);
}
/**
* 短信接收状态
* <p>
* 参见 https://help.aliyun.com/document_detail/101867.html 文档
*
* @author 捷道源码
......@@ -198,14 +294,14 @@ public class AliyunSmsClient extends AbstractSmsClient {
private String bizId;
/**
* 用户序列号
*
* <p>
* 这里我们传递的是 SysSmsLogDO 的日志编号
*/
@JsonProperty("out_id")
private String outId;
/**
* 短信长度,例如说 1、2、3
*
* <p>
* 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送
*/
@JsonProperty("sms_size")
......
......@@ -9,9 +9,7 @@ import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.*;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
......@@ -93,4 +91,13 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient {
return SmsCommonResult.build("0", "success", null, data, codeMapping);
}
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
return null;
}
@Override
public SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO) {
return null;
}
}
package cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.example.SendchampHttp;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.*;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.ReceiveStatusEnum;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Sendchamp客户端
*/
@Slf4j
public class SendchampSmsClient extends AbstractSmsClient {
// 创建HTTP客户端实例,用于发送短信
private final SendchampHttp sendchampHttp = new SendchampHttp();
// 定义发送方名称,此处为固定值
private final String senderName = "Sendchamp";
public SendchampSmsClient(SmsChannelProperties properties) {
super(properties, new SendchampSmsCodeMapping());
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
}
@Override
protected void doInit() {
}
@Override
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
return null;
}
/**
* 发送短信服务(版本2)
* 该方法用于发送短信,会根据模板内容和参数动态生成短信内容,并处理发送过程
*
* @param logId 日志ID,用于追踪和记录
* @param mobile 手机号码,短信接收方
* @param smsTemplateDTO 短信模板信息,包括模板内容等
* @param templateParams 模板参数,用于替换模板中的占位符
* @return 返回短信发送结果,包括状态码、消息ID等信息
*/
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
SmsCommonResult<SmsSendRespDTO> smsCommonResult = SmsCommonResult.build("400", "Bad Request", null, null, codeMapping);
// 获取短信模板内容
String content = smsTemplateDTO.getContent();
// 如果有模板参数,则进行参数替换
if (templateParams != null) {
for (KeyValue<String, Object> entry : templateParams) {
// 对键和值进行转义,以确保安全性
String key = escapeKey(entry.getKey());
String value = escapeValue(entry.getValue());
// 构建正则表达式,用于匹配模板中的占位符
String regex = "\\$\\{" + key + "\\}";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
// 将匹配到的占位符替换为实际值
content = matcher.replaceAll(value);
}
}
try {
// 如果信息超过200字符,需要拆分成多条短信发送
List<String> messages = splitContent(content, 200);
for (String message : messages) {
// 设置发送短信所需的参数
Map<String, Object> param = sendchampHttp.setParams(mobile, message, senderName);
// 设置请求头,包含认证信息
Map<String, String> header = sendchampHttp.setHeader("Bearer " + properties.getApiSecret());
// 发送POST请求
Response response = sendchampHttp.postReq(param, header);
// 获取响应体
String result = response.getBody();
// 解析响应结果
Map<?, ?> responseObj = JsonUtils.parseObject(result, Map.class);
Map<String, Object> data = (Map<String, Object>) responseObj.get("data");
// 构建发送结果
smsCommonResult = SmsCommonResult.build(String.valueOf(responseObj.get("status")), String.valueOf(responseObj.get("message")), String.valueOf(data.get("business_id")), null, codeMapping);
smsCommonResult.setData(new SmsSendRespDTO().setSerialNo(String.valueOf(data.get("business_id"))));
}
} catch (Exception e) {
// 记录发送短信时发生的错误
log.error("Error sending SMS: ", e);
// 返回内部服务器错误结果
return SmsCommonResult.build("500", "Internal Server Error", null, null, codeMapping);
}
// 返回请求结果
return smsCommonResult;
}
@Override
public SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO) {
// 初始化短信日志DTO,并默认设置接收状态为未接收到
SmsLogDTO smsLogDTO = new SmsLogDTO();
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_20.getValue());
return smsLogDTO;
}
/**
* 转义键
* 对键进行转义,以防止恶意代码注入
*
* @param key 待转义的键
* @return 转义后的键
*/
private String escapeKey(String key) {
return key.replaceAll("[^a-zA-Z0-9_]", "");
}
/**
* 转义值
* 对值进行转义,以防止恶意代码注入
*
* @param value 待转义的值
* @return 转义后的值
*/
private String escapeValue(Object value) {
if (value instanceof String) {
return ((String) value).replaceAll("[^a-zA-Z0-9_]", "");
}
return value.toString();
}
/**
* 拆分内容
* 将内容拆分成多个部分,每个部分不超过 maxLength 字符
*
* @param content 待拆分的内容
* @param maxLength 每个部分的最大长度
* @return 拆分后的内容列表
*/
private List<String> splitContent(String content, int maxLength) {
List<String> messages = new ArrayList<>();
int start = 0;
while (start < content.length()) {
int end = Math.min(start + maxLength, content.length());
messages.add(content.substring(start, end));
start = end;
}
return messages;
}
@Override
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
return null;
}
@Override
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) throws Throwable {
SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
return SmsCommonResult.build("accepted", "accepted", null, data, codeMapping);
}
/**
* 短信接收状态
* <p>
* @author 捷道源码
*/
@Data
public static class SmsReceiveStatus {
/**
* module
*/
@JsonProperty("module")
private String module;
/**
* reference
*/
@JsonProperty("reference")
private String reference;
/**
* 状态
*/
@JsonProperty("status")
private String status;
}
}
package cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.sms.core.client.SmsCodeMapping;
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
/**
* sendchamp SmsCodeMapping 实现类
*/
public class SendchampSmsCodeMapping implements SmsCodeMapping {
@Override
public ErrorCode apply(String apiCode) {
switch (apiCode) {
case "accepted": return GlobalErrorCodeConstants.SUCCESS;
}
return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
}
}
package cn.iocoder.yudao.framework.sms.core.client.impl.yCloud;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.http.core.Response;
import cn.iocoder.yudao.framework.http.example.YCloudWhatsappHttp;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.*;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
import cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp.SendchampSmsCodeMapping;
import cn.iocoder.yudao.framework.sms.core.enums.ReceiveStatusEnum;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
import lombok.extern.slf4j.Slf4j;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* ycloud客户端
*
* @author Jayden
* @date 2024/11/7
*/
@Slf4j
public class YCloudSmsClient extends AbstractSmsClient {
// 创建HTTP客户端实例,用于发送短信
private final YCloudWhatsappHttp yCloudWhatsappHttp = new YCloudWhatsappHttp();
public YCloudSmsClient(SmsChannelProperties properties) {
super(properties, new SendchampSmsCodeMapping());
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
}
@Override
protected void doInit() {
}
@Override
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
return null;
}
/**
* 发送短信V2版本
* 该方法用于发送短信,根据提供的日志ID、手机号码、短信模板和模板参数进行短信发送
* 主要逻辑包括处理模板参数、调用HTTP工具发送短信以及解析响应结果
*
* @param logId 日志ID,用于追踪和记录发送短信的日志
* @param mobile 手机号码,指定短信接收者的电话号码
* @param smsTemplateDTO 短信模板数据传输对象,包含短信模板的相关信息
* @param templateParams 模板参数列表,包含短信模板中的变量和对应的值
* @return 返回一个通用的结果对象,包含短信发送的相关信息和结果状态
*/
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
// 默认验证码为空字符串
String code = "";
// 如果模板参数不为空,则遍历模板参数以获取验证码
if (templateParams != null) {
for (KeyValue<String, Object> entry : templateParams) {
code = (String) entry.getValue();
}
}
// 发送短信并获取响应
Response response = yCloudWhatsappHttp.postWhatsapp(yCloudWhatsappHttp.setHeader(properties.getApiSecret()),
yCloudWhatsappHttp.setParams(code, smsTemplateDTO.getApiTemplateId(), smsTemplateDTO.getLanguage(), mobile));
// 解析结果
Map<String, Object> responseObj = JsonUtils.parseObject(response.getBody(), Map.class);
// 构建短信发送结果对象
SmsCommonResult<SmsSendRespDTO> smsCommonResult = SmsCommonResult.build(String.valueOf(responseObj.get("status")), String.valueOf(responseObj.get("status")), String.valueOf(responseObj.get("id")), null, codeMapping);
// 设置短信发送结果的详细信息
smsCommonResult.setData(new SmsSendRespDTO().setSerialNo(String.valueOf(responseObj.get("id"))));
// 返回短信发送结果
return smsCommonResult;
}
/**
* 获取短信接收状态
* 本方法通过调用第三方API来获取短信的实际接收状态,并更新短信日志DTO中的相关信息
*
* @param smsLogDO 短信日志DTO,包含需要查询接收状态的短信的相关信息
* @return 返回更新了接收状态和接收时间的短信日志DTO
*/
@Override
public SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO) {
// 初始化短信日志DTO,并默认设置接收状态为未接收到
SmsLogDTO smsLogDTO = new SmsLogDTO();
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_20.getValue());
try {
// 发送短信并获取响应
Response response = yCloudWhatsappHttp.getReceiveStatus(yCloudWhatsappHttp.setHeader(properties.getApiSecret()), smsLogDO.getApiSerialNo());
// 解析响应内容为Map对象,以便后续处理
Map<String, Object> responseObj = JsonUtils.parseObject(response.getBody(), Map.class);
// 获取短信状态
String status = (String) responseObj.get("status");
// 如果短信状态为已送达或已读取,则进一步处理
if (status != null && ("delivered".equals(status) || "read".equals(status))) {
// 获取送达时间
String deliverTime = (String) responseObj.get("deliverTime");
// 解析并转换送达时间为本地时间
Date localDate = parseAndConvertToLocalDate(deliverTime);
// 更新短信日志DTO的接收时间和接收状态
smsLogDTO.setReceiveTime(localDate);
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_10.getValue());
}
} catch (Exception e) {
// 记录异常信息
log.error("Error occurred while getting receive status: ", e);
// 保持默认的接收状态为未接收到
smsLogDTO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_20.getValue());
}
// 返回更新后的短信日志DTO
return smsLogDTO;
}
/**
* 解析并转换时间为本地时间
* 本方法将给定的ISO 8601格式的日期时间字符串解析为日期对象,并转换为本地时间
*
* @param deliverTime ISO 8601格式的日期时间字符串
* @return 返回转换后的本地时间日期对象
*/
private Date parseAndConvertToLocalDate(String deliverTime) {
// 定义 ISO 8601 格式的日期时间解析器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX");
// 将字符串解析为 ZonedDateTime 对象
ZonedDateTime utcDateTime = ZonedDateTime.parse(deliverTime, formatter);
// 获取本地时区
ZoneId localZoneId = ZoneId.systemDefault();
// 将 UTC 时间转换为本地时间
ZonedDateTime localDateTime = utcDateTime.withZoneSameInstant(localZoneId);
// 将 ZonedDateTime 转换为 Date
return Date.from(localDateTime.toInstant());
}
@Override
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) {
throw new UnsupportedOperationException("无回调");
}
@Override
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) {
SmsTemplateRespDTO data = new SmsTemplateRespDTO().setId(apiTemplateId).setContent("")
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason("");
return SmsCommonResult.build("accepted", "accepted", null, data, codeMapping);
}
}
package cn.iocoder.yudao.framework.sms.core.client.impl.yCloud;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.sms.core.client.SmsCodeMapping;
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
/**
* ycloud SmsCodeMapping 实现类
*
* @author Jayden
* @date 2024/11/7
*/
public class YCloudSmsCodeMapping implements SmsCodeMapping {
@Override
public ErrorCode apply(String apiCode) {
switch (apiCode) {
case "success": return GlobalErrorCodeConstants.SUCCESS;
}
return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
}
}
......@@ -6,9 +6,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.*;
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
......@@ -148,6 +146,16 @@ public class YunpianSmsClient extends AbstractSmsClient {
return sendResult.getMsg() + " => " + sendResult.getDetail();
}
@Override
public SmsCommonResult<SmsSendRespDTO> sendSmsV2(Long logId, String mobile, SmsTemplateDTO smsTemplateDTO, List<KeyValue<String, Object>> templateParams) {
return null;
}
@Override
public SmsLogDTO getReceiveStatus(SmsLogDTO smsLogDO) {
return null;
}
/**
* 短信接收状态
*
......
package cn.iocoder.yudao.framework.sms.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 短信接收状态
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum ReceiveStatusEnum {
RECEIVE_STATUS_0("等待结果", 0),
Receive_STATUS_10("接收成功", 10),
Receive_STATUS_20("接收失败", 20);
private String name;
private Integer value;
}
......@@ -17,6 +17,8 @@ public enum SmsChannelEnum {
DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
YUN_PIAN("YUN_PIAN", "云片"),
ALIYUN("ALIYUN", "阿里云"),
SENDCHAMP("SENDCHAMP","Sendchamp"),
YCLOUD("YCLOUD","Ycloud")
// TENCENT("TENCENT", "腾讯云"),
// HUA_WEI("HUA_WEI", "华为云"),
;
......
package cn.iocoder.yudao.framework.apollo.core.constants;
/**
* 缓存的key 常量
*
* @author Jayden
* @date 2024/11/6
*/
public class CacheConstants {
public static final String COLON = ":";
/**
* 短信节点表缓存
*/
public static final String SYSTEM_SMS_NODE_KEY = "system_sms_node:";
/**
* 短信节点表缓存
*/
public static final String SYSTEM_SMS_TEMPLATE_KEY = "system_sms_template:";
}
......@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.enums;
import java.util.stream.Stream;
public enum TransportTypeEnum {
OTHER(0, "其他"),
OCEAN_LCL(1, "海运拼柜"),
SPECIAL_LINE_AIR_FREIGHT(3, "专线空运");
......@@ -22,6 +23,7 @@ public enum TransportTypeEnum {
public String getName() {
return name;
}
public static TransportTypeEnum parseByValue(int value) {
return Stream.of(values()).filter(e -> e.getValue() == value).findAny().orElse(null);
}
......
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
......@@ -36,9 +33,8 @@ public class AppAuthCheckCodeReqVO {
@Pattern(regexp = "^[0-9]+$", message = "{app.auth.captcha.pattern}")
private String code;
@ApiModelProperty(value = "发送场景", example = "1", notes = "对应 SmsSceneEnum 枚举")
@ApiModelProperty(value = "节点")
@NotNull(message = "{app.sms.scene.not.blank}")
@InEnum(value = SmsSceneEnum.class, message = "{app.sms.scene.not.range}")
private Integer scene;
private String nodeValue;
}
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
......@@ -25,12 +22,27 @@ public class AppAuthSendSmsReqVO {
// @Mobile(message = "incorrect mobile number")
private String mobile;
@ApiModelProperty(value = "发送场景(0注册1登录2修改手机3忘记密码)", example = "1", notes = "对应 SmsSceneEnum 枚举")
@NotNull(message = "{app.sms.scene.not.blank}")
@InEnum(value = SmsSceneEnum.class, message = "{app.sms.scene.not.range}")
private Integer scene;
@ApiModelProperty(value = "业务节点", required = true)
@NotNull(message = "节点不能为空")
private String nodeValue;
@ApiModelProperty(value = "区号", required = true)
@NotNull(message = "区号不能为空")
private String areaCode;
@ApiModelProperty(value = "是否匹配运输方式", required = true)
@NotNull(message = "是否匹配运输方式不能为空")
private Integer isTransport;
@ApiModelProperty(value = "运输方式", required = true)
@NotNull(message = "运输方式不能空")
private Integer transportId;
@ApiModelProperty(value = "是否多订单不能为空", required = true)
@NotNull(message = "是否多订单不能为空")
private Integer isOrders;
@ApiModelProperty(value = "发送类型不能为空", required = true)
@NotNull(message = "发送类型不能为空")
private Integer messageType;
}
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
/**
* @author Administrator
......
......@@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
......@@ -32,8 +32,8 @@ public interface AuthConvert {
SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppAuthSocialUnbindReqVO reqVO);
SmsCodeSendReqDTO convert(AppAuthSendSmsReqVO reqVO);
SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthRegReqVO reqVO, Integer scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsNodeEnum scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, String nodeValue, String usedIp);
SmsCodeUseReqDTO convert(AppAuthRegReqVO reqVO, String nodeValue, String usedIp);
}
......@@ -2,11 +2,12 @@ package cn.iocoder.yudao.module.member.service.auth;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.spring.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.spring.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.common.util.spring.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.spring.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.validation.PhoneUtil;
import cn.iocoder.yudao.framework.i18n.core.I18nMessage;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
......@@ -31,7 +32,7 @@ import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateSendReqVO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import cn.iocoder.yudao.module.system.service.mail.MailSendService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
......@@ -113,7 +114,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
@Override
public String reg(@Valid AppAuthRegReqVO reqVO, String userIp, String userAgent, Boolean isAutoLogin) {
// 校验验证码
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_REG.getScene(), userIp));
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsNodeEnum.MEMBER_REG.getNodeValue(), userIp));
MemberUserDO userByMobile = userService.checkPhoneUnique(reqVO.getAreaCode(), reqVO.getMobile());
if (userByMobile != null) {
......@@ -164,7 +165,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
@Transactional
public String smsLogin(AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
// 校验验证码
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsNodeEnum.MEMBER_LOGIN.getNodeValue(), userIp));
// 获得获得注册用户
MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), reqVO.getAreaCode(), userIp, reqVO.getLoginPlatform());
......@@ -388,7 +389,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
MemberUserDO userDO = checkUserIfExists(reqVO.getMobile());
// 使用验证码
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_FORGET_PASSWORD,
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsNodeEnum.MEMBER_FORGET_PASSWORD,
getClientIP()));
// 更新密码
......@@ -430,7 +431,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
MemberUserDO userDO = checkUserIfExists(reqVO.getMobile());
// 使用验证码
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_FORGET_PASSWORD,
smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsNodeEnum.MEMBER_FORGET_PASSWORD,
getClientIP()));
// 更新密码
......@@ -451,10 +452,10 @@ public class MemberAuthServiceImpl implements MemberAuthService {
public void sendSmsCode(Long userId, AppAuthSendSmsReqVO reqVO) {
String mobileCode = reqVO.getAreaCode() + StrUtil.COLON + reqVO.getMobile();
paramValidatorApi.validatorMobile(mobileCode);
// String mobile = PhoneUtil.formatPhone(mobileCode);
// if (StringUtils.isNotBlank(mobile)) {
// reqVO.setMobile(mobile);
// }
String mobile = PhoneUtil.formatPhone(mobileCode);
if (StringUtils.isNotBlank(mobile)) {
reqVO.setMobile(mobile);
}
// TODO 要根据不同的场景,校验是否有用户
smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
}
......
......@@ -40,7 +40,7 @@ import cn.iocoder.yudao.module.member.vo.userOperationLog.UserOperationLogCreate
import cn.iocoder.yudao.module.product.service.coupon.CouponService;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.alibaba.excel.util.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
......@@ -333,7 +333,7 @@ public class MemberUserServiceImpl implements MemberUserService {
.setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));*/
// 使用新验证码
smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getMobile()).setCode(reqVO.getCode())
.setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));
.setNodeValue(SmsNodeEnum.MEMBER_UPDATE_MOBILE.getNodeValue()).setUsedIp(getClientIP()));
// 更新用户手机
memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).areaCode(reqVO.getAreaCode()).build());
......
......@@ -42,7 +42,7 @@ import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import cn.iocoder.yudao.module.system.framework.ue.UeProperties;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
......@@ -154,7 +154,7 @@ public class OrderCargoControlServiceImpl extends AbstractService<OrderCargoCont
if (StringUtils.isNotBlank(createReqVO.getCode()) && (StringUtils.isBlank(createReqVO.getControlPassword()) || isPasswordError || isPasswordNull)) {
SmsCodeUseReqDTO smsCodeSendReqDTO = new SmsCodeUseReqDTO();
smsCodeSendReqDTO.setUsedIp(getClientIP());
smsCodeSendReqDTO.setScene(SmsSceneEnum.TRANSFER_CONTROL_GOODS.getScene());
smsCodeSendReqDTO.setNodeValue(SmsNodeEnum.TRANSFER_CONTROL_GOODS.getNodeValue());
smsCodeSendReqDTO.setCode(String.valueOf(createReqVO.getCode()));
smsCodeSendReqDTO.setMobile(orgOrderCargoControlDO.getPhone());
smsCodeApi.useSmsCode(smsCodeSendReqDTO);
......@@ -294,7 +294,12 @@ public class OrderCargoControlServiceImpl extends AbstractService<OrderCargoCont
public void sendSmsCode(Long loginUserId, OrderSendSmsReqVO reqVO, MemberUserDO memberUserDO) {
SmsCodeSendReqDTO smsCodeSendReqDTO = new SmsCodeSendReqDTO();
smsCodeSendReqDTO.setCreateIp(getClientIP());
smsCodeSendReqDTO.setScene(reqVO.getScene());
smsCodeSendReqDTO.setNodeValue(reqVO.getNodeValue());
smsCodeSendReqDTO.setIsTransport(reqVO.getIsTransport());
smsCodeSendReqDTO.setTransportId(reqVO.getTransportId());
smsCodeSendReqDTO.setIsOrders(reqVO.getIsOrders());
smsCodeSendReqDTO.setMessageType(reqVO.getMessageType());
OrderCargoControlDO orderCargoControlDO = this.getOne(new LambdaQueryWrapper<OrderCargoControlDO>()
.eq(OrderCargoControlDO::getOrderId, reqVO.getOrderId())
.eq(OrderCargoControlDO::getIsActual, Boolean.TRUE)
......
package cn.iocoder.yudao.module.order.service.orderCargoControlPick;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.spring.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.spring.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO;
import cn.iocoder.yudao.framework.i18n.core.I18nMessage;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.bpm.api.BpmCreateServiceFactory;
......@@ -23,12 +20,16 @@ import cn.iocoder.yudao.module.ecw.api.internalMessage.dto.InternalMessageCreate
import cn.iocoder.yudao.module.ecw.api.paramValid.ParamValidatorApi;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
import cn.iocoder.yudao.module.member.enums.TransportTypeEnum;
import cn.iocoder.yudao.module.order.convert.approval.OrderApprovalConvert;
import cn.iocoder.yudao.module.order.convert.orderCargoControlPick.OrderCargoControlPickConvert;
import cn.iocoder.yudao.module.order.dal.dataobject.approval.OrderApprovalDO;
import cn.iocoder.yudao.module.order.dal.dataobject.order.OrderDO;
import cn.iocoder.yudao.module.order.dal.dataobject.orderCargoControl.OrderCargoControlDO;
import cn.iocoder.yudao.module.order.dal.dataobject.orderCargoControlPick.OrderCargoControlPickDO;
import cn.iocoder.yudao.module.order.dal.mysql.approval.OrderApprovalMapper;
import cn.iocoder.yudao.module.order.dal.mysql.orderCargoControl.OrderCargoControlMapper;
import cn.iocoder.yudao.module.order.dal.mysql.orderCargoControlPick.OrderCargoControlPickMapper;
import cn.iocoder.yudao.module.order.dal.mysql.orderPickup.OrderPickupMapper;
import cn.iocoder.yudao.module.order.dto.OrderCargoControlReleaseInfoDto;
import cn.iocoder.yudao.module.order.param.OrderControlLogParam;
......@@ -37,12 +38,19 @@ import cn.iocoder.yudao.module.order.service.order.OrderQueryService;
import cn.iocoder.yudao.module.order.service.order.OrderService;
import cn.iocoder.yudao.module.order.service.orderCargoControl.OrderCargoControlService;
import cn.iocoder.yudao.module.order.vo.order.OrderBackPageVO;
import cn.iocoder.yudao.module.order.vo.orderCargoControlPick.OrderCargoControlPickApplyVO;
import cn.iocoder.yudao.module.order.vo.orderCargoControlPick.OrderCargoControlPickCreateReqVO;
import cn.iocoder.yudao.module.order.vo.orderCargoControlPick.OrderCargoControlPickQueryVO;
import cn.iocoder.yudao.module.order.vo.orderCargoControlPick.OrderCargoControlPickUpdateReqVO;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTOV2;
import cn.iocoder.yudao.module.system.enums.sms.SmsIsOrdersEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsIsTransportEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsMessageTypeEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
......@@ -53,15 +61,13 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.module.order.vo.orderCargoControlPick.*;
import cn.iocoder.yudao.module.order.dal.dataobject.orderCargoControlPick.OrderCargoControlPickDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.order.convert.orderCargoControlPick.OrderCargoControlPickConvert;
import cn.iocoder.yudao.module.order.dal.mysql.orderCargoControlPick.OrderCargoControlPickMapper;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.apollo.core.constants.Constants.CUSTOMER_USER_APPROVAL_ID;
import static cn.iocoder.yudao.framework.apollo.core.constants.Constants.CUSTOMER_USER_TO_ADMIN;
......@@ -69,7 +75,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.USER_PASSWORD_FAILED;
import static cn.iocoder.yudao.module.order.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum.NOTIFICATION_SUCCESS_DELIVERY;
import static cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum.NOTIFICATION_SUCCESS_DELIVERY;
/**
* 订单控货人放货记录 Service 实现类
......@@ -250,7 +256,7 @@ public class OrderCargoControlPickServiceImpl extends AbstractService<OrderCargo
smsCodeSendReqDTO.setCode(createReqVO.getCode());
smsCodeSendReqDTO.setMobile(orderCargoControlDO.getPhone());
smsCodeSendReqDTO.setUsedIp(getClientIP());
smsCodeSendReqDTO.setScene(SmsSceneEnum.DELIVERY_VERIFICATION_SMS.getScene());
smsCodeSendReqDTO.setNodeValue(SmsNodeEnum.DELIVERY_VERIFICATION_SMS.getNodeValue());
smsCodeApi.useSmsCode(smsCodeSendReqDTO);
isPasswordPick = false;
}
......@@ -525,7 +531,7 @@ public class OrderCargoControlPickServiceImpl extends AbstractService<OrderCargo
smsCodeSendReqDTO.setCode(orderCargoControlPickApplyVO.getCode());
smsCodeSendReqDTO.setMobile(orderCargoControlDO.getPhone());
smsCodeSendReqDTO.setUsedIp(getClientIP());
smsCodeSendReqDTO.setScene(SmsSceneEnum.DELIVERY_VERIFICATION_SMS.getScene());
smsCodeSendReqDTO.setNodeValue(SmsNodeEnum.DELIVERY_VERIFICATION_SMS.getNodeValue());
smsCodeApi.useSmsCode(smsCodeSendReqDTO);
OrderDO orderDO = orderService.getById(orderCargoControlPickApplyVO.getOrderId());
......@@ -1100,7 +1106,7 @@ public class OrderCargoControlPickServiceImpl extends AbstractService<OrderCargo
.set(OrderDO::getReleaseNum, count)
.set(OrderDO::getReleaseRatio, new BigDecimal(count).divide(new BigDecimal(orderDO.getSumNum()), 2, RoundingMode.HALF_UP).multiply(new BigDecimal("100")))
.eq(OrderDO::getOrderId, orderCargoControlPickDO.getOrderId()));
}else {
} else {
// 放完货的改为放货中
orderService.update(new LambdaUpdateWrapper<OrderDO>()
.set(OrderDO::getReleaseNum, count)
......@@ -1162,7 +1168,7 @@ public class OrderCargoControlPickServiceImpl extends AbstractService<OrderCargo
controlLogParam.setApprovalId(orderApprovalDO.getOrderApprovalId());
controlLogParams.add(controlLogParam);
// 发送取消放货短信通知
this.smsSendSendSingle(orderDO, SmsSceneEnum.NOTIFICATION_SUCCESS_CANCEL_DELIVERY, 0, null, orderCargoControlPickDO);
this.smsSendSendSingle(orderDO, SmsNodeEnum.NOTIFICATION_SUCCESS_CANCEL_DELIVERY.getNodeValue(), 0, null, orderCargoControlPickDO);
}
}
orderApprovalMapper.updateById(orderApprovalDO);
......@@ -1205,7 +1211,7 @@ public class OrderCargoControlPickServiceImpl extends AbstractService<OrderCargo
return releaseWeight;
}
private void smsSendSendSingle(OrderDO orderDO, SmsSceneEnum smsSceneEnum, Integer num, String seasoningCondimentsOrderNumbers, OrderCargoControlPickDO orderCargoControlPickDO) {
private void smsSendSendSingle(OrderDO orderDO, String nodeValue, Integer num, String seasoningCondimentsOrderNumbers, OrderCargoControlPickDO orderCargoControlPickDO) {
Map<String, Object> templateParams = new HashMap<>(10);
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
......@@ -1226,7 +1232,7 @@ public class OrderCargoControlPickServiceImpl extends AbstractService<OrderCargo
templateParams.put("order2", seasoningCondimentsOrderNumbers);
}
// 定义发送短信的对象map,避免收发货人相同的重复发送
Map<String, SmsSendSingleToUserReqDTO> smsMap = new HashMap<>(2);
Map<String, SmsSendSingleToUserReqDTOV2> smsMap = new HashMap<>(2);
// OrderConsignorDO orderConsignorDO = orderConsignorService.getOne(new LambdaQueryWrapper<OrderConsignorDO>().eq(OrderConsignorDO::getOrderId, orderDO.getOrderId())
// .orderByDesc(OrderConsignorDO::getId)
// .last("limit 1"));
......@@ -1234,11 +1240,15 @@ public class OrderCargoControlPickServiceImpl extends AbstractService<OrderCargo
String consignorCountryCode = orderCargoControlPickDO.getCountryCode();
String consignorPhone = orderCargoControlPickDO.getPhone();
Long consignorCustomerId = 0L; // 当前控货人不一定绑定了客户信息,默认给0
SmsSendSingleToUserReqDTO reqDTO = new SmsSendSingleToUserReqDTO();
SmsSendSingleToUserReqDTOV2 reqDTO = new SmsSendSingleToUserReqDTOV2();
reqDTO.setAreaCode(consignorCountryCode);
reqDTO.setMobile(consignorPhone);
reqDTO.setUserId(consignorCustomerId);
reqDTO.setTemplateCode(smsSceneEnum.getTemplateCode());
reqDTO.setNodeValue(nodeValue);
reqDTO.setIsTransport(SmsIsTransportEnum.SMS_ORDERS_1.getValue());
reqDTO.setTransportId(orderDO.getTransportId());
reqDTO.setIsOrders(SmsIsOrdersEnum.SMS_ORDERS_0.getValue());
reqDTO.setMessageType(SmsMessageTypeEnum.SMS_MESSAGE_TYPE_1.getValue());
reqDTO.setTemplateParams(templateParams);
smsMap.put(consignorCountryCode + consignorPhone, reqDTO);
......@@ -1250,15 +1260,19 @@ public class OrderCargoControlPickServiceImpl extends AbstractService<OrderCargo
String consigneeCountryCode = orderCargoControlPickDO.getConsigneeCountryCode();
String consigneePhone = orderCargoControlPickDO.getConsigneePhone();
Long consigneeCustomerId = 0L; // 放货记录中的收货人信息未关联客户信息,默认给0
reqDTO = new SmsSendSingleToUserReqDTO();
reqDTO = new SmsSendSingleToUserReqDTOV2();
reqDTO.setAreaCode(consigneeCountryCode);
reqDTO.setMobile(consigneePhone);
reqDTO.setUserId(consigneeCustomerId);
reqDTO.setTemplateCode(smsSceneEnum.getTemplateCode());
reqDTO.setNodeValue(nodeValue);
reqDTO.setIsTransport(SmsIsTransportEnum.SMS_ORDERS_0.getValue());
reqDTO.setTransportId(TransportTypeEnum.OTHER.getValue());
reqDTO.setIsOrders(SmsIsOrdersEnum.SMS_ORDERS_0.getValue());
reqDTO.setMessageType(SmsMessageTypeEnum.SMS_MESSAGE_TYPE_1.getValue());
reqDTO.setTemplateParams(templateParams);
smsMap.put(consigneeCountryCode + consigneePhone, reqDTO);
for (Map.Entry<String, SmsSendSingleToUserReqDTO> entry : smsMap.entrySet()) {
smsSendApi.sendSingleSmsToAdmin(entry.getValue());
for (Map.Entry<String, SmsSendSingleToUserReqDTOV2> entry : smsMap.entrySet()) {
smsSendApi.sendSingleSmsToAdminV2(entry.getValue());
}
}
......@@ -1274,7 +1288,7 @@ public class OrderCargoControlPickServiceImpl extends AbstractService<OrderCargo
}
OrderDO orderDO = orderService.getById(orderCargoControlPickDO.getOrderId());
// 发送放货短信通知
this.smsSendSendSingle(orderDO, NOTIFICATION_SUCCESS_DELIVERY, orderCargoControlPickDO.getPickNum(), null, orderCargoControlPickDO);
this.smsSendSendSingle(orderDO, NOTIFICATION_SUCCESS_DELIVERY.getNodeValue(), orderCargoControlPickDO.getPickNum(), null, orderCargoControlPickDO);
// 需要处理订单状态
OrderCargoControlReleaseInfoDto releaseInfoDto = orderCargoControlMapper.getOrderCargoControlReleaseInfo(orderCargoControlPickDO.getOrderId());
if (Objects.nonNull(releaseInfoDto)) {
......
......@@ -5,9 +5,9 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.apollo.core.event.box.BoxCheckOrderSchedulingEvent;
import cn.iocoder.yudao.framework.apollo.core.event.Order.OrderApprovalTypeCheckEvent;
import cn.iocoder.yudao.framework.apollo.core.event.QueryChannelInfoEvent;
import cn.iocoder.yudao.framework.apollo.core.event.box.BoxCheckOrderSchedulingEvent;
import cn.iocoder.yudao.framework.apollo.core.event.box.BoxOrderRevokeWarehouseCheckEvent;
import cn.iocoder.yudao.framework.apollo.core.event.box.BoxOrderShipmentWarehouseInEvent;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
......@@ -106,8 +106,11 @@ import cn.iocoder.yudao.module.product.vo.productbrandempower.GetFeeTypeProductB
import cn.iocoder.yudao.module.product.vo.productbrandempower.GetFeeTypeProductBrandEmpowerRespVO;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTOV2;
import cn.iocoder.yudao.module.system.enums.sms.SmsIsOrdersEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsIsTransportEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsMessageTypeEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import cn.iocoder.yudao.module.wealth.dal.dataobject.receivable.ReceivableDO;
import cn.iocoder.yudao.module.wealth.service.receivable.ReceivableService;
import cn.iocoder.yudao.module.wealth.vo.receivable.ReceivableQueryVO;
......@@ -2321,24 +2324,26 @@ public class OrderWarehouseInServiceImpl extends AbstractService<OrderWarehouseI
paramMap.put("cbm", chargeWeight);
String templateCode = SmsSceneEnum.WAREHOUSE_IN_APPEND.getTemplateCode();
String nodeValue = SmsNodeEnum.WAREHOUSE_IN_APPEND.getNodeValue();
log.info("warehouseInSendSms追加入仓短信 orderNo={}", orderDO.getOrderNo());
SmsSendSingleToUserReqDTO reqDTO = new SmsSendSingleToUserReqDTO();
SmsSendSingleToUserReqDTOV2 reqDTO = new SmsSendSingleToUserReqDTOV2();
reqDTO.setTemplateParams(paramMap);
reqDTO.setTemplateCode(templateCode);
reqDTO.setNodeValue(nodeValue);
reqDTO.setUserId(consignorCustomerId);
reqDTO.setMobile(consignorPhone);
reqDTO.setAreaCode(consignorCountryCode);
reqDTO.setIsTransport(SmsIsTransportEnum.SMS_ORDERS_1.getValue());
reqDTO.setTransportId(orderDO.getTransportId());
reqDTO.setIsOrders(SmsIsOrdersEnum.SMS_ORDERS_0.getValue());
reqDTO.setMessageType(SmsMessageTypeEnum.SMS_MESSAGE_TYPE_1.getValue());
// 发送追加通知短信
this.smsSendNotice(consignorCountryCode, consignorPhone, consignorCustomerId, templateCode, paramMap);
smsSendApi.sendSingleSmsToAdminV2(reqDTO);
} else {
// 入仓短信 发送给发货人与收货人
String templateCode = SmsSceneEnum.WAREHOUSE_IN.getTemplateCode();
if (orderDO.getTransportId() == 3) {
templateCode = SmsSceneEnum.AIR_WAREHOUSE_IN.getTemplateCode();
}
String nodeValue = SmsNodeEnum.WAREHOUSE_IN.getNodeValue();
log.info("warehouseInSendSms入仓短信 orderNo={}", orderDO.getOrderNo());
try {
// 控货 就发给控货人, 非控货就发给发件人和收件人
......@@ -2377,14 +2382,20 @@ public class OrderWarehouseInServiceImpl extends AbstractService<OrderWarehouseI
// cargoControlParamMap.put("collectofgoods", orderDO.getCollectionProxy() + currencyName);
// templateCode = SmsSceneEnum.WAREHOUSE_IN_CONTROL_COLLECT_OF_GOODS.getTemplateCode();
// } else {
if (orderDO.getTransportId() == 3) {
templateCode = SmsSceneEnum.AIR_WAREHOUSE_IN_CONTROL.getTemplateCode();
} else {
templateCode = SmsSceneEnum.WAREHOUSE_IN_CONTROL.getTemplateCode();
}
nodeValue = SmsNodeEnum.WAREHOUSE_IN_CONTROL.getNodeValue();
// }
// 给控货人发送入仓控货成功短信
this.smsSendNotice(cargoControlCode, cargoControlPhone, cargoControlCustomerId, templateCode, cargoControlParamMap);
SmsSendSingleToUserReqDTOV2 reqDTO = new SmsSendSingleToUserReqDTOV2();
reqDTO.setTemplateParams(cargoControlParamMap);
reqDTO.setNodeValue(nodeValue);
reqDTO.setUserId(cargoControlCustomerId);
reqDTO.setMobile(cargoControlPhone);
reqDTO.setAreaCode(cargoControlCode);
reqDTO.setIsTransport(SmsIsTransportEnum.SMS_ORDERS_1.getValue());
reqDTO.setTransportId(orderDO.getTransportId());
reqDTO.setIsOrders(SmsIsOrdersEnum.SMS_ORDERS_0.getValue());
reqDTO.setMessageType(SmsMessageTypeEnum.SMS_MESSAGE_TYPE_1.getValue());
smsSendApi.sendSingleSmsToAdminV2(reqDTO);
}
} else {
Map<String, Object> consignorParamMap = new HashMap<>();
......@@ -2416,16 +2427,22 @@ public class OrderWarehouseInServiceImpl extends AbstractService<OrderWarehouseI
consignorParamMap.put("collectofgoods", orderDO.getCollectionProxy() + currencyName);
consigneeParamMap.put("collectofgoods", orderDO.getCollectionProxy() + currencyName);
// 入仓通知
if (orderDO.getTransportId() == 3) {
templateCode = SmsSceneEnum.AIR_WAREHOUSE_IN_COLLECT_OF_GOODS.getTemplateCode();
} else {
templateCode = SmsSceneEnum.WAREHOUSE_IN_COLLECT_OF_GOODS.getTemplateCode();
}
nodeValue = SmsNodeEnum.WAREHOUSE_IN_COLLECT_OF_GOODS.getNodeValue();
log.info("warehouseInSendSms非控货代收货款入仓短信 orderNo={}", orderDO.getOrderNo());
}
// 给发货人发送短信
this.smsSendNotice(consignorCountryCode, consignorPhone, consignorCustomerId, templateCode, consignorParamMap);
SmsSendSingleToUserReqDTOV2 reqDTO = new SmsSendSingleToUserReqDTOV2();
reqDTO.setTemplateParams(consignorParamMap);
reqDTO.setNodeValue(nodeValue);
reqDTO.setUserId(consignorCustomerId);
reqDTO.setMobile(consignorPhone);
reqDTO.setAreaCode(consignorCountryCode);
reqDTO.setIsTransport(SmsIsTransportEnum.SMS_ORDERS_1.getValue());
reqDTO.setTransportId(orderDO.getTransportId());
reqDTO.setIsOrders(SmsIsOrdersEnum.SMS_ORDERS_0.getValue());
reqDTO.setMessageType(SmsMessageTypeEnum.SMS_MESSAGE_TYPE_1.getValue());
smsSendApi.sendSingleSmsToAdminV2(reqDTO);
// 收货人 短信发送信息
OrderConsigneeDO orderConsigneeDO = orderConsigneeService.getOne(new LambdaQueryWrapper<OrderConsigneeDO>().eq(OrderConsigneeDO::getOrderId, orderDO.getOrderId())
......@@ -2457,7 +2474,17 @@ public class OrderWarehouseInServiceImpl extends AbstractService<OrderWarehouseI
}
if (!StringUtils.equals(consignorCountryCode.concat(consignorPhone), consigneeCountryCode.concat(consigneePhone))) {
// 给收货人发送短信
this.smsSendNotice(consigneeCountryCode, consigneePhone, consigneeCustomerId, templateCode, consigneeParamMap);
SmsSendSingleToUserReqDTOV2 reqDTO2 = new SmsSendSingleToUserReqDTOV2();
reqDTO2.setTemplateParams(consigneeParamMap);
reqDTO2.setNodeValue(nodeValue);
reqDTO2.setUserId(consigneeCustomerId);
reqDTO2.setMobile(consigneePhone);
reqDTO2.setAreaCode(consigneeCountryCode);
reqDTO2.setIsTransport(SmsIsTransportEnum.SMS_ORDERS_1.getValue());
reqDTO2.setTransportId(orderDO.getTransportId());
reqDTO2.setIsOrders(SmsIsOrdersEnum.SMS_ORDERS_0.getValue());
reqDTO2.setMessageType(SmsMessageTypeEnum.SMS_MESSAGE_TYPE_1.getValue());
smsSendApi.sendSingleSmsToAdminV2(reqDTO2);
}
}
}
......@@ -2472,16 +2499,16 @@ public class OrderWarehouseInServiceImpl extends AbstractService<OrderWarehouseI
}
}
private void smsSendNotice(String countryCode, String phone, Long customerId, String templateCode, Map<String, Object> paramMap) {
SmsSendSingleToUserReqDTO reqDTO = new SmsSendSingleToUserReqDTO();
reqDTO.setTemplateParams(paramMap);
reqDTO.setTemplateCode(templateCode);
reqDTO.setUserId(customerId);
reqDTO.setMobile(phone);
reqDTO.setAreaCode(countryCode);
// 给发货人发送短信
smsSendApi.sendSingleSmsToAdmin(reqDTO);
}
// private void smsSendNotice(String countryCode, String phone, Long customerId, String templateCode, Map<String, Object> paramMap) {
// SmsSendSingleToUserReqDTO reqDTO = new SmsSendSingleToUserReqDTO();
// reqDTO.setTemplateParams(paramMap);
// reqDTO.setTemplateCode(templateCode);
// reqDTO.setUserId(customerId);
// reqDTO.setMobile(phone);
// reqDTO.setAreaCode(countryCode);
// // 给发货人发送短信
// smsSendApi.sendSingleSmsToAdminV2(reqDTO);
// }
private void warehouseInSendInternalMessage(OrderWarehouseInFinishReqVO finishReqVO, OrderDO orderDO, OrderConsignorDO orderConsignorDO, String exceptionContent) {
if (orderDO.getHasSendWarehouseInNotice()) {
......
package cn.iocoder.yudao.module.order.vo.order;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
......@@ -16,9 +14,21 @@ public class OrderSendSmsReqVO {
@NotNull(message = "{order.id.not.null}")
private Long orderId;
@ApiModelProperty(value = "发送场景(4 控货全转移 5 放货验证短信 6 放货成功通知)", example = "1", notes = "对应 SmsSceneEnum 枚举", required = true)
@NotNull(message = "{app.sms.scene.not.blank}")
@InEnum(value = SmsSceneEnum.class, message = "{app.sms.scene.not.range}")
private Integer scene;
/**
* 发送场景
*/
@NotNull(message = "业务节点不能为空")
private String nodeValue;
@NotNull(message = "是否匹配运输方式不能为空")
private Integer isTransport;
@NotNull(message = "运输方式不能空")
private Integer transportId;
@NotNull(message = "是否多订单不能为空")
private Integer isOrders;
@NotNull(message = "发送类型不能为空")
private Integer messageType;
}
......@@ -57,7 +57,6 @@ public interface RedeemRewardApi {
/**
* 兑换礼品发送验证码
* @param memberId
*/
void sendSmsCode(Long memberId);
void sendSmsCode(RedeemRewardRespDTO reqVO);
}
......@@ -4,6 +4,8 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
@ApiModel("管理后台 - 礼品兑换 Response VO")
public class RedeemRewardRespDTO {
......@@ -14,4 +16,23 @@ public class RedeemRewardRespDTO {
private String msgZh;
@ApiModelProperty(value = "兑换操作详情英文")
private String msgEn;
@ApiModelProperty(value = "会员id")
@NotNull(message = "会员id不能为空")
private Long memberId;
@NotNull(message = "业务节点不能为空")
private String nodeValue;
@NotNull(message = "是否匹配运输方式不能为空")
private Integer isTransport;
@NotNull(message = "运输方式不能空")
private Integer transportId;
@NotNull(message = "是否多订单不能为空")
private Integer isOrders;
@NotNull(message = "发送类型不能为空")
private Integer messageType;
}
......@@ -25,7 +25,7 @@ import cn.iocoder.yudao.module.reward.util.RewardGenCodeUtils;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
......@@ -86,7 +86,7 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
smsCodeUseReqDTO.setMobile(memberUser.getMobile());
smsCodeUseReqDTO.setCode(appRedeemRewardReqVO.getCode());
smsCodeUseReqDTO.setUsedIp(getClientIP());
smsCodeUseReqDTO.setScene(SmsSceneEnum.MEMBER_REDEEM_REWARD.getScene());
smsCodeUseReqDTO.setNodeValue(SmsNodeEnum.MEMBER_REDEEM_REWARD.getNodeValue());
smsCodeApi.useSmsCode(smsCodeUseReqDTO);
}
// 校验收货人信息
......@@ -282,7 +282,8 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
}
@Override
public void sendSmsCode(Long memberId) {
public void sendSmsCode(RedeemRewardRespDTO reqVO) {
Long memberId = reqVO.getMemberId();
UserRespDTO memberUserApiUser = memberUserApi.getUser(memberId);
if (memberUserApiUser == null) {
throw exception(USER_NOT_EXISTS);
......@@ -291,8 +292,12 @@ public class RedeemRewardApiImpl implements RedeemRewardApi {
smsCodeSendReqDTO.setMobile(memberUserApiUser.getMobile());
smsCodeSendReqDTO.setAreaCode(memberUserApiUser.getAreaCode());
smsCodeSendReqDTO.setCreateIp(getClientIP());
smsCodeSendReqDTO.setScene(SmsSceneEnum.MEMBER_REDEEM_REWARD.getScene());
smsCodeSendReqDTO.setNodeValue(reqVO.getNodeValue());
smsCodeSendReqDTO.setIsTransport(reqVO.getIsTransport());
smsCodeSendReqDTO.setTransportId(reqVO.getTransportId());
smsCodeSendReqDTO.setIsOrders(reqVO.getIsOrders());
smsCodeSendReqDTO.setMessageType(reqVO.getMessageType());
smsCodeApi.sendSmsCode(smsCodeSendReqDTO);
}
......
......@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.reward.api.reward.RedeemRewardApi;
import cn.iocoder.yudao.module.reward.api.reward.dto.RedeemRewardRespDTO;
import cn.iocoder.yudao.module.reward.controller.app.redeem.vo.*;
import cn.iocoder.yudao.module.reward.service.redeem.RewardRedeemService;
import cn.iocoder.yudao.module.reward.vo.reward.RewardRedeemPageRespVO;
......@@ -74,8 +75,8 @@ public class AppRedeemRewardController {
@ApiOperation(value = "兑换礼品发送手机验证码")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
@Idempotent(timeout = 5)
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppRewardRedeemReqVO reqVO) {
redeemRewardApi.sendSmsCode(reqVO.getMemberId());
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid RedeemRewardRespDTO reqVO) {
redeemRewardApi.sendSmsCode(reqVO);
return success(true);
}
}
package cn.iocoder.yudao.module.shipment.dal.dataobject;
import lombok.*;
import java.util.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 出货短信记录 DO
......@@ -47,7 +47,7 @@ public class BoxOrderSmsLogDO extends BaseDO {
/**
* 场景编码
*/
private Integer scene;
private String nodeValue;
/**
* 说明
*/
......
package cn.iocoder.yudao.module.shipment.dal.mysql;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQuery;
import cn.iocoder.yudao.framework.mybatis.core.mapper.AbstractMapper;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxOrderSmsLogDO;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQuery;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxOrderSmsLogDO;
import cn.iocoder.yudao.module.shipment.vo.boxOrderSmsLog.BoxOrderSmsLogQueryVO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.shipment.vo.boxOrderSmsLog.*;
import java.util.List;
/**
* 出货短信记录 Mapper
......@@ -25,7 +26,7 @@ public interface BoxOrderSmsLogMapper extends AbstractMapper<BoxOrderSmsLogDO> {
.eqIfPresent(BoxOrderSmsLogDO::getOrderNo, vo.getOrderNo())
.eqIfPresent(BoxOrderSmsLogDO::getMarks, vo.getMarks())
.eqIfPresent(BoxOrderSmsLogDO::getSelfNo, vo.getSelfNo())
.eqIfPresent(BoxOrderSmsLogDO::getScene, vo.getScene())
.eqIfPresent(BoxOrderSmsLogDO::getNodeValue, vo.getNodeValue())
.eqIfPresent(BoxOrderSmsLogDO::getTemplateCode, vo.getTemplateCode())
.eqIfPresent(BoxOrderSmsLogDO::getDescription, vo.getDescription())
.betweenIfPresent(BoxOrderSmsLogDO::getCreateTime, vo.getBeginCreateTime(), vo.getEndCreateTime())
......@@ -45,7 +46,7 @@ public interface BoxOrderSmsLogMapper extends AbstractMapper<BoxOrderSmsLogDO> {
.eqIfPresent(BoxOrderSmsLogDO::getOrderNo, vo.getOrderNo())
.eqIfPresent(BoxOrderSmsLogDO::getMarks, vo.getMarks())
.eqIfPresent(BoxOrderSmsLogDO::getSelfNo, vo.getSelfNo())
.eqIfPresent(BoxOrderSmsLogDO::getScene, vo.getScene())
.eqIfPresent(BoxOrderSmsLogDO::getNodeValue, vo.getNodeValue())
.eqIfPresent(BoxOrderSmsLogDO::getTemplateCode, vo.getTemplateCode())
.eqIfPresent(BoxOrderSmsLogDO::getDescription, vo.getDescription())
.betweenIfPresent(BoxOrderSmsLogDO::getCreateTime, vo.getBeginCreateTime(), vo.getEndCreateTime())
......
......@@ -35,7 +35,7 @@ import cn.iocoder.yudao.module.shipment.vo.boxPreloadSection.BoxLoadSectionBackV
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxGuanlianOrderBackVO;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxOrderLocationUpdateReq;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxTallyBackVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import org.springframework.context.annotation.Description;
import javax.validation.Valid;
......@@ -141,11 +141,11 @@ public interface BoxService extends IService<BoxDO> {
* 发送短信
*
* @param shipment 出货单
* @param smsSceneEnum 场景
* @param smsNodeEnum 场景
*/
void sendSms(Long shipment, SmsSceneEnum smsSceneEnum);
void sendSms(Long shipment, SmsNodeEnum smsNodeEnum);
void sendSms(Long shipmentId, Collection<Long> orderIdList, SmsSceneEnum smsSceneEnum);
void sendSms(Long shipmentId, Collection<Long> orderIdList, SmsNodeEnum smsNodeEnum);
/**
* 新增销售日志
......
......@@ -189,11 +189,14 @@ import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxTallyBackVO;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxTallyVO;
import cn.iocoder.yudao.module.shipment.vo.boxTrailer.BoxTrailerBackVO;
import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTOV2;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsIsOrdersEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsIsTransportEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsMessageTypeEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import cn.iocoder.yudao.module.system.service.dict.DictDataService;
import cn.iocoder.yudao.module.wealth.dal.dataobject.receivable.ReceivableDO;
import cn.iocoder.yudao.module.wealth.service.receivable.ReceivableService;
......@@ -1594,7 +1597,7 @@ public class BoxServiceImpl extends AbstractService<BoxMapper, BoxDO> implements
@Override
public void sendSms(Long shipmentId, SmsSceneEnum smsSceneEnum) {
public void sendSms(Long shipmentId, SmsNodeEnum smsNodeEnum) {
// log.info("出货单【{}】, 暂停出货短信通知发送", shipmentId);
// if (Boolean.TRUE){
// return;
......@@ -1605,12 +1608,12 @@ public class BoxServiceImpl extends AbstractService<BoxMapper, BoxDO> implements
log.error("出货单【{}】, 未找到订单,发送短信失败", shipmentId);
return;
}
log.info("出货单【{}】, {}短信发送", shipmentId, smsSceneEnum.getDescription());
sendSms(shipmentId, orderIdList, smsSceneEnum);
log.info("出货单【{}】, {}短信发送", shipmentId, smsNodeEnum.getDescription());
sendSms(shipmentId, orderIdList, smsNodeEnum);
}
@Override
public void sendSms(Long shipmentId, Collection<Long> orderIdList, SmsSceneEnum smsSceneEnum) {
public void sendSms(Long shipmentId, Collection<Long> orderIdList, SmsNodeEnum smsNodeEnum) {
// 对订单列表进行分组, 统计通知发送人的订单数量,当通知人的订单数量大于1,切换模板,合并订单号逗号连接发送一次
Map<String, List<Map<String, String>>> sendPhoneMap = new HashMap<>();
Map<String, Object> templateParams = new HashMap<>();
......@@ -1621,17 +1624,17 @@ public class BoxServiceImpl extends AbstractService<BoxMapper, BoxDO> implements
long count = boxOrderSmsLogService.count(new LambdaQueryWrapper<BoxOrderSmsLogDO>()
.eq(BoxOrderSmsLogDO::getOrderId, orderId)
.eq(BoxOrderSmsLogDO::getShipmentId, shipmentId)
.eq(BoxOrderSmsLogDO::getScene, smsSceneEnum.getScene()));
.eq(BoxOrderSmsLogDO::getNodeValue, smsNodeEnum.getNodeValue()));
if (count > 0) {
// 当前货柜的当前订单已发送过短信,则不再发送
log.info("出货单【{}】,订单ID【{}】, {}短信已发送过", shipmentId, orderId, smsSceneEnum.getDescription());
log.info("出货单【{}】,订单ID【{}】, {}短信已发送过", shipmentId, orderId, smsNodeEnum.getDescription());
continue;
}
OrderBackVO orderBackVO = orderQueryService.getOrder(orderId);
String orderNo = orderBackVO.getOrderNo();
String marks = orderBackVO.getMarks();
// 封装货柜订单短信发送记录
this.addBoxOrderSmsLog(shipmentId, smsSceneEnum, boxOrderSmsLogDOList, orderId, orderNo, marks);
this.addBoxOrderSmsLog(shipmentId, smsNodeEnum, boxOrderSmsLogDOList, orderId, orderNo, marks);
Map<String, String> orderMap = new HashMap<>(2);
orderMap.put("orderNo", orderNo);
......@@ -1679,10 +1682,11 @@ public class BoxServiceImpl extends AbstractService<BoxMapper, BoxDO> implements
marks.append(m);
}
if (i % 3 == 0 || i == orderNoList.size()) {
String templateCode = smsSceneEnum.getTemplateCode();
String nodeValue = smsNodeEnum.getNodeValue();
Integer isOrders = SmsIsOrdersEnum.SMS_ORDERS_0.getValue();
this.setTimeParams(templateParams);
templateParams.put("marks", marks);
switch (smsSceneEnum) {
switch (smsNodeEnum) {
case SHIPMENT_CABINET:
BoxCabinetDO boxCabinetDO = boxCabinetService.getByShipmentId(boxDO.getId());
if (Objects.nonNull(boxCabinetDO) && Objects.nonNull(boxCabinetDO.getLdBoxTime())) {
......@@ -1695,7 +1699,8 @@ public class BoxServiceImpl extends AbstractService<BoxMapper, BoxDO> implements
}
// 装柜通知
if (orderNoList.size() > 1) {
templateCode = SmsSceneEnum.SHIPMENT_CABINETS.getTemplateCode();
nodeValue = SmsNodeEnum.SHIPMENT_CABINET.getNodeValue();
isOrders = SmsIsOrdersEnum.SMS_ORDERS_1.getValue();
templateParams.put("orders", orders.toString());
} else {
templateParams.put("order", orders.toString());
......@@ -1712,7 +1717,8 @@ public class BoxServiceImpl extends AbstractService<BoxMapper, BoxDO> implements
templateParams.put("day", shipmentWarehouseTime.dayOfMonth());
}
if (orderNoList.size() > 1) {
templateCode = SmsSceneEnum.AIR_SHIPMENT_WAREHOUSES.getTemplateCode();
nodeValue = SmsNodeEnum.AIR_SHIPMENT_WAREHOUSE.getNodeValue();
isOrders = SmsIsOrdersEnum.SMS_ORDERS_1.getValue();
templateParams.put("orders", orders.toString());
} else {
templateParams.put("order", orders.toString());
......@@ -1724,53 +1730,45 @@ public class BoxServiceImpl extends AbstractService<BoxMapper, BoxDO> implements
break;
case SHIPMENT_ARRIVAL:
// 到港通知
if (orderNoList.size() > 1) {
templateCode = SmsSceneEnum.SHIPMENT_ARRIVALS.getTemplateCode();
templateParams.put("orders", orders.toString());
} else {
templateParams.put("order", orders.toString());
}
break;
case AIR_SHIPMENT_ARRIVAL:
// 空运到港通知
// 单号:${orders}( 航班号:${number})的货物已到港,清关中。如您的是控货单请及时放货,客服电话:400 9009962(9:00--18:00)
if (orderNoList.size() > 1) {
templateCode = SmsSceneEnum.AIR_SHIPMENT_ARRIVALS.getTemplateCode();
}
nodeValue = SmsNodeEnum.SHIPMENT_ARRIVAL.getNodeValue();
isOrders = SmsIsOrdersEnum.SMS_ORDERS_1.getValue();
templateParams.put("orders", orders.toString());
} else {
templateParams.put("order", orders.toString());
}
break;
case SHIPMENT_CUSTOMS_CLEARANCE:
//清关通知2023.2.9号新excel模板调整为 柜号{number},具体如下:
//【捷道货运】单号${orders}(柜号${number})已清关。为了避免产生目的港仓租,请及时放货/提货。客服电话:400 9009962(9:00--18:00)。
if (orderNoList.size() > 1) {
templateCode = SmsSceneEnum.SHIPMENT_CUSTOMS_CLEARANCES.getTemplateCode();
templateParams.put("orders", orders.toString());
} else {
templateParams.put("order", orders.toString());
}
break;
case AIR_SHIPMENT_CUSTOMS_CLEARANCE:
//清关通知2023.2.9号新excel模板调整为 柜号{number},具体如下:
// 单号${orders}(航班号${number})已清关。为了避免产生目的港仓租,请及时放货/提货。客服电话:400 9009962(9:00--18:00)。
if (orderNoList.size() > 1) {
templateCode = SmsSceneEnum.AIR_SHIPMENT_CUSTOMS_CLEARANCES.getTemplateCode();
}
nodeValue = SmsNodeEnum.SHIPMENT_CUSTOMS_CLEARANCE.getNodeValue();
isOrders = SmsIsOrdersEnum.SMS_ORDERS_1.getValue();
templateParams.put("orders", orders.toString());
} else {
templateParams.put("order", orders.toString());
}
break;
default:
return;
}
SmsSendSingleToUserReqDTO reqDTO = new SmsSendSingleToUserReqDTO();
SmsSendSingleToUserReqDTOV2 reqDTO = new SmsSendSingleToUserReqDTOV2();
reqDTO.setTemplateParams(templateParams);
reqDTO.setTemplateCode(templateCode);
reqDTO.setNodeValue(nodeValue);
reqDTO.setUserId(0L);
reqDTO.setMobile(phone);
reqDTO.setAreaCode(areaCode);
reqDTO.setIsTransport(SmsIsTransportEnum.SMS_ORDERS_1.getValue());
reqDTO.setTransportId(Integer.valueOf(boxDO.getTransportType()));
reqDTO.setIsOrders(isOrders);
reqDTO.setMessageType(SmsMessageTypeEnum.SMS_MESSAGE_TYPE_1.getValue());
//给收发货人发送短信
smsSendApi.sendSingleSmsToAdmin(reqDTO);
smsSendApi.sendSingleSmsToAdminV2(reqDTO);
// 发送短信后初始化参数值
orders = null;
marks = null;
......@@ -1795,15 +1793,15 @@ public class BoxServiceImpl extends AbstractService<BoxMapper, BoxDO> implements
}
}
private void addBoxOrderSmsLog(Long shipmentId, SmsSceneEnum smsSceneEnum, List<BoxOrderSmsLogDO> boxOrderSmsLogDOList, Long orderId, String orderNo, String marks) {
private void addBoxOrderSmsLog(Long shipmentId, SmsNodeEnum smsNodeEnum, List<BoxOrderSmsLogDO> boxOrderSmsLogDOList, Long orderId, String orderNo, String marks) {
BoxOrderSmsLogDO boxOrderSmsLogDO = new BoxOrderSmsLogDO();
boxOrderSmsLogDO.setShipmentId(shipmentId);
boxOrderSmsLogDO.setOrderId(orderId);
boxOrderSmsLogDO.setOrderNo(orderNo);
boxOrderSmsLogDO.setMarks(marks);
boxOrderSmsLogDO.setScene(smsSceneEnum.getScene());
boxOrderSmsLogDO.setTemplateCode(smsSceneEnum.getTemplateCode());
boxOrderSmsLogDO.setDescription(smsSceneEnum.getDescription());
boxOrderSmsLogDO.setNodeValue(smsNodeEnum.getNodeValue());
boxOrderSmsLogDO.setTemplateCode(smsNodeEnum.getTemplateCode());
boxOrderSmsLogDO.setDescription(smsNodeEnum.getDescription());
boxOrderSmsLogDO.setCreator(SecurityFrameworkUtils.getLoginUserId().toString());
boxOrderSmsLogDO.setCreateTime(new Date());
boxOrderSmsLogDOList.add(boxOrderSmsLogDO);
......
......@@ -33,7 +33,7 @@ import cn.iocoder.yudao.module.shipment.vo.boxAirCheckout.BoxAirCheckoutCreateRe
import cn.iocoder.yudao.module.shipment.vo.boxAirCheckout.BoxAirCheckoutPDAVO;
import cn.iocoder.yudao.module.shipment.vo.boxAirCheckout.BoxAirCheckoutQueryVO;
import cn.iocoder.yudao.module.shipment.vo.boxAirCheckout.BoxAirCheckoutUpdateReqVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.beans.BeanUtils;
......@@ -168,7 +168,7 @@ public class BoxAirCheckoutServiceImpl extends AbstractService<BoxAirCheckoutMap
//出仓添加业绩日志
boxService.addTargetLog(shipmentId, TargetLogEnum.LOADING,checkoutTime);
// 出仓短信通知
boxService.sendSms(shipmentId, SmsSceneEnum.AIR_SHIPMENT_WAREHOUSE);
boxService.sendSms(shipmentId, SmsNodeEnum.AIR_SHIPMENT_WAREHOUSE);
//插入操作日志
boxOpLogService.createBoxOpLog(boxDO.getId(), checkout.getStatus(), checkout.getText(), checkout.getTextEn(), null);
}
......
package cn.iocoder.yudao.module.shipment.service.boxArrival;
import java.util.*;
import javax.annotation.Resource;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.order.enums.OrderStatusEnum;
import cn.iocoder.yudao.module.shipment.convert.BoxArrivalConvert;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxArrivalDO;
......@@ -16,15 +16,17 @@ import cn.iocoder.yudao.module.shipment.service.boxOpLog.BoxOpLogService;
import cn.iocoder.yudao.module.shipment.vo.boxArrival.BoxArrivalCreateReqVO;
import cn.iocoder.yudao.module.shipment.vo.boxArrival.BoxArrivalQueryVO;
import cn.iocoder.yudao.module.shipment.vo.boxArrival.BoxArrivalUpdateReqVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.shipment.enums.ErrorCodeConstants.BOX_NOT_EXISTS;
......@@ -101,11 +103,7 @@ public class BoxArrivalServiceImpl extends AbstractService<BoxArrivalMapper, Box
//确认到港
//发送短信通知
if (StringUtils.equals("3", boxDO.getTransportType())) {
boxService.sendSms(shipmentId, SmsSceneEnum.AIR_SHIPMENT_ARRIVAL);
} else {
boxService.sendSms(shipmentId, SmsSceneEnum.SHIPMENT_ARRIVAL);
}
boxService.sendSms(shipmentId, SmsNodeEnum.SHIPMENT_ARRIVAL);
//修改订单状态为已到港
......
package cn.iocoder.yudao.module.shipment.service.boxArrivalAir;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.order.dal.dataobject.order.OrderDO;
import cn.iocoder.yudao.module.order.enums.OrderStatusEnum;
import cn.iocoder.yudao.module.order.service.order.OrderQueryService;
......@@ -13,7 +12,6 @@ import cn.iocoder.yudao.module.shipment.convert.BoxArrivalAirConvert;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxArrivalAirDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.boxArrivalOrder.BoxArrivalOrderDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.boxClearanceOrder.BoxClearanceOrderDO;
import cn.iocoder.yudao.module.shipment.dal.mysql.BoxArrivalAirMapper;
import cn.iocoder.yudao.module.shipment.enums.*;
import cn.iocoder.yudao.module.shipment.service.box.BoxService;
......@@ -25,6 +23,7 @@ import cn.iocoder.yudao.module.shipment.vo.boxArrivalAir.BoxArrivalAirQueryVO;
import cn.iocoder.yudao.module.shipment.vo.boxArrivalAir.BoxArrivalAirUpdateReqVO;
import cn.iocoder.yudao.module.shipment.vo.boxArrivalOrder.BoxArrivalOrderAllCreateVO;
import cn.iocoder.yudao.module.shipment.vo.boxArrivalOrder.BoxArrivalOrderCreateVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
......@@ -32,9 +31,10 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.shipment.enums.ErrorCodeConstants.BOX_NOT_EXISTS;
......@@ -152,6 +152,8 @@ public class BoxArrivalAirServiceImpl extends AbstractService<BoxArrivalAirMappe
.eq(BoxArrivalOrderDO::getShipmentId, shipmentId)
);
if (arrivalCount == arrivalOrders.size()) {
//发送短信通知
boxService.sendSms(shipmentId, SmsNodeEnum.SHIPMENT_ARRIVAL);
SapStatueEnum sapStatueEnum = SapStatueEnum.HAS_ARRIVE;
boxDO.setSapStatus(sapStatueEnum.getSapStatus());
boxDO.setDgDate(actTime);
......
......@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.shipment.service.boxCabinet;
import java.util.*;
import javax.annotation.Resource;
import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.order.enums.TargetLogEnum;
......@@ -11,7 +10,6 @@ import cn.iocoder.yudao.module.shipment.convert.BoxCabinetConvert;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxBookSeaDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxCabinetDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxDO;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxPreloadGoodsDO;
import cn.iocoder.yudao.module.shipment.dal.mysql.BoxBookSeaMapper;
import cn.iocoder.yudao.module.shipment.dal.mysql.BoxCabinetMapper;
import cn.iocoder.yudao.module.shipment.enums.*;
......@@ -20,8 +18,7 @@ import cn.iocoder.yudao.module.shipment.service.boxOpLog.BoxOpLogService;
import cn.iocoder.yudao.module.shipment.vo.boxCabinet.BoxCabinetCreateReqVO;
import cn.iocoder.yudao.module.shipment.vo.boxCabinet.BoxCabinetQueryVO;
import cn.iocoder.yudao.module.shipment.vo.boxCabinet.BoxCabinetUpdateReqVO;
import cn.iocoder.yudao.module.shipment.vo.boxPreloadGoods.BoxPreloadGoodsQueryVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
......@@ -166,7 +163,7 @@ public class BoxCabinetServiceImpl extends AbstractService<BoxCabinetMapper, Box
boxService.addTargetLog(shipmentId, TargetLogEnum.LOADING, ldBoxTime);
//发送短信
try {
boxService.sendSms(shipmentId, SmsSceneEnum.SHIPMENT_CABINET);
boxService.sendSms(shipmentId, SmsNodeEnum.SHIPMENT_CABINET);
} catch (Exception e) {
log.error(e.getMessage());
}
......
package cn.iocoder.yudao.module.shipment.service.boxClearance;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.bpm.enums.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.order.dal.dataobject.order.OrderDO;
import cn.iocoder.yudao.module.order.enums.OrderStatusEnum;
......@@ -28,7 +27,7 @@ import cn.iocoder.yudao.module.shipment.vo.boxClearance.BoxClearanceQueryVO;
import cn.iocoder.yudao.module.shipment.vo.boxClearance.BoxClearanceUpdateReqVO;
import cn.iocoder.yudao.module.shipment.vo.boxClearanceOrder.BoxClearanceOrderAllCreateVO;
import cn.iocoder.yudao.module.shipment.vo.boxClearanceOrder.BoxClearanceOrderCreateVO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.base.Joiner;
import org.apache.commons.collections4.CollectionUtils;
......@@ -36,9 +35,10 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.shipment.enums.ErrorCodeConstants.BOX_NOT_EXISTS;
......@@ -118,7 +118,7 @@ public class BoxClearanceServiceImpl extends AbstractService<BoxClearanceMapper,
if (clClearTime != null) {
boxOpLogService.createBoxOpLog(shipmentId, ClStatueEnum.HAS_CLEARANCE.getClStatus(), "已清关,待到仓", "", null);
//发送清关短信
boxService.sendSms(shipmentId, SmsSceneEnum.SHIPMENT_CUSTOMS_CLEARANCE);
boxService.sendSms(shipmentId, SmsNodeEnum.SHIPMENT_CUSTOMS_CLEARANCE);
}
//修改订单状态为已清关
......@@ -274,7 +274,7 @@ public class BoxClearanceServiceImpl extends AbstractService<BoxClearanceMapper,
if (clearTime != null) {
boxOpLogService.createBoxOpLog(shipmentId, ClStatueEnum.HAS_CLEARANCE.getClStatus(), "已清关,待到仓", "", null);
//发送清关短信
boxService.sendSms(shipmentId, clearanceOrders, SmsSceneEnum.AIR_SHIPMENT_CUSTOMS_CLEARANCE);
boxService.sendSms(shipmentId, clearanceOrders, SmsNodeEnum.SHIPMENT_CUSTOMS_CLEARANCE);
}
//修改订单状态为已清关
......
package cn.iocoder.yudao.module.shipment.vo.boxOrderSmsLog;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
......@@ -40,9 +42,9 @@ public class BoxOrderSmsLogBackVO {
@ApiModelProperty(value = "自编号")
private String selfNo;
@ExcelProperty("场景编码")
@ApiModelProperty(value = "场景编码")
private Integer scene;
@ExcelProperty("节点")
@ApiModelProperty(value = "节点")
private String nodeValue;
@ExcelProperty("说明")
@ApiModelProperty(value = "说明")
......
package cn.iocoder.yudao.module.shipment.vo.boxOrderSmsLog;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 出货短信记录 Base VO,提供给添加、修改、详细的子 VO 使用
......@@ -27,8 +25,8 @@ public class BoxOrderSmsLogBaseVO {
@ApiModelProperty(value = "自编号")
private String selfNo;
@ApiModelProperty(value = "场景编码")
private Integer scene;
@ApiModelProperty(value = "节点")
private String nodeValue;
@ApiModelProperty(value = "说明")
private String templateCode;
......
package cn.iocoder.yudao.module.shipment.vo.boxOrderSmsLog;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Data
......@@ -26,8 +28,8 @@ public class BoxOrderSmsLogQueryVO {
@ApiModelProperty(value = "自编号")
private String selfNo;
@ApiModelProperty(value = "场景编码")
private Integer scene;
@ApiModelProperty(value = "节点")
private String nodeValue;
@ApiModelProperty(value = "说明")
private String templateCode;
......
package cn.iocoder.yudao.module.shipment.controller.admin;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.util.ExcelUtils;
import cn.iocoder.yudao.framework.i18n.core.I18nMessage;
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
import cn.iocoder.yudao.framework.limiter.dynamic.DynamicRateLimiter;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.redis.helper.RedisHelper;
import cn.iocoder.yudao.module.order.vo.order.OrderBackPageVO;
import cn.iocoder.yudao.module.order.vo.orderSplit.OrderSplitBackVO;
import cn.iocoder.yudao.module.shipment.convert.BoxConvert;
import cn.iocoder.yudao.module.shipment.dal.dataobject.BoxDO;
import cn.iocoder.yudao.module.shipment.service.box.BoxService;
import cn.iocoder.yudao.module.shipment.vo.box.*;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxGuanlianOrderBackVO;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxOrderLocationUpdateReq;
import cn.iocoder.yudao.module.shipment.vo.boxTally.BoxTallyBackVO;
import cn.iocoder.yudao.module.system.api.file.FileMakeApi;
import cn.iocoder.yudao.module.system.api.file.dto.FileMakeReqDTO;
import cn.iocoder.yudao.module.system.enums.download.DownloadTypeEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.enums.sms.SmsNodeEnum;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.annotations.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.io.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.module.shipment.controller.admin.constant.Constant.BOX_UPDATE_KEY;
import static cn.iocoder.yudao.module.shipment.enums.ErrorCodeConstants.BOX_UPDATE_REPEAT_COMMIT;
import cn.iocoder.yudao.module.shipment.vo.box.*;
import cn.iocoder.yudao.module.shipment.service.box.BoxService;
@Validated
@RestController
@Api(tags = "管理后台 - 出货")
......@@ -323,8 +321,8 @@ public class BoxController {
@ApiImplicitParam(name = "shipmentId", value = "出货单ID", required = true, example = "1024", dataTypeClass = Long.class),
@ApiImplicitParam(name = "scene", value = "场景编号", required = true, example = "20", dataTypeClass = String.class)
})
public CommonResult<Boolean> shipmentSmsSend(@RequestParam("shipmentId") Long shipmentId, @RequestParam("scene") Integer scene) {
boxService.sendSms(shipmentId, SmsSceneEnum.getCodeByScene(scene));
public CommonResult<Boolean> shipmentSmsSend(@RequestParam("shipmentId") Long shipmentId, @RequestParam("scene") String scene) {
boxService.sendSms(shipmentId, SmsNodeEnum.getCodeByScene(scene));
return success(true);
}
......
package cn.iocoder.yudao.module.system.api.sms;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTOV2;
import javax.validation.Valid;
......@@ -21,6 +22,16 @@ public interface SmsSendApi {
*/
Long sendSingleSmsToAdmin(@Valid SmsSendSingleToUserReqDTO reqDTO);
/**
* 发送单条短信给 Admin 用户
*
* 在 mobile 为空时,使用 userId 加载对应 Admin 的手机号
*
* @param reqDTO 发送请求
* @return 发送日志编号
*/
Long sendSingleSmsToAdminV2(@Valid SmsSendSingleToUserReqDTOV2 reqDTO);
/**
* 发送单条短信给 Member 用户
*
......
package cn.iocoder.yudao.module.system.api.sms.dto.code;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
......@@ -26,8 +24,7 @@ public class SmsCodeCheckReqDTO {
* 发送场景
*/
@NotNull(message = "发送场景不能为空")
@InEnum(SmsSceneEnum.class)
private Integer scene;
private String nodeValue;
/**
* 验证码
*/
......
package cn.iocoder.yudao.module.system.api.sms.dto.code;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
......@@ -25,9 +23,8 @@ public class SmsCodeSendReqDTO {
/**
* 发送场景
*/
@NotNull(message = "发送场景不能为空")
@InEnum(SmsSceneEnum.class)
private Integer scene;
@NotNull(message = "业务节点不能为空")
private String nodeValue;
/**
* 发送 IP
*/
......@@ -36,4 +33,16 @@ public class SmsCodeSendReqDTO {
@NotNull(message = "国家区号不能为空")
private String areaCode;
@NotNull(message = "是否匹配运输方式不能为空")
private Integer isTransport;
@NotNull(message = "运输方式不能空")
private Integer transportId;
@NotNull(message = "是否多订单不能为空")
private Integer isOrders;
@NotNull(message = "发送类型不能为空")
private Integer messageType;
}
package cn.iocoder.yudao.module.system.api.sms.dto.code;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
......@@ -26,8 +24,7 @@ public class SmsCodeUseReqDTO {
* 发送场景
*/
@NotNull(message = "发送场景不能为空")
@InEnum(SmsSceneEnum.class)
private Integer scene;
private String nodeValue;
/**
* 验证码
*/
......
package cn.iocoder.yudao.module.system.api.sms.dto.send;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.Map;
/**
* 短信发送给 Admin 或者 Member 用户
*
* @author 捷道源码
*/
@Data
public class SmsSendSingleToUserReqDTOV2 {
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
@NotNull(message = "手机号不能为空")
private String mobile;
@NotNull(message = "节点不能为空")
private String nodeValue;
@NotNull(message = "区号不能为空")
private String areaCode;
@NotNull(message = "是否匹配运输方式不能为空")
private Integer isTransport;
@NotNull(message = "运输方式不能空")
private Integer transportId;
@NotNull(message = "是否多订单不能为空")
private Integer isOrders;
@NotNull(message = "发送类型不能为空")
private Integer messageType;
/**
* 短信模板参数
*/
private Map<String, Object> templateParams;
}
......@@ -89,6 +89,9 @@ public interface ErrorCodeConstants {
ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1002013001, "模板参数({})缺失");
ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1002013002, "短信模板不存在");
// ========== 短信节点 1002013100 ==========
ErrorCode SMS_NODE_NOT_EXISTS = new ErrorCode(1002013100, "短信节点不存在");
// ========== 短信验证码 1002014000 ==========
ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1002014000, "验证码不存在");
ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1002014001, "验证码已过期");
......
package cn.iocoder.yudao.module.system.enums.sms;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.util.json.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 国家编号枚举
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum SmsCountryCodeEnum {
SMS_COUNTRY_CODE_0("全部", "0"),
SMS_COUNTRY_CODE_1("其他", "1");
private String name;
private String value;
}
package cn.iocoder.yudao.module.system.enums.sms;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 是否多订单枚举
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum SmsIsOrdersEnum {
SMS_ORDERS_0("否", 0),
SMS_ORDERS_1("是", 1);
private String name;
private Integer value;
}
package cn.iocoder.yudao.module.system.enums.sms;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 是否匹配运输方式枚举
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum SmsIsTransportEnum {
SMS_ORDERS_0("否", 0),
SMS_ORDERS_1("是", 1);
private String name;
private Integer value;
}
package cn.iocoder.yudao.module.system.enums.sms;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 发送类型枚举
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum SmsMessageTypeEnum {
SMS_MESSAGE_TYPE_1("短信", 1),
SMS_MESSAGE_TYPE_2("whatsapp", 2),
SMS_MESSAGE_TYPE_3("email", 3);
private String name;
private Integer value;
}
package cn.iocoder.yudao.module.system.enums.sms;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 用户短信验证码发送场景的枚举
*
* @author 捷道源码
*/
@Getter
@AllArgsConstructor
public enum SmsNodeEnum {
MEMBER_REG("user-sms-reg", "user-sms-reg", "会员用户 - 手机号注册"),//测了
MEMBER_LOGIN("user-sms-login", "user-sms-login", "会员用户 - 手机号登陆"),//测了
MEMBER_UPDATE_MOBILE("user-sms-update-mobile", "user-sms-update-mobile", "会员用户 - 解绑手机"),//测了
MEMBER_FORGET_PASSWORD("user-sms-reset-password", "user-sms-reset-password", "会员用户 - 忘记密码"),//测了
TRANSFER_CONTROL_GOODS("transfer-control-goods", "transfer-control-goods", "订单 - 控货权转移"),//测了
DELIVERY_VERIFICATION_SMS("delivery-verification-sms", "delivery-verification-sms", "订单 - 放货验证短信"),//测了
NOTIFICATION_SUCCESS_DELIVERY("notification-successful-delivery", "notification-successful-delivery", "订单 - 放货成功通知"),//测了
NOTIFICATION_SUCCESS_CANCEL_DELIVERY("notification-successful-cancel-delivery", "notification-successful-cancel-delivery", "订单 - 取消放货通知"),//测了
// NOTIFICATION_SUCCESS_SEASONING_CONDIMENTS("notification-successful-seasoning-condiments", "notification-successful-seasoning-condiments", "订单 - 调货通知"),//暂时没发现调用
MEMBER_REDEEM_REWARD("user-sms-redeem-reward", "user-sms-redeem-reward", "会员用户 - 兑换礼品"),
// SHIPMENT_ARRIVALS("shipment-arrivals", "shipment-arrivals", "出货 - 到港通知(多订单)"),//
// SHIPMENT_CUSTOMS_CLEARANCES("shipment-customs-clearances", "shipment-customs-clearances", "出货 - 清关通知(多订单)"),//
// SHIPMENT_CABINETS("shipment-cabinets", "shipment-cabinets", "出货 - 装柜通知(多订单)"),//
SHIPMENT_ARRIVAL("shipment-arrival", "shipment-arrival", "出货 - 到港通知"),//h
SHIPMENT_CUSTOMS_CLEARANCE("shipment-customs-clearance", "shipment-customs-clearance", "出货 - 清关通知"),//h
SHIPMENT_CABINET("shipment-cabinet", "shipment-cabinet", "出货 - 装柜通知"),//h
SHIPMENT_CLOSE_CONTAINER("shipment-close-container", "shipment-close-container", "出货 - 封柜反审"),//好像没发信息
WAREHOUSE_IN_CONTROL("warehouse-in-control", "warehouse-in-control", "入仓控货通知"),//h
WAREHOUSE_IN("warehouse-in", "warehouse-in", "入仓通知"),//h
WAREHOUSE_IN_APPEND("warehouse-in-append", "warehouse-in-append", "追加通知"),//k
WAREHOUSE_IN_CONTROL_COLLECT_OF_GOODS("warehouse-in-control-collect-of-goods", "warehouse-in-control-collect-of-goods", "入仓-控货代收货款"),//无此功能
WAREHOUSE_IN_COLLECT_OF_GOODS("warehouse-in-collect-of-goods", "warehouse-in-collect-of-goods", "入仓-非控货代收货款"),//h
/** 空运新增模板 **/
// AIR_WAREHOUSE_IN_CONTROL("air-warehouse-in-control", "air-warehouse-in-control", "入仓控货通知"),//x
// AIR_WAREHOUSE_IN("air-warehouse-in", "air-warehouse-in", "入仓通知"),//x
// AIR_SHIPMENT_ARRIVAL("air-shipment-arrival", "air-shipment-arrival", "出货 - 到港通知"),//x
// AIR_SHIPMENT_ARRIVALS("air-shipment-arrivals", "air-shipment-arrivals", "出货 - 到港通知(多订单)"),//x
// AIR_SHIPMENT_CUSTOMS_CLEARANCE("air-shipment-customs-clearance", "air-shipment-customs-clearance", "出货 - 清关通知"),//
// AIR_SHIPMENT_CUSTOMS_CLEARANCES("air-shipment-customs-clearances", "air-shipment-customs-clearances", "出货 - 清关通知(多订单)"),//
AIR_SHIPMENT_WAREHOUSE("air-shipment-warehouse", "air-shipment-warehouse", "出货 - 出仓通知"),//
// AIR_SHIPMENT_WAREHOUSES("air-shipment-warehouses", "air-shipment-warehouses", "出货 - 出仓通知(多订单)"),//
// AIR_WAREHOUSE_IN_COLLECT_OF_GOODS("air-warehouse-in-collect-of-goods", "air-warehouse-in-collect-of-goods", "入仓-非控货代收货款"),//
;
/**
* 验证场景的编号
*/
private final String nodeValue;
/**
* 模版编码
*/
private final String templateCode;
/**
* 描述
*/
private final String description;
public static SmsNodeEnum getCodeByScene(String scene) {
return ArrayUtil.firstMatch(sceneEnum -> sceneEnum.getNodeValue().equals(scene),
values());
}
}
package cn.iocoder.yudao.module.system.enums.sms;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 系统状态枚举
*
* @author jayden
*/
@Getter
@AllArgsConstructor
public enum SystemStatusEnum {
SYSTEM_STATUS_0("开启", 0),
SYSTEM_STATUS_1("关闭", 1);
private String name;
private Integer value;
}
package cn.iocoder.yudao.module.system.api.sms;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTOV2;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
......@@ -25,6 +26,13 @@ public class SmsSendApiImpl implements SmsSendApi {
reqDTO.getTemplateCode(), reqDTO.getTemplateParams(), reqDTO.getAreaCode());
}
@Override
public Long sendSingleSmsToAdminV2(SmsSendSingleToUserReqDTOV2 reqDTO) {
return smsSendService.sendSingleSmsToAdminV2(reqDTO.getMobile(), reqDTO.getUserId(),
reqDTO.getNodeValue(), reqDTO.getAreaCode(), reqDTO.getIsOrders(), reqDTO.getIsTransport(),
reqDTO.getTransportId(), reqDTO.getMessageType(), reqDTO.getTemplateParams());
}
@Override
public Long sendSingleSmsToMember(SmsSendSingleToUserReqDTO reqDTO) {
return smsSendService.sendSingleSmsToMember(reqDTO.getMobile(), reqDTO.getUserId(),
......
......@@ -2,13 +2,17 @@ package cn.iocoder.yudao.module.system.controller.admin.sms;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp.SendchampSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.SmsChannelEnum;
import cn.iocoder.yudao.module.system.service.sms.SmsLogService;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
......@@ -27,6 +31,9 @@ public class SmsCallbackController {
@Resource
private SmsSendService smsSendService;
@Autowired
private SmsLogService smsLogService;
@PostMapping("/yunpian")
@ApiOperation(value = "云片短信的回调", notes = "参见 https://www.yunpian.com/official/document/sms/zh_cn/domestic_push_report 文档")
@ApiImplicitParam(name = "sms_status", value = "发送状态", required = true, example = "[{具体内容}]", dataTypeClass = String.class)
......@@ -46,4 +53,13 @@ public class SmsCallbackController {
return success(true);
}
@PostMapping("/sendchamp")
@ApiOperation(value = "sendchamp短信的回调", notes = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
@OperateLog(enable = false)
public CommonResult<Boolean> receiveSendchampSmsStatus(HttpServletRequest request) throws Throwable {
String text = ServletUtil.getBody(request);
SendchampSmsClient.SmsReceiveStatus smsReceiveStatus = JsonUtils.parseObject(text, SendchampSmsClient.SmsReceiveStatus.class);
smsLogService.updateSendchampReceive(smsReceiveStatus);
return success(true);
}
}
package cn.iocoder.yudao.module.system.controller.admin.sms;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.system.convert.sms.SmsNodeConvert;
import cn.iocoder.yudao.module.system.service.sms.SmsNodeService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.*;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO;
@Validated
@RestController
@Api(tags = "管理后台 - 短信节点")
@RequestMapping("/system/sms-node")
public class SmsNodeController {
@Resource
private SmsNodeService smsNodeService;
@PostMapping("/create")
@ApiOperation("创建短信节点")
@PreAuthorize("@ss.hasPermission('system:sms-node:create')")
public CommonResult<Long> createSmsNode(@Valid @RequestBody SmsNodeCreateReqVO createReqVO) {
return success(smsNodeService.createSmsNode(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新短信节点")
@PreAuthorize("@ss.hasPermission('system:sms-node:update')")
public CommonResult<Boolean> updateSmsNode(@Valid @RequestBody SmsNodeUpdateReqVO updateReqVO) {
smsNodeService.updateSmsNode(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除短信节点")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('system:sms-node:delete')")
public CommonResult<Boolean> deleteSmsNode(@RequestParam("id") Long id) {
smsNodeService.deleteSmsNode(id);
return success(true);
}
@GetMapping("/get")
@ApiOperation("获得短信节点")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('system:sms-node:query')")
public CommonResult<SmsNodeBackVO> getSmsNode(@RequestParam("id") Long id) {
SmsNodeDO smsNode = smsNodeService.getSmsNode(id);
return success(SmsNodeConvert.INSTANCE.convert(smsNode));
}
@GetMapping("/list")
@ApiOperation("获得短信节点列表")
@ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
@PreAuthorize("@ss.hasPermission('system:sms-node:query')")
public CommonResult<List<SmsNodeBackVO>> getSmsNodeList(@RequestParam("ids") Collection<Long> ids) {
List<SmsNodeDO> list = smsNodeService.getSmsNodeList(ids);
return success(SmsNodeConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@ApiOperation("获得短信节点分页")
@PreAuthorize("@ss.hasPermission('system:sms-node:query')")
public CommonResult<PageResult<SmsNodeBackVO>> getSmsNodePage(@Valid SmsNodeQueryVO query, PageVO page) {
PageResult<SmsNodeDO> pageResult = smsNodeService.getSmsNodePage(query, page);
return success(SmsNodeConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/export-excel")
@ApiOperation("导出短信节点 Excel")
@PreAuthorize("@ss.hasPermission('system:sms-node:export')")
@OperateLog(type = EXPORT)
public void exportSmsNodeExcel(@Valid SmsNodeQueryVO query,
HttpServletResponse response) throws IOException {
List<SmsNodeDO> list = smsNodeService.getSmsNodeList(query);
// 导出 Excel
List<SmsNodeBackVO> datas = SmsNodeConvert.INSTANCE.convertList(list);
ExcelUtils.write(response, "短信节点.xls", "数据", SmsNodeBackVO.class, datas);
}
}
......@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.excel.convert.DictConvert;
import cn.iocoder.yudao.framework.excel.convert.JsonConvert;
import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
......@@ -107,4 +108,23 @@ public class SmsLogExcelVO {
*/
@ExcelProperty(value = "重发次数")
private Integer num;
/**
* 短信节点表id
*/
@ExcelProperty(value = "短信节点表id")
private Long nodeId;
/**
* 节点模板序列号
*/
@ExcelProperty(value = "节点模板序列号")
private Integer nodeTemplateSn;
/**
* 发送类型
*/
@ExcelProperty(value = "发送类型")
private Integer messageType;
}
......@@ -55,4 +55,24 @@ public class SmsLogExportReqVO {
@ApiModelProperty(value = "重发次数")
private Integer num;
/**
* 短信节点表id
*/
@ApiModelProperty(value = "短信节点表id")
private Long nodeId;
/**
* 节点模板序列号
*/
@ApiModelProperty(value = "节点模板序列号")
private Integer nodeTemplateSn;
/**
* 发送类型
*/
@ApiModelProperty(value = "发送类型")
private Integer messageType;
@ApiModelProperty(value = "短信 API 发送返回的序号")
private String apiSerialNo;
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.log;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
......@@ -62,4 +63,21 @@ public class SmsLogPageReqVO extends PageParam {
private Integer num;
/**
* 短信节点表id
*/
@ApiModelProperty(value = "短信节点表id")
private Long nodeId;
/**
* 节点模板序列号
*/
@ApiModelProperty(value = "节点模板序列号")
private Integer nodeTemplateSn;
/**
* 发送类型
*/
@ApiModelProperty(value = "发送类型")
private Integer messageType;
}
......@@ -97,4 +97,22 @@ public class SmsLogRespVO {
@ApiModelProperty(value = "重发次数")
private Integer num;
/**
* 短信节点表id
*/
@ApiModelProperty(value = "短信节点表id")
private Long nodeId;
/**
* 节点模板序列号
*/
@ApiModelProperty(value = "节点模板序列号")
private Integer nodeTemplateSn;
/**
* 发送类型
*/
@ApiModelProperty(value = "发送类型")
private Integer messageType;
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import com.alibaba.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.convert.DictConvert;
import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 短信节点 Response VO
*
* @author jayden
*/
@Data
@ApiModel("管理后台 - 短信节点 Response VO")
public class SmsNodeBackVO {
@ExcelProperty("编号")
@ApiModelProperty(value = "编号", required = true)
private Long id;
@ExcelProperty("节点")
@ApiModelProperty(value = "节点", required = true)
private String nodeValue;
@ExcelProperty(value = "运输方式", converter = DictConvert.class)
@DictFormat("transport_type") // TODO 代码优化:建议设置到对应的 XXXDictTypeConstants 枚举类中
@ApiModelProperty(value = "运输方式", required = true)
private Long transportId;
@ExcelProperty("国家区号")
@ApiModelProperty(value = "国家区号", required = true)
private Long countryId;
@ExcelProperty("国家区号")
@ApiModelProperty(value = "国家区号,和订单中国家区号保持一致", required = true)
private String countryCode;
@ExcelProperty("启用状态")
@ApiModelProperty(value = "启用状态(0:启用,1:关闭)", required = true)
private Integer status;
@ExcelProperty("模板1")
@ApiModelProperty(value = "模板1", required = true)
private Long templateIdOne;
@ExcelProperty("模板2")
@ApiModelProperty(value = "模板2")
private Long templateIdTwo;
@ExcelProperty("模板3")
@ApiModelProperty(value = "模板3")
private Long templateIdThree;
@ExcelProperty("模板4")
@ApiModelProperty(value = "模板4")
private Long templateIdFour;
@ExcelProperty("创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
@ExcelProperty("是否匹配运输方式")
@ApiModelProperty(value = "是否匹配运输方式", required = true)
private Integer isTransport;
@ExcelProperty("多订单")
@ApiModelProperty(value = "多订单", required = true)
private Integer isOrders;
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
/**
* 短信节点 Base VO,提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class SmsNodeBaseVO {
@ApiModelProperty(value = "节点", required = true)
@NotNull(message = "节点不能为空")
private String nodeValue;
@ApiModelProperty(value = "运输方式ID", required = true)
@NotNull(message = "运输方式ID不能为空")
private Long transportId;
@ApiModelProperty(value = "国家区号id", required = true)
@NotNull(message = "国家区号id不能为空")
private Long countryId;
@ApiModelProperty(value = "国家区号,和订单中国家区号保持一致", required = true)
@NotNull(message = "国家区号,和订单中国家区号保持一致不能为空")
private String countryCode;
@ApiModelProperty(value = "启用状态(0:启用,1:关闭)", required = true)
@NotNull(message = "启用状态(0:启用,1:关闭)不能为空")
private Integer status;
@ApiModelProperty(value = "模板1", required = true)
@NotNull(message = "模板1不能为空")
private Long templateIdOne;
@ApiModelProperty(value = "模板2")
private Long templateIdTwo;
@ApiModelProperty(value = "模板3")
private Long templateIdThree;
@ApiModelProperty(value = "模板4")
private Long templateIdFour;
@ApiModelProperty(value = "是否匹配运输方式", required = true)
private Integer isTransport;
@ApiModelProperty(value = "多订单", required = true)
private Integer isOrders;
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@ApiModel("管理后台 - 短信节点创建 Request VO")
public class SmsNodeCreateReqVO extends SmsNodeBaseVO {
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Data
@ApiModel("管理后台 - 短信节点查询 VO")
public class SmsNodeQueryVO {
@ApiModelProperty(value = "节点")
private String nodeValue;
@ApiModelProperty(value = "运输方式ID")
private Long transportId;
@ApiModelProperty(value = "国家区号id")
private Long countryId;
@ApiModelProperty(value = "国家区号,和订单中国家区号保持一致")
private String countryCode;
@ApiModelProperty(value = "启用状态(0:启用,1:关闭)")
private Integer status;
@ApiModelProperty(value = "模板1")
private Long templateIdOne;
@ApiModelProperty(value = "模板2")
private Long templateIdTwo;
@ApiModelProperty(value = "模板3")
private Long templateIdThree;
@ApiModelProperty(value = "模板4")
private Long templateIdFour;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "开始创建时间")
private Date beginCreateTime;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "结束创建时间")
private Date endCreateTime;
@ApiModelProperty(value = "是否匹配运输方式", required = true)
private Integer isTransport;
@ApiModelProperty(value = "多订单", required = true)
private Integer isOrders;
}
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
@ApiModel("管理后台 - 短信节点更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SmsNodeUpdateReqVO extends SmsNodeBaseVO {
@ApiModelProperty(value = "编号", required = true)
@NotNull(message = "编号不能为空")
private Long id;
}
......@@ -6,9 +6,9 @@ import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 短信模板 Base VO,提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
* 短信模板 Base VO,提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class SmsTemplateBaseVO {
......@@ -43,11 +43,25 @@ public class SmsTemplateBaseVO {
@NotNull(message = "短信渠道编号不能为空")
private Long channelId;
@ApiModelProperty(value = "短信 API 的英文模板编号", required = true, example = "4383920")
@NotNull(message = "短信 API 的英文模板编号不能为空")
private String apiTemplateIdEn;
// @ApiModelProperty(value = "短信 API 的英文模板编号", required = true, example = "4383920")
// @NotNull(message = "短信 API 的英文模板编号不能为空")
// private String apiTemplateIdEn;
//
// @ApiModelProperty(value = "英文模板内容", required = true, example = "你好,{name}。你长的太{like}啦!")
// @NotNull(message = "英文模板内容不能为空")
// private String contentEn;
@ApiModelProperty(value = "英文模板内容", required = true, example = "你好,{name}。你长的太{like}啦!")
@NotNull(message = "英文模板内容不能为空")
private String contentEn;
@ApiModelProperty(value = "节点", required = true)
@NotNull(message = "节点不能为空")
private String nodeValue;
@ApiModelProperty(value = "运输方式", required = true)
@NotNull(message = "运输方式不能为空")
private Long transportId;
@ApiModelProperty(value = "发送类型", required = true)
private Integer messageType;
@ApiModelProperty(value = "语言")
private String language;
}
......@@ -52,9 +52,21 @@ public class SmsTemplateExcelVO {
@ExcelProperty("创建时间")
private Date createTime;
@ExcelProperty("英文模板内容")
private String contentEn;
// @ExcelProperty("英文模板内容")
// private String contentEn;
//
// @ExcelProperty("短信 API 的英文模板编号")
// private String apiTemplateIdEn;
@ExcelProperty("短信 API 的英文模板编号")
private String apiTemplateIdEn;
@ExcelProperty(value = "节点")
private String nodeValue;
@ExcelProperty(value = "运输方式")
private Long transportId;
@ExcelProperty(value = "发送类型")
private Integer messageType;
@ExcelProperty(value = "语言")
private String language;
}
......@@ -44,4 +44,16 @@ public class SmsTemplateExportReqVO {
@ApiModelProperty(value = "短信 API 的英文模板编号", example = "4383920", notes = "模糊匹配")
private String apiTemplateIdEn;
@ApiModelProperty(value = "节点")
private String nodeValue;
@ApiModelProperty(value = "运输方式")
private Long transportId;
@ApiModelProperty(value = "发送类型")
private Integer messageType;
@ApiModelProperty(value = "语言")
private String language;
}
......@@ -49,4 +49,16 @@ public class SmsTemplatePageReqVO extends PageParam {
@ApiModelProperty(value = "短信 API 的英文模板编号", example = "4383920", notes = "模糊匹配")
private String apiTemplateIdEn;
@ApiModelProperty(value = "节点")
private String nodeValue;
@ApiModelProperty(value = "运输方式")
private Long transportId;
@ApiModelProperty(value = "发送类型")
private Integer messageType;
@ApiModelProperty(value = "语言")
private String language;
}
package cn.iocoder.yudao.module.system.convert.sms;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsLogDTO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExcelVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
......@@ -21,6 +22,8 @@ public interface SmsLogConvert {
SmsLogRespVO convert(SmsLogDO bean);
SmsLogDTO toDto(SmsLogDO smsLogDO);
List<SmsLogRespVO> convertList(List<SmsLogDO> list);
PageResult<SmsLogRespVO> convertPage(PageResult<SmsLogDO> page);
......
package cn.iocoder.yudao.module.system.convert.sms;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeBackVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeUpdateReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 短信节点 Convert
* @author jayden
*/
@Mapper
public interface SmsNodeConvert {
/*****转换MapStruct*****/
SmsNodeConvert INSTANCE = Mappers.getMapper(SmsNodeConvert.class);
/***
* 创建VO转实体
* @param bean
* @return
*/
SmsNodeDO convert(SmsNodeCreateReqVO bean);
/***
* 修改VO转实体
* @param bean
* @return
*/
SmsNodeDO convert(SmsNodeUpdateReqVO bean);
/***
* 实体转返回VO
* @param bean
* @return
*/
SmsNodeBackVO convert(SmsNodeDO bean);
/***
* 实体列表转返回VO列表
* @param list
* @return
*/
List<SmsNodeBackVO> convertList(List<SmsNodeDO> list);
/***
* 实体分页转返回分页
* @param page
* @return
*/
PageResult<SmsNodeBackVO> convertPage(PageResult<SmsNodeDO> page);
}
package cn.iocoder.yudao.module.system.convert.sms;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateDTO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExcelVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateRespVO;
......@@ -20,6 +21,8 @@ public interface SmsTemplateConvert {
SmsTemplateDO convert(SmsTemplateUpdateReqVO bean);
SmsTemplateDTO toDto(SmsTemplateDO smsTemplateDO);
SmsTemplateRespVO convert(SmsTemplateDO bean);
List<SmsTemplateRespVO> convertList(List<SmsTemplateDO> list);
......
......@@ -34,11 +34,10 @@ public class SmsCodeDO extends BaseDO {
*/
private String code;
/**
* 发送场景
* 业务节点
*
* 枚举 {@link SmsCodeDO}
*/
private Integer scene;
private String nodeValue;
/**
* 创建 IP
*/
......
......@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import java.util.Date;
......@@ -37,13 +38,13 @@ public class SmsLogDO extends BaseDO {
/**
* 短信渠道编号
*
* <p>
* 关联 {@link SmsChannelDO#getId()}
*/
private Long channelId;
/**
* 短信渠道编码
*
* <p>
* 冗余 {@link SmsChannelDO#getCode()}
*/
private String channelCode;
......@@ -52,19 +53,19 @@ public class SmsLogDO extends BaseDO {
/**
* 模板编号
*
* <p>
* 关联 {@link SmsTemplateDO#getId()}
*/
private Long templateId;
/**
* 模板编码
*
* <p>
* 冗余 {@link SmsTemplateDO#getCode()}
*/
private String templateCode;
/**
* 短信类型
*
* <p>
* 冗余 {@link SmsTemplateDO#getType()}
*/
private Integer templateType;
......@@ -79,7 +80,7 @@ public class SmsLogDO extends BaseDO {
private Map<String, Object> templateParams;
/**
* 短信 API 的模板编号
*
* <p>
* 冗余 {@link SmsTemplateDO#getApiTemplateId()}
*/
private String apiTemplateId;
......@@ -96,7 +97,7 @@ public class SmsLogDO extends BaseDO {
private Long userId;
/**
* 用户类型
*
* <p>
* 枚举 {@link UserTypeEnum}
*/
private Integer userType;
......@@ -105,7 +106,7 @@ public class SmsLogDO extends BaseDO {
/**
* 发送状态
*
* <p>
* 枚举 {@link SmsSendStatusEnum}
*/
private Integer sendStatus;
......@@ -115,20 +116,20 @@ public class SmsLogDO extends BaseDO {
private Date sendTime;
/**
* 发送结果的编码
*
* <p>
* 枚举 {@link SmsFrameworkErrorCodeConstants}
*/
private Integer sendCode;
/**
* 发送结果的提示
*
* <p>
* 一般情况下,使用 {@link SmsFrameworkErrorCodeConstants}
* 异常情况下,通过格式化 Exception 的提示存储
*/
private String sendMsg;
/**
* 短信 API 发送结果的编码
*
* <p>
* 由于第三方的错误码可能是字符串,所以使用 String 类型
*/
private String apiSendCode;
......@@ -138,13 +139,13 @@ public class SmsLogDO extends BaseDO {
private String apiSendMsg;
/**
* 短信 API 发送返回的唯一请求 ID
*
* <p>
* 用于和短信 API 进行定位于排错
*/
private String apiRequestId;
/**
* 短信 API 发送返回的序号
*
* <p>
* 用于和短信 API 平台的发送记录关联
*/
private String apiSerialNo;
......@@ -153,7 +154,7 @@ public class SmsLogDO extends BaseDO {
/**
* 接收状态
*
* <p>
* 枚举 {@link SmsReceiveStatusEnum}
*/
private Integer receiveStatus;
......@@ -179,5 +180,19 @@ public class SmsLogDO extends BaseDO {
*/
private Integer num;
/**
* 短信节点表id
*/
private Long nodeId;
/**
* 节点模板序列号
*/
private Integer nodeTemplateSn;
/**
* 发送类型
*/
private Integer messageType;
}
package cn.iocoder.yudao.module.system.dal.dataobject.sms;
import lombok.*;
import java.util.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 短信节点 DO
*
* @author jayden
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("system_sms_node")
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SmsNodeDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 节点
*/
private String nodeValue;
/**
* 运输方式ID
*
*/
private Integer transportId;
/**
* 国家区号id
*/
private Long countryId;
/**
* 国家区号,和订单中国家区号保持一致
*/
private String countryCode;
/**
* 启用状态(0:启用,1:关闭)
*/
private Integer status;
/**
* 模板1
*/
private Long templateIdOne;
/**
* 模板2
*/
@TableField(updateStrategy = FieldStrategy.IGNORED)
private Long templateIdTwo;
/**
* 模板3
*/
@TableField(updateStrategy = FieldStrategy.IGNORED)
private Long templateIdThree;
/**
* 模板4
*/
@TableField(updateStrategy = FieldStrategy.IGNORED)
private Long templateIdFour;
/**
* 是否匹配运输方式(0:否,1:是)
*/
private Integer isTransport;
/**
* 多订单(0:否,1:是)
*/
private Integer isOrders;
}
......@@ -95,5 +95,20 @@ public class SmsTemplateDO extends BaseDO {
* 短信 API 的英文模板编号
*/
private String apiTemplateIdEn;
/**
* 节点
*/
private String nodeValue;
/**
* 运输方式
*/
private Long transportId;
/**
* 发送类型
*/
private Integer messageType;
/**
* 语言
*/
private String language;
}
......@@ -16,13 +16,13 @@ public interface SmsCodeMapper extends BaseMapperX<SmsCodeDO> {
* 获得手机号的最后一个手机验证码
*
* @param mobile 手机号
* @param scene 发送场景,选填
* @param nodeValue 发送场景,选填
* @param code 验证码 选填
* @return 手机验证码
*/
default SmsCodeDO selectLastByMobile(String mobile, String code, Integer scene) {
default SmsCodeDO selectLastByMobile(String mobile, String code, String nodeValue) {
return selectOne(new QueryWrapperX<SmsCodeDO>()
.eq("mobile", mobile).eqIfPresent("scene", scene).eqIfPresent("code", code)
.eq("mobile", mobile).eqIfPresent("node_value", nodeValue).eqIfPresent("code", code)
.between("create_time", DateUtil.beginOfDay(new Date()), DateUtil.endOfDay(new Date()))
.orderByDesc("id").last(SqlConstants.LIMIT1));
}
......
package cn.iocoder.yudao.module.system.dal.mysql.sms;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
......@@ -18,6 +19,8 @@ public interface SmsLogMapper extends BaseMapperX<SmsLogDO> {
return selectPage(reqVO, new QueryWrapperX<SmsLogDO>()
.eqIfPresent("channel_id", reqVO.getChannelId())
.eqIfPresent("template_id", reqVO.getTemplateId())
.eqIfPresent("node_id", reqVO.getNodeId())
.eqIfPresent("node_template_sn", reqVO.getNodeTemplateSn())
.likeIfPresent("mobile", reqVO.getMobile())
.eqIfPresent("send_status", reqVO.getSendStatus())
.betweenIfPresent("send_time", reqVO.getBeginSendTime(), reqVO.getEndSendTime())
......@@ -30,11 +33,14 @@ public interface SmsLogMapper extends BaseMapperX<SmsLogDO> {
return selectList(new QueryWrapperX<SmsLogDO>()
.eqIfPresent("channel_id", reqVO.getChannelId())
.eqIfPresent("template_id", reqVO.getTemplateId())
.eqIfPresent("node_id", reqVO.getNodeId())
.eqIfPresent("node_template_sn", reqVO.getNodeTemplateSn())
.likeIfPresent("mobile", reqVO.getMobile())
.eqIfPresent("send_status", reqVO.getSendStatus())
.betweenIfPresent("send_time", reqVO.getBeginSendTime(), reqVO.getEndSendTime())
.eqIfPresent("receive_status", reqVO.getReceiveStatus())
.betweenIfPresent("receive_time", reqVO.getBeginReceiveTime(), reqVO.getEndReceiveTime())
.eq(StringUtils.isNotBlank(reqVO.getApiSerialNo()),"api_serial_no",reqVO.getApiSerialNo())
.orderByDesc("id"));
}
......@@ -42,4 +48,11 @@ public interface SmsLogMapper extends BaseMapperX<SmsLogDO> {
"DELETE FROM system_sms_log WHERE date(create_time) <= date(date_sub(now(), interval 90 day))"
})
int clearLog(Integer day);
default List<SmsLogDO> selectReceiveList() {
return selectList(new QueryWrapperX<SmsLogDO>()
.apply("create_time >= NOW() - INTERVAL 20 HOUR")
.eq("template_type", 2)
.lt("num", 1)
.ne("receive_status", 10));
}
}
package cn.iocoder.yudao.module.system.dal.mysql.sms;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.AbstractMapper;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQuery;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeQueryVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 短信节点 Mapper
*
* @author jayden
*/
@Mapper
public interface SmsNodeMapper extends AbstractMapper<SmsNodeDO> {
@Override
default PageResult<SmsNodeDO> selectPage(PageVO page, Object object) {
if (object instanceof SmsNodeQueryVO) {
SmsNodeQueryVO vo = (SmsNodeQueryVO) object;
return selectPage(page, new LambdaQuery<SmsNodeDO>()
.eq(StringUtils.isNotBlank(vo.getNodeValue()), SmsNodeDO::getNodeValue, vo.getNodeValue())
.eqIfPresent(SmsNodeDO::getTransportId, vo.getTransportId())
.eqIfPresent(SmsNodeDO::getCountryId, vo.getCountryId())
.eqIfPresent(SmsNodeDO::getCountryCode, vo.getCountryCode())
.eqIfPresent(SmsNodeDO::getIsTransport, vo.getIsTransport())
.eqIfPresent(SmsNodeDO::getIsOrders, vo.getIsOrders())
.eqIfPresent(SmsNodeDO::getStatus, vo.getStatus())
.eqIfPresent(SmsNodeDO::getTemplateIdOne, vo.getTemplateIdOne())
.eqIfPresent(SmsNodeDO::getTemplateIdTwo, vo.getTemplateIdTwo())
.eqIfPresent(SmsNodeDO::getTemplateIdThree, vo.getTemplateIdThree())
.eqIfPresent(SmsNodeDO::getTemplateIdFour, vo.getTemplateIdFour())
.betweenIfPresent(SmsNodeDO::getCreateTime, vo.getBeginCreateTime(), vo.getEndCreateTime())
.orderByDesc(SmsNodeDO::getId));
}
return null;
}
@Override
default List<SmsNodeDO> selectList(Object object) {
if (object instanceof SmsNodeQueryVO) {
SmsNodeQueryVO vo = (SmsNodeQueryVO) object;
return selectList(new LambdaQuery<SmsNodeDO>()
.eq(StringUtils.isNotBlank(vo.getNodeValue()), SmsNodeDO::getNodeValue, vo.getNodeValue())
.eqIfPresent(SmsNodeDO::getTransportId, vo.getTransportId())
.eqIfPresent(SmsNodeDO::getCountryId, vo.getCountryId())
.eqIfPresent(SmsNodeDO::getCountryCode, vo.getCountryCode())
.eqIfPresent(SmsNodeDO::getIsTransport, vo.getIsTransport())
.eqIfPresent(SmsNodeDO::getIsOrders, vo.getIsOrders())
.eqIfPresent(SmsNodeDO::getStatus, vo.getStatus())
.eqIfPresent(SmsNodeDO::getTemplateIdOne, vo.getTemplateIdOne())
.eqIfPresent(SmsNodeDO::getTemplateIdTwo, vo.getTemplateIdTwo())
.eqIfPresent(SmsNodeDO::getTemplateIdThree, vo.getTemplateIdThree())
.eqIfPresent(SmsNodeDO::getTemplateIdFour, vo.getTemplateIdFour())
.betweenIfPresent(SmsNodeDO::getCreateTime, vo.getBeginCreateTime(), vo.getEndCreateTime())
.orderByDesc(SmsNodeDO::getId));
}
return null;
}
}
......@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
......@@ -26,6 +27,10 @@ public interface SmsTemplateMapper extends BaseMapperX<SmsTemplateDO> {
return selectPage(reqVO, new LambdaQueryWrapperX<SmsTemplateDO>()
.eqIfPresent(SmsTemplateDO::getType, reqVO.getType())
.eqIfPresent(SmsTemplateDO::getStatus, reqVO.getStatus())
.eq(StringUtils.isNotBlank(reqVO.getNodeValue()), SmsTemplateDO::getNodeValue, reqVO.getNodeValue())
.eqIfPresent(SmsTemplateDO::getTransportId, reqVO.getTransportId())
.eqIfPresent(SmsTemplateDO::getMessageType, reqVO.getMessageType())
.eqIfPresent(SmsTemplateDO::getLanguage, reqVO.getLanguage())
.likeIfPresent(SmsTemplateDO::getCode, reqVO.getCode())
.likeIfPresent(SmsTemplateDO::getContent, reqVO.getContent())
.likeIfPresent(SmsTemplateDO::getContentEn, reqVO.getContentEn())
......@@ -40,6 +45,10 @@ public interface SmsTemplateMapper extends BaseMapperX<SmsTemplateDO> {
return selectList(new LambdaQueryWrapperX<SmsTemplateDO>()
.eqIfPresent(SmsTemplateDO::getType, reqVO.getType())
.eqIfPresent(SmsTemplateDO::getStatus, reqVO.getStatus())
.eq(StringUtils.isNotBlank(reqVO.getNodeValue()), SmsTemplateDO::getNodeValue, reqVO.getNodeValue())
.eqIfPresent(SmsTemplateDO::getTransportId, reqVO.getTransportId())
.eqIfPresent(SmsTemplateDO::getMessageType, reqVO.getMessageType())
.eqIfPresent(SmsTemplateDO::getLanguage, reqVO.getLanguage())
.likeIfPresent(SmsTemplateDO::getCode, reqVO.getCode())
.likeIfPresent(SmsTemplateDO::getContent, reqVO.getContent())
.likeIfPresent(SmsTemplateDO::getApiTemplateId, reqVO.getApiTemplateId())
......
package cn.iocoder.yudao.module.system.mq.consumer.sms;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link SmsSendMessage} 的消费者
*
* @author zzf
*/
@Component
@Slf4j
public class SmsSendConsumer extends AbstractStreamMessageListener<SmsSendMessage> {
@Resource
private SmsSendService smsSendService;
@Override
public void onMessage(SmsSendMessage message) {
log.info("[onMessage][消息内容({})]", message);
smsSendService.doSendSms(message);
}
}
//package cn.iocoder.yudao.module.system.mq.consumer.sms;
//
//import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
//import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
//import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.stereotype.Component;
//
//import javax.annotation.Resource;
//
///**
// * 针对 {@link SmsSendMessage} 的消费者
// *
// * @author zzf
// */
//@Component
//@Slf4j
//public class SmsSendConsumer extends AbstractStreamMessageListener<SmsSendMessage> {
//
// @Resource
// private SmsSendService smsSendService;
//
// @Override
// public void onMessage(SmsSendMessage message) {
// log.info("[onMessage][消息内容({})]", message);
// smsSendService.doSendSms(message);
// }
//
//}
package cn.iocoder.yudao.module.system.mq.consumer.sms;
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessageV2;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link SmsSendMessage} 的消费者
*
* @author zzf
*/
@Component
@Slf4j
public class SmsSendConsumerV2 extends AbstractStreamMessageListener<SmsSendMessageV2> {
@Resource
private SmsSendService smsSendService;
@Override
public void onMessage(SmsSendMessageV2 message) {
log.info("[onMessageV2][消息内容({})]", message);
smsSendService.doSendSmsV2(message);
}
}
package cn.iocoder.yudao.module.system.mq.message.sms;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 短信发送消息
*
* @author 捷道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SmsSendMessageV2 extends AbstractStreamMessage {
/**
* 短信日志编号
*/
@NotNull(message = "短信日志编号不能为空")
private Long logId;
/**
* 手机号
*/
@NotNull(message = "手机号不能为空")
private String mobile;
/**
* 模板
*/
@NotNull(message = "模板不能为空")
private SmsTemplateDTO smsTemplateDTO;
/**
* 短信模板参数
*/
private List<KeyValue<String, Object>> templateParams;
@Override
public String getStreamKey() {
return "system.sms.send";
}
}
......@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.mq.producer.sms;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsChannelRefreshMessage;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessageV2;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsTemplateRefreshMessage;
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
import lombok.extern.slf4j.Slf4j;
......@@ -56,4 +57,7 @@ public class SmsProducer {
redisMQTemplate.send(message);
}
public void sendSmsSendMessageV2(SmsSendMessageV2 message) {
redisMQTemplate.send(message);
}
}
package cn.iocoder.yudao.module.system.service.mail.impl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeCheckReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsCodeDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsCodeMapper;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.framework.sms.SmsCodeProperties;
import cn.iocoder.yudao.module.system.service.sms.SmsCodeService;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Date;
import static cn.hutool.core.util.RandomUtil.randomInt;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
* 短信验证码 Service 实现类
*
* @author 捷道源码
*/
@Service
@Validated
public class EmailCodeServiceImpl implements EmailCodeService {
@Resource
private SmsCodeProperties smsCodeProperties;
@Resource
private SmsCodeMapper smsCodeMapper;
@Resource
private SmsSendService smsSendService;
@Override
public void sendEmailCode(SmsCodeSendReqDTO reqDTO) {
SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
// 创建验证码
String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
// 发送验证码
smsSendService.sendSingleSms(reqDTO.getAreaCode()+reqDTO.getMobile(), null, null,
sceneEnum.getTemplateCode(), MapUtil.of("code", code));
}
private String createSmsCode(String mobile, Integer scene, String ip) {
// 校验是否可以发送验证码,不用筛选场景
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null,null);
if (lastSmsCode != null) {
if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
throw ServiceExceptionUtil.exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
}
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
throw ServiceExceptionUtil.exception(SMS_CODE_SEND_TOO_FAST);
}
// TODO 芋艿:提升,每个 IP 每天可发送数量
// TODO 芋艿:提升,每个 IP 每小时可发送数量
}
// 创建验证码记录
String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code)
.scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
.createIp(ip).used(false).build();
smsCodeMapper.insert(newSmsCode);
return code;
}
@Override
public void useSmsCode(SmsCodeUseReqDTO reqDTO) {
// 检测验证码是否有效
SmsCodeDO lastSmsCode = this.checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
// 使用验证码
smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId())
.used(true).usedTime(new Date()).usedIp(reqDTO.getUsedIp()).build());
}
@Override
public void checkSmsCode(SmsCodeCheckReqDTO reqDTO) {
checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
}
public SmsCodeDO checkSmsCode0(String mobile, String code, Integer scene) {
// 校验验证码
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile,code,scene);
// 若验证码不存在,抛出异常
if (lastSmsCode == null) {
throw ServiceExceptionUtil.exception(SMS_CODE_NOT_FOUND);
}
// 超过时间
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
>= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
throw ServiceExceptionUtil.exception(SMS_CODE_EXPIRED);
}
// 判断验证码是否已被使用
if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {
throw ServiceExceptionUtil.exception(SMS_CODE_USED);
}
return lastSmsCode;
}
}
//package cn.iocoder.yudao.module.system.service.mail.impl;
//
//import cn.hutool.core.lang.Assert;
//import cn.hutool.core.map.MapUtil;
//import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
//import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeCheckReqDTO;
//import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
//import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
//import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsCodeDO;
//import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsCodeMapper;
//import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
//import cn.iocoder.yudao.module.system.framework.sms.SmsCodeProperties;
//import cn.iocoder.yudao.module.system.service.sms.SmsCodeService;
//import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
//import org.springframework.stereotype.Service;
//import org.springframework.validation.annotation.Validated;
//
//import javax.annotation.Resource;
//import java.util.Date;
//
//import static cn.hutool.core.util.RandomUtil.randomInt;
//import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
//
///**
// * 短信验证码 Service 实现类
// *
// * @author 捷道源码
// */
//@Service
//@Validated
//public class EmailCodeServiceImpl implements EmailCodeService {
//
// @Resource
// private SmsCodeProperties smsCodeProperties;
//
// @Resource
// private SmsCodeMapper smsCodeMapper;
//
// @Resource
// private SmsSendService smsSendService;
//
// @Override
// public void sendEmailCode(SmsCodeSendReqDTO reqDTO) {
// SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
// Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
// // 创建验证码
// String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
// // 发送验证码
// smsSendService.sendSingleSms(reqDTO.getAreaCode()+reqDTO.getMobile(), null, null,
// sceneEnum.getTemplateCode(), MapUtil.of("code", code));
// }
//
// private String createSmsCode(String mobile, Integer scene, String ip) {
// // 校验是否可以发送验证码,不用筛选场景
// SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null,null);
// if (lastSmsCode != null) {
// if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
// throw ServiceExceptionUtil.exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
// }
// if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
// < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
// throw ServiceExceptionUtil.exception(SMS_CODE_SEND_TOO_FAST);
// }
// // TODO 芋艿:提升,每个 IP 每天可发送数量
// // TODO 芋艿:提升,每个 IP 每小时可发送数量
// }
//
// // 创建验证码记录
// String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
// SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code)
// .scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
// .createIp(ip).used(false).build();
// smsCodeMapper.insert(newSmsCode);
// return code;
// }
//
// @Override
// public void useSmsCode(SmsCodeUseReqDTO reqDTO) {
// // 检测验证码是否有效
// SmsCodeDO lastSmsCode = this.checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
// // 使用验证码
// smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId())
// .used(true).usedTime(new Date()).usedIp(reqDTO.getUsedIp()).build());
// }
//
// @Override
// public void checkSmsCode(SmsCodeCheckReqDTO reqDTO) {
// checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
// }
//
// public SmsCodeDO checkSmsCode0(String mobile, String code, Integer scene) {
// // 校验验证码
// SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile,code,scene);
// // 若验证码不存在,抛出异常
// if (lastSmsCode == null) {
// throw ServiceExceptionUtil.exception(SMS_CODE_NOT_FOUND);
// }
// // 超过时间
// if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
// >= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
// throw ServiceExceptionUtil.exception(SMS_CODE_EXPIRED);
// }
// // 判断验证码是否已被使用
// if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {
// throw ServiceExceptionUtil.exception(SMS_CODE_USED);
// }
// return lastSmsCode;
// }
//
//}
package cn.iocoder.yudao.module.system.service.sms;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* @author Jayden
* @date 2024/11/12
*/
public class QuerySendDetails {
/**
* <b>description</b> :
* <p>使用AK&amp;SK初始化账号Client</p>
*
* @return Client
* @throws Exception
*/
public static IAcsClient createClient() throws Exception {
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
// 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html。
String key = "LTAIh5ooNeMRzsuz";
String secret = "jdqgtCP2Z0nZllEcxMiX8pv8qUDOTq";
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", key, secret);
IAcsClient defaultAcsClient = new DefaultAcsClient(profile);
return defaultAcsClient;
}
public static void main(String[] args_) throws Exception {
String key = "LTAIh5ooNeMRzsuz";
String secret = "jdqgtCP2Z0nZllEcxMiX8pv8qUDOTq";
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", key, secret);
IAcsClient client = new DefaultAcsClient(profile);
QuerySendDetailsRequest querySendDetailsRequest = new QuerySendDetailsRequest();
querySendDetailsRequest.setPhoneNumber("233591972886");
querySendDetailsRequest.setPageSize(1L);
querySendDetailsRequest.setCurrentPage(1L);
querySendDetailsRequest.setSendDate("20240905");
querySendDetailsRequest.setBizId("373707825513736449^0");
QuerySendDetailsResponse acsResponse = client.getAcsResponse(querySendDetailsRequest);
List<QuerySendDetailsResponse.SmsSendDetailDTO> smsSendDetailDTOs = acsResponse.getSmsSendDetailDTOs();
String receiveDate = smsSendDetailDTOs.get(0).getReceiveDate();
System.out.println(convertDateFormat(receiveDate));
}
/**
* 将日期字符串从 "yyyy-MM-dd HH:mm:ss" 格式转换为 "yyyyMMdd" 格式
*
* @param inputDateStr 输入的日期字符串,格式为 "yyyy-MM-dd HH:mm:ss"
* @return 转换后的日期字符串,格式为 "yyyyMMdd"
*/
public static String convertDateFormat(String inputDateStr) {
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat outputFormat = new SimpleDateFormat("yyyyMMdd");
try {
// 解析输入的日期字符串
Date date = inputFormat.parse(inputDateStr);
// 格式化日期为输出格式
return outputFormat.format(date);
} catch (ParseException e) {
// 处理解析异常
e.printStackTrace();
return null;
}
}
}
package cn.iocoder.yudao.module.system.service.sms;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.web.config.BusinessProperties;
......@@ -9,14 +8,12 @@ import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsCodeDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsCodeMapper;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
import cn.iocoder.yudao.module.system.framework.sms.SmsCodeProperties;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Objects;
import static cn.hutool.core.util.RandomUtil.randomInt;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
......@@ -44,38 +41,38 @@ public class SmsCodeServiceImpl implements SmsCodeService {
@Override
public void sendSmsCode(SmsCodeSendReqDTO reqDTO) {
SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
// if (Objects.nonNull(businessProperties) && businessProperties.isDebug()){
// SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
// Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
// if (Objects.nonNull(businessProperties) && businessProperties.isDebug()) {
// // TODO debug业务模式下不需要生成发送验证码,固定使用9999
// return;
// }
// 创建验证码
String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
String code = createSmsCode(reqDTO.getMobile(), reqDTO.getNodeValue(), reqDTO.getCreateIp());
// 发送验证码
smsSendService.sendSingleSms(reqDTO.getAreaCode() + reqDTO.getMobile(), null, null,
sceneEnum.getTemplateCode(), MapUtil.of("code", code));
smsSendService.sendSingleSmsV2(reqDTO.getAreaCode() + reqDTO.getMobile(), null, null, reqDTO.getNodeValue(), reqDTO.getAreaCode(), reqDTO.getIsOrders(), reqDTO.getIsTransport(),
reqDTO.getTransportId(), reqDTO.getMessageType(), MapUtil.of("code", code), null, 1);
}
private String createSmsCode(String mobile, Integer scene, String ip) {
private String createSmsCode(String mobile, String nodeValue, String ip) {
// 校验是否可以发送验证码,不用筛选场景
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null, null);
if (lastSmsCode != null) {
if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
throw ServiceExceptionUtil.exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
}
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
throw ServiceExceptionUtil.exception(SMS_CODE_SEND_TOO_FAST);
}
// TODO 芋艿:提升,每个 IP 每天可发送数量
// TODO 芋艿:提升,每个 IP 每小时可发送数量
}
// if (lastSmsCode != null) {
// if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
// throw ServiceExceptionUtil.exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
// }
// if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
// < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
// throw ServiceExceptionUtil.exception(SMS_CODE_SEND_TOO_FAST);
// }
// // TODO 芋艿:提升,每个 IP 每天可发送数量
// // TODO 芋艿:提升,每个 IP 每小时可发送数量
// }
// 创建验证码记录
String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code)
.scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
.nodeValue(nodeValue).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
.createIp(ip).used(false).build();
smsCodeMapper.insert(newSmsCode);
return code;
......@@ -84,20 +81,20 @@ public class SmsCodeServiceImpl implements SmsCodeService {
@Override
public void useSmsCode(SmsCodeUseReqDTO reqDTO) {
if (Objects.equals(reqDTO.getCode(), "9999")) {
return;
}
if (Objects.nonNull(businessProperties) && businessProperties.isDebug()) {
// TODO debug业务模式下不需要生成发送验证码,固定使用9999
if (Objects.equals(reqDTO.getCode(), "9999")) {
return;
} else {
throw ServiceExceptionUtil.exception(SMS_CODE_NOT_FOUND);
}
}
// if (Objects.equals(reqDTO.getCode(), "9999")) {
// return;
// }
//
// if (Objects.nonNull(businessProperties) && businessProperties.isDebug()) {
// // TODO debug业务模式下不需要生成发送验证码,固定使用9999
// if (Objects.equals(reqDTO.getCode(), "9999")) {
// return;
// } else {
// throw ServiceExceptionUtil.exception(SMS_CODE_NOT_FOUND);
// }
// }
// 检测验证码是否有效
SmsCodeDO lastSmsCode = this.checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
SmsCodeDO lastSmsCode = this.checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getNodeValue());
// 使用验证码
smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId())
.used(true).usedTime(new Date()).usedIp(reqDTO.getUsedIp()).build());
......@@ -105,12 +102,12 @@ public class SmsCodeServiceImpl implements SmsCodeService {
@Override
public void checkSmsCode(SmsCodeCheckReqDTO reqDTO) {
checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
checkSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getNodeValue());
}
public SmsCodeDO checkSmsCode0(String mobile, String code, Integer scene) {
public SmsCodeDO checkSmsCode0(String mobile, String code, String nodeValue) {
// 校验验证码
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, code, scene);
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, code, nodeValue);
// 若验证码不存在,抛出异常
if (lastSmsCode == null) {
throw ServiceExceptionUtil.exception(SMS_CODE_NOT_FOUND);
......
package cn.iocoder.yudao.module.system.service.sms;
import cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp.SendchampSmsClient;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
......@@ -36,6 +37,27 @@ public interface SmsLogService {
Long createSmsLog(String mobile, Long userId, Integer userType, Boolean isSend,
SmsTemplateDO template, String templateContent, Map<String, Object> templateParams, String apiTemplateId, Long smsLogId);
/**
* 创建短信日志
*
* @param mobile 手机号
* @param userId 用户编号
* @param userType 用户类型
* @param isSend 是否发送
* @param template 短信模板
* @param templateContent 短信内容
* @param templateParams 短信参数
* @param apiTemplateId 短信 API 的模板编号
* @param smsLogId 重发短信时的原短信日志id
* @param nodeId 节点id
* @param nodeTemplateSn 节点模板序列号
* @param messageType 发送类型
* @return 发送日志编号
*/
Long createSmsLogV2(String mobile, Long userId, Integer userType, Boolean isSend,
SmsTemplateDO template, String templateContent, Map<String, Object> templateParams, String apiTemplateId, Long smsLogId,
Long nodeId, Integer nodeTemplateSn, Integer messageType);
/**
* 更新日志的发送结果
*
......@@ -90,4 +112,11 @@ public interface SmsLogService {
void updateBatchSmsLog(List<SmsLogDO> resendSmsLogList);
List<SmsLogDO> selectReceiveList();
/**
* 更新sendchamp回调状态
* @param smsReceiveStatus
*/
void updateSendchampReceive(SendchampSmsClient.SmsReceiveStatus smsReceiveStatus);
}
package cn.iocoder.yudao.module.system.service.sms;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.sms.core.client.impl.sendchamp.SendchampSmsClient;
import cn.iocoder.yudao.framework.sms.core.enums.ReceiveStatusEnum;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
......@@ -55,6 +57,57 @@ public class SmsLogServiceImpl implements SmsLogService {
return logDO.getId();
}
/**
* 创建短信日志
*
* @param mobile 手机号
* @param userId 用户编号
* @param userType 用户类型
* @param isSend 是否发送
* @param template 短信模板
* @param templateContent 短信内容
* @param templateParams 短信参数
* @param apiTemplateId 短信 API 的模板编号
* @param smsLogId 重发短信时的原短信日志id
* @param nodeId 节点id
* @param nodeTemplateSn 节点模板序列号
* @param messageType 发送类型
* @return 发送日志编号
*/
@Override
public Long createSmsLogV2(String mobile, Long userId, Integer userType, Boolean isSend,
SmsTemplateDO template, String templateContent, Map<String, Object> templateParams, String apiTemplateId, Long smsLogId,
Long nodeId, Integer nodeTemplateSn, Integer messageType) {
SmsLogDO.SmsLogDOBuilder logBuilder = SmsLogDO.builder();
// 根据是否要发送,设置状态
logBuilder.sendStatus(Objects.equals(isSend, true) ? SmsSendStatusEnum.INIT.getStatus()
: SmsSendStatusEnum.IGNORE.getStatus());
// 设置手机相关字段
logBuilder.mobile(mobile).userId(userId).userType(userType);
// 设置模板相关字段
logBuilder.templateId(template.getId()).templateCode(template.getCode()).templateType(template.getType());
logBuilder.templateContent(templateContent).templateParams(templateParams)
.apiTemplateId(apiTemplateId);
// 设置渠道相关字段
logBuilder.channelId(template.getChannelId()).channelCode(template.getChannelCode());
// 设置接收相关字段
logBuilder.receiveStatus(SmsReceiveStatusEnum.INIT.getStatus());
// 重发短信的原短信日志id
logBuilder.smsLogId(smsLogId);
// 默认重发次数是0
logBuilder.num(0);
// 节点id
logBuilder.nodeId(nodeId);
// 节点模板序列号
logBuilder.nodeTemplateSn(nodeTemplateSn);
// 默认重发次数是0
logBuilder.messageType(messageType);
// 发送类型
SmsLogDO logDO = logBuilder.build();
smsLogMapper.insert(logDO);
return logDO.getId();
}
@Override
public void updateSmsSendResult(Long id, Integer sendCode, String sendMsg,
String apiSendCode, String apiSendMsg,
......@@ -100,4 +153,22 @@ public class SmsLogServiceImpl implements SmsLogService {
public void updateBatchSmsLog(List<SmsLogDO> resendSmsLogList) {
smsLogMapper.updateBatch(resendSmsLogList);
}
@Override
public List<SmsLogDO> selectReceiveList() {
return smsLogMapper.selectReceiveList();
}
@Override
public void updateSendchampReceive(SendchampSmsClient.SmsReceiveStatus smsReceiveStatus) {
if ("delivered".equals(smsReceiveStatus.getStatus())) {
List<SmsLogDO> smsLogDOS = smsLogMapper.selectList(new SmsLogExportReqVO().setApiSerialNo(smsReceiveStatus.getReference()));
if (smsLogDOS != null) {
SmsLogDO smsLogDO = smsLogDOS.get(0);
smsLogDO.setReceiveStatus(ReceiveStatusEnum.Receive_STATUS_10.getValue());
smsLogDO.setReceiveTime(new Date());
smsLogMapper.updateById(smsLogDO);
}
}
}
}
package cn.iocoder.yudao.module.system.service.sms;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.mybatis.core.service.IService;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.*;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
/**
* 短信节点 Service 接口
*
* @author jayden
*/
public interface SmsNodeService extends IService<SmsNodeDO> {
/**
* 创建短信节点
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createSmsNode(@Valid SmsNodeCreateReqVO createReqVO);
/**
* 更新短信节点
*
* @param updateReqVO 更新信息
*/
void updateSmsNode(@Valid SmsNodeUpdateReqVO updateReqVO);
/**
* 删除短信节点
*
* @param id 编号
*/
void deleteSmsNode(Long id);
/**
* 获得短信节点
*
* @param id 编号
* @return 短信节点
*/
SmsNodeDO getSmsNode(Long id);
/**
* 获得短信节点列表
*
* @param ids 编号
* @return 短信节点列表
*/
List<SmsNodeDO> getSmsNodeList(Collection<Long> ids);
/**
* 获得短信节点分页
*
* @param page 分页查询
* @param query 查询
* @return 短信节点分页
*/
PageResult<SmsNodeDO> getSmsNodePage(SmsNodeQueryVO query, PageVO page);
/**
* 获得短信节点列表, 用于 Excel 导出
*
* @param query 查询
* @return 短信节点列表
*/
List<SmsNodeDO> getSmsNodeList(SmsNodeQueryVO query);
/**
* 构建缓存键
* 根据SmsNodeDO对象的属性生成一个唯一的缓存键
* 这是为了确保每个配置项在缓存中都有一个唯一的标识
*
* @param smsNodeDO SmsNodeDO 对象
* @return 缓存键
*/
String buildCacheKey(SmsNodeDO smsNodeDO);
}
package cn.iocoder.yudao.module.system.service.sms;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.service.AbstractService;
import cn.iocoder.yudao.framework.mybatis.core.vo.PageVO;
import cn.iocoder.yudao.framework.redis.helper.RedisHelper;
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsLogDTO;
import cn.iocoder.yudao.framework.sms.core.enums.ReceiveStatusEnum;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeQueryVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.smsNode.SmsNodeUpdateReqVO;
import cn.iocoder.yudao.module.system.convert.sms.SmsLogConvert;
import cn.iocoder.yudao.module.system.convert.sms.SmsNodeConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsNodeMapper;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.apollo.core.constants.CacheConstants.SYSTEM_SMS_NODE_KEY;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_NODE_NOT_EXISTS;
/**
* 短信节点 Service 实现类
*
* @author jayden
*/
@Slf4j
@Service
@Validated
public class SmsNodeServiceImpl extends AbstractService<SmsNodeMapper, SmsNodeDO> implements SmsNodeService {
@Resource
private SmsNodeMapper smsNodeMapper;
@Autowired
private RedisHelper redisHelper;
@Autowired
private SmsLogService smsLogService;
@Autowired
private SmsClientFactory smsClientFactory;
@Autowired
private SmsSendService smsSendService;
/**
* 项目启动时,初始化参数到缓存
*/
@PostConstruct
public void init() {
loadingConfigCache();
}
/**
* 加载参数缓存数据
* 该方法从数据库中读取短信节点配置,并将它们缓存到Redis中
* 这样做是为了提高配置数据的读取效率,避免频繁访问数据库
*/
public void loadingConfigCache() {
try {
// 从数据库中选择所有短信节点数据对象
List<SmsNodeDO> smsNodeDOList = smsNodeMapper.selectList();
for (SmsNodeDO smsNodeDO : smsNodeDOList) {
// 构建缓存键
String key = buildCacheKey(smsNodeDO);
// 将对象转换为JSON字符串作为缓存值
String value = JSON.toJSONString(smsNodeDO);
// 将键值对存入Redis缓存
redisHelper.set(key, value);
redisHelper.set(SYSTEM_SMS_NODE_KEY + smsNodeDO.getId(), value);
// 记录日志信息
// log.info("Cache key: {}, value: {}", key, value);
}
} catch (Exception e) {
// 记录错误日志信息
log.error("Error loading config cache", e);
}
}
/**
* 构建缓存键
* 根据SmsNodeDO对象的属性生成一个唯一的缓存键
* 这是为了确保每个配置项在缓存中都有一个唯一的标识
*
* @param smsNodeDO SmsNodeDO 对象
* @return 缓存键
*/
@Override
public String buildCacheKey(SmsNodeDO smsNodeDO) {
// 使用格式化字符串构建缓存键,SYSTEM_SMS_NODE_KEY:短信节点:是否匹配运输方式:运输方式:是否多订单:国家代码:启用状态
return String.format("%s%s:%s:%s:%s:%s:%s",
SYSTEM_SMS_NODE_KEY,
smsNodeDO.getNodeValue(),
smsNodeDO.getIsTransport(),
smsNodeDO.getTransportId(),
smsNodeDO.getIsOrders(),
smsNodeDO.getCountryCode(),
smsNodeDO.getStatus());
}
/**
* 每半小时执行一次
* 短信重发定时任务
*/
@Scheduled(cron = "0 0/30 * * * ?")
public void scheduleReceiveRefresh() {
try {
// 获取需要重发短信的日志列表
List<SmsLogDO> smsLogs = smsLogService.selectReceiveList();
if (smsLogs != null) {
for (SmsLogDO smsLog : smsLogs) {
try {
processSmsLog(smsLog);
} catch (Exception e) {
log.error("Error processing sms log: {}", smsLog, e);
}
}
// 批量更新短信日志
smsLogService.updateBatchSmsLog(smsLogs);
}
} catch (Exception e) {
log.error("Error in scheduleReceiveRefresh", e);
}
}
/**
* 处理短信日志
*
* @param smsLog 短信日志对象
*/
private void processSmsLog(SmsLogDO smsLog) {
// 根据渠道ID获取短信客户端
SmsClient smsClient = smsClientFactory.getSmsClient(smsLog.getChannelId());
// 获取短信接收状态
SmsLogDTO receiveStatus = smsClient.getReceiveStatus(SmsLogConvert.INSTANCE.toDto(smsLog));
smsLog.setReceiveStatus(receiveStatus.getReceiveStatus());
// 接收失败,则重发
if (ReceiveStatusEnum.Receive_STATUS_20.getValue() == receiveStatus.getReceiveStatus()) {
smsLog.setReceiveTime(receiveStatus.getReceiveTime());
smsLog.setNum(1);
// 从Redis中获取短信节点信息
String node = redisHelper.get(SYSTEM_SMS_NODE_KEY + smsLog.getNodeId());
if (node == null) {
return;
}
// 解析短信节点信息
SmsNodeDO smsNodeDO = JSON.parseObject(node, SmsNodeDO.class);
// 调用短信发送服务重新发送短信
smsSendService.sendSingleSmsV2(smsLog.getMobile(), smsLog.getUserId(), smsLog.getUserType(), smsNodeDO.getNodeValue(),
smsNodeDO.getCountryCode(), smsNodeDO.getIsOrders(), smsNodeDO.getIsTransport(), smsNodeDO.getTransportId(),
smsLog.getMessageType(), smsLog.getTemplateParams(), smsLog.getId(), smsLog.getNodeTemplateSn() + 1);
}
}
@Override
public Long createSmsNode(SmsNodeCreateReqVO createReqVO) {
// 插入
SmsNodeDO smsNode = SmsNodeConvert.INSTANCE.convert(createReqVO);
smsNodeMapper.insert(smsNode);
// 设置新的缓存
redisHelper.set(buildCacheKey(smsNode), JSON.toJSONString(smsNode));
redisHelper.set(SYSTEM_SMS_NODE_KEY + smsNode.getId(), JSON.toJSONString(smsNode));
// 返回
return smsNode.getId();
}
@Override
public void updateSmsNode(SmsNodeUpdateReqVO updateReqVO) {
// 校验存在
SmsNodeDO smsNodeDO = this.validateSmsNodeExists(updateReqVO.getId());
// 更新
SmsNodeDO updateObj = SmsNodeConvert.INSTANCE.convert(updateReqVO);
smsNodeMapper.updateById(updateObj);
// 删除旧的缓存
redisHelper.delete(buildCacheKey(smsNodeDO));
// 设置新的缓存
redisHelper.set(buildCacheKey(updateObj), JSON.toJSONString(updateObj));
redisHelper.set(SYSTEM_SMS_NODE_KEY + updateObj.getId(), JSON.toJSONString(updateObj));
}
@Override
public void deleteSmsNode(Long id) {
// 校验存在
SmsNodeDO smsNodeDO = this.validateSmsNodeExists(id);
// 删除
smsNodeMapper.deleteById(id);
// 删除缓存中的短信节点信息
redisHelper.delete(buildCacheKey(smsNodeDO));
redisHelper.delete(SYSTEM_SMS_NODE_KEY + id);
}
private SmsNodeDO validateSmsNodeExists(Long id) {
SmsNodeDO smsNodeDO = smsNodeMapper.selectById(id);
if (smsNodeDO == null) {
throw exception(SMS_NODE_NOT_EXISTS);
}
return smsNodeDO;
}
@Override
public SmsNodeDO getSmsNode(Long id) {
return smsNodeMapper.selectById(id);
}
@Override
public List<SmsNodeDO> getSmsNodeList(Collection<Long> ids) {
return smsNodeMapper.selectBatchIds(ids);
}
@Override
public PageResult<SmsNodeDO> getSmsNodePage(SmsNodeQueryVO query, PageVO page) {
return smsNodeMapper.selectPage(page, query);
}
@Override
public List<SmsNodeDO> getSmsNodeList(SmsNodeQueryVO query) {
return smsNodeMapper.selectList(query);
}
}
package cn.iocoder.yudao.module.system.service.sms;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessageV2;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.Map;
......@@ -15,7 +19,7 @@ public interface SmsSendService {
/**
* 发送单条短信给管理后台的用户
*
* <p>
* 在 mobile 为空时,使用 userId 加载对应管理员的手机号
*
* @param mobile 手机号
......@@ -27,10 +31,28 @@ public interface SmsSendService {
Long sendSingleSmsToAdmin(String mobile, Long userId, String templateCode,
Map<String, Object> templateParams, String areaCode);
/**
* 发送短信服务方法
* 该方法负责根据提供的参数构建短信发送请求,并处理发送逻辑
*
* @param mobile 手机号码
* @param userId 用户ID
* @param nodeValue 节点值,用于确定短信内容
* @param areaCode 区域代码,用于确定短信发送的国家或地区
* @param isOrders 订单相关标志
* @param isTransport 物流相关标志
* @param transportId 物流
* @param messageType 发送类型
* @param templateParams 短信模板参数
*/
Long sendSingleSmsToAdminV2(@Mobile @NotEmpty(message = "手机号不能为空") String mobile, Long userId,
@NotNull(message = "业务节点不能为空") String nodeValue, @NotNull(message = "国家区号不能为空") String areaCode,
@NotNull(message = "是否多订单不能为空") Integer isOrders, @NotNull(message = "是否匹配运输方式不能为空") Integer isTransport,
@NotNull(message = "运输方式不能空") Integer transportId, @NotNull(message = "发送类型不能为空") Integer messageType, Map<String, Object> templateParams);
/**
* 发送单条短信给用户 APP 的用户
*
* <p>
* 在 mobile 为空时,使用 userId 加载对应会员的手机号
*
* @param mobile 手机号
......@@ -42,6 +64,25 @@ public interface SmsSendService {
Long sendSingleSmsToMember(String mobile, Long userId, String templateCode,
Map<String, Object> templateParams, String areaCode);
/**
* 发送单条短信给用户 APP 的用户
*
* @param mobile 手机号码
* @param userId 用户ID
* @param nodeValue 节点值,用于确定短信内容
* @param areaCode 区域代码,用于确定短信发送的国家或地区
* @param isOrders 订单相关标志
* @param isTransport 物流相关标志
* @param transportId 物流
* @param messageType 发送类型
* @param templateParams 短信模板参数
*/
Long sendSingleSmsToMemberV2(@Mobile @NotEmpty(message = "手机号不能为空") String mobile, Long userId,
@NotNull(message = "业务节点不能为空") String nodeValue, @NotNull(message = "国家区号不能为空") String areaCode,
@NotNull(message = "是否多订单不能为空") Integer isOrders, @NotNull(message = "是否匹配运输方式不能为空") Integer isTransport,
@NotNull(message = "运输方式不能空") Integer transportId, @NotNull(message = "发送类型不能为空") Integer messageType, Map<String, Object> templateParams);
/**
* 发送单条短信给用户
*
......@@ -56,7 +97,26 @@ public interface SmsSendService {
String templateCode, Map<String, Object> templateParams);
/**
* 发送短信服务方法
* 该方法负责根据提供的参数构建短信发送请求,并处理发送逻辑
*
* @param mobile 手机号码
* @param userId 用户ID
* @param userType 用户类型
* @param nodeValue 节点值,用于确定短信内容
* @param areaCode 区域代码,用于确定短信发送的国家或地区
* @param isOrders 订单相关标志
* @param isTransport 物流相关标志
* @param transportId 物流
* @param messageType 发送类型
* @param templateParams 短信模板参数
* @param smsLogId 重发短信时的原短信日志id
* @param nodeTemplateSn 模板序号
*/
Long sendSingleSmsV2(@Mobile @NotEmpty(message = "手机号不能为空") String mobile, Long userId, Integer userType, @NotNull(message = "业务节点不能为空") String nodeValue,
@NotNull(message = "国家区号不能为空") String areaCode, @NotNull(message = "是否多订单不能为空") Integer isOrders, @NotNull(message = "是否匹配运输方式不能为空") Integer isTransport,
@NotNull(message = "运输方式不能空") Integer transportId, @NotNull(message = "发送类型不能为空") Integer messageType, Map<String, Object> templateParams, Long smsLogId, Integer nodeTemplateSn);
void resendSingleSmsBySmsLogIds(Collection<Long> smsLogIds);
......@@ -73,6 +133,14 @@ public interface SmsSendService {
*/
void doSendSms(SmsSendMessage message);
/**
* 执行真正的短信发送
* 注意,该方法仅仅提供给 MQ Consumer 使用
*
* @param message 短信
*/
void doSendSmsV2(SmsSendMessageV2 message);
/**
* 接收短信的接收结果
*
......@@ -82,4 +150,5 @@ public interface SmsSendService {
*/
void receiveSmsStatus(String channelCode, String text) throws Throwable;
}
......@@ -6,22 +6,31 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.json.core.KeyValue;
import cn.iocoder.yudao.framework.common.util.spring.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.spring.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.redis.helper.RedisHelper;
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateDTO;
import cn.iocoder.yudao.framework.web.config.BusinessProperties;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
import cn.iocoder.yudao.module.system.convert.sms.SmsTemplateConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsLogDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.sms.SmsCountryCodeEnum;
import cn.iocoder.yudao.module.system.enums.sms.SystemStatusEnum;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessageV2;
import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.alibaba.fastjson.JSON;
import com.google.common.annotations.VisibleForTesting;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
......@@ -62,6 +71,12 @@ public class SmsSendServiceImpl implements SmsSendService {
@Resource
private BusinessProperties businessProperties;
@Autowired
private SmsNodeService smsNodeService;
@Autowired
private RedisHelper redisHelper;
@Override
public Long sendSingleSmsToAdmin(String mobile, Long userId, String templateCode, Map<String, Object> templateParams, String areaCode) {
// 如果 mobile 为空,则加载用户编号对应的手机号
......@@ -76,6 +91,21 @@ public class SmsSendServiceImpl implements SmsSendService {
return this.sendSingleSms(areaCode + mobile, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);
}
@Override
public Long sendSingleSmsToAdminV2(String mobile, Long userId, String nodeValue, String areaCode,
Integer isOrders, Integer isTransport, Integer transportId, Integer messageType, Map<String, Object> templateParams) {
// 如果 mobile 为空,则加载用户编号对应的手机号
if (StrUtil.isEmpty(mobile)) {
AdminUserDO user = adminUserService.getUser(userId);
if (user != null) {
mobile = user.getMobile();
areaCode = "86"; //TODO 待管理端用户信息存入国家区号
}
}
// 执行发送
return this.sendSingleSmsV2(areaCode + mobile, userId, UserTypeEnum.ADMIN.getValue(), nodeValue, areaCode, isOrders, isTransport, transportId, messageType, templateParams, null, 1);
}
@Override
public Long sendSingleSmsToMember(String mobile, Long userId, String templateCode, Map<String, Object> templateParams, String areaCode) {
// 如果 mobile 为空,则加载用户编号对应的手机号
......@@ -90,6 +120,21 @@ public class SmsSendServiceImpl implements SmsSendService {
return this.sendSingleSms(areaCode + mobile, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams);
}
@Override
public Long sendSingleSmsToMemberV2(String mobile, Long userId, String nodeValue, String areaCode,
Integer isOrders, Integer isTransport, Integer transportId, Integer messageType, Map<String, Object> templateParams) {
// 如果 mobile 为空,则加载用户编号对应的手机号
if (StrUtil.isEmpty(mobile)) {
UserRespDTO user = memberUserApi.getUser(userId);
if (user != null) {
mobile = user.getMobile();
areaCode = user.getAreaCode(); //TODO 待管理端用户信息存入国家区号
}
}
// 执行发送
return this.sendSingleSmsV2(areaCode + mobile, userId, UserTypeEnum.MEMBER.getValue(), nodeValue, areaCode, isOrders, isTransport, transportId, messageType, templateParams, null, 1);
}
@Override
public Long sendSingleSms(String mobile, Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams) {
......@@ -224,6 +269,21 @@ public class SmsSendServiceImpl implements SmsSendService {
sendResult.getData() != null ? sendResult.getData().getSerialNo() : null);
}
@Override
public void doSendSmsV2(SmsSendMessageV2 message) {
SmsTemplateDTO smsTemplateDTO = message.getSmsTemplateDTO();
// 获得渠道对应的 SmsClient 客户端
SmsClient smsClient = smsClientFactory.getSmsClient(smsTemplateDTO.getChannelId());
Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", smsTemplateDTO.getChannelId()));
// 发送短信
SmsCommonResult<SmsSendRespDTO> sendResult = smsClient.sendSmsV2(message.getLogId(), message.getMobile(),
smsTemplateDTO, message.getTemplateParams());
smsLogService.updateSmsSendResult(message.getLogId(), sendResult.getCode(), sendResult.getMsg(),
sendResult.getApiCode(), sendResult.getApiMsg(), sendResult.getApiRequestId(),
sendResult.getData() != null ? sendResult.getData().getSerialNo() : null);
}
@Override
public void receiveSmsStatus(String channelCode, String text) throws Throwable {
// 获得渠道对应的 SmsClient 客户端
......@@ -239,4 +299,126 @@ public class SmsSendServiceImpl implements SmsSendService {
result.getSuccess(), result.getReceiveTime(), result.getErrorCode(), result.getErrorCode()));
}
/**
* 发送短信服务方法V2版本
* 该方法负责根据提供的参数构建短信发送请求,并处理发送逻辑
*
* @param mobile 手机号码
* @param userId 用户ID
* @param userType 用户类型
* @param nodeValue 节点值,用于确定短信内容
* @param areaCode 区域代码,用于确定短信发送的国家或地区
* @param isOrders 订单相关标志
* @param isTransport 物流相关标志
* @param transportId 物流
* @param messageType 发送类型
* @param templateParams 短信模板参数
* @param smsLogId 重发短信时的原短信日志id
* @param nodeTemplateSn 模板序号
*/
@Override
public Long sendSingleSmsV2(String mobile, Long userId, Integer userType, String nodeValue, String areaCode,
Integer isOrders, Integer isTransport, Integer transportId, Integer messageType, Map<String, Object> templateParams, Long smsLogId, Integer nodeTemplateSn) {
// 创建SmsNodeDO对象并设置相关属性
SmsNodeDO smsNodeDO = new SmsNodeDO()
.setNodeValue(nodeValue)
.setIsTransport(isTransport)
.setTransportId(transportId)
.setIsOrders(isOrders)
.setCountryCode(areaCode)
.setStatus(SystemStatusEnum.SYSTEM_STATUS_0.getValue());
// 构建缓存键,用于查询短信节点缓存
String cacheKey = smsNodeService.buildCacheKey(smsNodeDO);
String smsNodeCache = redisHelper.get(cacheKey);
// 第一次找不到,则在全部国家里面找
if (smsNodeCache == null) {
smsNodeDO.setCountryCode(SmsCountryCodeEnum.SMS_COUNTRY_CODE_0.getValue());
cacheKey = smsNodeService.buildCacheKey(smsNodeDO);
smsNodeCache = redisHelper.get(cacheKey);
// 第二次找不到,则在其他国家里面找
if (smsNodeCache == null) {
smsNodeDO.setCountryCode(SmsCountryCodeEnum.SMS_COUNTRY_CODE_1.getValue());
cacheKey = smsNodeService.buildCacheKey(smsNodeDO);
smsNodeCache = redisHelper.get(cacheKey);
}
}
// 如果 still null, throw exception
if (smsNodeCache == null) {
throw exception(SMS_SEND_TEMPLATE_NOT_EXISTS);
}
// 解析缓存获取短信节点信息
SmsNodeDO smsNode = JSON.parseObject(smsNodeCache, SmsNodeDO.class);
String smsTemplateDO = null;
// 从缓存中获取短信模板信息
for (int i = nodeTemplateSn; i <= 4; i++) {
nodeTemplateSn = i;
Long templateId = getTemplateIdBySn(smsNode, nodeTemplateSn);
if (templateId != null) {
String key = smsTemplateService.buildCacheKey(new SmsTemplateDO().setId(templateId).setMessageType(messageType).setStatus(SystemStatusEnum.SYSTEM_STATUS_0.getValue()));
String smsTemplate = redisHelper.get(key);
if (smsTemplate != null) {
smsTemplateDO = smsTemplate;
break;
}
}
}
if (smsTemplateDO == null) {
// throw exception(SMS_SEND_TEMPLATE_NOT_EXISTS);
return null;
}
// 解析缓存获取短信模板信息
SmsTemplateDO smsTemplate = JSON.parseObject(smsTemplateDO, SmsTemplateDO.class);
// 格式化短信模板内容
String content = smsTemplateService.formatSmsTemplateContent(smsTemplate.getContent(), templateParams);
// 创建短信发送日志
Long sendLogId = smsLogService.createSmsLogV2(mobile, userId, userType, true, smsTemplate,
content, templateParams, smsTemplate.getApiTemplateId(), smsLogId, smsNode.getId(), nodeTemplateSn, messageType);
// 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志
List<KeyValue<String, Object>> newTemplateParams = this.buildTemplateParams(smsTemplate, templateParams);
// 创建SmsSendMessageV2对象并设置相关属性
SmsSendMessageV2 smsSendMessage = new SmsSendMessageV2()
.setLogId(sendLogId)
.setMobile(mobile)
.setSmsTemplateDTO(SmsTemplateConvert.INSTANCE.toDto(smsTemplate))
.setTemplateParams(newTemplateParams);
smsProducer.sendSmsSendMessageV2(smsSendMessage);
// 根据debug配置决定是否发送短信
// if (Objects.isNull(businessProperties) || !businessProperties.isDebug()) {
// smsProducer.sendSmsSendMessageV2(smsSendMessage);
// }
return sendLogId;
}
/**
* 根据节点模板序号获取模板ID
*
* @param smsNode 短信节点信息
* @param sn 模板序号
* @return 对应的模板ID,如果找不到则返回null
*/
private Long getTemplateIdBySn(SmsNodeDO smsNode, int sn) {
switch (sn) {
case 1:
return smsNode.getTemplateIdOne();
case 2:
return smsNode.getTemplateIdTwo();
case 3:
return smsNode.getTemplateIdThree();
case 4:
return smsNode.getTemplateIdFour();
default:
return null;
}
}
}
......@@ -25,6 +25,16 @@ public interface SmsTemplateService {
*/
void initLocalCache();
/**
* 构建缓存键
* 根据smsTemplateDO对象的属性生成一个唯一的缓存键
* 这是为了确保每个配置项在缓存中都有一个唯一的标识
*
* @param smsTemplateDO smsTemplateDO 对象
* @return 缓存键
*/
public String buildCacheKey(SmsTemplateDO smsTemplateDO);
/**
* 获得短信模板,从缓存中
*
......
package cn.iocoder.yudao.module.system.service.sms;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.spring.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.apollo.core.constants.CacheConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.spring.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.redis.helper.RedisHelper;
import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
......@@ -20,9 +20,10 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
import com.alibaba.fastjson.JSON;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
......@@ -67,10 +68,13 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
@Resource
private SmsProducer smsProducer;
@Autowired
private RedisHelper redisHelper;
/**
* 短信模板缓存
* key:短信模板编码 {@link SmsTemplateDO#getCode()}
*
* <p>
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
private volatile Map<String, SmsTemplateDO> smsTemplateCache;
......@@ -87,11 +91,32 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
if (CollUtil.isEmpty(smsTemplateList)) {
return;
}
for (SmsTemplateDO smsTemplateDO : smsTemplateList) {
// 构建缓存键
String key = buildCacheKey(smsTemplateDO);
// 将对象转换为JSON字符串作为缓存值
String value = JSON.toJSONString(smsTemplateDO);
// 将键值对存入Redis缓存
redisHelper.set(key, value);
}
// 写入缓存
smsTemplateCache = CollectionUtils.convertMap(smsTemplateList, SmsTemplateDO::getCode);
maxUpdateTime = CollectionUtils.getMaxValue(smsTemplateList, SmsTemplateDO::getUpdateTime);
log.info("[initLocalCache][初始化 SmsTemplate 数量为 {}]", smsTemplateList.size());
// smsTemplateCache = CollectionUtils.convertMap(smsTemplateList, SmsTemplateDO::getCode);
// maxUpdateTime = CollectionUtils.getMaxValue(smsTemplateList, SmsTemplateDO::getUpdateTime);
// log.info("[initLocalCache][初始化 SmsTemplate 数量为 {}]", smsTemplateList.size());
}
/**
* 构建缓存键
* 根据smsTemplateDO对象的属性生成一个唯一的缓存键
* 这是为了确保每个配置项在缓存中都有一个唯一的标识
*
* @param smsTemplateDO smsTemplateDO 对象
* @return 缓存键
*/
@Override
public String buildCacheKey(SmsTemplateDO smsTemplateDO) {
// 使用格式化字符串构建缓存键,SYSTEM_SMS_TEMPLATE_KEY:id:发送类型:启用状态
return String.format("%s%s:%s:%s", CacheConstants.SYSTEM_SMS_TEMPLATE_KEY, smsTemplateDO.getId(), smsTemplateDO.getMessageType(), smsTemplateDO.getStatus());
}
/**
......@@ -115,7 +140,7 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
return smsTemplateMapper.selectList();
}
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
// @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
public void schedulePeriodicRefresh() {
initLocalCache();
}
......@@ -149,20 +174,22 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
// 校验短信模板
checkApiTemplate(createReqVO.getChannelId(), createReqVO.getApiTemplateId());
// 英文短信模板校验
checkApiTemplate(createReqVO.getChannelId(), createReqVO.getApiTemplateIdEn());
// checkApiTemplate(createReqVO.getChannelId(), createReqVO.getApiTemplateIdEn());
// 插入
SmsTemplateDO template = SmsTemplateConvert.INSTANCE.convert(createReqVO);
List<String> paramsZh = parseTemplateContentParams(createReqVO.getContent());
List<String> paramsEn = parseTemplateContentParams(createReqVO.getContentEn());
List<String> params = (List<String>) CollectionUtil.union(paramsZh, paramsEn);
// List<String> paramsEn = parseTemplateContentParams(createReqVO.getContentEn());
// List<String> params = (List<String>) CollectionUtil.union(paramsZh, paramsEn);
template.setParams(params);
template.setParams(paramsZh);
template.setChannelCode(channelDO.getCode());
smsTemplateMapper.insert(template);
// 发送刷新消息
smsProducer.sendSmsTemplateRefreshMessage();
// smsProducer.sendSmsTemplateRefreshMessage();
// 设置新的缓存
redisHelper.set(buildCacheKey(template), JSON.toJSONString(template));
// 返回
return template.getId();
}
......@@ -170,7 +197,7 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
@Override
public void updateSmsTemplate(SmsTemplateUpdateReqVO updateReqVO) {
// 校验存在
this.validateSmsTemplateExists(updateReqVO.getId());
SmsTemplateDO smsTemplateDO = this.validateSmsTemplateExists(updateReqVO.getId());
// 校验短信渠道
SmsChannelDO channelDO = checkSmsChannel(updateReqVO.getChannelId());
// 校验短信编码是否重复
......@@ -181,29 +208,37 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
// 更新
SmsTemplateDO updateObj = SmsTemplateConvert.INSTANCE.convert(updateReqVO);
List<String> paramsZh = parseTemplateContentParams(updateObj.getContent());
List<String> paramsEn = parseTemplateContentParams(updateObj.getContentEn());
List<String> params = (List<String>) CollectionUtil.union(paramsZh, paramsEn);
updateObj.setParams(params);
// List<String> paramsEn = parseTemplateContentParams(updateObj.getContentEn());
// List<String> params = (List<String>) CollectionUtil.union(paramsZh, paramsEn);
updateObj.setParams(paramsZh);
updateObj.setChannelCode(channelDO.getCode());
smsTemplateMapper.updateById(updateObj);
// 发送刷新消息
smsProducer.sendSmsTemplateRefreshMessage();
// smsProducer.sendSmsTemplateRefreshMessage();
// 删除旧的缓存
redisHelper.delete(buildCacheKey(smsTemplateDO));
// 设置新的缓存
redisHelper.set(buildCacheKey(updateObj), JSON.toJSONString(updateObj));
}
@Override
public void deleteSmsTemplate(Long id) {
// 校验存在
this.validateSmsTemplateExists(id);
SmsTemplateDO smsTemplateDO = this.validateSmsTemplateExists(id);
// 更新
smsTemplateMapper.deleteById(id);
// 发送刷新消息
smsProducer.sendSmsTemplateRefreshMessage();
// smsProducer.sendSmsTemplateRefreshMessage();
// 删除缓存中的短信节点信息
redisHelper.delete(buildCacheKey(smsTemplateDO));
}
private void validateSmsTemplateExists(Long id) {
if (smsTemplateMapper.selectById(id) == null) {
private SmsTemplateDO validateSmsTemplateExists(Long id) {
SmsTemplateDO SmsTemplateDO = smsTemplateMapper.selectById(id);
if (SmsTemplateDO == null) {
throw exception(SMS_TEMPLATE_NOT_EXISTS);
}
return SmsTemplateDO;
}
@Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment