feat(wxcp): 新增企业微信模块,支持成员扫码登录、活动通知与通讯录
一、独立模块 yudao-module-wxcp
- 企业微信扫码登录(JustAuth)+ 首次扫码自动注册账号(用户名 wx_ 前缀)
- 应用消息推送,抽成 WxCpMessagePushService 供活动/通知复用
- 通讯录查看:部门树、部门平铺、成员列表,用于推送时挑选目标
- 回调接口(URL 验证 + 消息接收),本次未启用接收消息
二、活动管理
- 创建/编辑/发布/结束/取消,活动状态 草稿-已发布-已结束-已取消
- 推送:企业微信文本卡片消息,按钮直达报名页
- 报名二维码:后端生成 PNG,扫码打开 H5 报名页
- 报名页支持填写参与说明并上传图片/附件,免登录
- 报名统计:总数、状态分布、按部门与日期分布、来源分布、签到率
三、通知管理
- 创建/编辑/发布/撤回/推送,与活动共用推送服务
- 通知用纯文本推送(textcard 类型强制要求 url,通知无详情页)
四、跨模块与规范
- 报名附件走 system 模块附件中间表,未新增 upload 接口、未加文件地址字段
- StorageRecordTypeEnum 注册 wxcp_activity_signup,否则绑定报「无效的记录类型」
- 登录通知通过 Spring 事件解耦,避免 wxcp 与 system 循环依赖
- 监听用 AFTER_COMMIT,避免异步线程读不到未提交的绑定记录
五、数据库脚本(docs/sql)
- wxcp_module.sql:活动/报名/通知三张表
- wxcp_menu.sql:菜单与角色授权,幂等
- config_export_all_20260929.sql:全库初始化脚本,新增「阶段三」企业微信增量段
(因企业微信菜单只存在于业务库 mom-jssm,源库无此数据,无法按阶段二的跨库方式补导)
六、注意:以下配置未包含在本提交中,部署时需自行补齐
yudao-server/src/main/resources/application-local.yaml
- wx.cp.corp-id / agent-id / secret / token / aes-key
(企业微信企业ID、应用ID、应用密钥、回调令牌、回调加解密密钥)
- wx.cp.signup-url-prefix(活动报名页外网地址前缀,二维码指向它)
- wx.cp.qrcode-size
以上均为环境相关配置,刻意不入库。
yudao-server/src/main/resources/application.yaml
- yudao.security.permit-all_urls 需追加三条免登录白名单,否则对应功能不可用:
/admin-api/wxcp/callback/** 企业微信回调
/admin-api/wxcp/activity/signup-page 活动报名落地页(扫码打开)
/admin-api/wxcp/activity/signup 活动报名提交
/admin-api/system/storage-blob/public-upload 报名页附件上传
Co-Authored-By: Claude <noreply@anthropic.com>
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================================ |
| | | -- ä¼ä¸å¾®ä¿¡æ¨¡åèå䏿é |
| | | -- ç®æ åºï¼mom-jssmï¼36.213.150.237:10000ï¼ |
| | | -- |
| | | -- ä½ç¨ï¼ |
| | | -- 1. 让 /wxcp/activityã/wxcp/notice 两个页é¢è½å¨å·¦ä¾§èåä¸åºç° |
| | | -- ï¼åç«¯è·¯ç±æ¯èå驱å¨çï¼æ²¡æèåè®°å½é¡µé¢å°±è®¿é®ä¸å°ï¼ |
| | | -- 2. 注å @PreAuthorize ç¨å°çæéæ è¯ï¼å¦åæ¥å£ä¸å¾ 403 |
| | | -- |
| | | -- èåç±»åï¼1 ç®å½ã2 èåã3 æé® |
| | | -- ç¼å·æ®µï¼6100000 èµ·ï¼ä¸ç°æèåä¸å²çª |
| | | -- |
| | | -- å¹çï¼å
æç¼å·å é¤åæå
¥ï¼å¯é夿§è¡ |
| | | -- ============================================================ |
| | | |
| | | DELETE FROM `system_menu` WHERE `id` BETWEEN 6100000 AND 6100999; |
| | | |
| | | -- ---------------------------- |
| | | -- ä¸çº§ç®å½ï¼ä¼ä¸å¾®ä¿¡ |
| | | -- ---------------------------- |
| | | INSERT INTO `system_menu` |
| | | (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, |
| | | `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | VALUES |
| | | (6100000, 'ä¼ä¸å¾®ä¿¡', '', 1, 80, 0, '/wxcp', 'ep:chat-dot-round', NULL, NULL, |
| | | 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'); |
| | | |
| | | -- ---------------------------- |
| | | -- æ´»å¨ç®¡ç |
| | | -- ---------------------------- |
| | | INSERT INTO `system_menu` |
| | | (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, |
| | | `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | VALUES |
| | | (6100001, 'æ´»å¨ç®¡ç', '', 2, 1, 6100000, 'activity', 'ep:calendar', '/wxcp/activity/index', 'WxCpActivityList', |
| | | 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100010, 'æ´»å¨æ¥è¯¢', 'wxcp:activity:query', 3, 1, 6100001, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100011, 'æ´»å¨æ°å¢', 'wxcp:activity:create', 3, 2, 6100001, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100012, 'æ´»å¨ä¿®æ¹', 'wxcp:activity:update', 3, 3, 6100001, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100013, 'æ´»å¨å é¤', 'wxcp:activity:delete', 3, 4, 6100001, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100014, 'æ´»å¨æ¨é', 'wxcp:activity:push', 3, 5, 6100001, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | -- æ¥åè®°å½æå¨æ´»å¨èåä¸ï¼å 为å®åªè½ä»æ´»å¨è¯¦æ
è¿å
¥ |
| | | (6100015, 'æ¥åæ¥è¯¢', 'wxcp:signup:query', 3, 6, 6100001, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100016, 'æ¥å管ç', 'wxcp:signup:update', 3, 7, 6100001, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | -- é讯å½å¨æ¨éå¼¹çªéç¨ï¼ä¸åç¬å»ºé¡µé¢ï¼ä½æéè¦æ³¨å |
| | | (6100017, 'éè®¯å½æ¥è¯¢', 'wxcp:member:query', 3, 8, 6100001, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'); |
| | | |
| | | -- ---------------------------- |
| | | -- éç¥ç®¡ç |
| | | -- ---------------------------- |
| | | INSERT INTO `system_menu` |
| | | (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, |
| | | `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | VALUES |
| | | (6100002, 'éç¥ç®¡ç', '', 2, 2, 6100000, 'notice', 'ep:bell', '/wxcp/notice/index', 'WxCpNoticeList', |
| | | 0, b'1', b'1', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100020, 'éç¥æ¥è¯¢', 'wxcp:notice:query', 3, 1, 6100002, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100021, 'éç¥æ°å¢', 'wxcp:notice:create', 3, 2, 6100002, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100022, 'éç¥ä¿®æ¹', 'wxcp:notice:update', 3, 3, 6100002, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100023, 'éç¥å é¤', 'wxcp:notice:delete', 3, 4, 6100002, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'), |
| | | (6100024, 'éç¥æ¨é', 'wxcp:notice:push', 3, 5, 6100002, '', '', '', '', 0, b'1', b'0', b'1', 'admin', NOW(), 'admin', NOW(), b'0'); |
| | | |
| | | -- ============================================================ |
| | | -- è§è²ææ |
| | | -- |
| | | -- åé
æ¯ä¾æ²¿ç¨ç°ææ¨¡åï¼å¦å®å管çï¼ï¼ |
| | | -- super_admin(1) ä¸ 001(160) æ¿ãæ´ä¸ªæ¨¡åçå
¨é¨èåã |
| | | -- å
¶ä»è§è²ä¸èªå¨æäºï¼éè¦æ¶å¨ãç³»ç»ç®¡ç â è§è²ç®¡çãéæå¨å¾é |
| | | -- |
| | | -- 注ï¼super_admin ç»å½æ¶ä»£ç ä¼ç´æ¥è¿åå
¨é¨èåï¼è¿éçæ å°æ¯ä¸ºäº |
| | | -- 让 system_role_menu æ°æ®å®æ´ï¼ä¸å
¶å®æ¨¡åä¿æä¸è´ã |
| | | -- ============================================================ |
| | | |
| | | DELETE `rm` FROM `system_role_menu` `rm` |
| | | WHERE `rm`.`role_id` IN (1, 160) |
| | | AND `rm`.`menu_id` BETWEEN 6100000 AND 6100999; |
| | | |
| | | INSERT INTO `system_role_menu` |
| | | (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) |
| | | SELECT `r`.`id`, `m`.`id`, 'admin', NOW(), 'admin', NOW(), b'0', 0 |
| | | FROM `system_role` `r` |
| | | JOIN `system_menu` `m` |
| | | ON `m`.`id` BETWEEN 6100000 AND 6100999 |
| | | AND `m`.`deleted` = b'0' |
| | | WHERE `r`.`id` IN (1, 160) |
| | | AND `r`.`deleted` = b'0'; |
| | | |
| | | -- ============================================================ |
| | | -- å¦éç»å
¶å®è§è²å¼éï¼æä¸ä¸æ¡ INSERT ç r.id IN (1, 160) |
| | | -- æ¢æç®æ è§è²ç¼å·å³å¯ï¼ä¾å¦ï¼ |
| | | -- WHERE `r`.`id` IN (1, 160, 2) -- 追å ç»ãæ®éè§è²ã |
| | | -- ============================================================ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================================ |
| | | -- ä¼ä¸å¾®ä¿¡æ¨¡åå»ºè¡¨èæ¬ï¼æ´»å¨ / æ¥å / éç¥ï¼ |
| | | -- ç®æ åºï¼mom-jssmï¼36.213.150.237:10000ï¼ |
| | | -- 说æï¼é¡¹ç®ä¸ä½¿ç¨å¤ç§æ·ï¼å æ¤ä¸å« tenant_id åæ®µ |
| | | -- |
| | | -- æ¥åæäº¤çå¾ç/éä»¶ä¸è½æ¬æ¨¡åçè¡¨ï¼ |
| | | -- ç»ä¸èµ° system_storage_attachment ä¸é´è¡¨ï¼record_type = 'wxcp_activity_signup' |
| | | -- ============================================================ |
| | | |
| | | -- ---------------------------- |
| | | -- æ´»å¨ä¸»è¡¨ |
| | | -- ---------------------------- |
| | | CREATE TABLE IF NOT EXISTS `wxcp_activity` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ç¼å·', |
| | | `title` varchar(128) NOT NULL COMMENT 'æ´»å¨æ é¢', |
| | | `content` text COMMENT 'æ´»å¨è¯¦æ
', |
| | | `cover_url` varchar(512) DEFAULT '' COMMENT 'å°é¢å¾å°å', |
| | | `location` varchar(255) DEFAULT '' COMMENT 'æ´»å¨å°ç¹', |
| | | `start_time` datetime DEFAULT NULL COMMENT 'æ´»å¨å¼å§æ¶é´', |
| | | `end_time` datetime DEFAULT NULL COMMENT 'æ´»å¨ç»ææ¶é´', |
| | | `signup_start_time` datetime DEFAULT NULL COMMENT 'æ¥åå¼å§æ¶é´', |
| | | `signup_end_time` datetime DEFAULT NULL COMMENT 'æ¥åç»ææ¶é´', |
| | | `signup_limit` int NOT NULL DEFAULT 0 COMMENT 'æ¥å人æ°ä¸éï¼0 表示ä¸é', |
| | | `signup_count` int NOT NULL DEFAULT 0 COMMENT 'å·²æ¥å人æ°ï¼åä½ï¼ä¾¿äºå表å±ç¤ºï¼', |
| | | `status` tinyint NOT NULL DEFAULT 0 COMMENT 'ç¶æï¼0è稿 1å·²åå¸ 2å·²ç»æ 3已忶', |
| | | `push_scope` tinyint NOT NULL DEFAULT 0 COMMENT 'æ¨éèå´ï¼0å
¨å 1æå®é¨é¨ 2æå®æå', |
| | | `push_target` varchar(1024) DEFAULT '' COMMENT 'æ¨éç®æ ï¼é¨é¨IDææåè´¦å·ï¼å¤ä¸ªç¨éå·åé', |
| | | `push_status` tinyint NOT NULL DEFAULT 0 COMMENT 'æ¨éç¶æï¼0æªæ¨é 1å·²æ¨é', |
| | | `push_time` datetime DEFAULT NULL COMMENT 'æè¿æ¨éæ¶é´', |
| | | `remark` varchar(512) DEFAULT '' COMMENT '夿³¨', |
| | | `creator` varchar(64) DEFAULT '' COMMENT 'å建è
', |
| | | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `updater` varchar(64) DEFAULT '' COMMENT 'æ´æ°è
', |
| | | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT 'æ¯å¦å é¤', |
| | | PRIMARY KEY (`id`), |
| | | KEY `idx_status` (`status`), |
| | | KEY `idx_start_time` (`start_time`) |
| | | ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'ä¼ä¸å¾®ä¿¡æ´»å¨'; |
| | | |
| | | -- ---------------------------- |
| | | -- æ´»å¨æ¥åè®°å½è¡¨ |
| | | -- å¯ä¸æ§ï¼å䏿´»å¨å䏿ååªè½æ¥ä¸æ¬¡ï¼å¨ Service 屿 ¡éªï¼ä¸å å¯ä¸ç´¢å¼ââ |
| | | -- å 为 deleted æ¯é»è¾å é¤ï¼å¯ä¸ç´¢å¼ä¼ä¸"åæ¶æ¥ååéæ°æ¥å"å²çªã |
| | | -- ---------------------------- |
| | | CREATE TABLE IF NOT EXISTS `wxcp_activity_signup` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ç¼å·', |
| | | `activity_id` bigint NOT NULL COMMENT 'æ´»å¨ç¼å·', |
| | | `wx_user_id` varchar(128) DEFAULT '' COMMENT 'ä¼ä¸å¾®ä¿¡æåè´¦å·ï¼UserIdï¼ï¼ä¸ºç©ºè¡¨ç¤ºæªéè¿ä¼ä¸å¾®ä¿¡ææç´æ¥å¡«å表å', |
| | | `name` varchar(64) DEFAULT '' COMMENT 'å§å', |
| | | `mobile` varchar(32) DEFAULT '' COMMENT 'ææºå·', |
| | | `dept_name` varchar(255) DEFAULT '' COMMENT 'é¨é¨åç§°', |
| | | `content` text COMMENT 'åä¸è¯´æï¼æåæäº¤çæåå
容ï¼', |
| | | `status` tinyint NOT NULL DEFAULT 0 COMMENT 'ç¶æï¼0å·²æ¥å 1已忶 2å·²ç¾å°', |
| | | `signup_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'æ¥åæ¶é´', |
| | | `check_in_time` datetime DEFAULT NULL COMMENT 'ç¾å°æ¶é´', |
| | | `remark` varchar(512) DEFAULT '' COMMENT '夿³¨', |
| | | `creator` varchar(64) DEFAULT '' COMMENT 'å建è
', |
| | | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `updater` varchar(64) DEFAULT '' COMMENT 'æ´æ°è
', |
| | | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT 'æ¯å¦å é¤', |
| | | PRIMARY KEY (`id`), |
| | | KEY `idx_activity_id` (`activity_id`), |
| | | KEY `idx_wx_user_id` (`wx_user_id`) |
| | | ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'ä¼ä¸å¾®ä¿¡æ´»å¨æ¥åè®°å½'; |
| | | |
| | | -- ---------------------------- |
| | | -- éç¥è¡¨ |
| | | -- ---------------------------- |
| | | CREATE TABLE IF NOT EXISTS `wxcp_notice` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ç¼å·', |
| | | `title` varchar(128) NOT NULL COMMENT 'éç¥æ é¢', |
| | | `content` text COMMENT 'éç¥æ£æ', |
| | | `status` tinyint NOT NULL DEFAULT 0 COMMENT 'ç¶æï¼0è稿 1å·²åå¸ 2å·²æ¤å', |
| | | `push_scope` tinyint NOT NULL DEFAULT 0 COMMENT 'æ¨éèå´ï¼0å
¨å 1æå®é¨é¨ 2æå®æå', |
| | | `push_target` varchar(1024) DEFAULT '' COMMENT 'æ¨éç®æ ï¼é¨é¨IDææåè´¦å·ï¼å¤ä¸ªç¨éå·åé', |
| | | `push_status` tinyint NOT NULL DEFAULT 0 COMMENT 'æ¨éç¶æï¼0æªæ¨é 1å·²æ¨é', |
| | | `push_time` datetime DEFAULT NULL COMMENT 'æè¿æ¨éæ¶é´', |
| | | `remark` varchar(512) DEFAULT '' COMMENT '夿³¨', |
| | | `creator` varchar(64) DEFAULT '' COMMENT 'å建è
', |
| | | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `updater` varchar(64) DEFAULT '' COMMENT 'æ´æ°è
', |
| | | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT 'æ¯å¦å é¤', |
| | | PRIMARY KEY (`id`), |
| | | KEY `idx_status` (`status`) |
| | | ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'ä¼ä¸å¾®ä¿¡éç¥'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # ä¼ä¸å¾®ä¿¡æ¨¡åï¼æ´»å¨ / éç¥ / é讯å½ï¼- å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - æ´»å¨ç®¡çï¼å表 + æ°å»º/ç¼è¾å¼¹çªï¼ |
| | | - æ´»å¨è¯¦æ
ï¼å«æ¥åè®°å½åè¡¨ãæ¥åç»è®¡ãäºç»´ç å±ç¤ºï¼ |
| | | - éç¥ç®¡çï¼å表 + æ°å»º/ç¼è¾å¼¹çªï¼ |
| | | - æåéæ©å¨ï¼æ¨éæ¶æéé¨é¨/æåï¼æ°æ®æ¥èªä¼ä¸å¾®ä¿¡é讯å½ï¼ |
| | | - æ´»å¨æ¥åè½å°é¡µï¼**ä¸éè¦å端å¼å**ï¼ç±åç«¯ç´æ¥æ¸²æ HTMLï¼è§ææ«è¯´æï¼ |
| | | |
| | | ## æ¬æ¬¡æ°å¢è½å |
| | | |
| | | | è½å | 说æ | |
| | | |------|------| |
| | | | åèµ·æ´»å¨ | å建è稿 â ç¼è¾ â åå¸ | |
| | | | æ¨éç»æå | éè¿ä¼ä¸å¾®ä¿¡åºç¨æ¶æ¯æ¨éï¼æåç¹æ¶æ¯éçæé®ç´è¾¾æ¥å页 | |
| | | | æ¥åäºç»´ç | åç«¯çæ PNGï¼å¯åå°å±ç¤º/ä¸è½½æå° | |
| | | | æ«ç æ¥å | æåæ«ç æå¼æ¥å页ï¼å¡«å§å+ææºå·æäº¤ï¼**å
ç»å½** | |
| | | |
| | | ## API |
| | | |
| | | æææ¥å£åç¼ `/admin-api`ã |
| | | |
| | | ### æ´»å¨ç®¡ç |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æéæ è¯ | 说æ | |
| | | |------|------|----------|------| |
| | | | POST | `/wxcp/activity/create` | `wxcp:activity:create` | å建活å¨ï¼å为èç¨¿ï¼ | |
| | | | PUT | `/wxcp/activity/update` | `wxcp:activity:update` | ä¿®æ¹æ´»å¨ | |
| | | | DELETE | `/wxcp/activity/delete` | `wxcp:activity:delete` | å 餿´»å¨ | |
| | | | GET | `/wxcp/activity/get` | `wxcp:activity:query` | æ´»å¨è¯¦æ
| |
| | | | GET | `/wxcp/activity/page` | `wxcp:activity:query` | æ´»å¨å页 | |
| | | | PUT | `/wxcp/activity/publish` | `wxcp:activity:update` | å叿´»å¨ | |
| | | | PUT | `/wxcp/activity/finish` | `wxcp:activity:update` | ç»ææ´»å¨ | |
| | | | PUT | `/wxcp/activity/cancel` | `wxcp:activity:update` | åæ¶æ´»å¨ | |
| | | |
| | | **æ´»å¨å页请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | pageNo | Integer | æ¯ | 页ç | |
| | | | pageSize | Integer | æ¯ | æ¯é¡µæ¡æ° | |
| | | | title | String | å¦ | æ´»å¨æ é¢ï¼æ¨¡ç³å¹é
| |
| | | | status | Integer | å¦ | ç¶æï¼0è稿 1å·²åå¸ 2å·²ç»æ 3已忶 | |
| | | | pushStatus | Integer | å¦ | æ¨éç¶æï¼0æªæ¨é 1å·²æ¨é | |
| | | | startTime | DateTime[] | å¦ | æ´»å¨å¼å§æ¶é´èå´ï¼æ ¼å¼ `yyyy-MM-dd HH:mm:ss` | |
| | | |
| | | **å建/ä¿®æ¹è¯·æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | id | Long | ä¿®æ¹æ¶å¿
å¡« | ç¼å· | |
| | | | title | String | æ¯ | æ´»å¨æ é¢ï¼â¤128 å符 | |
| | | | content | String | å¦ | æ´»å¨è¯¦æ
| |
| | | | coverUrl | String | å¦ | å°é¢å¾å°å | |
| | | | location | String | å¦ | æ´»å¨å°ç¹ï¼â¤255 å符 | |
| | | | startTime | DateTime | å¦ | æ´»å¨å¼å§æ¶é´ | |
| | | | endTime | DateTime | å¦ | æ´»å¨ç»ææ¶é´ | |
| | | | signupStartTime | DateTime | å¦ | æ¥åå¼å§æ¶é´ | |
| | | | signupEndTime | DateTime | å¦ | æ¥åç»ææ¶é´ | |
| | | | signupLimit | Integer | æ¯ | æ¥åä¸éï¼**ä¸éä¼ 0** | |
| | | | pushScope | Integer | æ¯ | æ¨éèå´ï¼0å
¨å 1æå®é¨é¨ 2æå®æå | |
| | | | pushTarget | String | å¦ | æ¨éç®æ ï¼éå·åéï¼èå´é 1 æ 2 æ¶å¿
å¡« | |
| | | | remark | String | å¦ | 夿³¨ | |
| | | |
| | | **æ´»å¨ååºæ°å¢å段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | statusName | String | ç¶æåï¼å¦ãå·²åå¸ã | |
| | | | pushScopeName | String | æ¨éèå´åï¼å¦ãå
¨åã | |
| | | | signupCount | Integer | å·²æ¥åäººæ° | |
| | | | pushStatus | Integer | 0æªæ¨é 1å·²æ¨é | |
| | | | pushTime | DateTime | æè¿æ¨éæ¶é´ | |
| | | |
| | | ### æ¨éä¸äºç»´ç |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æéæ è¯ | 说æ | |
| | | |------|------|----------|------| |
| | | | POST | `/wxcp/activity/push` | `wxcp:activity:push` | æ¨éç»ä¼ä¸å¾®ä¿¡æå | |
| | | | GET | `/wxcp/activity/qrcode` | `wxcp:activity:query` | è·åæ¥åäºç»´ç ï¼è¿å PNGï¼ | |
| | | | GET | `/wxcp/activity/signup-url` | `wxcp:activity:query` | è·åæ¥å页å°å | |
| | | |
| | | **æ¨é请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | id | Long | æ¯ | æ´»å¨ç¼å· | |
| | | | pushScope | Integer | å¦ | è¦çæ´»å¨ä¸çæ¨éèå´ï¼ä¸ä¼ åæ²¿ç¨ | |
| | | | pushTarget | String | å¦ | è¦çæ´»å¨ä¸çæ¨éç®æ ï¼ä¸ä¼ åæ²¿ç¨ | |
| | | |
| | | **æ¨éååºï¼** |
| | | |
| | | ```json |
| | | { "code": 0, "data": { "success": true, "errCode": 0, "message": "æ¨éæå" } } |
| | | ``` |
| | | |
| | | **äºç»´ç æ¥å£ï¼** ç´æ¥è¿å `image/png` åèæµï¼ä¸æ¯ JSONã |
| | | 页é¢éæè¿ä¸ª URL å½å¾çå°åç¨å³å¯ï¼å¦ `<img :src="...">`ï¼ï¼éè¦å¸¦ä»¤çæ¶èªè¡æ¼æ¥è¯¢åæ°ã |
| | | |
| | | ### æ¥åè®°å½ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æéæ è¯ | 说æ | |
| | | |------|------|----------|------| |
| | | | GET | `/wxcp/activity/signup/page` | `wxcp:signup:query` | æ¥åè®°å½å页 | |
| | | | PUT | `/wxcp/activity/signup/cancel` | `wxcp:signup:update` | åæ¶æ¥å | |
| | | | PUT | `/wxcp/activity/signup/check-in` | `wxcp:signup:update` | ç¾å° | |
| | | |
| | | **å页请æ±åæ°ï¼** `activityId`ã`name`ï¼æ¨¡ç³ï¼ã`mobile`ï¼æ¨¡ç³ï¼ã`status` |
| | | |
| | | **ååºå段ï¼** `id`ã`activityId`ã`activityTitle`ã`wxUserId`ã`name`ã`mobile`ã`deptName`ã`status`ã`statusName`ã`signupTime`ã`checkInTime`ã`remark` |
| | | |
| | | ### å
¬å¼æ¥å£ï¼å
ç»å½ï¼å端**ä¸éè¦**è°ç¨ï¼ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | GET | `/wxcp/activity/signup-page?id={æ´»å¨ç¼å·}` | æ¥åè½å°é¡µ HTMLï¼ç±å端渲æ | |
| | | | POST | `/wxcp/activity/signup` | æäº¤æ¥åï¼æ¯æ `content` æå + `blobIds` éä»¶ï¼ | |
| | | |
| | | ### æ¥åç»è®¡ä¸éä»¶ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æéæ è¯ | 说æ | |
| | | |------|------|----------|------| |
| | | | GET | `/wxcp/activity/stats?id=` | `wxcp:activity:query` | æ¥åç»è®¡ | |
| | | | GET | `/wxcp/activity/signup/attachments?signupId=` | `wxcp:signup:query` | ææ¡æ¥åä¸ä¼ çå¾ç/éä»¶ | |
| | | |
| | | **ç»è®¡ååºï¼** |
| | | |
| | | ```json |
| | | { |
| | | "activityId": 1, "activityTitle": "2026æ¥å£å¢å»ºæ´»å¨", |
| | | "signupLimit": 50, "totalSignup": 1, |
| | | "signedUpCount": 1, "checkedInCount": 0, "canceledCount": 0, |
| | | "remaining": 49, "signupPercent": 2, "checkInPercent": 0, |
| | | "deptStats": [{ "name": "ææ¯é¨", "count": 3 }], |
| | | "dateStats": [{ "name": "2026-09-29", "count": 1 }], |
| | | "sourceStats": [{ "name": "ä¼ä¸å¾®ä¿¡èº«ä»½", "count": 0 }, { "name": "表åå¡«å", "count": 1 }] |
| | | } |
| | | ``` |
| | | |
| | | > `remaining` / `signupPercent` 卿´»å¨**ä¸éåé¢**ï¼`signupLimit=0`ï¼æ¶ä¸º `null`ï¼åç«¯è¦æ¾ç¤º"ä¸é"ã |
| | | > ç»è®¡**ä¸å«å·²åæ¶**çæ¥åï¼æä»¥ `totalSignup â æ¥åè®°å½æ»æ¡æ°`ã |
| | | |
| | | ### éç¥ç®¡ç |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æéæ è¯ | 说æ | |
| | | |------|------|----------|------| |
| | | | POST | `/wxcp/notice/create` | `wxcp:notice:create` | å建éç¥ï¼å为èç¨¿ï¼ | |
| | | | PUT | `/wxcp/notice/update` | `wxcp:notice:update` | ä¿®æ¹éç¥ | |
| | | | DELETE | `/wxcp/notice/delete` | `wxcp:notice:delete` | å é¤éç¥ | |
| | | | GET | `/wxcp/notice/get` | `wxcp:notice:query` | éç¥è¯¦æ
| |
| | | | GET | `/wxcp/notice/page` | `wxcp:notice:query` | éç¥å页 | |
| | | | PUT | `/wxcp/notice/publish` | `wxcp:notice:update` | åå¸éç¥ | |
| | | | PUT | `/wxcp/notice/revoke` | `wxcp:notice:update` | æ¤åéç¥ | |
| | | | POST | `/wxcp/notice/push` | `wxcp:notice:push` | æ¨ééç¥ | |
| | | |
| | | **éç¥ä¸æ´»å¨çå·®å¼ï¼** |
| | | |
| | | | 项 | æ´»å¨ | éç¥ | |
| | | |----|------|------| |
| | | | æ¨éæ¶æ¯ç±»å | ææ¬å¡çï¼å¸¦ãç«å³æ¥åãæé®è·³æ¥åé¡µï¼ | çº¯ææ¬ | |
| | | | æ£æé¿åº¦ | ç±å¡ç模æ¿éå¶ | è¶
è¿ 600 å**èªå¨æªæ**å¹¶æç¤ºå°ç³»ç»å
æ¥ç | |
| | | | ç¶æ | è稿/å·²åå¸/å·²ç»æ/已忶 | è稿/å·²åå¸/å·²æ¤å | |
| | | | æ¨éåæ | å¿
é¡»ãå·²åå¸ã | å¿
é¡»ãå·²åå¸ã | |
| | | |
| | | ### ä¼ä¸å¾®ä¿¡éè®¯å½ |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æéæ è¯ | 说æ | |
| | | |------|------|----------|------| |
| | | | GET | `/wxcp/member/department/tree` | `wxcp:member:query` | é¨é¨æ ï¼å« childrenï¼ | |
| | | | GET | `/wxcp/member/department/list` | `wxcp:member:query` | é¨é¨å¹³éºå表ï¼åä¸ææ´æ¹ä¾¿ï¼ | |
| | | | GET | `/wxcp/member/list?departmentId=&fetchChild=` | `wxcp:member:query` | æåå表 | |
| | | |
| | | **æåååºå段ï¼** `userId`ï¼æ¨éæ¶å¡«è¿ä¸ªï¼ã`name`ã`mobile`ã`email`ã`avatar`ã`position`ã`gender`ã`status`ã`departmentIds`ã`departmentNames` |
| | | |
| | | > è¿å 个æ¥å£æ¯**宿¶è°ç¨ä¼ä¸å¾®ä¿¡**çï¼ä¼ä¸å¾®ä¿¡å¯¹éè®¯å½æ¥å£æé¢çéå¶ãå端请åç¼åï¼å¦æå¼éæ©å¨æ¶æä¸æ¬¡ï¼ï¼**ä¸è¦**å¨æç´¢æ¡éæ¯è¾å
¥ä¸ä¸ªå符就请æ±ä¸æ¬¡ã |
| | | |
| | | ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å
¥ |
| | | |
| | | 1. æ°å»ºæ´»å¨ â ä¿å为**è稿**ï¼æ¤æ¶æ«ç æ¥åä¼æç¤ºãæ¥åå·²ç»æã |
| | | 2. ç¹ãåå¸ãâ ç¶æåãå·²åå¸ãï¼æ¤æ¶äºç»´ç 忥å页æå¯ç¨ |
| | | 3. ç¹ãæ¨éãâ æåä¼ä¸å¾®ä¿¡æ¶å°å¡çæ¶æ¯ï¼æ é¢ä¸ºæ´»å¨æ é¢ï¼æ£æå«**æ´»å¨æ¶é´ / å°ç¹ / æ¥åæªæ¢ / åé¢**ï¼æé®ãç«å³æ¥åãç´è¾¾æ¥å页 |
| | | 4. æåç¹æé®ææ«ç â æå¼æ¥å页 â å¡«å§ååææºå· â æäº¤ |
| | | 5. æ¥åæå â æ´»å¨ `signupCount` èªå¨ +1 |
| | | 6. åå°å¨æ´»å¨è¯¦æ
çæ¥åè®°å½å表éå¯çå°è¯¥æåï¼å¯æ§è¡ãåæ¶æ¥åããç¾å°ã |
| | | |
| | | **æ°æ®å¸¦å
¥å
³ç³»ï¼** |
| | | |
| | | | æ¥æº | ç®æ | 带å
¥å
容 | |
| | | |------|------|----------| |
| | | | æ´»å¨ç¼è¾ä¿å | æ¨éæ¶æ¯ | æ é¢ãæ¶é´ãå°ç¹ãæ¥åæªæ¢ãåé¢ | |
| | | | æ´»å¨ç¼å· | äºç»´ç å
容 | `{signupUrlPrefix}/admin-api/wxcp/activity/signup-page?id={id}` | |
| | | | æ«ç æ¥å | æ¥åè®°å½ | æ´»å¨ç¼å· | |
| | | |
| | | ## åæ®µå±ç¤ºè§å |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | title | å表ã详æ
| æ´»å¨æ é¢ï¼ç¹å»è¿è¯¦æ
| |
| | | | statusName | å表ã详æ
| ç¨å端è¿åç䏿åï¼å端ä¸è¦èªå·±æ å° | |
| | | | startTime ~ endTime | å表ã详æ
| æ´»å¨æ¶é´èå´ | |
| | | | signupCount / signupLimit | å表ã详æ
| `0/50` è¿ç§å½¢å¼ï¼ä¸é为 0 æ¶æ¾ç¤ºãä¸éã | |
| | | | pushStatus / pushTime | å表ã详æ
| æªæ¨é / å·²æ¨é + æ¶é´ | |
| | | | pushScopeName | 详æ
| æ¨éèå´ä¸æå | |
| | | | qrcodeï¼å¾çï¼ | 详æ
| æ¥åäºç»´ç ï¼éæ¯æä¸è½½ | |
| | | | statusNameï¼æ¥åï¼ | æ¥åå表 | å·²æ¥å / 已忶 / å·²ç¾å° | |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | åå»ºæ´»å¨ | ä¸å¾å为è稿ï¼ä¸å¯ç´æ¥å建为已åå¸ | |
| | | | ä¿®æ¹æ´»å¨ | å·²ç»æãå·²åæ¶çæ´»å¨ä¸å¯ä¿®æ¹ï¼è稿åå·²åå¸å¯æ¹ | |
| | | | æ¨éåæ | **æ´»å¨å¿
é¡»æ¯ãå·²åå¸ãç¶æ**ï¼èç¨¿ç¶ææ¨é伿¥ãæ´»å¨å°æªåå¸ï¼è¯·å
åå¸å忍éã | |
| | | | æ¨éèå´=å
¨å | 使ç¨ä¼ä¸å¾®ä¿¡ `@all`ï¼å³åºç¨å¯è§èå´å
ææäºº | |
| | | | æ¨éèå´=æå®æå | `pushTarget` å¡«æåè´¦å·ï¼UserIdï¼ï¼å¤ä¸ªç¨**è±æéå·**åé | |
| | | | æ¨éèå´=æå®é¨é¨ | `pushTarget` å¡«é¨é¨ IDï¼å¤ä¸ªç¨è±æéå·åé | |
| | | | æ¨é失败 | æ¥å£è¿å `success=false` + ä¼ä¸å¾®ä¿¡é误ç ï¼**ä¸ä¼æå¼å¸¸**ï¼åç«¯åºæ `message` ç´æ¥æç¤ºç»ç¨æ· | |
| | | | æ¨éæåå | æ´»å¨ `pushStatus` ç½® 1ã`pushTime` è®°å½æ¶é´ï¼é夿¨éä¼è¦çè¿ä¸¤ä¸ªå段 | |
| | | | æ¥åæ¶é´çªå£ | æªå° `signupStartTime` æå·²è¿ `signupEndTime` åä¸è½æ¥å | |
| | | | æ¥ååé¢ | `signupLimit=0` 表示ä¸éï¼å·²æ»¡æ¶æç¤ºãæ¥å人æ°å·²æ»¡ã | |
| | | | é夿¥å | æä¼ä¸å¾®ä¿¡èº«ä»½ææåè´¦å·å¤éï¼æ®éæµè§å¨æ**ææºå·**å¤é | |
| | | | åæ¶åéæ°æ¥å | å¤ç¨åè®°å½ï¼ä¸ä¼äº§çç¬¬äºæ¡åå² | |
| | | | åæ¶æ¥å | æ´»å¨ `signupCount` èªå¨ -1 | |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - **äºç»´ç æ¥å£è¿åçæ¯å¾çäºè¿å¶ï¼ä¸æ¯ JSON**ï¼ä¸è¦ç¨æ®é request å°è£
å»åï¼ç´æ¥ç¨å¾ç URLã |
| | | - **`signupLimit` å¿
å¡«**ï¼ä¸éè¦ä¼ `0` è䏿¯ç空ï¼å¦å伿 ¡éªå¤±è´¥ã |
| | | - **æ¨éèå´éãæå®é¨é¨/æåãæ¶ `pushTarget` ä¸è½ä¸ºç©º**ï¼å¦åæ¥ãæ¨éç®æ ä¸è½ä¸ºç©ºãã |
| | | - æ¨éæ¯**忥**è°ç¨ä¼ä¸å¾®ä¿¡æ¥å£ï¼æå夿¶å¯è½èæ¶ 1~3 ç§ï¼å端æé®è¦æ loadingã |
| | | - æ´»å¨ `content` åæ®µå¨æ¥å页æ¯**æçº¯ææ¬æ¸²æ**çï¼åäº HTML 转ä¹é² XSSï¼ãå¦ææ´»å¨è¯¦æ
éè¦å¯ææ¬æçï¼éè¦å¦è¡æ¥å
¥å¯ææ¬ç½ååè¿æ»¤ï¼å½åçæ¬ä¸æ¯æã |
| | | - æ¥å页å°åä¾èµå端é
ç½® `wx.cp.signup-url-prefix`ï¼å½å为 `http://wcf.viphk.nnhk.cc`ï¼ã该å°åå¿
é¡»æ¯**æåææºè½è®¿é®å°çå¤ç½å°å**ï¼å¦åäºç»´ç æ«åºæ¥æä¸å¼ã |
| | | - æ¥åè®°å½éç `wxUserId` åªæå¨**ä¼ä¸å¾®ä¿¡å
æå¼æ¥å页ä¸å¸¦ä¸äºåæ°**æ¶ææå¼ï¼æ®éæµè§å¨æ«ç æ¥åæ¶ä¸ºç©ºï¼æ¤æ¶æææºå·å»éã |
| | | - æéæ è¯éè¦å¨ãç³»ç»ç®¡ç â èå管çãéé
置好ï¼å¦åæ¥å£ä¼è¿å 403ã |
| | | - **æ¨éç®æ ç¨çæ¯ `userId`ï¼æåè´¦å·ï¼å¦ `LiuYi`ï¼ï¼ä¸æ¯å§åãä¹ä¸æ¯ææºå·ã** ä»éè®¯å½æ¥å£å `userId` å¡«è¿ `pushTarget`ã |
| | | - **éä»¶ä¸ä¼ èµ° system 模åçå
Œ
±ä¸ä¼ æ¥å£**ï¼æ¥å页å
è° `POST /admin-api/system/storage-blob/public-upload`ï¼è¡¨ååæ®µå `files`ï¼æ¿å° `blobId`ï¼åæ `blobIds` 鿥åæäº¤ï¼ç±**æå¡ç«¯**宿䏿¥åè®°å½çç»å®ã管çåå°æ¥éä»¶ç¨ `/wxcp/activity/signup/attachments`ã |
| | | - **æ¥åç `recordType` åºå®ä¸º `wxcp_activity_signup`**ï¼å·²æ³¨åè¿ system 模åç `StorageRecordTypeEnum` ç½ååã以忰å¢å«çéä»¶å
³èç±»åï¼å¿
é¡»åæ¥æ³¨åï¼å¦åç»å®ä¼æ¥ãæ æçè®°å½ç±»åãã |
| | | - éè®¯å½æ¥å£**宿¶è°ä¼ä¸å¾®ä¿¡**ï¼æé¢çéå¶ï¼å端å¡å¿
ç¼åï¼ä¸è¦éè¾å
¥å®æ¶è¯·æ±ã |
| | | - éç¥æ£æè¶
è¿ 600 åä¼**èªå¨æªæ**ï¼ä¼ä¸å¾®ä¿¡ææ¬æ¶æ¯æé¿åº¦ä¸éï¼ï¼æªææç¤ºä¼å卿¶æ¯æ«å°¾ã |
| | | |
| | | ## å
³äºæ¥åè½å°é¡µ |
| | | |
| | | æ¥å页 `GET /admin-api/wxcp/activity/signup-page?id=xxx` ç±**åç«¯ç´æ¥æ¸²æ HTML**ï¼å«å
èæ ·å¼åå°é JSï¼ï¼å端**æ éå¼å**ã |
| | | |
| | | è¿æ ·åçåå ï¼äºç»´ç å¿
é¡»æåä¸ä¸ªæåææºè½ç´æ¥æå¼ç页é¢ï¼èå端渲æå¯ä»¥ç«å³è·éãæ¨é â æ«ç â æ¥åãéç¯ï¼ä¸å¿
çå端ææãè¥åç»å¸ææ¹ä¸ºå端 SPA 页é¢ï¼æäºç»´ç å
å®¹æ¹æå端路ç±å°åå³å¯ï¼å端æ¥å£ï¼`/wxcp/activity/signup`ï¼ä¸ç¨å¨ã |
| | | |
| | | > 该页é¢çè·¯å¾å»ææ¾å¨ `/admin-api` ä¸ï¼èé `/app-api`ï¼ï¼å 为å端 Vite å¼å代çåªè½¬å `/admin-api`ï¼æ¾ `/app-api` ä¼å¯¼è´æ«ç åè¯·æ±æå°åç«¯éææå¡è¿å 404ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # ä¼ä¸å¾®ä¿¡ç»å½ä¸éç¥ - å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - ç»å½é¡µï¼`src/views/_core/authentication/login.vue`ï¼ |
| | | - ç¬¬ä¸æ¹ææåè°ä¸è½¬é¡µï¼`src/views/_core/authentication/social-login.vue`ï¼ |
| | | - äºç»´ç ç»å½é¡µï¼`src/views/_core/authentication/qrcode-login.vue`ï¼ |
| | | |
| | | ## æ¬æ¬¡åæ´æ¦è¿° |
| | | |
| | | | åæ´ | å½±åå端 | |
| | | |------|----------| |
| | | | ä¼ä¸å¾®ä¿¡é¦æ¬¡æ«ç **èªå¨æ³¨å**è´¦å·ï¼ä¸åæ¥"æªç»å®è´¦å·" | æ¯ ââ ç»å½é¡µéæ°å¢ä¼ä¸å¾®ä¿¡ç»å½å
¥å£ï¼å¹¶å»æé¦æ¬¡ç»å½çç»å®å¼¹çªåæ¯ | |
| | | | ç»å½æåååç¨æ·ä¼ä¸å¾®ä¿¡æ¨é**ç»å½éç¥** | å¦ ââ åç«¯å¼æ¥åéï¼åç«¯æ æç¥ | |
| | | | æ°å¢ä¼ä¸å¾®ä¿¡**åè°æ¥å£**ï¼URL éªè¯ + æ¶æ¯æ¥æ¶ï¼ | å¦ ââ ä¼ä¸å¾®ä¿¡æå¡å¨è°ç¨ï¼ä¸å端æ å
³ | |
| | | |
| | | ## API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | å端æ¯å¦è°ç¨ | |
| | | |------|------|------|--------------| |
| | | | GET | `/admin-api/system/auth/social-auth-redirect` | è·åä¼ä¸å¾®ä¿¡ææè·³è½¬å°å | æ¯ | |
| | | | POST | `/admin-api/system/auth/social-login` | ä½¿ç¨ææ code æ¢åç»å½ä»¤ç | æ¯ | |
| | | | POST | `/admin-api/system/auth/login` | è´¦å·å¯ç ç»å½ï¼å¯é带社交ç»å®åæ°ï¼ | æ¯ï¼ä¿çï¼ | |
| | | | GET | `/admin-api/wxcp/callback` | ä¼ä¸å¾®ä¿¡åè° URL éªè¯ | å¦ | |
| | | | POST | `/admin-api/wxcp/callback` | ä¼ä¸å¾®ä¿¡æ¶æ¯/äºä»¶æ¥æ¶ | å¦ | |
| | | |
| | | ### 1. è·åä¼ä¸å¾®ä¿¡ææå°å |
| | | |
| | | **请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | type | Integer | æ¯ | 社交类åï¼ä¼ä¸å¾®ä¿¡åºå®ä¼ **30** | |
| | | | redirectUri | String | æ¯ | ææåè°å°åï¼å³å端 social-login 页é¢å®æ´å°å | |
| | | |
| | | **ååºï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": "https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=xxx&agentid=xxx&redirect_uri=xxx&state=xxx" |
| | | } |
| | | ``` |
| | | |
| | | å端æ¿å° `data` åç´æ¥è·³è½¬è¯¥å°åï¼ç¨æ·æ«ç 确认åä¼ä¸å¾®ä¿¡ä¼å¸¦ `code` å `state` åè·³å° `redirectUri`ã |
| | | |
| | | ### 2. ä½¿ç¨ code æ¢åç»å½ä»¤ç |
| | | |
| | | **请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | type | Integer | æ¯ | 社交类åï¼ä¼ä¸å¾®ä¿¡åºå®ä¼ **30** | |
| | | | code | String | æ¯ | ä¼ä¸å¾®ä¿¡åè°å¸¦åçææç | |
| | | | state | String | æ¯ | ä¼ä¸å¾®ä¿¡åè°å¸¦åç state | |
| | | |
| | | **ååºï¼é¦æ¬¡ç»å½ä¸å次ç»å½ç»æä¸è´ï¼ï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "userId": 123, |
| | | "accessToken": "xxx", |
| | | "refreshToken": "xxx", |
| | | "expiresTime": "2026-09-29T12:00:00" |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å
¥ |
| | | |
| | | 1. ç¨æ·ç¹å»ãä¼ä¸å¾®ä¿¡ç»å½ãâ è°ç¨ `social-auth-redirect`ï¼type=30ï¼â æ¿å°è·³è½¬å°å â æµè§å¨è·³è½¬å°ä¼ä¸å¾®ä¿¡æ«ç 页 |
| | | 2. ç¨æ·æ«ç 确认 â ä¼ä¸å¾®ä¿¡åè·³å° `redirectUri`ï¼URL æºå¸¦ `code` å `state` â ç± social-login 页颿¿æ¥ |
| | | 3. social-login 页é¢è¯»å URL ä¸ç `type`ã`code`ã`state` â è°ç¨ `social-login` |
| | | 4. åç«¯å¤æè¯¥ä¼ä¸å¾®ä¿¡è´¦å·æ¯å¦å·²ç»å®ç³»ç»ç¨æ·ï¼ |
| | | - **å·²ç»å®** â ç´æ¥ç¾å令çè¿å |
| | | - **æªç»å®ï¼é¦æ¬¡ï¼** â èªå¨å建系ç»ç¨æ·å¹¶ç»å® â ç¾å令çè¿å |
| | | 5. å端æ¿å° `accessToken` â åå
¥è®¿é®ä»¤çåå¨ â æåæéä¿¡æ¯ â 跳转é¦é¡µ |
| | | |
| | | **å
³é®ç¹ï¼ç¬¬ 4 æ¥æ è®ºé¦æ¬¡è¿æ¯å次ç»å½ï¼è¿åç»æå®å
¨ä¸è´ï¼å端ä¸éè¦åºå两æ¡åæ¯ã** |
| | | |
| | | ## åæ®µå±ç¤ºè§å |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | accessToken | ç»å½æåååå
¥è®¿é®ä»¤çåå¨ | åç»è¯·æ±å¤´æºå¸¦ | |
| | | | refreshToken | ç»å½æåååå
¥è®¿é®ä»¤çåå¨ | 令çè¿ææ¶å·æ° | |
| | | | userId | ç»å½æåååå
¥ç¨æ·ä¿¡æ¯åå¨ | ç¨äºä¸ªäººä¸å¿çåºæ¯ | |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | ä¼ä¸å¾®ä¿¡é¦æ¬¡æ«ç ï¼æªç»å®ï¼ | èªå¨å建系ç»ç¨æ·ï¼ç¨æ·å为 `wx_` + 8 ä½éæºåå
è¿å¶ï¼æµç§°åä¼ä¸å¾®ä¿¡æµç§°ï¼è´¦å·ç¶æä¸ºå¯ç¨ï¼éæºçæå¯ç ï¼ç¨æ·æ éç¥æï¼ä»
ç¨äºå ä½ï¼ | |
| | | | ä¼ä¸å¾®ä¿¡å次æ«ç ï¼å·²ç»å®ï¼ | ç´æ¥ç»å½ï¼ä¸å建æ°ç¨æ· | |
| | | | éä¼ä¸å¾®ä¿¡ç社交平å°é¦æ¬¡ç»å½ | ä»è¿å `1_002_000_005`ãæªç»å®è´¦å·ï¼éè¦è¿è¡ç»å®ãï¼éå¼å¯¼ç¨æ·èµ°è´¦å·å¯ç ç»å½å¹¶ç»å® | |
| | | | è´¦å·å¯ç ç»å½æ¶æºå¸¦ç¤¾äº¤åæ° | èµ°åæç»å®é»è¾ï¼ç»å½æåå该社交账å·ä¸è´¦å·ç»å® | |
| | | | ç»å½æåéç¥ | éè¿ä¼ä¸å¾®ä¿¡åé䏿¡ææ¬å¡çæ¶æ¯ç»è¯¥ç¨æ·ï¼å
容å«ç»å½æ¶é´ä¸ç»å½æ¹å¼ï¼ç¨æ·æªç»å®ä¼ä¸å¾®ä¿¡æ¶è·³è¿åéï¼ä¸å½±åç»å½ | |
| | | | éç¥åé失败 | ä»
è®°å½æ¥å¿ï¼**ä¸é»å¡ãä¸åæ»ç»å½**ï¼ç¨æ·ä»å¯æ£å¸¸è¿å
¥ç³»ç» | |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - ä¼ä¸å¾®ä¿¡ææç»å½è¦æ±**已夿¡åå**ä¸å¤æ¡ä¸»ä½ä¸ä¼ä¸å¾®ä¿¡è®¤è¯ä¸»ä½ä¸è´ï¼æ¬å° `localhost` è°è¯éç¨å
ç½ç©¿éå¹¶ä¿è¯åå夿¡ã |
| | | - 馿¬¡èªå¨æ³¨åä¼è·³è¿"注ååè½å·²å
³é"é
置项ï¼`system.user.register-enabled`ï¼ï¼ä½**ä»åç§æ·è´¦å·é
é¢éå¶**ï¼é
é¢ç¨å°½æ¶è¿å `USER_COUNT_MAX`ã |
| | | - èªå¨æ³¨åçæçç¨æ·å以 `wx_` å¼å¤´ï¼ä¾¿äºä¸äººå·¥å建çè´¦å·åºåï¼å¦ééå¶ä¼ä¸å¾®ä¿¡ç»å½çç¨æ·èå´ï¼éå¨ä¸å¡ä¾§æç¨æ·ååç¼æè§è²åäºæ¬¡ç®¡æ§ã |
| | | - åè°æ¥å£ `/admin-api/wxcp/callback` å·²å å
¥å
ç»å½ç½ååï¼**ä¸éè¦ä¹ä¸åºè¯¥å¸¦å端令ç**ã |
| | | - å端åæçãæªç»å® â å¼¹åºç»å®è´¦å·å¯ç å¼¹çªãé»è¾ï¼å¯¹ä¼ä¸å¾®ä¿¡åºæ¯ä¸å触åï¼è¥äº§åä¸ä»å¸æé¦æ¬¡ç»å½å
ç»å®æ¢æè´¦å·ï¼éè¦å¦è¡æç¡®ï¼å½åå®ç°ä¸ºç´æ¥èªå¨æ³¨åã |
| | | |
| | | ## é¨ç½²é
ç½®æ¸
åï¼ç¼ºä¸ä¸å¯ï¼ |
| | | |
| | | ä¼ä¸å¾®ä¿¡ç»å½è¦è·éï¼ä»¥ä¸ä¸å¤é
ç½®**å¿
须忶åå¨**ï¼ |
| | | |
| | | | # | é
ç½®ä½ç½® | å
容 | ç¼ºå¤±åæ | |
| | | |---|----------|------|----------| |
| | | | 1 | `application-<profile>.yaml` â `wx.cp.*` | corp-idãagent-idãsecretãtokenãaes-key | åè°æ¥å£æ æ³è§£å¯ï¼URL éªè¯å¤±è´¥ | |
| | | | 2 | `application-<profile>.yaml` â `justauth.type.WECHAT_ENTERPRISE` | client-idãclient-secretãagent-idãredirect-uri | è°ç¨ `social-auth-redirect` æ `NullPointerException` | |
| | | | 3 | æ°æ®åº `system_social_client` | social_type=30ãuser_type=1ãclient_id=CorpIdãclient_secret=Secretãagent_idãstatus=0ãdeleted=0 | `social-auth-redirect` æ¥"社交平å°(30) ä¸åå¨" | |
| | | |
| | | **第 2 é¡¹å®¹ææ¼æ**ï¼`AuthRequestFactory` éè¦å
ä» yaml æ¿å°"社交平å°åºç±»é
ç½®"æè½æé åºå¯¹åºç `AuthRequest` å®ä¾ï¼ä¹åæä¼ç¨æ°æ®åºéçå¯é¥è¦çå®ãæä»¥å³ä½¿å¯é¥å
¨é½é
卿°æ®åºï¼yaml éä¹å¿
é¡»ä¿çä¸ä»½å ä½é
ç½®ã |
| | | |
| | | ### åè° URL é
ç½® |
| | | |
| | | å¨ä¼ä¸å¾®ä¿¡ç®¡çåå° â åºç¨ç®¡ç â èªå»ºåºç¨ â ãæ¥æ¶æ¶æ¯ãâã设置APIæ¥æ¶ãï¼ |
| | | |
| | | - **URL**ï¼`https://<已夿¡åå>/admin-api/wxcp/callback` |
| | | - **Token**ï¼ä¸ `wx.cp.token` å®å
¨ä¸è´ |
| | | - **EncodingAESKey**ï¼ä¸ `wx.cp.aes-key` å®å
¨ä¸è´ |
| | | |
| | | ç¹ãä¿åãæ¶ä¼ä¸å¾®ä¿¡ä¼ç«å³åèµ·ä¸æ¬¡ GET éªè¯ï¼è¿å `echostr` ææå³ä¸ºéè¿ã |
| | |
| | | <module>yudao-module-bi</module> |
| | | <!-- æºè½è´¨æ£æ¥åè®¾è®¡å¹³å°æ¨¡å --> |
| | | <module>yudao-module-qcreport</module> |
| | | <!-- ä¼ä¸å¾®ä¿¡æ¨¡å --> |
| | | <module>yudao-module-wxcp</module> |
| | | <!-- è´¢å¡ç®¡ç模å --> |
| | | <module>yudao-module-finance</module> |
| | | <!-- ä¼åç®¡çæ¨¡å --> |
| | |
| | | <jimubi.version>2.3.2</jimubi.version> |
| | | <bouncycastle.version>1.80</bouncycastle.version> |
| | | <alipay-sdk-java.version>4.40.865.ALL</alipay-sdk-java.version> |
| | | <weixin-java-cp.version>4.6.0</weixin-java-cp.version> |
| | | <zxing.version>3.5.2</zxing.version> |
| | | </properties> |
| | | |
| | | <dependencyManagement> |
| | |
| | | <version>${bouncycastle.version}</version> |
| | | </dependency> |
| | | |
| | | <!-- ä¼ä¸å¾®ä¿¡ SDK --> |
| | | <dependency> |
| | | <groupId>com.github.binarywang</groupId> |
| | | <artifactId>weixin-java-cp</artifactId> |
| | | <version>${weixin-java-cp.version}</version> |
| | | </dependency> |
| | | |
| | | <!-- äºç»´ç çæï¼æ´»å¨æ¥åç ï¼ --> |
| | | <dependency> |
| | | <groupId>com.google.zxing</groupId> |
| | | <artifactId>core</artifactId> |
| | | <version>${zxing.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.google.zxing</groupId> |
| | | <artifactId>javase</artifactId> |
| | | <version>${zxing.version}</version> |
| | | </dependency> |
| | | |
| | | <!-- ç§¯æ¨æ¥è¡¨--> |
| | | <dependency> |
| | | <groupId>org.jeecgframework.jimureport</groupId> |
| | |
| | | // ===== 课é¢ç®¡ç ===== |
| | | RESEARCH_GUIDELINE("research_guideline", "è¯¾é¢æå"), |
| | | RESEARCH_APPLICATION("research_application", "课é¢ç³æ¥ä¹¦"), |
| | | RESEARCH_FUND_RECORD("research_fund_record", "ç»è´¹æ¨ä»åè¯"); |
| | | RESEARCH_FUND_RECORD("research_fund_record", "ç»è´¹æ¨ä»åè¯"), |
| | | |
| | | // ===== ä¼ä¸å¾®ä¿¡æ´»å¨ ===== |
| | | WXCP_ACTIVITY_SIGNUP("wxcp_activity_signup", "ä¼ä¸å¾®ä¿¡æ´»å¨æ¥å"); |
| | | |
| | | private final String type; |
| | | private final String description; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.system.event; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * 社交ç»å½æåäºä»¶ |
| | | * |
| | | * å½ç¨æ·éè¿ç¤¾äº¤å¹³å°ï¼å¦ä¼ä¸å¾®ä¿¡ï¼æ«ç ç»å½æååå叿¤äºä»¶ï¼ |
| | | * ä¾å
¶ä»æ¨¡åçå¬å¹¶æ§è¡åç»æä½ï¼å¦åééç¥çï¼ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Data |
| | | public class SocialLoginEvent { |
| | | |
| | | /** |
| | | * 社交类åï¼å¯¹åº {@link cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum} |
| | | */ |
| | | private final Integer socialType; |
| | | |
| | | /** |
| | | * ç³»ç»ç¨æ· ID |
| | | */ |
| | | private final Long userId; |
| | | |
| | | /** |
| | | * ç¨æ·ç»å½å |
| | | */ |
| | | private final String username; |
| | | |
| | | /** |
| | | * ç»å½æ¹å¼æè¿° |
| | | */ |
| | | private final String loginType; |
| | | |
| | | public SocialLoginEvent(Integer socialType, Long userId, String username, String loginType) { |
| | | this.socialType = socialType; |
| | | this.userId = userId; |
| | | this.username = username; |
| | | this.loginType = loginType; |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
| | | import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; |
| | | 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.oauth2.OAuth2ClientConstants; |
| | | import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; |
| | | import cn.iocoder.yudao.module.system.event.SocialLoginEvent; |
| | | import cn.iocoder.yudao.module.system.service.logger.LoginLogService; |
| | | import cn.iocoder.yudao.module.system.service.member.MemberService; |
| | | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; |
| | |
| | | import lombok.Setter; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.context.ApplicationEventPublisher; |
| | | import org.springframework.security.crypto.password.PasswordEncoder; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.Objects; |
| | | import java.util.UUID; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; |
| | |
| | | @Slf4j |
| | | public class AdminAuthServiceImpl implements AdminAuthService { |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡ç¤¾äº¤ç±»åï¼å¯¹åº {@link cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum#WECHAT_ENTERPRISE} |
| | | */ |
| | | private static final Integer WECHAT_ENTERPRISE_TYPE = 30; |
| | | |
| | | @Resource |
| | | private AdminUserService userService; |
| | | @Resource |
| | | private AdminUserMapper userMapper; |
| | | @Resource |
| | | private LoginLogService loginLogService; |
| | | @Resource |
| | |
| | | private CaptchaService captchaService; |
| | | @Resource |
| | | private SmsCodeApi smsCodeApi; |
| | | @Resource |
| | | private PasswordEncoder passwordEncoder; |
| | | @Resource |
| | | private ApplicationEventPublisher eventPublisher; |
| | | |
| | | /** |
| | | * éªè¯ç çå¼å
³ï¼é»è®¤ä¸º true |
| | |
| | | reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())); |
| | | } |
| | | // å建 Token 令çï¼è®°å½ç»å½æ¥å¿ |
| | | return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); |
| | | AuthLoginRespVO result = createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); |
| | | |
| | | // åå¸ç¤¾äº¤ç»å½äºä»¶ï¼ç»å®æ°ç¤¾äº¤è´¦å·æ¶ï¼ |
| | | if (reqVO.getSocialType() != null) { |
| | | eventPublisher.publishEvent(new SocialLoginEvent( |
| | | reqVO.getSocialType(), user.getId(), user.getUsername(), "è´¦å·å¯ç ç»å®")); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public AuthLoginRespVO socialLogin(AuthSocialLoginReqVO reqVO) { |
| | | // ä½¿ç¨ code ææç ï¼è¿è¡ç»å½ãç¶åï¼è·å¾å°ç»å®çç¨æ·ç¼å· |
| | | // 1. ä½¿ç¨ code ææç ï¼è·åç¤¾äº¤ç¨æ·ä¿¡æ¯ |
| | | SocialUserRespDTO socialUser = socialUserService.getSocialUserByCode(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), |
| | | reqVO.getCode(), reqVO.getState()); |
| | | if (socialUser == null || socialUser.getUserId() == null) { |
| | | if (socialUser == null) { |
| | | throw exception(AUTH_THIRD_LOGIN_NOT_BIND); |
| | | } |
| | | |
| | | // è·å¾ç¨æ· |
| | | AdminUserDO user = userService.getUser(socialUser.getUserId()); |
| | | if (user == null) { |
| | | throw exception(USER_NOT_EXISTS); |
| | | // 2. 夿æ¯å¦å·²æç»å®çç³»ç»ç¨æ· |
| | | Long bindUserId = socialUser.getUserId(); |
| | | AdminUserDO user; |
| | | |
| | | if (bindUserId == null) { |
| | | // 2.1 馿¬¡ç»å½ï¼èªå¨æ³¨åæ°ç¨æ· |
| | | user = autoRegisterUser(socialUser, reqVO.getType()); |
| | | // ç»å®ç¤¾äº¤è´¦å· |
| | | socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), |
| | | reqVO.getType(), reqVO.getCode(), reqVO.getState())); |
| | | log.info("[socialLogin][社交ç»å½èªå¨æ³¨å userId={} username={} socialType={}]", |
| | | user.getId(), user.getUsername(), reqVO.getType()); |
| | | } else { |
| | | // 2.2 å·²æç»å®ï¼ç´æ¥è·åç¨æ· |
| | | user = userService.getUser(bindUserId); |
| | | if (user == null) { |
| | | throw exception(USER_NOT_EXISTS); |
| | | } |
| | | } |
| | | |
| | | // å建 Token 令çï¼è®°å½ç»å½æ¥å¿ |
| | | return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); |
| | | // 3. å建 Token 令çï¼è®°å½ç»å½æ¥å¿ |
| | | AuthLoginRespVO result = createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); |
| | | |
| | | // 4. åå¸ç¤¾äº¤ç»å½äºä»¶ï¼ä¾å
¶ä»æ¨¡åçå¬ï¼å¦åéä¼ä¸å¾®ä¿¡éç¥ï¼ |
| | | eventPublisher.publishEvent(new SocialLoginEvent( |
| | | reqVO.getType(), user.getId(), user.getUsername(), "社交æ«ç ")); |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 社交ç»å½èªå¨æ³¨åç¨æ· |
| | | * |
| | | * å½ç¨æ·é¦æ¬¡éè¿ç¤¾äº¤è´¦å·ï¼å¦ä¼ä¸å¾®ä¿¡ï¼æ«ç 䏿ªç»å®æ¶ï¼èªå¨å建系ç»ç¨æ· |
| | | * |
| | | * @param socialUser ç¤¾äº¤ç¨æ·ä¿¡æ¯ |
| | | * @param socialType 社交类å |
| | | * @return æ°å建çç¨æ· |
| | | */ |
| | | private AdminUserDO autoRegisterUser(SocialUserRespDTO socialUser, Integer socialType) { |
| | | // 1. èªå¨çæç¨æ·åï¼wx_ + 8ä½éæºåå
è¿å¶ï¼ |
| | | String rawUsername = "wx_" + UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8); |
| | | |
| | | // 2. ç¡®ä¿ç¨æ·åå¯ä¸ï¼å¦æå²çªåéè¯ï¼ |
| | | String username = rawUsername; |
| | | int retry = 0; |
| | | while (userService.getUserByUsername(username) != null && retry < 5) { |
| | | username = "wx_" + UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8); |
| | | retry++; |
| | | } |
| | | if (retry >= 5) { |
| | | throw exception(USER_USERNAME_EXISTS); |
| | | } |
| | | |
| | | // 3. çæéæºå¯ç |
| | | String password = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16); |
| | | |
| | | // 4. åå»ºç¨æ· |
| | | AdminUserDO user = new AdminUserDO(); |
| | | user.setUsername(username); |
| | | user.setNickname(socialUser.getNickname() != null ? socialUser.getNickname() : username); |
| | | user.setPassword(passwordEncoder.encode(password)); |
| | | user.setStatus(CommonStatusEnum.ENABLE.getStatus()); |
| | | userMapper.insert(user); |
| | | |
| | | return user; |
| | | } |
| | | |
| | | @VisibleForTesting |
| | |
| | | |
| | | userService.updateUserPassword(userByMobile.getId(), reqVO.getPassword()); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project xmlns="http://maven.apache.org/POM/4.0.0" |
| | | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| | | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| | | <parent> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao</artifactId> |
| | | <version>${revision}</version> |
| | | </parent> |
| | | <modelVersion>4.0.0</modelVersion> |
| | | |
| | | <artifactId>yudao-module-wxcp</artifactId> |
| | | <packaging>jar</packaging> |
| | | |
| | | <name>${project.artifactId}</name> |
| | | <description>wxcp å
ä¸ï¼ä¼ä¸å¾®ä¿¡éæç®¡çã</description> |
| | | |
| | | <dependencies> |
| | | <!-- ç³»ç»æ¨¡åï¼ç¨äºè·å社交ç»å®ä¿¡æ¯çï¼ --> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-module-system</artifactId> |
| | | <version>${revision}</version> |
| | | </dependency> |
| | | |
| | | <!-- Web ç¸å
³ --> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-spring-boot-starter-web</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-spring-boot-starter-security</artifactId> |
| | | </dependency> |
| | | |
| | | <!-- DB ç¸å
³ --> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-spring-boot-starter-mybatis</artifactId> |
| | | </dependency> |
| | | |
| | | <!-- Redis --> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-spring-boot-starter-redis</artifactId> |
| | | </dependency> |
| | | |
| | | <!-- ä¼ä¸å¾®ä¿¡ SDK --> |
| | | <dependency> |
| | | <groupId>com.github.binarywang</groupId> |
| | | <artifactId>weixin-java-cp</artifactId> |
| | | </dependency> |
| | | |
| | | <!-- äºç»´ç çæï¼æ´»å¨æ¥åç ï¼ --> |
| | | <dependency> |
| | | <groupId>com.google.zxing</groupId> |
| | | <artifactId>core</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.google.zxing</groupId> |
| | | <artifactId>javase</artifactId> |
| | | </dependency> |
| | | </dependencies> |
| | | |
| | | </project> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.activity; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageBlobRespVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo.*; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityDO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupDO; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityStatusEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpSignupStatusEnum; |
| | | import cn.iocoder.yudao.module.wxcp.service.activity.WxCpActivityService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.http.HttpHeaders; |
| | | import org.springframework.http.MediaType; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | /** |
| | | * 管çåå° - ä¼ä¸å¾®ä¿¡æ´»å¨ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Tag(name = "管çåå° - ä¼ä¸å¾®ä¿¡æ´»å¨") |
| | | @RestController |
| | | @RequestMapping("/wxcp/activity") |
| | | @Validated |
| | | public class WxCpActivityController { |
| | | |
| | | @Resource |
| | | private WxCpActivityService activityService; |
| | | |
| | | // ==================== æ´»å¨ç®¡ç ==================== |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建活å¨") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:create')") |
| | | public CommonResult<Long> createActivity(@Valid @RequestBody WxCpActivitySaveReqVO createReqVO) { |
| | | return success(activityService.createActivity(createReqVO)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "ä¿®æ¹æ´»å¨") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:update')") |
| | | public CommonResult<Boolean> updateActivity(@Valid @RequestBody WxCpActivitySaveReqVO updateReqVO) { |
| | | activityService.updateActivity(updateReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å 餿´»å¨") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:delete')") |
| | | public CommonResult<Boolean> deleteActivity(@RequestParam("id") Long id) { |
| | | activityService.deleteActivity(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾æ´»å¨") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:query')") |
| | | public CommonResult<WxCpActivityRespVO> getActivity(@RequestParam("id") Long id) { |
| | | WxCpActivityDO activity = activityService.getActivity(id); |
| | | return success(buildActivityRespVO(activity)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å¾æ´»å¨å页") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:query')") |
| | | public CommonResult<PageResult<WxCpActivityRespVO>> getActivityPage(@Valid WxCpActivityPageReqVO pageReqVO) { |
| | | PageResult<WxCpActivityDO> pageResult = activityService.getActivityPage(pageReqVO); |
| | | return success(BeanUtils.toBean(pageResult, WxCpActivityRespVO.class, this::fillActivityNames)); |
| | | } |
| | | |
| | | @PutMapping("/publish") |
| | | @Operation(summary = "å叿´»å¨") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:update')") |
| | | public CommonResult<Boolean> publishActivity(@RequestParam("id") Long id) { |
| | | activityService.publishActivity(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/finish") |
| | | @Operation(summary = "ç»ææ´»å¨") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:update')") |
| | | public CommonResult<Boolean> finishActivity(@RequestParam("id") Long id) { |
| | | activityService.finishActivity(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/cancel") |
| | | @Operation(summary = "åæ¶æ´»å¨") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:update')") |
| | | public CommonResult<Boolean> cancelActivity(@RequestParam("id") Long id) { |
| | | activityService.cancelActivity(id); |
| | | return success(true); |
| | | } |
| | | |
| | | // ==================== æ¨éä¸äºç»´ç ==================== |
| | | |
| | | @PostMapping("/push") |
| | | @Operation(summary = "æ¨éæ´»å¨ç»ä¼ä¸å¾®ä¿¡æå", description = "æ¯æè¦çæ¨éèå´ä¸ç®æ ï¼ä¸ä¼ åæ²¿ç¨æ´»å¨ä¸å·²é
ç½®çå¼") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:push')") |
| | | public CommonResult<WxCpPushResultVO> pushActivity(@Valid @RequestBody WxCpPushReqVO pushReqVO) { |
| | | return success(activityService.pushActivity(pushReqVO)); |
| | | } |
| | | |
| | | @GetMapping("/qrcode") |
| | | @Operation(summary = "è·å¾æ´»å¨æ¥åäºç»´ç ", description = "è¿å PNG å¾çï¼å¯ç´æ¥ç¨äºå±ç¤ºæä¸è½½æå°") |
| | | @Parameter(name = "id", description = "æ´»å¨ç¼å·", required = true) |
| | | @Parameter(name = "size", description = "è¾¹é¿åç´ ï¼ä¸ä¼ 使ç¨é»è®¤å¼") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:query')") |
| | | public ResponseEntity<byte[]> getSignupQrCode(@RequestParam("id") Long id, |
| | | @RequestParam(value = "size", required = false) Integer size) { |
| | | byte[] png = activityService.generateSignupQrCode(id, size != null ? size : 0); |
| | | return ResponseEntity.ok() |
| | | .contentType(MediaType.IMAGE_PNG) |
| | | .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"activity-signup-" + id + ".png\"") |
| | | .body(png); |
| | | } |
| | | |
| | | @GetMapping("/signup-url") |
| | | @Operation(summary = "è·å¾æ´»å¨æ¥å页å°å", description = "便äºå端èªè¡çæäºç»´ç æå¤å¶é¾æ¥") |
| | | @Parameter(name = "id", description = "æ´»å¨ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:query')") |
| | | public CommonResult<String> getSignupUrl(@RequestParam("id") Long id) { |
| | | return success(activityService.buildSignupUrl(id)); |
| | | } |
| | | |
| | | // ==================== æ¥åè®°å½ ==================== |
| | | |
| | | @GetMapping("/signup/page") |
| | | @Operation(summary = "è·å¾æ¥åè®°å½å页") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:signup:query')") |
| | | public CommonResult<PageResult<WxCpSignupRespVO>> getSignupPage(@Valid WxCpSignupPageReqVO pageReqVO) { |
| | | PageResult<WxCpActivitySignupDO> pageResult = activityService.getSignupPage(pageReqVO); |
| | | PageResult<WxCpSignupRespVO> result = BeanUtils.toBean(pageResult, WxCpSignupRespVO.class); |
| | | // è¡¥ä¸æ´»å¨æ é¢ä¸ç¶æåï¼é¿å
å端为ä¸è¡è®°å½åæ¥ä¸æ¬¡æ´»å¨ |
| | | result.getList().forEach(vo -> { |
| | | WxCpActivityDO activity = vo.getActivityId() != null |
| | | ? activityService.getActivity(vo.getActivityId()) : null; |
| | | if (activity != null) { |
| | | vo.setActivityTitle(activity.getTitle()); |
| | | } |
| | | WxCpSignupStatusEnum status = WxCpSignupStatusEnum.valueOf(vo.getStatus()); |
| | | vo.setStatusName(status != null ? status.getName() : null); |
| | | }); |
| | | return success(result); |
| | | } |
| | | |
| | | @PutMapping("/signup/cancel") |
| | | @Operation(summary = "åæ¶æ¥å") |
| | | @Parameter(name = "id", description = "æ¥åè®°å½ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:signup:update')") |
| | | public CommonResult<Boolean> cancelSignup(@RequestParam("id") Long id) { |
| | | activityService.cancelSignup(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/signup/check-in") |
| | | @Operation(summary = "æ¥åç¾å°") |
| | | @Parameter(name = "id", description = "æ¥åè®°å½ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:signup:update')") |
| | | public CommonResult<Boolean> checkInSignup(@RequestParam("id") Long id) { |
| | | activityService.checkInSignup(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/stats") |
| | | @Operation(summary = "è·å¾æ´»å¨æ¥åç»è®¡", description = "嫿»æ°ãç¶æåå¸ãæé¨é¨åå¸ãææ¥æè¶å¿ãç¾å°ç") |
| | | @Parameter(name = "id", description = "æ´»å¨ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:activity:query')") |
| | | public CommonResult<WxCpActivityStatsRespVO> getActivityStats(@RequestParam("id") Long id) { |
| | | return success(activityService.getActivityStats(id)); |
| | | } |
| | | |
| | | @GetMapping("/signup/attachments") |
| | | @Operation(summary = "è·å¾ææ¡æ¥åè®°å½ä¸ä¼ çå¾ç/éä»¶") |
| | | @Parameter(name = "signupId", description = "æ¥åè®°å½ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:signup:query')") |
| | | public CommonResult<List<SystemStorageBlobRespVO>> getSignupAttachments(@RequestParam("signupId") Long signupId) { |
| | | return success(activityService.getSignupAttachments(signupId)); |
| | | } |
| | | |
| | | // ==================== 转æ¢è¾
å© ==================== |
| | | |
| | | private WxCpActivityRespVO buildActivityRespVO(WxCpActivityDO activity) { |
| | | if (activity == null) { |
| | | return null; |
| | | } |
| | | WxCpActivityRespVO vo = BeanUtils.toBean(activity, WxCpActivityRespVO.class); |
| | | fillActivityNames(vo); |
| | | return vo; |
| | | } |
| | | |
| | | private void fillActivityNames(WxCpActivityRespVO vo) { |
| | | WxCpActivityStatusEnum status = WxCpActivityStatusEnum.valueOf(vo.getStatus()); |
| | | vo.setStatusName(status != null ? status.getName() : null); |
| | | WxCpPushScopeEnum scope = WxCpPushScopeEnum.valueOf(vo.getPushScope()); |
| | | vo.setPushScopeName(scope != null ? scope.getName() : null); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.activity; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo.WxCpSignupPublicReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityDO; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityStatusEnum; |
| | | import cn.iocoder.yudao.module.wxcp.service.activity.WxCpActivityService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.annotation.security.PermitAll; |
| | | import jakarta.validation.Valid; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.http.MediaType; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ´»å¨ - æ¥åè½å°é¡µä¸æ¥åæ¥å£ï¼å
ç»å½ï¼ |
| | | * |
| | | * 说æï¼è¿ä¸¤ä¸ªæ¥å£å»ææ¾å¨ controller.admin å
ä¸ï¼ä»èå¸¦ä¸ /admin-api åç¼ã |
| | | * åå æ¯å端 Vite çå¼å代çåªè½¬å /admin-apiï¼è¥æ¾å° /app-apiï¼ |
| | | * æ«ç å请æ±ä¼æå°åç«¯éææå¡è¿å 404ï¼æ¥å页æä¸å¼ã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Tag(name = "ç¨æ· App - ä¼ä¸å¾®ä¿¡æ´»å¨æ¥å") |
| | | @RestController |
| | | @RequestMapping("/wxcp/activity") |
| | | @Validated |
| | | @Slf4j |
| | | public class WxCpActivityPublicController { |
| | | |
| | | private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); |
| | | |
| | | @Resource |
| | | private WxCpActivityService activityService; |
| | | |
| | | @GetMapping(value = "/signup-page", produces = MediaType.TEXT_HTML_VALUE) |
| | | @PermitAll |
| | | @Operation(summary = "æ´»å¨æ¥å页ï¼HTMLï¼", description = "äºç»´ç æåæ¤é¡µé¢ï¼ä¼ä¸å¾®ä¿¡æåæ«ç åæå¼") |
| | | @Parameter(name = "id", description = "æ´»å¨ç¼å·", required = true) |
| | | public String signupPage(@RequestParam("id") Long id, |
| | | @RequestParam(value = "wxUserId", required = false) String wxUserId) { |
| | | WxCpActivityDO activity = activityService.getActivity(id); |
| | | if (activity == null) { |
| | | return renderMessagePage("æ´»å¨ä¸åå¨", "æ´»å¨å¯è½å·²è¢«å é¤ï¼è¯·è系活å¨å起人确认ã"); |
| | | } |
| | | if (!isSignupOpen(activity)) { |
| | | return renderMessagePage("æ¥åå·²ç»æ", |
| | | "该活å¨å½åä¸å¯æ¥åï¼å¯è½æªåå¸ãå·²ç»ææä¸å¨æ¥åæ¶é´å
ï¼ã"); |
| | | } |
| | | return renderSignupPage(activity, wxUserId); |
| | | } |
| | | |
| | | @PostMapping("/signup") |
| | | @PermitAll |
| | | @Operation(summary = "æäº¤æ´»å¨æ¥å", description = "å
ç»å½ï¼æä¼ä¸å¾®ä¿¡èº«ä»½æ¶ææåè´¦å·å»éï¼å¦åæææºå·å»é") |
| | | public CommonResult<Long> signup(@Valid @RequestBody WxCpSignupPublicReqVO reqVO) { |
| | | return success(activityService.createSignup(reqVO)); |
| | | } |
| | | |
| | | /** |
| | | * æ¥åæ¯å¦å¼æ¾ï¼æ´»å¨é为已åå¸ï¼ä¸å¤äºæ¥åæ¶é´çªå£å
|
| | | */ |
| | | private boolean isSignupOpen(WxCpActivityDO activity) { |
| | | if (!WxCpActivityStatusEnum.PUBLISHED.getStatus().equals(activity.getStatus())) { |
| | | return false; |
| | | } |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | if (activity.getSignupStartTime() != null && now.isBefore(activity.getSignupStartTime())) { |
| | | return false; |
| | | } |
| | | return activity.getSignupEndTime() == null || !now.isAfter(activity.getSignupEndTime()); |
| | | } |
| | | |
| | | private String renderSignupPage(WxCpActivityDO activity, String wxUserId) { |
| | | StringBuilder sb = new StringBuilder(2048); |
| | | sb.append("<!DOCTYPE html><html lang=\"zh-CN\"><head><meta charset=\"UTF-8\">") |
| | | .append("<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1\">") |
| | | .append("<title>").append(esc(activity.getTitle())).append("</title>") |
| | | .append("<style>") |
| | | .append("*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}") |
| | | .append("body{margin:0;padding:16px;font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;") |
| | | .append("background:#f5f6f8;color:#1f2329;font-size:15px;line-height:1.6}") |
| | | .append(".card{background:#fff;border-radius:12px;padding:18px;margin-bottom:12px;box-shadow:0 1px 4px rgba(0,0,0,.06)}") |
| | | .append("h1{font-size:19px;margin:0 0 14px;font-weight:600;word-break:break-all}") |
| | | .append(".meta{color:#646a73;font-size:14px}") |
| | | .append(".meta div{margin-bottom:6px;display:flex}") |
| | | .append(".meta b{color:#1f2329;font-weight:500;flex:0 0 72px}") |
| | | .append(".meta span{flex:1;word-break:break-all}") |
| | | .append(".desc{white-space:pre-wrap;word-break:break-word;color:#3f444d}") |
| | | .append("label{display:block;font-size:14px;color:#646a73;margin:14px 0 6px}") |
| | | .append("input,textarea{width:100%;padding:11px 12px;border:1px solid #dee0e3;border-radius:8px;font-size:15px;outline:none;background:#fff;font-family:inherit}") |
| | | .append("input:focus,textarea:focus{border-color:#3370ff}") |
| | | .append("textarea{resize:vertical;min-height:84px}") |
| | | .append(".file-row{margin-top:8px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}") |
| | | .append(".file-btn{display:inline-block;padding:8px 14px;border:1px dashed #c9cdd4;border-radius:8px;color:#3370ff;font-size:14px;background:#fff}") |
| | | .append(".file-list{margin-top:8px;font-size:13px;color:#646a73;word-break:break-all}") |
| | | .append(".file-list div{padding:3px 0}") |
| | | .append("button{width:100%;margin-top:22px;padding:13px;border:0;border-radius:8px;background:#3370ff;color:#fff;font-size:16px;font-weight:500}") |
| | | .append("button:disabled{background:#a8bdf5}") |
| | | .append(".tip{margin-top:12px;font-size:12px;color:#8f959e;text-align:center}") |
| | | .append(".toast{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,.78);color:#fff;") |
| | | .append("padding:11px 20px;border-radius:8px;font-size:14px;display:none;max-width:80%;text-align:center;z-index:99}") |
| | | .append("</style></head><body>"); |
| | | |
| | | // æ´»å¨ä¿¡æ¯ |
| | | sb.append("<div class=\"card\"><h1>").append(esc(activity.getTitle())).append("</h1>") |
| | | .append("<div class=\"meta\">"); |
| | | String timeText = formatTimeRange(activity.getStartTime(), activity.getEndTime()); |
| | | if (StrUtil.isNotBlank(timeText)) { |
| | | sb.append("<div><b>æ´»å¨æ¶é´</b><span>").append(esc(timeText)).append("</span></div>"); |
| | | } |
| | | if (StrUtil.isNotBlank(activity.getLocation())) { |
| | | sb.append("<div><b>æ´»å¨å°ç¹</b><span>").append(esc(activity.getLocation())).append("</span></div>"); |
| | | } |
| | | String signupText = formatTimeRange(activity.getSignupStartTime(), activity.getSignupEndTime()); |
| | | if (StrUtil.isNotBlank(signupText)) { |
| | | sb.append("<div><b>æ¥åæ¶é´</b><span>").append(esc(signupText)).append("</span></div>"); |
| | | } |
| | | sb.append("<div><b>å·²æ¥å</b><span>").append(activity.getSignupCount() != null ? activity.getSignupCount() : 0); |
| | | if (activity.getSignupLimit() != null && activity.getSignupLimit() > 0) { |
| | | sb.append(" / ").append(activity.getSignupLimit()).append(" 人"); |
| | | } else { |
| | | sb.append(" 人ï¼ä¸éåé¢ï¼"); |
| | | } |
| | | sb.append("</span></div></div>"); |
| | | |
| | | if (StrUtil.isNotBlank(activity.getContent())) { |
| | | sb.append("<div class=\"desc\">").append(esc(activity.getContent())).append("</div>"); |
| | | } |
| | | sb.append("</div>"); |
| | | |
| | | // æ¥å表å |
| | | sb.append("<div class=\"card\">") |
| | | .append("<label>å§å <span style=\"color:#f53f3f\">*</span></label>") |
| | | .append("<input id=\"name\" type=\"text\" placeholder=\"请è¾å
¥å§å\" maxlength=\"64\" autocomplete=\"name\">") |
| | | .append("<label>ææºå· <span style=\"color:#f53f3f\">*</span></label>") |
| | | .append("<input id=\"mobile\" type=\"tel\" inputmode=\"numeric\" placeholder=\"请è¾å
¥ 11 使æºå·\" maxlength=\"11\" autocomplete=\"tel\">") |
| | | .append("<label>åä¸è¯´æ</label>") |
| | | .append("<textarea id=\"content\" placeholder=\"å¯å¡«å夿³¨ãéæ±æå
¶ä»è¯´æï¼éå¡«ï¼\" maxlength=\"1000\"></textarea>") |
| | | .append("<label>å¾ç / éä»¶</label>") |
| | | .append("<div class=\"file-row\">") |
| | | .append("<label class=\"file-btn\" for=\"files\">+ éæ©å¾çææä»¶</label>") |
| | | .append("<input id=\"files\" type=\"file\" multiple accept=\"image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt\" style=\"display:none\">") |
| | | .append("</div><div class=\"file-list\" id=\"fileList\"></div>") |
| | | .append("<button id=\"submit\">æäº¤æ¥å</button>") |
| | | .append("<div class=\"tip\">æäº¤åå¦éä¿®æ¹ï¼è¯·è系活å¨å起人</div>") |
| | | .append("</div><div class=\"toast\" id=\"toast\"></div>"); |
| | | |
| | | // æäº¤é»è¾ï¼å
ææä»¶ä¼ å° system çå
Œ
±ä¸ä¼ æ¥å£æ¿å° blobIdï¼åå¸¦ä¸ blobId æäº¤æ¥å |
| | | sb.append("<script>(function(){") |
| | | .append("var AID=").append(activity.getId()).append(";") |
| | | .append("var WXUID=").append(wxUserId == null ? "null" : "\"" + esc(jsStr(wxUserId)) + "\"").append(";") |
| | | .append("var btn=document.getElementById('submit'),toast=document.getElementById('toast');") |
| | | .append("var fileInput=document.getElementById('files'),fileList=document.getElementById('fileList');") |
| | | .append("function tip(m){toast.textContent=m;toast.style.display='block';setTimeout(function(){toast.style.display='none'},2200)}") |
| | | .append("function reset(){btn.disabled=false;btn.textContent='æäº¤æ¥å'}") |
| | | .append("fileInput.addEventListener('change',function(){") |
| | | .append("var fs=fileInput.files,h='';") |
| | | .append("for(var i=0;i<fs.length;i++){h+='<div>· '+fs[i].name+'</div>'}") |
| | | .append("fileList.innerHTML=h})") |
| | | .append("btn.addEventListener('click',function(){") |
| | | .append("var n=document.getElementById('name').value.trim();") |
| | | .append("var m=document.getElementById('mobile').value.trim();") |
| | | .append("var c=document.getElementById('content').value.trim();") |
| | | .append("if(!n){tip('请填åå§å');return}") |
| | | .append("if(!/^1[3-9]\\d{9}$/.test(m)){tip('ææºå·æ ¼å¼ä¸æ£ç¡®');return}") |
| | | .append("btn.disabled=true;") |
| | | .append("var fs=fileInput.files;") |
| | | .append("var upload=Promise.resolve([]);") |
| | | .append("if(fs.length>0){") |
| | | .append("btn.textContent='ä¸ä¼ éä»¶ä¸...';") |
| | | .append("var fd=new FormData();") |
| | | .append("for(var i=0;i<fs.length;i++){fd.append('files',fs[i])}") |
| | | .append("upload=fetch('/admin-api/system/storage-blob/public-upload',{method:'POST',body:fd})") |
| | | .append(".then(function(r){return r.json()})") |
| | | .append(".then(function(r){") |
| | | .append("if(r.code!==0){throw new Error(r.msg||'éä»¶ä¸ä¼ 失败')}") |
| | | .append("return (r.data||[]).map(function(b){return b.id})})}") |
| | | .append("upload.then(function(blobIds){") |
| | | .append("btn.textContent='æäº¤ä¸...';") |
| | | .append("var body={activityId:AID,name:n,mobile:m};") |
| | | .append("if(c){body.content=c}") |
| | | .append("if(WXUID){body.wxUserId=WXUID}") |
| | | .append("if(blobIds.length>0){body.blobIds=blobIds}") |
| | | .append("return fetch('signup',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})") |
| | | .append(".then(function(r){return r.json()})") |
| | | .append(".then(function(r){") |
| | | .append("if(r.code===0){tip('æ¥åæå');btn.textContent='å·²æ¥å';}") |
| | | .append("else{tip(r.msg||'æ¥å失败');reset()}})}") |
| | | .append(".catch(function(e){tip((e&&e.message)||'ç½ç»å¼å¸¸ï¼è¯·ç¨åéè¯');reset()})") |
| | | .append("})})();</script></body></html>"); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | private String renderMessagePage(String title, String desc) { |
| | | return "<!DOCTYPE html><html lang=\"zh-CN\"><head><meta charset=\"UTF-8\">" |
| | | + "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" |
| | | + "<title>" + esc(title) + "</title><style>" |
| | | + "body{margin:0;padding:60px 24px;font-family:-apple-system,'PingFang SC','Microsoft YaHei',sans-serif;" |
| | | + "background:#f5f6f8;color:#1f2329;text-align:center}" |
| | | + "h1{font-size:18px;font-weight:600;margin:0 0 12px}" |
| | | + "p{color:#646a73;font-size:14px;line-height:1.7;margin:0}" |
| | | + "</style></head><body><h1>" + esc(title) + "</h1><p>" + esc(desc) + "</p></body></html>"; |
| | | } |
| | | |
| | | private String formatTimeRange(LocalDateTime start, LocalDateTime end) { |
| | | if (start == null && end == null) { |
| | | return ""; |
| | | } |
| | | if (start == null) { |
| | | return "æªæ¢ " + end.format(TIME_FMT); |
| | | } |
| | | if (end == null) { |
| | | return start.format(TIME_FMT) + " èµ·"; |
| | | } |
| | | return start.format(TIME_FMT) + " ~ " + end.format(TIME_FMT); |
| | | } |
| | | |
| | | /** |
| | | * HTML 转ä¹ï¼é¿å
æ´»å¨æ é¢/å
容éçç¹æ®åç¬¦ç ´åé¡µé¢æé æ XSS |
| | | */ |
| | | private String esc(String raw) { |
| | | if (raw == null) { |
| | | return ""; |
| | | } |
| | | return raw.replace("&", "&") |
| | | .replace("<", "<") |
| | | .replace(">", ">") |
| | | .replace("\"", """) |
| | | .replace("'", "'"); |
| | | } |
| | | |
| | | /** |
| | | * JS å符串åé¢é转ä¹ï¼å¨ esc åºç¡ä¸åå¤çæ¢è¡ä¸åææ ï¼ |
| | | */ |
| | | private String jsStr(String raw) { |
| | | return raw.replace("\\", "\\\\") |
| | | .replace("\n", "\\n") |
| | | .replace("\r", "\\r"); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityStatusEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.ToString; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡æ´»å¨ å页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | public class WxCpActivityPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "æ´»å¨æ é¢ï¼æ¨¡ç³å¹é
", example = "å¢å»º") |
| | | private String title; |
| | | |
| | | @Schema(description = "ç¶æ", example = "1") |
| | | @InEnum(WxCpActivityStatusEnum.class) |
| | | private Integer status; |
| | | |
| | | @Schema(description = "æ¨éç¶æï¼0 æªæ¨éã1 å·²æ¨é", example = "1") |
| | | private Integer pushStatus; |
| | | |
| | | @Schema(description = "æ´»å¨å¼å§æ¶é´èå´") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime[] startTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡æ´»å¨ Response VO") |
| | | @Data |
| | | public class WxCpActivityRespVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private Long id; |
| | | |
| | | @Schema(description = "æ´»å¨æ é¢", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026 å¹´æ¥å£å¢å»º") |
| | | private String title; |
| | | |
| | | @Schema(description = "æ´»å¨è¯¦æ
") |
| | | private String content; |
| | | |
| | | @Schema(description = "å°é¢å¾å°å") |
| | | private String coverUrl; |
| | | |
| | | @Schema(description = "æ´»å¨å°ç¹", example = "å
¬å¸å¤§ä¼è®®å®¤") |
| | | private String location; |
| | | |
| | | @Schema(description = "æ´»å¨å¼å§æ¶é´") |
| | | private LocalDateTime startTime; |
| | | |
| | | @Schema(description = "æ´»å¨ç»ææ¶é´") |
| | | private LocalDateTime endTime; |
| | | |
| | | @Schema(description = "æ¥åå¼å§æ¶é´") |
| | | private LocalDateTime signupStartTime; |
| | | |
| | | @Schema(description = "æ¥åç»ææ¶é´") |
| | | private LocalDateTime signupEndTime; |
| | | |
| | | @Schema(description = "æ¥å人æ°ä¸éï¼0 表示ä¸é", example = "50") |
| | | private Integer signupLimit; |
| | | |
| | | @Schema(description = "å·²æ¥å人æ°", example = "12") |
| | | private Integer signupCount; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "ç¶æå", example = "å·²åå¸") |
| | | private String statusName; |
| | | |
| | | @Schema(description = "æ¨éèå´", example = "0") |
| | | private Integer pushScope; |
| | | |
| | | @Schema(description = "æ¨éèå´å", example = "å
¨å") |
| | | private String pushScopeName; |
| | | |
| | | @Schema(description = "æ¨éç®æ ") |
| | | private String pushTarget; |
| | | |
| | | @Schema(description = "æ¨éç¶æï¼0 æªæ¨éã1 å·²æ¨é", example = "1") |
| | | private Integer pushStatus; |
| | | |
| | | @Schema(description = "æè¿æ¨éæ¶é´") |
| | | private LocalDateTime pushTime; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "å建æ¶é´", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡æ´»å¨ å建/ä¿®æ¹ Request VO") |
| | | @Data |
| | | public class WxCpActivitySaveReqVO { |
| | | |
| | | @Schema(description = "ç¼å·", example = "1024") |
| | | private Long id; |
| | | |
| | | @Schema(description = "æ´»å¨æ é¢", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026 å¹´æ¥å£å¢å»º") |
| | | @NotEmpty(message = "æ´»å¨æ é¢ä¸è½ä¸ºç©º") |
| | | @Size(max = 128, message = "æ´»å¨æ é¢é¿åº¦ä¸è½è¶
è¿ 128 个å符") |
| | | private String title; |
| | | |
| | | @Schema(description = "æ´»å¨è¯¦æ
") |
| | | private String content; |
| | | |
| | | @Schema(description = "å°é¢å¾å°å") |
| | | @Size(max = 512, message = "å°é¢å¾å°åé¿åº¦ä¸è½è¶
è¿ 512 个å符") |
| | | private String coverUrl; |
| | | |
| | | @Schema(description = "æ´»å¨å°ç¹", example = "å
¬å¸å¤§ä¼è®®å®¤") |
| | | @Size(max = 255, message = "æ´»å¨å°ç¹é¿åº¦ä¸è½è¶
è¿ 255 个å符") |
| | | private String location; |
| | | |
| | | @Schema(description = "æ´»å¨å¼å§æ¶é´") |
| | | private LocalDateTime startTime; |
| | | |
| | | @Schema(description = "æ´»å¨ç»ææ¶é´") |
| | | private LocalDateTime endTime; |
| | | |
| | | @Schema(description = "æ¥åå¼å§æ¶é´") |
| | | private LocalDateTime signupStartTime; |
| | | |
| | | @Schema(description = "æ¥åç»ææ¶é´") |
| | | private LocalDateTime signupEndTime; |
| | | |
| | | @Schema(description = "æ¥å人æ°ä¸éï¼0 表示ä¸é", example = "50") |
| | | @NotNull(message = "æ¥å人æ°ä¸éä¸è½ä¸ºç©ºï¼ä¸é请填 0") |
| | | private Integer signupLimit; |
| | | |
| | | @Schema(description = "æ¨éèå´ï¼éå¡«ï¼ä¸ä¼ é»è®¤å
¨åï¼ï¼æ¨éæ¶ä»å¯å¨æ¨éå¼¹çªééæ°éæ©", example = "0") |
| | | @InEnum(WxCpPushScopeEnum.class) |
| | | private Integer pushScope; |
| | | |
| | | @Schema(description = "æ¨éç®æ ï¼é¨é¨IDææåè´¦å·ï¼å¤ä¸ªç¨éå·åé", example = "1,2 æ LiuYi,ZhangSan") |
| | | @Size(max = 1024, message = "æ¨éç®æ é¿åº¦ä¸è½è¶
è¿ 1024 个å符") |
| | | private String pushTarget; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | @Size(max = 512, message = "夿³¨é¿åº¦ä¸è½è¶
è¿ 512 个å符") |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - æ´»å¨æ¥åç»è®¡ Response VO") |
| | | @Data |
| | | public class WxCpActivityStatsRespVO { |
| | | |
| | | @Schema(description = "æ´»å¨ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private Long activityId; |
| | | |
| | | @Schema(description = "æ´»å¨æ é¢", example = "2026 å¹´æ¥å£å¢å»º") |
| | | private String activityTitle; |
| | | |
| | | @Schema(description = "æ¥åä¸éï¼0 表示ä¸é", example = "50") |
| | | private Integer signupLimit; |
| | | |
| | | @Schema(description = "æææ¥åæ°ï¼ä¸å«å·²åæ¶ï¼", example = "12") |
| | | private Integer totalSignup; |
| | | |
| | | @Schema(description = "å·²æ¥åï¼æªç¾å°ï¼", example = "8") |
| | | private Integer signedUpCount; |
| | | |
| | | @Schema(description = "å·²ç¾å°", example = "3") |
| | | private Integer checkedInCount; |
| | | |
| | | @Schema(description = "已忶", example = "1") |
| | | private Integer canceledCount; |
| | | |
| | | @Schema(description = "å©ä½åé¢ï¼ä¸éå颿¶ä¸º null", example = "38") |
| | | private Integer remaining; |
| | | |
| | | @Schema(description = "æ¥åè¿åº¦ç¾åæ¯ï¼0-100ï¼ï¼ä¸éå颿¶ä¸º null", example = "24") |
| | | private Integer signupPercent; |
| | | |
| | | @Schema(description = "ç¾å°çç¾åæ¯ï¼0-100ï¼ï¼ææææ¥åæ°è®¡ç®", example = "25") |
| | | private Integer checkInPercent; |
| | | |
| | | @Schema(description = "æé¨é¨åå¸") |
| | | private List<NameCount> deptStats; |
| | | |
| | | @Schema(description = "ææ¥åæ¥æè¶å¿") |
| | | private List<NameCount> dateStats; |
| | | |
| | | @Schema(description = "ææåè´¦å·æ¥æºåå¸ï¼ä¼ä¸å¾®ä¿¡èº«ä»½ / æ®éå¡«åï¼") |
| | | private List<NameCount> sourceStats; |
| | | |
| | | @Schema(description = "åç§°-æ°éç»è®¡é¡¹") |
| | | @Data |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public static class NameCount { |
| | | |
| | | @Schema(description = "åç§°", example = "ææ¯é¨") |
| | | private String name; |
| | | |
| | | @Schema(description = "æ°é", example = "5") |
| | | private Integer count; |
| | | |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpSignupStatusEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.ToString; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡æ´»å¨æ¥å å页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | public class WxCpSignupPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "æ´»å¨ç¼å·", example = "1024") |
| | | private Long activityId; |
| | | |
| | | @Schema(description = "å§åï¼æ¨¡ç³å¹é
", example = "å") |
| | | private String name; |
| | | |
| | | @Schema(description = "ææºå·ï¼æ¨¡ç³å¹é
", example = "138") |
| | | private String mobile; |
| | | |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | @InEnum(WxCpSignupStatusEnum.class) |
| | | private Integer status; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Pattern; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "ç¨æ· App - æ´»å¨æ¥åæäº¤ Request VOï¼å
ç»å½ï¼") |
| | | @Data |
| | | public class WxCpSignupPublicReqVO { |
| | | |
| | | @Schema(description = "æ´»å¨ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @NotNull(message = "æ´»å¨ç¼å·ä¸è½ä¸ºç©º") |
| | | private Long activityId; |
| | | |
| | | @Schema(description = "å§å", requiredMode = Schema.RequiredMode.REQUIRED, example = "åæ") |
| | | @NotEmpty(message = "å§åä¸è½ä¸ºç©º") |
| | | @Size(max = 64, message = "å§åé¿åº¦ä¸è½è¶
è¿ 64 个å符") |
| | | private String name; |
| | | |
| | | @Schema(description = "ææºå·", requiredMode = Schema.RequiredMode.REQUIRED, example = "13800138000") |
| | | @NotEmpty(message = "ææºå·ä¸è½ä¸ºç©º") |
| | | @Pattern(regexp = "^1[3-9]\\d{9}$", message = "ææºå·æ ¼å¼ä¸æ£ç¡®") |
| | | private String mobile; |
| | | |
| | | @Schema(description = "ä¼ä¸å¾®ä¿¡æåè´¦å·ï¼ä»ä¼ä¸å¾®ä¿¡å
æå¼æ¶ç±å端èªå¨å¸¦ä¸ï¼æ®éæµè§å¨æ¥åå¯ä¸ä¼ ") |
| | | @Size(max = 128, message = "ä¼ä¸å¾®ä¿¡æåè´¦å·é¿åº¦ä¸è½è¶
è¿ 128 个å符") |
| | | private String wxUserId; |
| | | |
| | | @Schema(description = "é¨é¨åç§°") |
| | | @Size(max = 255, message = "é¨é¨åç§°é¿åº¦ä¸è½è¶
è¿ 255 个å符") |
| | | private String deptName; |
| | | |
| | | @Schema(description = "åä¸è¯´æï¼æåå¡«åçæåå
容ï¼") |
| | | private String content; |
| | | |
| | | @Schema(description = "éä»¶æä»¶ç¼å·å表", example = "[1,2]") |
| | | private List<Long> blobIds; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | @Size(max = 512, message = "夿³¨é¿åº¦ä¸è½è¶
è¿ 512 个å符") |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡æ´»å¨æ¥å Response VO") |
| | | @Data |
| | | public class WxCpSignupRespVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") |
| | | private Long id; |
| | | |
| | | @Schema(description = "æ´»å¨ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private Long activityId; |
| | | |
| | | @Schema(description = "æ´»å¨æ é¢", example = "2026 å¹´æ¥å£å¢å»º") |
| | | private String activityTitle; |
| | | |
| | | @Schema(description = "ä¼ä¸å¾®ä¿¡æåè´¦å·") |
| | | private String wxUserId; |
| | | |
| | | @Schema(description = "å§å", example = "åæ") |
| | | private String name; |
| | | |
| | | @Schema(description = "ææºå·", example = "13800138000") |
| | | private String mobile; |
| | | |
| | | @Schema(description = "é¨é¨åç§°") |
| | | private String deptName; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "ç¶æå", example = "å·²æ¥å") |
| | | private String statusName; |
| | | |
| | | @Schema(description = "æ¥åæ¶é´") |
| | | private LocalDateTime signupTime; |
| | | |
| | | @Schema(description = "ç¾å°æ¶é´") |
| | | private LocalDateTime checkInTime; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.callback; |
| | | |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.Parameters; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.servlet.http.HttpServletRequest; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import me.chanjar.weixin.common.util.crypto.SHA1; |
| | | import me.chanjar.weixin.cp.api.WxCpService; |
| | | import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; |
| | | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; |
| | | import me.chanjar.weixin.cp.bean.message.WxCpXmlOutTextMessage; |
| | | import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡åè° Controller |
| | | * |
| | | * ç¨äºæ¥æ¶ä¼ä¸å¾®ä¿¡çåè°éç¥ï¼å
æ¬ URL éªè¯åæ¶æ¯/äºä»¶æ¨é |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Tag(name = "管çåå° - ä¼ä¸å¾®ä¿¡åè°") |
| | | @RestController |
| | | @RequestMapping("/wxcp/callback") |
| | | @Slf4j |
| | | public class WxCpCallbackController { |
| | | |
| | | @Resource |
| | | private WxCpService wxCpService; |
| | | |
| | | /** |
| | | * GET è¯·æ± â ä¼ä¸å¾®ä¿¡åè° URL éªè¯ |
| | | * |
| | | * ä¼ä¸å¾®ä¿¡ç®¡çåå°é
ç½®"æ¥æ¶æ¶æ¯"URL æ¶ï¼ä¼åé GET 请æ±éªè¯ URL æææ§ |
| | | * éè¦å¨éªè¯éè¿ååæ ·è¿å echostr è§£å¯åçå
容 |
| | | */ |
| | | @GetMapping(produces = "text/plain;charset=utf-8") |
| | | @Operation(summary = "ä¼ä¸å¾®ä¿¡åè° URL éªè¯") |
| | | @Parameters({ |
| | | @Parameter(name = "msg_signature", description = "ä¼ä¸å¾®ä¿¡å å¯ç¾å"), |
| | | @Parameter(name = "timestamp", description = "æ¶é´æ³"), |
| | | @Parameter(name = "nonce", description = "éæºæ°"), |
| | | @Parameter(name = "echostr", description = "å å¯çå符串ï¼éè¦è§£å¯åæ¾") |
| | | }) |
| | | public String verifyUrl(@RequestParam("msg_signature") String msgSignature, |
| | | @RequestParam("timestamp") String timestamp, |
| | | @RequestParam("nonce") String nonce, |
| | | @RequestParam("echostr") String echostr) { |
| | | log.info("[verifyUrl][æ¶å°ä¼ä¸å¾®ä¿¡ URL éªè¯è¯·æ± msg_signature={} timestamp={} nonce={} echostr={}]", |
| | | msgSignature, timestamp, nonce, echostr); |
| | | try { |
| | | // 1. éªè¯ç¾å |
| | | // 注æï¼SHA1.gen è¿åå°ååå
è¿å¶ï¼ä¼ä¸å¾®ä¿¡ä¸åç msg_signature 乿¯å°åï¼ |
| | | // å æ¤å¿
é¡»ç¨ equalsIgnoreCase æ¯è¾ï¼è¥æä¼ å
¥å¼è½¬å¤§ååä¸è®¡ç®å¼æ¯è¾ï¼ä¼æ°¸è¿ä¸ç¸ç |
| | | String signature = SHA1.gen(wxCpService.getWxCpConfigStorage().getToken(), timestamp, nonce, echostr); |
| | | if (!signature.equalsIgnoreCase(msgSignature)) { |
| | | log.error("[verifyUrl][ç¾åéªè¯å¤±è´¥ï¼è®¡ç®ç¾å={}ï¼ä¼ å
¥ç¾å={}]", signature, msgSignature); |
| | | return "error: signature verification failed"; |
| | | } |
| | | |
| | | // 2. è§£å¯ echostr å¹¶è¿åï¼ä½¿ç¨ WxCpCryptUtilï¼ |
| | | WxCpCryptUtil cryptUtil = new WxCpCryptUtil(wxCpService.getWxCpConfigStorage()); |
| | | String plainText = cryptUtil.decrypt(echostr); |
| | | log.info("[verifyUrl][URL éªè¯æå]"); |
| | | return plainText; |
| | | } catch (Exception e) { |
| | | log.error("[verifyUrl][URL éªè¯å¤±è´¥]", e); |
| | | return "error: " + e.getMessage(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * POST è¯·æ± â æ¥æ¶ä¼ä¸å¾®ä¿¡æ¨éçæ¶æ¯åäºä»¶ |
| | | * |
| | | * å
æ¬ï¼ææ¬æ¶æ¯ãå¾çæ¶æ¯ãè¿å
¥åºç¨äºä»¶ãæ«ç äºä»¶ç |
| | | */ |
| | | @PostMapping(produces = "application/xml;charset=UTF-8") |
| | | @Operation(summary = "æ¥æ¶ä¼ä¸å¾®ä¿¡æ¶æ¯/äºä»¶æ¨é") |
| | | @Parameters({ |
| | | @Parameter(name = "msg_signature", description = "ä¼ä¸å¾®ä¿¡å å¯ç¾å"), |
| | | @Parameter(name = "timestamp", description = "æ¶é´æ³"), |
| | | @Parameter(name = "nonce", description = "éæºæ°"), |
| | | }) |
| | | public String handleMessage(HttpServletRequest request, |
| | | @RequestParam("msg_signature") String msgSignature, |
| | | @RequestParam("timestamp") String timestamp, |
| | | @RequestParam("nonce") String nonce, |
| | | @RequestBody String requestBody) { |
| | | log.info("[handleMessage][æ¶å°ä¼ä¸å¾®ä¿¡æ¶æ¯æ¨é msg_signature={} timestamp={} nonce={} body={}]", |
| | | msgSignature, timestamp, nonce, requestBody); |
| | | |
| | | try { |
| | | // 1. è§£å¯æ¶æ¯ |
| | | WxCpXmlMessage inMessage = WxCpXmlMessage.fromEncryptedXml( |
| | | requestBody, wxCpService.getWxCpConfigStorage(), timestamp, nonce, msgSignature); |
| | | if (inMessage == null) { |
| | | log.warn("[handleMessage][æ¶æ¯è§£å¯å¤±è´¥ï¼è¿å空对象]"); |
| | | return "error: decrypt failed"; |
| | | } |
| | | |
| | | log.info("[handleMessage][è§£å¯æå msgType={} event={} fromUser={}]", |
| | | inMessage.getMsgType(), inMessage.getEvent(), inMessage.getFromUserName()); |
| | | |
| | | // 2. æ ¹æ®æ¶æ¯ç±»åå¤ç |
| | | WxCpXmlOutMessage outMessage = route(inMessage); |
| | | |
| | | // 3. è¿åååº |
| | | if (outMessage != null) { |
| | | String response = outMessage.toEncryptedXml(wxCpService.getWxCpConfigStorage()); |
| | | log.info("[handleMessage][ååºæ¶æ¯: {}]", response); |
| | | return response; |
| | | } |
| | | return "success"; |
| | | } catch (Exception e) { |
| | | log.error("[handleMessage][å¤çæ¶æ¯å¼å¸¸]", e); |
| | | return "success"; // ä¼ä¸å¾®ä¿¡è¦æ±æ 论å¦ä½é½è¦è¿å success |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è·¯ç±æ¶æ¯å°å¯¹åºçå¤çå¨ |
| | | * |
| | | * @param inMessage è§£å¯åçæ¶æ¯ |
| | | * @return ååºæ¶æ¯ï¼å¯ä¸º nullï¼ |
| | | */ |
| | | private WxCpXmlOutMessage route(WxCpXmlMessage inMessage) { |
| | | // äºä»¶ç±»å |
| | | if ("event".equals(inMessage.getMsgType())) { |
| | | String event = inMessage.getEvent(); |
| | | if ("enter_agent".equals(event)) { |
| | | log.info("[route][è¿å
¥åºç¨] ä»ç¨æ· {}", inMessage.getFromUserName()); |
| | | return buildTextReply(inMessage, "欢è¿è¿å
¥åºç¨ï¼"); |
| | | } else if ("subscribe".equals(event)) { |
| | | log.info("[route][ç¨æ·å
³æ³¨]"); |
| | | return buildTextReply(inMessage, "欢è¿å
³æ³¨ï¼"); |
| | | } else if ("change_contact".equals(event)) { |
| | | log.info("[route][é讯å½åæ´] åæ´ç±»å={}", inMessage.getChangeType()); |
| | | return null; |
| | | } else { |
| | | log.info("[route][æªå¤çäºä»¶ç±»å={}]", event); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | // ææ¬æ¶æ¯ |
| | | if ("text".equals(inMessage.getMsgType())) { |
| | | log.info("[route][æ¶å°ææ¬æ¶æ¯ from={} content={}]", |
| | | inMessage.getFromUserName(), inMessage.getContent()); |
| | | return buildTextReply(inMessage, "å·²æ¶å°æ¨çæ¶æ¯"); |
| | | } |
| | | |
| | | log.info("[route][æªå¤çæ¶æ¯ç±»å={}]", inMessage.getMsgType()); |
| | | return null; |
| | | } |
| | | |
| | | private WxCpXmlOutMessage buildTextReply(WxCpXmlMessage inMessage, String content) { |
| | | WxCpXmlOutTextMessage out = new WxCpXmlOutTextMessage(); |
| | | out.setContent(content); |
| | | out.setCreateTime(System.currentTimeMillis() / 1000); |
| | | out.setFromUserName(inMessage.getToUserName()); |
| | | out.setToUserName(inMessage.getFromUserName()); |
| | | out.setMsgType("text"); |
| | | return out; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.member; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.member.vo.WxCpDepartmentRespVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.member.vo.WxCpMemberRespVO; |
| | | import cn.iocoder.yudao.module.wxcp.service.member.WxCpMemberService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | /** |
| | | * 管çåå° - ä¼ä¸å¾®ä¿¡é讯å½ï¼æåä¸é¨é¨ï¼ |
| | | * |
| | | * æ°æ®å®æ¶åèªä¼ä¸å¾®ä¿¡ï¼å¯ç¨äºæ¨éæ¶æéç®æ æå/é¨é¨ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Tag(name = "管çåå° - ä¼ä¸å¾®ä¿¡é讯å½") |
| | | @RestController |
| | | @RequestMapping("/wxcp/member") |
| | | @Validated |
| | | public class WxCpMemberController { |
| | | |
| | | @Resource |
| | | private WxCpMemberService memberService; |
| | | |
| | | @GetMapping("/department/tree") |
| | | @Operation(summary = "è·å¾é¨é¨æ ") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:member:query')") |
| | | public CommonResult<List<WxCpDepartmentRespVO>> getDepartmentTree() { |
| | | return success(memberService.getDepartmentTree()); |
| | | } |
| | | |
| | | @GetMapping("/department/list") |
| | | @Operation(summary = "è·å¾é¨é¨å¹³éºå表", description = "便äºåä¸æéæ©ï¼æ éèªè¡æå¹³æ ") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:member:query')") |
| | | public CommonResult<List<WxCpDepartmentRespVO>> getDepartmentList() { |
| | | return success(memberService.getDepartmentList()); |
| | | } |
| | | |
| | | @GetMapping("/list") |
| | | @Operation(summary = "è·å¾æåå表") |
| | | @Parameter(name = "departmentId", description = "é¨é¨IDï¼ä¸ä¼ åæ ¹é¨é¨") |
| | | @Parameter(name = "fetchChild", description = "æ¯å¦å
å«åé¨é¨æåï¼é»è®¤ true") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:member:query')") |
| | | public CommonResult<List<WxCpMemberRespVO>> getMemberList( |
| | | @RequestParam(value = "departmentId", required = false) Long departmentId, |
| | | @RequestParam(value = "fetchChild", required = false) Boolean fetchChild) { |
| | | return success(memberService.getMemberList(departmentId, fetchChild)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.member.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡é¨é¨ Response VO") |
| | | @Data |
| | | public class WxCpDepartmentRespVO { |
| | | |
| | | @Schema(description = "é¨é¨ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "é¨é¨åç§°", example = "ææ¯é¨") |
| | | private String name; |
| | | |
| | | @Schema(description = "ç¶é¨é¨ID", example = "0") |
| | | private Long parentId; |
| | | |
| | | @Schema(description = "å¨ç¶é¨é¨ä¸ç次åºå¼", example = "1") |
| | | private Long order; |
| | | |
| | | @Schema(description = "åé¨é¨") |
| | | private List<WxCpDepartmentRespVO> children = new ArrayList<>(); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.member.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡æå Response VO") |
| | | @Data |
| | | public class WxCpMemberRespVO { |
| | | |
| | | @Schema(description = "æåè´¦å·ï¼UserIdï¼ï¼å¯ä½ä¸ºæ¨éç®æ ", requiredMode = Schema.RequiredMode.REQUIRED, example = "LiuYi") |
| | | private String userId; |
| | | |
| | | @Schema(description = "æååç§°", example = "åæ") |
| | | private String name; |
| | | |
| | | @Schema(description = "ææºå·", example = "13800138000") |
| | | private String mobile; |
| | | |
| | | @Schema(description = "é®ç®±") |
| | | private String email; |
| | | |
| | | @Schema(description = "头åå°å") |
| | | private String avatar; |
| | | |
| | | @Schema(description = "èä½", example = "å¼åå·¥ç¨å¸") |
| | | private String position; |
| | | |
| | | @Schema(description = "æ§å«ï¼0æªå®ä¹ 1ç· 2女", example = "1") |
| | | private String gender; |
| | | |
| | | @Schema(description = "æ¯å¦å¯ç¨ï¼1å·²å¯ç¨ 2å·²ç¦ç¨ 4æªæ¿æ´» 5éåºä¼ä¸", example = "1") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "æå±é¨é¨IDå表", example = "[1,2]") |
| | | private List<Long> departmentIds; |
| | | |
| | | @Schema(description = "æå±é¨é¨åç§°ï¼ä¾¿äºåè¡¨ç´æ¥å±ç¤º", example = "ææ¯é¨") |
| | | private String departmentNames; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.notice; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo.WxCpNoticePageReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo.WxCpNoticeRespVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo.WxCpNoticeSaveReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.notice.WxCpNoticeDO; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeStatusEnum; |
| | | import cn.iocoder.yudao.module.wxcp.service.notice.WxCpNoticeService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | /** |
| | | * 管çåå° - ä¼ä¸å¾®ä¿¡éç¥ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Tag(name = "管çåå° - ä¼ä¸å¾®ä¿¡éç¥") |
| | | @RestController |
| | | @RequestMapping("/wxcp/notice") |
| | | @Validated |
| | | public class WxCpNoticeController { |
| | | |
| | | @Resource |
| | | private WxCpNoticeService noticeService; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建éç¥") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:notice:create')") |
| | | public CommonResult<Long> createNotice(@Valid @RequestBody WxCpNoticeSaveReqVO createReqVO) { |
| | | return success(noticeService.createNotice(createReqVO)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "ä¿®æ¹éç¥") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:notice:update')") |
| | | public CommonResult<Boolean> updateNotice(@Valid @RequestBody WxCpNoticeSaveReqVO updateReqVO) { |
| | | noticeService.updateNotice(updateReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å é¤éç¥") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:notice:delete')") |
| | | public CommonResult<Boolean> deleteNotice(@RequestParam("id") Long id) { |
| | | noticeService.deleteNotice(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾éç¥") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:notice:query')") |
| | | public CommonResult<WxCpNoticeRespVO> getNotice(@RequestParam("id") Long id) { |
| | | WxCpNoticeDO notice = noticeService.getNotice(id); |
| | | return success(buildRespVO(notice)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å¾éç¥å页") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:notice:query')") |
| | | public CommonResult<PageResult<WxCpNoticeRespVO>> getNoticePage(@Valid WxCpNoticePageReqVO pageReqVO) { |
| | | PageResult<WxCpNoticeDO> pageResult = noticeService.getNoticePage(pageReqVO); |
| | | return success(BeanUtils.toBean(pageResult, WxCpNoticeRespVO.class, this::fillNames)); |
| | | } |
| | | |
| | | @PutMapping("/publish") |
| | | @Operation(summary = "åå¸éç¥") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:notice:update')") |
| | | public CommonResult<Boolean> publishNotice(@RequestParam("id") Long id) { |
| | | noticeService.publishNotice(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/revoke") |
| | | @Operation(summary = "æ¤åéç¥") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('wxcp:notice:update')") |
| | | public CommonResult<Boolean> revokeNotice(@RequestParam("id") Long id) { |
| | | noticeService.revokeNotice(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @PostMapping("/push") |
| | | @Operation(summary = "æ¨ééç¥ç»ä¼ä¸å¾®ä¿¡æå", description = "æ¯æè¦çæ¨éèå´ä¸ç®æ ï¼ä¸ä¼ åæ²¿ç¨éç¥ä¸å·²é
ç½®çå¼") |
| | | @PreAuthorize("@ss.hasPermission('wxcp:notice:push')") |
| | | public CommonResult<WxCpPushResultVO> pushNotice(@Valid @RequestBody WxCpPushReqVO pushReqVO) { |
| | | return success(noticeService.pushNotice(pushReqVO)); |
| | | } |
| | | |
| | | // ==================== 转æ¢è¾
å© ==================== |
| | | |
| | | private WxCpNoticeRespVO buildRespVO(WxCpNoticeDO notice) { |
| | | if (notice == null) { |
| | | return null; |
| | | } |
| | | WxCpNoticeRespVO vo = BeanUtils.toBean(notice, WxCpNoticeRespVO.class); |
| | | fillNames(vo); |
| | | return vo; |
| | | } |
| | | |
| | | private void fillNames(WxCpNoticeRespVO vo) { |
| | | WxCpNoticeStatusEnum status = WxCpNoticeStatusEnum.valueOf(vo.getStatus()); |
| | | vo.setStatusName(status != null ? status.getName() : null); |
| | | WxCpPushScopeEnum scope = WxCpPushScopeEnum.valueOf(vo.getPushScope()); |
| | | vo.setPushScopeName(scope != null ? scope.getName() : null); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeStatusEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.ToString; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡éç¥ å页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | public class WxCpNoticePageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "éç¥æ é¢ï¼æ¨¡ç³å¹é
", example = "æ¾å") |
| | | private String title; |
| | | |
| | | @Schema(description = "ç¶æ", example = "1") |
| | | @InEnum(WxCpNoticeStatusEnum.class) |
| | | private Integer status; |
| | | |
| | | @Schema(description = "æ¨éç¶æï¼0 æªæ¨éã1 å·²æ¨é", example = "1") |
| | | private Integer pushStatus; |
| | | |
| | | @Schema(description = "å建æ¶é´èå´") |
| | | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| | | private LocalDateTime[] createTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡éç¥ Response VO") |
| | | @Data |
| | | public class WxCpNoticeRespVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private Long id; |
| | | |
| | | @Schema(description = "éç¥æ é¢", requiredMode = Schema.RequiredMode.REQUIRED, example = "å
³äºå½åºæ¾åçéç¥") |
| | | private String title; |
| | | |
| | | @Schema(description = "éç¥æ£æ") |
| | | private String content; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "ç¶æå", example = "å·²åå¸") |
| | | private String statusName; |
| | | |
| | | @Schema(description = "æ¨éèå´", example = "0") |
| | | private Integer pushScope; |
| | | |
| | | @Schema(description = "æ¨éèå´å", example = "å
¨å") |
| | | private String pushScopeName; |
| | | |
| | | @Schema(description = "æ¨éç®æ ") |
| | | private String pushTarget; |
| | | |
| | | @Schema(description = "æ¨éç¶æï¼0 æªæ¨éã1 å·²æ¨é", example = "1") |
| | | private Integer pushStatus; |
| | | |
| | | @Schema(description = "æè¿æ¨éæ¶é´") |
| | | private LocalDateTime pushTime; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡éç¥ å建/ä¿®æ¹ Request VO") |
| | | @Data |
| | | public class WxCpNoticeSaveReqVO { |
| | | |
| | | @Schema(description = "ç¼å·", example = "1024") |
| | | private Long id; |
| | | |
| | | @Schema(description = "éç¥æ é¢", requiredMode = Schema.RequiredMode.REQUIRED, example = "å
³äºå½åºæ¾åçéç¥") |
| | | @NotEmpty(message = "éç¥æ é¢ä¸è½ä¸ºç©º") |
| | | @Size(max = 128, message = "éç¥æ é¢é¿åº¦ä¸è½è¶
è¿ 128 个å符") |
| | | private String title; |
| | | |
| | | @Schema(description = "éç¥æ£æ", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @NotEmpty(message = "éç¥æ£æä¸è½ä¸ºç©º") |
| | | private String content; |
| | | |
| | | @Schema(description = "æ¨éèå´ï¼éå¡«ï¼ä¸ä¼ é»è®¤å
¨åï¼ï¼æ¨éæ¶ä»å¯å¨æ¨éå¼¹çªééæ°éæ©", example = "0") |
| | | @InEnum(WxCpPushScopeEnum.class) |
| | | private Integer pushScope; |
| | | |
| | | @Schema(description = "æ¨éç®æ ï¼é¨é¨IDææåè´¦å·ï¼å¤ä¸ªç¨éå·åé", example = "1,2 æ LiuYi,ZhangSan") |
| | | @Size(max = 1024, message = "æ¨éç®æ é¿åº¦ä¸è½è¶
è¿ 1024 个å符") |
| | | private String pushTarget; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | @Size(max = 512, message = "夿³¨é¿åº¦ä¸è½è¶
è¿ 512 个å符") |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡æ¨é Request VOï¼æ´»å¨ä¸éç¥å
±ç¨ï¼") |
| | | @Data |
| | | public class WxCpPushReqVO { |
| | | |
| | | @Schema(description = "ä¸å¡ç¼å·ï¼æ´»å¨ç¼å·æéç¥ç¼å·ï¼", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @NotNull(message = "ç¼å·ä¸è½ä¸ºç©º") |
| | | private Long id; |
| | | |
| | | @Schema(description = "æ¨éèå´ï¼ä¸ä¼ åæ²¿ç¨è®°å½ä¸å·²é
ç½®çèå´", example = "0") |
| | | @InEnum(WxCpPushScopeEnum.class) |
| | | private Integer pushScope; |
| | | |
| | | @Schema(description = "æ¨éç®æ ï¼ä¸ä¼ åæ²¿ç¨è®°å½ä¸å·²é
ç½®çç®æ ", example = "LiuYi,ZhangSan") |
| | | @Size(max = 1024, message = "æ¨éç®æ é¿åº¦ä¸è½è¶
è¿ 1024 个å符") |
| | | private String pushTarget; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.controller.admin.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | @Schema(description = "管çåå° - ä¼ä¸å¾®ä¿¡æ¶æ¯æ¨éç»æ VO") |
| | | @Data |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class WxCpPushResultVO { |
| | | |
| | | @Schema(description = "æ¯å¦æ¨éæå", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") |
| | | private Boolean success; |
| | | |
| | | @Schema(description = "ä¼ä¸å¾®ä¿¡è¿åçé误ç ï¼0 表示æå", example = "0") |
| | | private Integer errCode; |
| | | |
| | | @Schema(description = "ç»æè¯´æ") |
| | | private String message; |
| | | |
| | | @Schema(description = "å®é
åéçæåè´¦å·ï¼å¤ä¸ªç¨ | åéï¼") |
| | | private String toUser; |
| | | |
| | | @Schema(description = "å®é
åéçé¨é¨IDï¼å¤ä¸ªç¨ | åéï¼") |
| | | private String toParty; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.dal.dataobject.activity; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityStatusEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.*; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ´»å¨ DO |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @TableName("wxcp_activity") |
| | | @KeySequence("wxcp_activity_seq") // ç¨äº OracleãPostgreSQLãKingbaseãDB2ãH2 æ°æ®åºç主é®èªå¢ãå¦ææ¯ MySQL çæ°æ®åºï¼å¯ä¸åã |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class WxCpActivityDO extends BaseDO { |
| | | |
| | | /** |
| | | * ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * æ´»å¨æ é¢ |
| | | */ |
| | | private String title; |
| | | /** |
| | | * æ´»å¨è¯¦æ
|
| | | */ |
| | | private String content; |
| | | /** |
| | | * å°é¢å¾å°å |
| | | */ |
| | | private String coverUrl; |
| | | /** |
| | | * æ´»å¨å°ç¹ |
| | | */ |
| | | private String location; |
| | | |
| | | /** |
| | | * æ´»å¨å¼å§æ¶é´ |
| | | */ |
| | | private LocalDateTime startTime; |
| | | /** |
| | | * æ´»å¨ç»ææ¶é´ |
| | | */ |
| | | private LocalDateTime endTime; |
| | | /** |
| | | * æ¥åå¼å§æ¶é´ |
| | | */ |
| | | private LocalDateTime signupStartTime; |
| | | /** |
| | | * æ¥åç»ææ¶é´ |
| | | */ |
| | | private LocalDateTime signupEndTime; |
| | | |
| | | /** |
| | | * æ¥å人æ°ä¸éï¼0 表示ä¸é |
| | | */ |
| | | private Integer signupLimit; |
| | | /** |
| | | * å·²æ¥å人æ°ï¼åä½å段ï¼ä¾¿äºå表å±ç¤ºï¼ |
| | | */ |
| | | private Integer signupCount; |
| | | |
| | | /** |
| | | * ç¶æ |
| | | * |
| | | * æä¸¾ {@link WxCpActivityStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | |
| | | /** |
| | | * æ¨éèå´ |
| | | * |
| | | * æä¸¾ {@link WxCpPushScopeEnum} |
| | | */ |
| | | private Integer pushScope; |
| | | /** |
| | | * æ¨éç®æ ï¼é¨é¨IDææåè´¦å·ï¼å¤ä¸ªç¨éå·åé |
| | | */ |
| | | private String pushTarget; |
| | | /** |
| | | * æ¨éç¶æï¼0 æªæ¨éã1 å·²æ¨é |
| | | */ |
| | | private Integer pushStatus; |
| | | /** |
| | | * æè¿æ¨éæ¶é´ |
| | | */ |
| | | private LocalDateTime pushTime; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.dal.dataobject.activity; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpSignupStatusEnum; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.*; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ´»å¨æ¥åè®°å½ DO |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @TableName("wxcp_activity_signup") |
| | | @KeySequence("wxcp_activity_signup_seq") // ç¨äº OracleãPostgreSQLãKingbaseãDB2ãH2 æ°æ®åºç主é®èªå¢ãå¦ææ¯ MySQL çæ°æ®åºï¼å¯ä¸åã |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class WxCpActivitySignupDO extends BaseDO { |
| | | |
| | | /** |
| | | * ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * æ´»å¨ç¼å· |
| | | * |
| | | * å
³è {@link WxCpActivityDO#getId()} |
| | | */ |
| | | private Long activityId; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æåè´¦å·ï¼UserIdï¼ |
| | | */ |
| | | private String wxUserId; |
| | | /** |
| | | * å§å |
| | | */ |
| | | private String name; |
| | | /** |
| | | * ææºå· |
| | | */ |
| | | private String mobile; |
| | | /** |
| | | * é¨é¨åç§° |
| | | */ |
| | | private String deptName; |
| | | /** |
| | | * åä¸è¯´æï¼æåæäº¤çæåå
å®¹ï¼ |
| | | */ |
| | | private String content; |
| | | |
| | | /** |
| | | * ç¶æ |
| | | * |
| | | * æä¸¾ {@link WxCpSignupStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | /** |
| | | * æ¥åæ¶é´ |
| | | */ |
| | | private LocalDateTime signupTime; |
| | | /** |
| | | * ç¾å°æ¶é´ |
| | | */ |
| | | private LocalDateTime checkInTime; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.dal.dataobject.notice; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeStatusEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.*; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡éç¥ DO |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @TableName("wxcp_notice") |
| | | @KeySequence("wxcp_notice_seq") // ç¨äº OracleãPostgreSQLãKingbaseãDB2ãH2 æ°æ®åºç主é®èªå¢ãå¦ææ¯ MySQL çæ°æ®åºï¼å¯ä¸åã |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class WxCpNoticeDO extends BaseDO { |
| | | |
| | | /** |
| | | * ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * éç¥æ é¢ |
| | | */ |
| | | private String title; |
| | | /** |
| | | * éç¥æ£æ |
| | | */ |
| | | private String content; |
| | | |
| | | /** |
| | | * ç¶æ |
| | | * |
| | | * æä¸¾ {@link WxCpNoticeStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | |
| | | /** |
| | | * æ¨éèå´ |
| | | * |
| | | * æä¸¾ {@link WxCpPushScopeEnum} |
| | | */ |
| | | private Integer pushScope; |
| | | /** |
| | | * æ¨éç®æ ï¼é¨é¨IDææåè´¦å·ï¼å¤ä¸ªç¨éå·åé |
| | | */ |
| | | private String pushTarget; |
| | | /** |
| | | * æ¨éç¶æï¼0 æªæ¨éã1 å·²æ¨é |
| | | */ |
| | | private Integer pushStatus; |
| | | /** |
| | | * æè¿æ¨éæ¶é´ |
| | | */ |
| | | private LocalDateTime pushTime; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.dal.mysql.activity; |
| | | |
| | | 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.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo.WxCpActivityPageReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ´»å¨ Mapper |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Mapper |
| | | public interface WxCpActivityMapper extends BaseMapperX<WxCpActivityDO> { |
| | | |
| | | default PageResult<WxCpActivityDO> selectPage(WxCpActivityPageReqVO reqVO) { |
| | | return selectPage(reqVO, new LambdaQueryWrapperX<WxCpActivityDO>() |
| | | .likeIfPresent(WxCpActivityDO::getTitle, reqVO.getTitle()) |
| | | .eqIfPresent(WxCpActivityDO::getStatus, reqVO.getStatus()) |
| | | .eqIfPresent(WxCpActivityDO::getPushStatus, reqVO.getPushStatus()) |
| | | .betweenIfPresent(WxCpActivityDO::getStartTime, reqVO.getStartTime()) |
| | | .orderByDesc(WxCpActivityDO::getId)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.dal.mysql.activity; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | 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.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo.WxCpSignupPageReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ´»å¨æ¥å Mapper |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Mapper |
| | | public interface WxCpActivitySignupMapper extends BaseMapperX<WxCpActivitySignupDO> { |
| | | |
| | | default WxCpActivitySignupDO selectByActivityIdAndWxUserId(Long activityId, String wxUserId) { |
| | | return CollUtil.getFirst(selectList(new LambdaQueryWrapperX<WxCpActivitySignupDO>() |
| | | .eq(WxCpActivitySignupDO::getActivityId, activityId) |
| | | .eq(WxCpActivitySignupDO::getWxUserId, wxUserId) |
| | | .orderByAsc(WxCpActivitySignupDO::getId))); |
| | | } |
| | | |
| | | /** |
| | | * æææºå·æ¥æ¥åè®°å½ |
| | | * |
| | | * æ®éæµè§å¨æ«ç æ¥åæ¶æ¿ä¸å°ä¼ä¸å¾®ä¿¡æåè´¦å·ï¼æ¹ç¨ææºå·åå»é便® |
| | | */ |
| | | default WxCpActivitySignupDO selectByActivityIdAndMobile(Long activityId, String mobile) { |
| | | return CollUtil.getFirst(selectList(new LambdaQueryWrapperX<WxCpActivitySignupDO>() |
| | | .eq(WxCpActivitySignupDO::getActivityId, activityId) |
| | | .eq(WxCpActivitySignupDO::getMobile, mobile) |
| | | .orderByAsc(WxCpActivitySignupDO::getId))); |
| | | } |
| | | |
| | | default List<WxCpActivitySignupDO> selectListByActivityId(Long activityId) { |
| | | return selectList(new LambdaQueryWrapperX<WxCpActivitySignupDO>() |
| | | .eq(WxCpActivitySignupDO::getActivityId, activityId) |
| | | .orderByAsc(WxCpActivitySignupDO::getId)); |
| | | } |
| | | |
| | | default PageResult<WxCpActivitySignupDO> selectPage(WxCpSignupPageReqVO reqVO) { |
| | | return selectPage(reqVO, new LambdaQueryWrapperX<WxCpActivitySignupDO>() |
| | | .eqIfPresent(WxCpActivitySignupDO::getActivityId, reqVO.getActivityId()) |
| | | .likeIfPresent(WxCpActivitySignupDO::getName, reqVO.getName()) |
| | | .likeIfPresent(WxCpActivitySignupDO::getMobile, reqVO.getMobile()) |
| | | .eqIfPresent(WxCpActivitySignupDO::getStatus, reqVO.getStatus()) |
| | | .orderByDesc(WxCpActivitySignupDO::getId)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.dal.mysql.notice; |
| | | |
| | | 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.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo.WxCpNoticePageReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.notice.WxCpNoticeDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡éç¥ Mapper |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Mapper |
| | | public interface WxCpNoticeMapper extends BaseMapperX<WxCpNoticeDO> { |
| | | |
| | | default PageResult<WxCpNoticeDO> selectPage(WxCpNoticePageReqVO reqVO) { |
| | | return selectPage(reqVO, new LambdaQueryWrapperX<WxCpNoticeDO>() |
| | | .likeIfPresent(WxCpNoticeDO::getTitle, reqVO.getTitle()) |
| | | .eqIfPresent(WxCpNoticeDO::getStatus, reqVO.getStatus()) |
| | | .eqIfPresent(WxCpNoticeDO::getPushStatus, reqVO.getPushStatus()) |
| | | .betweenIfPresent(WxCpNoticeDO::getCreateTime, reqVO.getCreateTime()) |
| | | .orderByDesc(WxCpNoticeDO::getId)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.enums; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ErrorCode; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ¨¡åé误ç |
| | | * |
| | | * 段ä½ï¼1-050-000-000ï¼ä¸å
¶å®æ¨¡åä¸å²çª |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public interface ErrorCodeConstants { |
| | | |
| | | // ========== æ´»å¨ 1-050-000-000 ========== |
| | | ErrorCode ACTIVITY_NOT_EXISTS = new ErrorCode(1_050_000_000, "æ´»å¨ä¸åå¨"); |
| | | ErrorCode ACTIVITY_CANNOT_UPDATE = new ErrorCode(1_050_000_001, "æ´»å¨å·²ç»ææå·²åæ¶ï¼æ æ³ä¿®æ¹"); |
| | | ErrorCode ACTIVITY_STATUS_NOT_PUBLISHED = new ErrorCode(1_050_000_002, "æ´»å¨å°æªåå¸ï¼è¯·å
åå¸å忍é"); |
| | | ErrorCode ACTIVITY_STATUS_FINISHED = new ErrorCode(1_050_000_003, "æ´»å¨å·²ç»ææå·²åæ¶ï¼æ æ³ç»§ç»æä½"); |
| | | ErrorCode ACTIVITY_PUSH_TARGET_EMPTY = new ErrorCode(1_050_000_004, "æ¨éèå´éæ©ãæå®æåãæãæå®é¨é¨ãæ¶ï¼æ¨éç®æ ä¸è½ä¸ºç©º"); |
| | | ErrorCode ACTIVITY_PUSH_FAILED = new ErrorCode(1_050_000_005, "æ´»å¨æ¨é失败"); |
| | | |
| | | // ========== æ¥å 1-050-001-000 ========== |
| | | ErrorCode SIGNUP_ACTIVITY_NOT_PUBLISHED = new ErrorCode(1_050_001_000, "æ´»å¨æªåå¸ï¼æä¸è½æ¥å"); |
| | | ErrorCode SIGNUP_NOT_IN_PERIOD = new ErrorCode(1_050_001_001, "ä¸å¨æ¥åæ¶é´å
"); |
| | | ErrorCode SIGNUP_DUPLICATED = new ErrorCode(1_050_001_002, "æ¨å·²æ¥å该活å¨ï¼è¯·å¿éå¤æäº¤"); |
| | | ErrorCode SIGNUP_FULL = new ErrorCode(1_050_001_003, "æ¥å人æ°å·²æ»¡"); |
| | | ErrorCode SIGNUP_NOT_EXISTS = new ErrorCode(1_050_001_004, "æ¥åè®°å½ä¸åå¨"); |
| | | ErrorCode SIGNUP_NOT_SIGNED_UP = new ErrorCode(1_050_001_005, "该æåæªæ¥åæ¤æ´»å¨ï¼æ æ³ç¾å°"); |
| | | ErrorCode SIGNUP_ALREADY_CANCELED = new ErrorCode(1_050_001_006, "该æ¥å已忶"); |
| | | ErrorCode SIGNUP_ALREADY_CHECKED_IN = new ErrorCode(1_050_001_007, "该æ¥åå·²ç¾å°ï¼è¯·å¿é夿ä½"); |
| | | ErrorCode SIGNUP_WX_USER_REQUIRED = new ErrorCode(1_050_001_008, "æªè·åå°ä¼ä¸å¾®ä¿¡æå身份ï¼è¯·ä»ä¼ä¸å¾®ä¿¡å
æå¼æ¥å页"); |
| | | |
| | | // ========== äºç»´ç 1-050-002-000 ========== |
| | | ErrorCode QRCODE_GENERATE_FAILED = new ErrorCode(1_050_002_000, "çææ¥åäºç»´ç 失败"); |
| | | ErrorCode QRCODE_SIGNUP_URL_NOT_CONFIGURED = new ErrorCode(1_050_002_001, |
| | | "æªé
ç½®æ¥å页å¤ç½å°åï¼wx.cp.signup-url-prefixï¼ï¼æ æ³çæäºç»´ç ï¼è¯·å
å¨ application é
ç½®ä¸è¡¥ä¸"); |
| | | |
| | | // ========== éç¥ 1-050-003-000 ========== |
| | | ErrorCode NOTICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "éç¥ä¸åå¨"); |
| | | ErrorCode NOTICE_CANNOT_UPDATE = new ErrorCode(1_050_003_001, "éç¥å·²æ¤åï¼æ æ³ä¿®æ¹"); |
| | | ErrorCode NOTICE_STATUS_REVOKED = new ErrorCode(1_050_003_002, "éç¥å·²æ¤åï¼æ æ³åå¸"); |
| | | ErrorCode NOTICE_STATUS_NOT_PUBLISHED = new ErrorCode(1_050_003_003, "éç¥å°æªåå¸ï¼è¯·å
åå¸å忍é"); |
| | | |
| | | // ========== ä¼ä¸å¾®ä¿¡éè®¯å½ 1-050-004-000 ========== |
| | | ErrorCode WXCP_API_ERROR = new ErrorCode(1_050_004_000, "è°ç¨ä¼ä¸å¾®ä¿¡æ¥å£å¤±è´¥"); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.enums.activity; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ´»å¨ç¶ææä¸¾ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum WxCpActivityStatusEnum implements ArrayValuable<Integer> { |
| | | |
| | | DRAFT(0, "è稿"), |
| | | PUBLISHED(1, "å·²åå¸"), |
| | | FINISHED(2, "å·²ç»æ"), |
| | | CANCELED(3, "已忶"), |
| | | ; |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()) |
| | | .map(WxCpActivityStatusEnum::getStatus).toArray(Integer[]::new); |
| | | |
| | | /** |
| | | * ç¶æå¼ |
| | | */ |
| | | private final Integer status; |
| | | /** |
| | | * ç¶æå |
| | | */ |
| | | private final String name; |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | public static WxCpActivityStatusEnum valueOf(Integer status) { |
| | | return ArrayUtil.firstMatch(o -> o.getStatus().equals(status), values()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.enums.activity; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ´»å¨æ¨éè崿䏾 |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum WxCpPushScopeEnum implements ArrayValuable<Integer> { |
| | | |
| | | ALL(0, "å
¨å"), |
| | | PARTY(1, "æå®é¨é¨"), |
| | | USER(2, "æå®æå"), |
| | | ; |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()) |
| | | .map(WxCpPushScopeEnum::getScope).toArray(Integer[]::new); |
| | | |
| | | /** |
| | | * èå´å¼ |
| | | */ |
| | | private final Integer scope; |
| | | /** |
| | | * èå´å |
| | | */ |
| | | private final String name; |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | public static WxCpPushScopeEnum valueOf(Integer scope) { |
| | | return ArrayUtil.firstMatch(o -> o.getScope().equals(scope), values()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.enums.activity; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ´»å¨æ¥åç¶ææä¸¾ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum WxCpSignupStatusEnum implements ArrayValuable<Integer> { |
| | | |
| | | SIGNED_UP(0, "å·²æ¥å"), |
| | | CANCELED(1, "已忶"), |
| | | CHECKED_IN(2, "å·²ç¾å°"), |
| | | ; |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()) |
| | | .map(WxCpSignupStatusEnum::getStatus).toArray(Integer[]::new); |
| | | |
| | | /** |
| | | * ç¶æå¼ |
| | | */ |
| | | private final Integer status; |
| | | /** |
| | | * ç¶æå |
| | | */ |
| | | private final String name; |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | public static WxCpSignupStatusEnum valueOf(Integer status) { |
| | | return ArrayUtil.firstMatch(o -> o.getStatus().equals(status), values()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.enums.notice; |
| | | |
| | | import cn.hutool.core.util.ArrayUtil; |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡éç¥ç¶ææä¸¾ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum WxCpNoticeStatusEnum implements ArrayValuable<Integer> { |
| | | |
| | | DRAFT(0, "è稿"), |
| | | PUBLISHED(1, "å·²åå¸"), |
| | | REVOKED(2, "å·²æ¤å"), |
| | | ; |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()) |
| | | .map(WxCpNoticeStatusEnum::getStatus).toArray(Integer[]::new); |
| | | |
| | | /** |
| | | * ç¶æå¼ |
| | | */ |
| | | private final Integer status; |
| | | /** |
| | | * ç¶æå |
| | | */ |
| | | private final String name; |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | public static WxCpNoticeStatusEnum valueOf(Integer status) { |
| | | return ArrayUtil.firstMatch(o -> o.getStatus().equals(status), values()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.framework.config; |
| | | |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import lombok.Data; |
| | | import org.springframework.boot.context.properties.ConfigurationProperties; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡é
ç½®å±æ§ç±» |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Data |
| | | @Validated |
| | | @ConfigurationProperties(prefix = "wx.cp") |
| | | public class WxCpProperties { |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡ CorpIdï¼ä¼ä¸IDï¼ |
| | | */ |
| | | @NotEmpty(message = "ä¼ä¸å¾®ä¿¡ CorpId ä¸è½ä¸ºç©º") |
| | | private String corpId; |
| | | |
| | | /** |
| | | * åºç¨ AgentId |
| | | */ |
| | | @NotEmpty(message = "ä¼ä¸å¾®ä¿¡ AgentId ä¸è½ä¸ºç©º") |
| | | private String agentId; |
| | | |
| | | /** |
| | | * åºç¨ Secret |
| | | */ |
| | | @NotEmpty(message = "ä¼ä¸å¾®ä¿¡ Secret ä¸è½ä¸ºç©º") |
| | | private String secret; |
| | | |
| | | /** |
| | | * Tokenï¼åè° URL éªè¯ç¨ï¼éå¨ä¼ä¸å¾®ä¿¡ç®¡çåå°é
ç½®ï¼ |
| | | */ |
| | | private String token; |
| | | |
| | | /** |
| | | * EncodingAESKeyï¼åè°æ¶æ¯å è§£å¯ç¨ï¼éå¨ä¼ä¸å¾®ä¿¡ç®¡çåå°é
ç½®ï¼ |
| | | */ |
| | | private String aesKey; |
| | | |
| | | /** |
| | | * æ¥å页å¤ç½å°ååç¼ï¼ç¨äºçææ´»å¨æ¥åäºç»´ç |
| | | * |
| | | * å¿
é¡»æ¯ä¼ä¸å¾®ä¿¡æåææºè½è®¿é®å°çå°åï¼ä¾å¦å
ç½ç©¿éååææ£å¼ååï¼ |
| | | * çæçäºç»´ç å
容为 {@code {signupUrlPrefix}/admin-api/wxcp/activity/signup-page?id={æ´»å¨ç¼å·}} |
| | | */ |
| | | private String signupUrlPrefix; |
| | | |
| | | /** |
| | | * çæäºç»´ç çè¾¹é¿ï¼åç´ ï¼ï¼ä¸é
ç½®æ¶é»è®¤ 400 |
| | | */ |
| | | private Integer qrcodeSize; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.framework.config; |
| | | |
| | | import cn.iocoder.yudao.module.wxcp.framework.core.WxCpClientFactory; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import me.chanjar.weixin.cp.api.WxCpService; |
| | | import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; |
| | | import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; |
| | | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; |
| | | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| | | import org.springframework.boot.context.properties.EnableConfigurationProperties; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡é
置类 |
| | | * |
| | | * 注æï¼ä¸å¡æ¨¡åä½äºç»ä»¶æ«æè·¯å¾ cn.iocoder.yudao.module ä¸ï¼å æ¤ä½¿ç¨ @Configurationï¼ |
| | | * è¥ç¨ @AutoConfiguration ä¼è¢« Spring Boot ç AutoConfigurationExcludeFilter æé¤ï¼å¯¼è´ Bean 䏿³¨å |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Configuration(proxyBeanMethods = false) |
| | | @Slf4j |
| | | @ConditionalOnClass(WxCpService.class) |
| | | @EnableConfigurationProperties(WxCpProperties.class) |
| | | @ConditionalOnProperty(value = "wx.cp.enabled", havingValue = "true", matchIfMissing = true) |
| | | public class YudaoWxCpConfiguration { |
| | | |
| | | /** |
| | | * å建ä¼ä¸å¾®ä¿¡ WxCpService Beanï¼ååºç¨æ¨¡å¼ï¼ |
| | | */ |
| | | @Bean |
| | | public WxCpService wxCpService(WxCpProperties properties) { |
| | | WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl(); |
| | | config.setCorpId(properties.getCorpId()); |
| | | config.setAgentId(Integer.valueOf(properties.getAgentId())); |
| | | config.setCorpSecret(properties.getSecret()); |
| | | config.setToken(properties.getToken()); |
| | | config.setAesKey(properties.getAesKey()); |
| | | |
| | | WxCpServiceImpl wxCpService = new WxCpServiceImpl(); |
| | | wxCpService.setWxCpConfigStorage(config); |
| | | |
| | | log.info("[wxCpService][ä¼ä¸å¾®ä¿¡æå¡åå§å宿 corpId={} agentId={}]", |
| | | properties.getCorpId(), properties.getAgentId()); |
| | | return wxCpService; |
| | | } |
| | | |
| | | /** |
| | | * å建ä¼ä¸å¾®ä¿¡å®¢æ·ç«¯å·¥åï¼æ¯æå¤åºç¨ï¼ |
| | | */ |
| | | @Bean |
| | | public WxCpClientFactory wxCpClientFactory(WxCpProperties properties) { |
| | | return new WxCpClientFactory(properties); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.framework.core; |
| | | |
| | | import cn.iocoder.yudao.module.wxcp.framework.config.WxCpProperties; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import me.chanjar.weixin.cp.api.WxCpService; |
| | | import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; |
| | | import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡å®¢æ·ç«¯å·¥å |
| | | * |
| | | * æ¯æå¤åºç¨åºæ¯ï¼æ¯ä¸ªåºç¨æèªå·±ç AgentId å Secret |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Slf4j |
| | | public class WxCpClientFactory { |
| | | |
| | | private final WxCpProperties properties; |
| | | |
| | | public WxCpClientFactory(WxCpProperties properties) { |
| | | this.properties = properties; |
| | | } |
| | | |
| | | /** |
| | | * è·åé»è®¤åºç¨ï¼ä¸»åºç¨ï¼ç WxCpService |
| | | */ |
| | | public WxCpService getService() { |
| | | return buildService(properties.getAgentId(), properties.getSecret()); |
| | | } |
| | | |
| | | /** |
| | | * è·åæå® AgentId ç WxCpService |
| | | * |
| | | * @param agentId åºç¨ AgentId |
| | | * @param secret åºç¨ Secret |
| | | */ |
| | | public WxCpService getService(String agentId, String secret) { |
| | | return buildService(agentId, secret); |
| | | } |
| | | |
| | | private WxCpService buildService(String agentId, String secret) { |
| | | WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl(); |
| | | config.setCorpId(properties.getCorpId()); |
| | | config.setAgentId(Integer.valueOf(agentId)); |
| | | config.setCorpSecret(secret); |
| | | config.setToken(properties.getToken()); |
| | | config.setAesKey(properties.getAesKey()); |
| | | |
| | | WxCpServiceImpl wxCpService = new WxCpServiceImpl(); |
| | | wxCpService.setWxCpConfigStorage(config); |
| | | return wxCpService; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.framework.qrcode; |
| | | |
| | | import com.google.zxing.BarcodeFormat; |
| | | import com.google.zxing.EncodeHintType; |
| | | import com.google.zxing.client.j2se.MatrixToImageWriter; |
| | | import com.google.zxing.common.BitMatrix; |
| | | import com.google.zxing.qrcode.QRCodeWriter; |
| | | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; |
| | | |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.wxcp.enums.ErrorCodeConstants.QRCODE_GENERATE_FAILED; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ¨¡å - äºç»´ç çæå·¥å
· |
| | | * |
| | | * ç¨äºææ´»å¨æ¥å页å°åçæäºç»´ç å¾çï¼ä¾ä¼ä¸å¾®ä¿¡æ¨éæåå°ä¸è½½ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public class WxCpQrCodeUtil { |
| | | |
| | | /** |
| | | * é»è®¤äºç»´ç è¾¹é¿ï¼åç´ ï¼ |
| | | */ |
| | | public static final int DEFAULT_SIZE = 400; |
| | | |
| | | private WxCpQrCodeUtil() { |
| | | } |
| | | |
| | | /** |
| | | * çæäºç»´ç PNG åèæµ |
| | | * |
| | | * @param content äºç»´ç å
容ï¼ä¸è¬æ¯ä¸ä¸ª URLï¼ |
| | | * @param size è¾¹é¿åç´ ï¼ä¼ 0 æè´æ°å使ç¨é»è®¤å¼ |
| | | * @return PNG å¾çåè |
| | | */ |
| | | public static byte[] generatePng(String content, int size) { |
| | | int actualSize = size > 0 ? size : DEFAULT_SIZE; |
| | | try { |
| | | Map<EncodeHintType, Object> hints = new HashMap<>(4); |
| | | hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); |
| | | // 容éçº§å« Mï¼çº¦ 15%ï¼å
¼é¡¾æ«ç æåçä¸ç ç¹å¯åº¦ï¼éåå°å¨ç©æä¸ |
| | | hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); |
| | | hints.put(EncodeHintType.MARGIN, 1); |
| | | |
| | | BitMatrix matrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, |
| | | actualSize, actualSize, hints); |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | | MatrixToImageWriter.writeToStream(matrix, "PNG", out); |
| | | return out.toByteArray(); |
| | | } catch (Exception e) { |
| | | throw exception(QRCODE_GENERATE_FAILED); |
| | | } |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.service.activity; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageBlobRespVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo.*; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityDO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupDO; |
| | | import jakarta.validation.Valid; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ´»å¨ Service æ¥å£ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public interface WxCpActivityService { |
| | | |
| | | // ==================== æ´»å¨ç®¡ç ==================== |
| | | |
| | | /** |
| | | * å建活å¨ï¼åå§ä¸ºèç¨¿ç¶æï¼ |
| | | * |
| | | * @param createReqVO åå»ºä¿¡æ¯ |
| | | * @return æ´»å¨ç¼å· |
| | | */ |
| | | Long createActivity(@Valid WxCpActivitySaveReqVO createReqVO); |
| | | |
| | | /** |
| | | * ä¿®æ¹æ´»å¨ |
| | | * |
| | | * @param updateReqVO ä¿®æ¹ä¿¡æ¯ |
| | | */ |
| | | void updateActivity(@Valid WxCpActivitySaveReqVO updateReqVO); |
| | | |
| | | /** |
| | | * å 餿´»å¨ |
| | | * |
| | | * @param id æ´»å¨ç¼å· |
| | | */ |
| | | void deleteActivity(Long id); |
| | | |
| | | /** |
| | | * è·å¾æ´»å¨ |
| | | * |
| | | * @param id æ´»å¨ç¼å· |
| | | * @return æ´»å¨ |
| | | */ |
| | | WxCpActivityDO getActivity(Long id); |
| | | |
| | | /** |
| | | * è·å¾æ´»å¨å页 |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return æ´»å¨å页 |
| | | */ |
| | | PageResult<WxCpActivityDO> getActivityPage(@Valid WxCpActivityPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * å叿´»å¨ï¼è稿 -> å·²åå¸ï¼ |
| | | * |
| | | * @param id æ´»å¨ç¼å· |
| | | */ |
| | | void publishActivity(Long id); |
| | | |
| | | /** |
| | | * ç»ææ´»å¨ |
| | | * |
| | | * @param id æ´»å¨ç¼å· |
| | | */ |
| | | void finishActivity(Long id); |
| | | |
| | | /** |
| | | * åæ¶æ´»å¨ |
| | | * |
| | | * @param id æ´»å¨ç¼å· |
| | | */ |
| | | void cancelActivity(Long id); |
| | | |
| | | // ==================== æ¨é ==================== |
| | | |
| | | /** |
| | | * æ¨éæ´»å¨ç»ä¼ä¸å¾®ä¿¡æå |
| | | * |
| | | * @param pushReqVO æ¨éä¿¡æ¯ |
| | | * @return æ¨éç»æ |
| | | */ |
| | | WxCpPushResultVO pushActivity(@Valid WxCpPushReqVO pushReqVO); |
| | | |
| | | /** |
| | | * çææ´»å¨æ¥åäºç»´ç ï¼PNG åèæµï¼ |
| | | * |
| | | * @param id æ´»å¨ç¼å· |
| | | * @param size è¾¹é¿åç´ ï¼ä¼ 0 使ç¨é»è®¤å¼ |
| | | * @return PNG åè |
| | | */ |
| | | byte[] generateSignupQrCode(Long id, int size); |
| | | |
| | | /** |
| | | * æ¼è£
æ´»å¨æ¥å页å°å |
| | | * |
| | | * @param id æ´»å¨ç¼å· |
| | | * @return æ¥å页宿´ URL |
| | | */ |
| | | String buildSignupUrl(Long id); |
| | | |
| | | // ==================== æ¥å ==================== |
| | | |
| | | /** |
| | | * æäº¤æ¥åï¼å
ç»å½æ¥å£ä½¿ç¨ï¼ |
| | | * |
| | | * @param reqVO æ¥åä¿¡æ¯ |
| | | * @return æ¥åè®°å½ç¼å· |
| | | */ |
| | | Long createSignup(@Valid WxCpSignupPublicReqVO reqVO); |
| | | |
| | | /** |
| | | * åæ¶æ¥å |
| | | * |
| | | * @param id æ¥åè®°å½ç¼å· |
| | | */ |
| | | void cancelSignup(Long id); |
| | | |
| | | /** |
| | | * æ¥åç¾å° |
| | | * |
| | | * @param id æ¥åè®°å½ç¼å· |
| | | */ |
| | | void checkInSignup(Long id); |
| | | |
| | | /** |
| | | * è·å¾æ¥åè®°å½ |
| | | * |
| | | * @param id æ¥åè®°å½ç¼å· |
| | | * @return æ¥åè®°å½ |
| | | */ |
| | | WxCpActivitySignupDO getSignup(Long id); |
| | | |
| | | /** |
| | | * è·å¾æ¥åè®°å½å页 |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return æ¥åè®°å½å页 |
| | | */ |
| | | PageResult<WxCpActivitySignupDO> getSignupPage(@Valid WxCpSignupPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * è·å¾ææ¡æ¥åè®°å½çéä»¶å表 |
| | | * |
| | | * @param signupId æ¥åè®°å½ç¼å· |
| | | * @return éä»¶ï¼å«è®¿é®å°åï¼ |
| | | */ |
| | | List<SystemStorageBlobRespVO> getSignupAttachments(Long signupId); |
| | | |
| | | /** |
| | | * è·å¾æ´»å¨æ¥åç»è®¡ |
| | | * |
| | | * @param activityId æ´»å¨ç¼å· |
| | | * @return ç»è®¡æ°æ® |
| | | */ |
| | | WxCpActivityStatsRespVO getActivityStats(Long activityId); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.service.activity; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageAttachmentListReqVO; |
| | | import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageAttachmentSaveReqVO; |
| | | import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageBlobRespVO; |
| | | import cn.iocoder.yudao.module.system.service.storage.SystemStorageAttachmentService; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.activity.vo.*; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivityDO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.activity.WxCpActivitySignupDO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.mysql.activity.WxCpActivityMapper; |
| | | import cn.iocoder.yudao.module.wxcp.dal.mysql.activity.WxCpActivitySignupMapper; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpActivityStatusEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpSignupStatusEnum; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO; |
| | | import cn.iocoder.yudao.module.wxcp.framework.config.WxCpProperties; |
| | | import cn.iocoder.yudao.module.wxcp.framework.qrcode.WxCpQrCodeUtil; |
| | | import cn.iocoder.yudao.module.wxcp.service.push.WxCpMessagePushService; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.TreeMap; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.wxcp.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ´»å¨ Service å®ç°ç±» |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class WxCpActivityServiceImpl implements WxCpActivityService { |
| | | |
| | | private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); |
| | | |
| | | /** |
| | | * æ¥åéä»¶çå
³èè®°å½ç±»åï¼å¯¹åº system_storage_attachment.record_type |
| | | */ |
| | | public static final String SIGNUP_RECORD_TYPE = "wxcp_activity_signup"; |
| | | |
| | | @Resource |
| | | private WxCpActivityMapper wxcpActivityMapper; |
| | | @Resource |
| | | private WxCpActivitySignupMapper wxcpSignupMapper; |
| | | |
| | | @Resource |
| | | private SystemStorageAttachmentService storageAttachmentService; |
| | | |
| | | @Resource |
| | | private WxCpMessagePushService messagePushService; |
| | | @Resource |
| | | private WxCpProperties wxCpProperties; |
| | | |
| | | // ==================== æ´»å¨ç®¡ç ==================== |
| | | |
| | | @Override |
| | | public Long createActivity(WxCpActivitySaveReqVO createReqVO) { |
| | | WxCpActivityDO activity = BeanUtils.toBean(createReqVO, WxCpActivityDO.class); |
| | | // æ°å»ºçä¸å¾æ¯è稿ï¼é¿å
æªé
ç½®å®å°±è¢«æ«ç æ¥å |
| | | activity.setStatus(WxCpActivityStatusEnum.DRAFT.getStatus()); |
| | | activity.setSignupCount(0); |
| | | activity.setPushStatus(0); |
| | | // æ¨éèå´éå¡«ï¼ä¸ä¼ é»è®¤å
¨åï¼æ¨éæ¶è¿è½å¨å¼¹çªéæ¹ |
| | | if (activity.getPushScope() == null) { |
| | | activity.setPushScope(WxCpPushScopeEnum.ALL.getScope()); |
| | | } |
| | | wxcpActivityMapper.insert(activity); |
| | | return activity.getId(); |
| | | } |
| | | |
| | | @Override |
| | | public void updateActivity(WxCpActivitySaveReqVO updateReqVO) { |
| | | WxCpActivityDO activity = validateActivityExists(updateReqVO.getId()); |
| | | // å·²ç»ææå·²åæ¶çæ´»å¨ä¸åå
è®¸ä¿®æ¹ |
| | | if (Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.FINISHED.getStatus()) |
| | | || Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.CANCELED.getStatus())) { |
| | | throw exception(ACTIVITY_CANNOT_UPDATE); |
| | | } |
| | | WxCpActivityDO updateObj = BeanUtils.toBean(updateReqVO, WxCpActivityDO.class); |
| | | // 以ä¸å段ç±ç³»ç»ç»´æ¤ï¼ä¸å
许å端è¦ç |
| | | updateObj.setStatus(null); |
| | | updateObj.setSignupCount(null); |
| | | updateObj.setPushStatus(null); |
| | | updateObj.setPushTime(null); |
| | | wxcpActivityMapper.updateById(updateObj); |
| | | } |
| | | |
| | | @Override |
| | | public void deleteActivity(Long id) { |
| | | validateActivityExists(id); |
| | | wxcpActivityMapper.deleteById(id); |
| | | } |
| | | |
| | | @Override |
| | | public WxCpActivityDO getActivity(Long id) { |
| | | return wxcpActivityMapper.selectById(id); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<WxCpActivityDO> getActivityPage(WxCpActivityPageReqVO pageReqVO) { |
| | | return wxcpActivityMapper.selectPage(pageReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public void publishActivity(Long id) { |
| | | WxCpActivityDO activity = validateActivityExists(id); |
| | | if (Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.PUBLISHED.getStatus())) { |
| | | return; // å·²åå¸ï¼å¹çè¿å |
| | | } |
| | | if (Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.FINISHED.getStatus()) |
| | | || Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.CANCELED.getStatus())) { |
| | | throw exception(ACTIVITY_STATUS_FINISHED); |
| | | } |
| | | wxcpActivityMapper.updateById(WxCpActivityDO.builder() |
| | | .id(id).status(WxCpActivityStatusEnum.PUBLISHED.getStatus()).build()); |
| | | } |
| | | |
| | | @Override |
| | | public void finishActivity(Long id) { |
| | | validateActivityExists(id); |
| | | wxcpActivityMapper.updateById(WxCpActivityDO.builder() |
| | | .id(id).status(WxCpActivityStatusEnum.FINISHED.getStatus()).build()); |
| | | } |
| | | |
| | | @Override |
| | | public void cancelActivity(Long id) { |
| | | validateActivityExists(id); |
| | | wxcpActivityMapper.updateById(WxCpActivityDO.builder() |
| | | .id(id).status(WxCpActivityStatusEnum.CANCELED.getStatus()).build()); |
| | | } |
| | | |
| | | // ==================== æ¨é ==================== |
| | | |
| | | @Override |
| | | public WxCpPushResultVO pushActivity(WxCpPushReqVO pushReqVO) { |
| | | WxCpActivityDO activity = validateActivityExists(pushReqVO.getId()); |
| | | if (!Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.PUBLISHED.getStatus())) { |
| | | throw exception(ACTIVITY_STATUS_NOT_PUBLISHED); |
| | | } |
| | | |
| | | // æ¨éèå´ä¸ç®æ ï¼è¯·æ±éä¼ äºå°±ç¨è¯·æ±çï¼å¦åæ²¿ç¨æ´»å¨ä¸é
ç½®ç |
| | | Integer scope = pushReqVO.getPushScope() != null ? pushReqVO.getPushScope() : activity.getPushScope(); |
| | | String target = StrUtil.isNotBlank(pushReqVO.getPushTarget()) |
| | | ? pushReqVO.getPushTarget() : activity.getPushTarget(); |
| | | if (scope == null) { |
| | | scope = WxCpPushScopeEnum.ALL.getScope(); |
| | | } |
| | | |
| | | // ææ¬å¡çæ¶æ¯ï¼æé®ç´è¾¾æ¥å页ï¼ç¹æ¶æ¯ææ«ç é½è½æ¥å |
| | | WxCpPushResultVO result = messagePushService.pushCard( |
| | | activity.getTitle(), buildPushDescription(activity), |
| | | buildSignupUrl(activity.getId()), "ç«å³æ¥å", scope, target); |
| | | |
| | | if (Boolean.TRUE.equals(result.getSuccess())) { |
| | | wxcpActivityMapper.updateById(WxCpActivityDO.builder() |
| | | .id(activity.getId()) |
| | | .pushScope(scope) |
| | | .pushTarget(target) |
| | | .pushStatus(1) |
| | | .pushTime(LocalDateTime.now()) |
| | | .build()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * æ¼è£
æ¨éæ¶æ¯æ£æ |
| | | * |
| | | * åªæ¾æåæ¥ååæå
³å¿çä¿¡æ¯ï¼æ¶é´ãå°ç¹ãæ¥åæªæ¢ï¼è¯¦æ
è®©ç¨æ·ç¹è¿æ¥å页ç |
| | | */ |
| | | private String buildPushDescription(WxCpActivityDO activity) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | if (activity.getStartTime() != null) { |
| | | sb.append("æ´»å¨æ¶é´ï¼").append(activity.getStartTime().format(TIME_FMT)); |
| | | if (activity.getEndTime() != null) { |
| | | sb.append(" ~ ").append(activity.getEndTime().format(TIME_FMT)); |
| | | } |
| | | sb.append("\n"); |
| | | } |
| | | if (StrUtil.isNotBlank(activity.getLocation())) { |
| | | sb.append("æ´»å¨å°ç¹ï¼").append(activity.getLocation()).append("\n"); |
| | | } |
| | | if (activity.getSignupEndTime() != null) { |
| | | sb.append("æ¥åæªæ¢ï¼").append(activity.getSignupEndTime().format(TIME_FMT)).append("\n"); |
| | | } |
| | | if (activity.getSignupLimit() != null && activity.getSignupLimit() > 0) { |
| | | sb.append("åé¢ï¼").append(activity.getSignupCount()).append("/") |
| | | .append(activity.getSignupLimit()).append("\n"); |
| | | } |
| | | sb.append("ç¹å»ä¸æ¹æé®æ¥ç详æ
å¹¶æ¥å"); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | @Override |
| | | public byte[] generateSignupQrCode(Long id, int size) { |
| | | validateActivityExists(id); |
| | | String url = buildSignupUrl(id); |
| | | Integer configured = wxCpProperties.getQrcodeSize(); |
| | | int actualSize = size > 0 ? size : (configured != null ? configured : WxCpQrCodeUtil.DEFAULT_SIZE); |
| | | return WxCpQrCodeUtil.generatePng(url, actualSize); |
| | | } |
| | | |
| | | @Override |
| | | public String buildSignupUrl(Long id) { |
| | | String prefix = wxCpProperties.getSignupUrlPrefix(); |
| | | if (StrUtil.isBlank(prefix)) { |
| | | throw exception(QRCODE_SIGNUP_URL_NOT_CONFIGURED); |
| | | } |
| | | // 廿ç»å°¾ææ ï¼é¿å
æ¼åº //admin-api |
| | | String base = StrUtil.removeSuffix(prefix.trim(), "/"); |
| | | return base + "/admin-api/wxcp/activity/signup-page?id=" + id; |
| | | } |
| | | |
| | | // ==================== æ¥å ==================== |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createSignup(WxCpSignupPublicReqVO reqVO) { |
| | | WxCpActivityDO activity = validateActivityExists(reqVO.getActivityId()); |
| | | |
| | | // 1. æ´»å¨å¿
é¡»æ¯å·²åå¸ç¶æ |
| | | if (!Objects.equals(activity.getStatus(), WxCpActivityStatusEnum.PUBLISHED.getStatus())) { |
| | | throw exception(SIGNUP_ACTIVITY_NOT_PUBLISHED); |
| | | } |
| | | |
| | | // 2. æ ¡éªæ¥åæ¶é´çªå£ |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | if (activity.getSignupStartTime() != null && now.isBefore(activity.getSignupStartTime())) { |
| | | throw exception(SIGNUP_NOT_IN_PERIOD); |
| | | } |
| | | if (activity.getSignupEndTime() != null && now.isAfter(activity.getSignupEndTime())) { |
| | | throw exception(SIGNUP_NOT_IN_PERIOD); |
| | | } |
| | | |
| | | // 3. æ ¡éªåé¢ |
| | | if (activity.getSignupLimit() != null && activity.getSignupLimit() > 0 |
| | | && activity.getSignupCount() != null && activity.getSignupCount() >= activity.getSignupLimit()) { |
| | | throw exception(SIGNUP_FULL); |
| | | } |
| | | |
| | | // 4. å»éï¼æä¼ä¸å¾®ä¿¡è´¦å·æè´¦å·æ¥ï¼å¦åæææºå·æ¥ |
| | | WxCpActivitySignupDO exists = StrUtil.isNotBlank(reqVO.getWxUserId()) |
| | | ? wxcpSignupMapper.selectByActivityIdAndWxUserId(activity.getId(), reqVO.getWxUserId().trim()) |
| | | : wxcpSignupMapper.selectByActivityIdAndMobile(activity.getId(), reqVO.getMobile().trim()); |
| | | if (exists != null && !Objects.equals(exists.getStatus(), WxCpSignupStatusEnum.CANCELED.getStatus())) { |
| | | throw exception(SIGNUP_DUPLICATED); |
| | | } |
| | | // ä¹ååæ¶è¿ï¼åå¤ç¨åè®°å½éæ°æ¥åï¼é¿å
åä¸ä¸ªäººç夿¡åå² |
| | | if (exists != null) { |
| | | wxcpSignupMapper.updateById(WxCpActivitySignupDO.builder() |
| | | .id(exists.getId()) |
| | | .name(reqVO.getName()) |
| | | .mobile(reqVO.getMobile()) |
| | | .deptName(reqVO.getDeptName()) |
| | | .content(reqVO.getContent()) |
| | | .remark(reqVO.getRemark()) |
| | | .status(WxCpSignupStatusEnum.SIGNED_UP.getStatus()) |
| | | .signupTime(now) |
| | | .checkInTime(null) |
| | | .build()); |
| | | // éæ°æ¥åæ¶æ§é件已失æï¼å
æ¸
æåç»æ°ç |
| | | storageAttachmentService.deleteAttachmentsByRecordTypeAndRecordId( |
| | | SIGNUP_RECORD_TYPE, exists.getId()); |
| | | bindSignupAttachments(exists.getId(), reqVO.getBlobIds()); |
| | | incrementSignupCount(activity.getId()); |
| | | return exists.getId(); |
| | | } |
| | | |
| | | // 5. æ°å¢æ¥åè®°å½ |
| | | WxCpActivitySignupDO signup = BeanUtils.toBean(reqVO, WxCpActivitySignupDO.class); |
| | | signup.setWxUserId(StrUtil.trimToEmpty(reqVO.getWxUserId())); |
| | | signup.setStatus(WxCpSignupStatusEnum.SIGNED_UP.getStatus()); |
| | | signup.setSignupTime(now); |
| | | wxcpSignupMapper.insert(signup); |
| | | |
| | | // 6. ç»å®æ¥å人ä¸ä¼ çå¾ç/éä»¶ |
| | | bindSignupAttachments(signup.getId(), reqVO.getBlobIds()); |
| | | |
| | | // 7. ç¨ SQL èªå¢ï¼é¿å
å¹¶åä¸ç读æ¹åä¸¢å¤±æ´æ° |
| | | incrementSignupCount(activity.getId()); |
| | | return signup.getId(); |
| | | } |
| | | |
| | | /** |
| | | * ç»å®æ¥åéä»¶ |
| | | * |
| | | * èµ° system 模åçéä»¶ä¸é´è¡¨ï¼record_type = wxcp_activity_signupï¼ï¼ |
| | | * 䏿°å»º upload æ¥å£ãä¸è½æä»¶å°ååæ®µï¼ç¬¦å项ç®ç»ä¸æä»¶ç®¡çè§èã |
| | | */ |
| | | private void bindSignupAttachments(Long signupId, List<Long> blobIds) { |
| | | if (CollUtil.isEmpty(blobIds)) { |
| | | return; |
| | | } |
| | | SystemStorageAttachmentSaveReqVO bindReqVO = new SystemStorageAttachmentSaveReqVO(); |
| | | bindReqVO.setApplication("file"); |
| | | bindReqVO.setRecordType(SIGNUP_RECORD_TYPE); |
| | | bindReqVO.setRecordId(signupId); |
| | | bindReqVO.setBlobItems(blobIds.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(blobId -> { |
| | | SystemStorageAttachmentSaveReqVO.BlobItem item = new SystemStorageAttachmentSaveReqVO.BlobItem(); |
| | | item.setBlobId(blobId); |
| | | return item; |
| | | }) |
| | | .toList()); |
| | | storageAttachmentService.bindAttachments(bindReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public List<SystemStorageBlobRespVO> getSignupAttachments(Long signupId) { |
| | | SystemStorageAttachmentListReqVO listReqVO = new SystemStorageAttachmentListReqVO(); |
| | | listReqVO.setRecordType(SIGNUP_RECORD_TYPE); |
| | | listReqVO.setRecordId(signupId); |
| | | return storageAttachmentService.listAttachments(listReqVO); |
| | | } |
| | | |
| | | private void incrementSignupCount(Long activityId) { |
| | | wxcpActivityMapper.update(null, new LambdaUpdateWrapper<WxCpActivityDO>() |
| | | .setSql("signup_count = signup_count + 1") |
| | | .eq(WxCpActivityDO::getId, activityId)); |
| | | } |
| | | |
| | | private void decrementSignupCount(Long activityId) { |
| | | wxcpActivityMapper.update(null, new LambdaUpdateWrapper<WxCpActivityDO>() |
| | | .setSql("signup_count = CASE WHEN signup_count > 0 THEN signup_count - 1 ELSE 0 END") |
| | | .eq(WxCpActivityDO::getId, activityId)); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void cancelSignup(Long id) { |
| | | WxCpActivitySignupDO signup = validateSignupExists(id); |
| | | if (Objects.equals(signup.getStatus(), WxCpSignupStatusEnum.CANCELED.getStatus())) { |
| | | throw exception(SIGNUP_ALREADY_CANCELED); |
| | | } |
| | | wxcpSignupMapper.updateById(WxCpActivitySignupDO.builder() |
| | | .id(id).status(WxCpSignupStatusEnum.CANCELED.getStatus()).build()); |
| | | decrementSignupCount(signup.getActivityId()); |
| | | } |
| | | |
| | | @Override |
| | | public void checkInSignup(Long id) { |
| | | WxCpActivitySignupDO signup = validateSignupExists(id); |
| | | if (Objects.equals(signup.getStatus(), WxCpSignupStatusEnum.CANCELED.getStatus())) { |
| | | throw exception(SIGNUP_ALREADY_CANCELED); |
| | | } |
| | | if (Objects.equals(signup.getStatus(), WxCpSignupStatusEnum.CHECKED_IN.getStatus())) { |
| | | throw exception(SIGNUP_ALREADY_CHECKED_IN); |
| | | } |
| | | wxcpSignupMapper.updateById(WxCpActivitySignupDO.builder() |
| | | .id(id) |
| | | .status(WxCpSignupStatusEnum.CHECKED_IN.getStatus()) |
| | | .checkInTime(LocalDateTime.now()) |
| | | .build()); |
| | | } |
| | | |
| | | @Override |
| | | public WxCpActivitySignupDO getSignup(Long id) { |
| | | return wxcpSignupMapper.selectById(id); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<WxCpActivitySignupDO> getSignupPage(WxCpSignupPageReqVO pageReqVO) { |
| | | return wxcpSignupMapper.selectPage(pageReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public WxCpActivityStatsRespVO getActivityStats(Long activityId) { |
| | | WxCpActivityDO activity = validateActivityExists(activityId); |
| | | List<WxCpActivitySignupDO> all = wxcpSignupMapper.selectListByActivityId(activityId); |
| | | |
| | | // 䏿¬¡éååæ¶ç®æ»æ°ä¸ä¸ç±»åå¸ï¼é¿å
夿¬¡å
¨è¡¨æ«æ |
| | | int signedUp = 0, checkedIn = 0, canceled = 0; |
| | | Map<String, Integer> deptMap = new LinkedHashMap<>(); |
| | | Map<String, Integer> dateMap = new TreeMap<>(); |
| | | int fromWxCp = 0, fromForm = 0; |
| | | for (WxCpActivitySignupDO s : all) { |
| | | Integer st = s.getStatus(); |
| | | if (Objects.equals(st, WxCpSignupStatusEnum.CANCELED.getStatus())) { |
| | | canceled++; |
| | | continue; // 已忶ä¸è®¡å
¥é¨é¨/æ¥æåå¸ï¼å¦åç»è®¡ä¼èé« |
| | | } |
| | | if (Objects.equals(st, WxCpSignupStatusEnum.CHECKED_IN.getStatus())) { |
| | | checkedIn++; |
| | | } else { |
| | | signedUp++; |
| | | } |
| | | deptMap.merge(StrUtil.blankToDefault(s.getDeptName(), "æªå¡«åé¨é¨"), 1, Integer::sum); |
| | | if (s.getSignupTime() != null) { |
| | | dateMap.merge(s.getSignupTime().toLocalDate().toString(), 1, Integer::sum); |
| | | } |
| | | if (StrUtil.isNotBlank(s.getWxUserId())) { |
| | | fromWxCp++; |
| | | } else { |
| | | fromForm++; |
| | | } |
| | | } |
| | | |
| | | int totalSignup = signedUp + checkedIn; |
| | | WxCpActivityStatsRespVO vo = new WxCpActivityStatsRespVO(); |
| | | vo.setActivityId(activity.getId()); |
| | | vo.setActivityTitle(activity.getTitle()); |
| | | vo.setSignupLimit(activity.getSignupLimit()); |
| | | vo.setTotalSignup(totalSignup); |
| | | vo.setSignedUpCount(signedUp); |
| | | vo.setCheckedInCount(checkedIn); |
| | | vo.setCanceledCount(canceled); |
| | | |
| | | // åé¢ç¸å
³ææ ï¼ä¸éå颿¶ä¿æ nullï¼åç«¯æ®æ¤æ¾ç¤º"ä¸é" |
| | | Integer limit = activity.getSignupLimit(); |
| | | if (limit != null && limit > 0) { |
| | | vo.setRemaining(Math.max(0, limit - totalSignup)); |
| | | vo.setSignupPercent(Math.min(100, (int) Math.round(totalSignup * 100.0 / limit))); |
| | | } |
| | | vo.setCheckInPercent(totalSignup == 0 ? 0 |
| | | : (int) Math.round(checkedIn * 100.0 / totalSignup)); |
| | | |
| | | vo.setDeptStats(toNameCountList(deptMap)); |
| | | vo.setDateStats(toNameCountList(dateMap)); |
| | | // æ¥æºåå¸ï¼ç¨æ¾å¼ putï¼é¿å
å大æ¬å·åå§åæè·å¾ªç¯ä¸åæ´çå±é¨åé |
| | | Map<String, Integer> sourceMap = new LinkedHashMap<>(); |
| | | sourceMap.put("ä¼ä¸å¾®ä¿¡èº«ä»½", fromWxCp); |
| | | sourceMap.put("表åå¡«å", fromForm); |
| | | vo.setSourceStats(toNameCountList(sourceMap)); |
| | | return vo; |
| | | } |
| | | |
| | | private List<WxCpActivityStatsRespVO.NameCount> toNameCountList(Map<String, Integer> map) { |
| | | return map.entrySet().stream() |
| | | .map(e -> new WxCpActivityStatsRespVO.NameCount(e.getKey(), e.getValue())) |
| | | .toList(); |
| | | } |
| | | |
| | | // ==================== æ ¡éªè¾
å© ==================== |
| | | |
| | | private WxCpActivityDO validateActivityExists(Long id) { |
| | | if (id == null) { |
| | | throw exception(ACTIVITY_NOT_EXISTS); |
| | | } |
| | | WxCpActivityDO activity = wxcpActivityMapper.selectById(id); |
| | | if (activity == null) { |
| | | throw exception(ACTIVITY_NOT_EXISTS); |
| | | } |
| | | return activity; |
| | | } |
| | | |
| | | private WxCpActivitySignupDO validateSignupExists(Long id) { |
| | | WxCpActivitySignupDO signup = wxcpSignupMapper.selectById(id); |
| | | if (signup == null) { |
| | | throw exception(SIGNUP_NOT_EXISTS); |
| | | } |
| | | return signup; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.service.listener; |
| | | |
| | | import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; |
| | | import cn.iocoder.yudao.module.system.event.SocialLoginEvent; |
| | | import cn.iocoder.yudao.module.wxcp.service.notify.WxCpNotifyService; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.transaction.event.TransactionPhase; |
| | | import org.springframework.transaction.event.TransactionalEventListener; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡ç»å½äºä»¶çå¬å¨ |
| | | * |
| | | * çå¬ç¤¾äº¤ç»å½æåäºä»¶ï¼å½ç»å½æ¹å¼ä¸ºä¼ä¸å¾®ä¿¡æ¶ï¼åéç»å½éç¥æ¶æ¯ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Component |
| | | @Slf4j |
| | | public class WxCpLoginEventListener { |
| | | |
| | | @Resource |
| | | private WxCpNotifyService wxCpNotifyService; |
| | | |
| | | /** |
| | | * çå¬ç¤¾äº¤ç»å½äºä»¶ |
| | | * |
| | | * ä½¿ç¨ AFTER_COMMITï¼ç¡®ä¿äºå¡æäº¤åååééç¥ï¼ |
| | | * å¦å弿¥çº¿ç¨æ¥è¯¢ç»å®å
³ç³»æ¶ä¼å äºå¡æªæäº¤èæ¥ä¸å°è®°å½ï¼é¦æ¬¡èªå¨æ³¨ååºæ¯å°¤å
¶ææ¾ï¼ |
| | | * fallbackExecution = trueï¼è°ç¨æ¹æ äºå¡æ¶ä¹æ£å¸¸è§¦å |
| | | */ |
| | | @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true) |
| | | public void onSocialLogin(SocialLoginEvent event) { |
| | | // ä»
å¤çä¼ä¸å¾®ä¿¡ç»å½äºä»¶ |
| | | if (!SocialTypeEnum.WECHAT_ENTERPRISE.getType().equals(event.getSocialType())) { |
| | | return; |
| | | } |
| | | |
| | | log.info("[onSocialLogin][æ¶å°ä¼ä¸å¾®ä¿¡ç»å½äºä»¶ userId={} username={}]", |
| | | event.getUserId(), event.getUsername()); |
| | | |
| | | // åéä¼ä¸å¾®ä¿¡ç»å½éç¥ |
| | | wxCpNotifyService.sendLoginNotification( |
| | | event.getUserId(), |
| | | event.getUsername(), |
| | | event.getLoginType()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.service.member; |
| | | |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.member.vo.WxCpDepartmentRespVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.member.vo.WxCpMemberRespVO; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡éè®¯å½ Service æ¥å£ |
| | | * |
| | | * æ°æ®å®æ¶åèªä¼ä¸å¾®ä¿¡æ¥å£ãä¼ä¸å¾®ä¿¡å¯¹éè®¯å½æ¥å£æé¢çéå¶ï¼ |
| | | * è°ç¨æ¹ï¼ç®¡çåå°é¡µé¢ï¼åºé¿å
é«é¢è½®è¯¢ã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public interface WxCpMemberService { |
| | | |
| | | /** |
| | | * è·å¾é¨é¨æ ï¼å«æåæ°ï¼ |
| | | * |
| | | * @return é¨é¨æ ï¼æ ¹èç¹ä¸ºé¡¶çº§é¨é¨ |
| | | */ |
| | | List<WxCpDepartmentRespVO> getDepartmentTree(); |
| | | |
| | | /** |
| | | * è·å¾é¨é¨å¹³éºå表 |
| | | * |
| | | * @return é¨é¨å表 |
| | | */ |
| | | List<WxCpDepartmentRespVO> getDepartmentList(); |
| | | |
| | | /** |
| | | * è·å¾æåå表 |
| | | * |
| | | * @param departmentId é¨é¨IDï¼ä¼ null æ¶åæ ¹é¨é¨ï¼å³å
¨é¨å¯è§æåï¼ |
| | | * @param fetchChild æ¯å¦éå½è·ååé¨é¨æå |
| | | * @return æåå表 |
| | | */ |
| | | List<WxCpMemberRespVO> getMemberList(Long departmentId, Boolean fetchChild); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.service.member; |
| | | |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.member.vo.WxCpDepartmentRespVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.member.vo.WxCpMemberRespVO; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import me.chanjar.weixin.cp.api.WxCpService; |
| | | import me.chanjar.weixin.cp.bean.WxCpDepart; |
| | | import me.chanjar.weixin.cp.bean.WxCpUser; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.wxcp.enums.ErrorCodeConstants.WXCP_API_ERROR; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡éè®¯å½ Service å®ç°ç±» |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class WxCpMemberServiceImpl implements WxCpMemberService { |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ ¹é¨é¨IDåºå®ä¸º 1 |
| | | */ |
| | | private static final Long ROOT_DEPARTMENT_ID = 1L; |
| | | |
| | | @Resource |
| | | private WxCpService wxCpService; |
| | | |
| | | @Override |
| | | public List<WxCpDepartmentRespVO> getDepartmentList() { |
| | | List<WxCpDepart> departs = listDeparts(); |
| | | return departs.stream().map(this::toDeptVO).toList(); |
| | | } |
| | | |
| | | @Override |
| | | public List<WxCpDepartmentRespVO> getDepartmentTree() { |
| | | List<WxCpDepart> departs = listDeparts(); |
| | | Map<Long, WxCpDepartmentRespVO> voMap = new LinkedHashMap<>(); |
| | | for (WxCpDepart d : departs) { |
| | | voMap.put(d.getId(), toDeptVO(d)); |
| | | } |
| | | List<WxCpDepartmentRespVO> roots = new ArrayList<>(); |
| | | for (WxCpDepartmentRespVO vo : voMap.values()) { |
| | | Long parentId = vo.getParentId(); |
| | | WxCpDepartmentRespVO parent = parentId == null ? null : voMap.get(parentId); |
| | | // æ¾ä¸å°ç¶èç¹çï¼å«é¡¶çº§é¨é¨ï¼é½ä½ä¸ºæ ¹è¿åï¼é¿å
å æ°æ®ç¼ºå¤±å¯¼è´é¨é¨"æ¶å¤±" |
| | | if (parent == null || parent == vo) { |
| | | roots.add(vo); |
| | | } else { |
| | | parent.getChildren().add(vo); |
| | | } |
| | | } |
| | | return roots; |
| | | } |
| | | |
| | | @Override |
| | | public List<WxCpMemberRespVO> getMemberList(Long departmentId, Boolean fetchChild) { |
| | | Long deptId = departmentId != null ? departmentId : ROOT_DEPARTMENT_ID; |
| | | boolean child = fetchChild == null || fetchChild; |
| | | List<WxCpUser> users; |
| | | try { |
| | | users = wxCpService.getUserService().listSimpleByDepartment(deptId, child, null); |
| | | } catch (Exception e) { |
| | | log.error("[getMemberList][è·åä¼ä¸å¾®ä¿¡æå失败 deptId={} fetchChild={}]", deptId, child, e); |
| | | throw exception(WXCP_API_ERROR); |
| | | } |
| | | |
| | | // é¨é¨åæ å°ï¼ç¨äºææåçé¨é¨IDç¿»è¯æå¯è¯»åç§° |
| | | Map<Long, String> deptNameMap = new LinkedHashMap<>(); |
| | | for (WxCpDepart d : listDeparts()) { |
| | | deptNameMap.put(d.getId(), d.getName()); |
| | | } |
| | | |
| | | return users.stream().map(u -> toMemberVO(u, deptNameMap)).toList(); |
| | | } |
| | | |
| | | /** |
| | | * è·åå
¨é¨é¨é¨ï¼å¹³éºï¼ãä¼ä¸å¾®ä¿¡ department/list ä¸ä¼ id æ¶è¿åå
¨éã |
| | | */ |
| | | private List<WxCpDepart> listDeparts() { |
| | | try { |
| | | List<WxCpDepart> departs = wxCpService.getDepartmentService().list(null); |
| | | return departs != null ? departs : List.of(); |
| | | } catch (Exception e) { |
| | | log.error("[listDeparts][è·åä¼ä¸å¾®ä¿¡é¨é¨å¤±è´¥]", e); |
| | | throw exception(WXCP_API_ERROR); |
| | | } |
| | | } |
| | | |
| | | private WxCpDepartmentRespVO toDeptVO(WxCpDepart depart) { |
| | | WxCpDepartmentRespVO vo = new WxCpDepartmentRespVO(); |
| | | vo.setId(depart.getId()); |
| | | vo.setName(depart.getName()); |
| | | vo.setParentId(depart.getParentId()); |
| | | vo.setOrder(depart.getOrder()); |
| | | return vo; |
| | | } |
| | | |
| | | private WxCpMemberRespVO toMemberVO(WxCpUser user, Map<Long, String> deptNameMap) { |
| | | WxCpMemberRespVO vo = new WxCpMemberRespVO(); |
| | | vo.setUserId(user.getUserId()); |
| | | vo.setName(user.getName()); |
| | | vo.setMobile(user.getMobile()); |
| | | vo.setEmail(user.getEmail()); |
| | | vo.setAvatar(user.getAvatar()); |
| | | // ä¼å
å positionï¼èæ¥å£å¯è½åªè¿å positions æ°ç» |
| | | String position = user.getPosition(); |
| | | if (position == null && user.getPositions() != null && user.getPositions().length > 0) { |
| | | position = String.join("ã", user.getPositions()); |
| | | } |
| | | vo.setPosition(position); |
| | | vo.setStatus(user.getStatus()); |
| | | // Gender.getCode() è¿ååºæ¬ç±»å intï¼ä¸è½ä¸ null ç»æä¸å
表达å¼ï¼è¿éåç¬å¤æ |
| | | if (user.getGender() != null) { |
| | | vo.setGender(user.getGender().getCode()); |
| | | } |
| | | |
| | | Long[] deptIds = user.getDepartIds(); |
| | | if (deptIds != null && deptIds.length > 0) { |
| | | List<Long> ids = Arrays.asList(deptIds); |
| | | vo.setDepartmentIds(ids); |
| | | vo.setDepartmentNames(String.join("ã", ids.stream() |
| | | .map(id -> deptNameMap.getOrDefault(id, "é¨é¨" + id)) |
| | | .toList())); |
| | | } |
| | | return vo; |
| | | } |
| | | |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.service.notice; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo.WxCpNoticePageReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo.WxCpNoticeSaveReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.notice.WxCpNoticeDO; |
| | | import jakarta.validation.Valid; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡éç¥ Service æ¥å£ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public interface WxCpNoticeService { |
| | | |
| | | /** |
| | | * å建éç¥ï¼åå§ä¸ºèç¨¿ç¶æï¼ |
| | | * |
| | | * @param createReqVO åå»ºä¿¡æ¯ |
| | | * @return éç¥ç¼å· |
| | | */ |
| | | Long createNotice(@Valid WxCpNoticeSaveReqVO createReqVO); |
| | | |
| | | /** |
| | | * ä¿®æ¹éç¥ |
| | | * |
| | | * @param updateReqVO ä¿®æ¹ä¿¡æ¯ |
| | | */ |
| | | void updateNotice(@Valid WxCpNoticeSaveReqVO updateReqVO); |
| | | |
| | | /** |
| | | * å é¤éç¥ |
| | | * |
| | | * @param id éç¥ç¼å· |
| | | */ |
| | | void deleteNotice(Long id); |
| | | |
| | | /** |
| | | * è·å¾éç¥ |
| | | * |
| | | * @param id éç¥ç¼å· |
| | | * @return éç¥ |
| | | */ |
| | | WxCpNoticeDO getNotice(Long id); |
| | | |
| | | /** |
| | | * è·å¾éç¥å页 |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return éç¥å页 |
| | | */ |
| | | PageResult<WxCpNoticeDO> getNoticePage(@Valid WxCpNoticePageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * åå¸éç¥ï¼è稿 -> å·²åå¸ï¼ |
| | | * |
| | | * @param id éç¥ç¼å· |
| | | */ |
| | | void publishNotice(Long id); |
| | | |
| | | /** |
| | | * æ¤åéç¥ |
| | | * |
| | | * @param id éç¥ç¼å· |
| | | */ |
| | | void revokeNotice(Long id); |
| | | |
| | | /** |
| | | * æ¨ééç¥ç»ä¼ä¸å¾®ä¿¡æå |
| | | * |
| | | * @param pushReqVO æ¨éä¿¡æ¯ |
| | | * @return æ¨éç»æ |
| | | */ |
| | | WxCpPushResultVO pushNotice(@Valid WxCpPushReqVO pushReqVO); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.service.notice; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo.WxCpNoticePageReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.notice.vo.WxCpNoticeSaveReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushReqVO; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.dataobject.notice.WxCpNoticeDO; |
| | | import cn.iocoder.yudao.module.wxcp.dal.mysql.notice.WxCpNoticeMapper; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum; |
| | | import cn.iocoder.yudao.module.wxcp.enums.notice.WxCpNoticeStatusEnum; |
| | | import cn.iocoder.yudao.module.wxcp.service.push.WxCpMessagePushService; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Objects; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.wxcp.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡éç¥ Service å®ç°ç±» |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class WxCpNoticeServiceImpl implements WxCpNoticeService { |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡ææ¬æ¶æ¯ä¸é约 2048 åèï¼ä¸ææ 3 åèç®ã |
| | | * è¿éæå符æ°çåºè¶³å¤ä½éï¼è¶
åºé¨åæªæå¹¶å¨æ«å°¾æç¤ºï¼é¿å
æ´æ¡æ¨é失败ã |
| | | */ |
| | | private static final int MAX_PUSH_CONTENT_LENGTH = 600; |
| | | |
| | | @Resource |
| | | private WxCpNoticeMapper wxcpNoticeMapper; |
| | | @Resource |
| | | private WxCpMessagePushService messagePushService; |
| | | |
| | | @Override |
| | | public Long createNotice(WxCpNoticeSaveReqVO createReqVO) { |
| | | WxCpNoticeDO notice = BeanUtils.toBean(createReqVO, WxCpNoticeDO.class); |
| | | notice.setStatus(WxCpNoticeStatusEnum.DRAFT.getStatus()); |
| | | notice.setPushStatus(0); |
| | | // æ¨éèå´éå¡«ï¼ä¸ä¼ é»è®¤å
¨åï¼æ¨éæ¶è¿è½å¨å¼¹çªéæ¹ |
| | | if (notice.getPushScope() == null) { |
| | | notice.setPushScope(WxCpPushScopeEnum.ALL.getScope()); |
| | | } |
| | | wxcpNoticeMapper.insert(notice); |
| | | return notice.getId(); |
| | | } |
| | | |
| | | @Override |
| | | public void updateNotice(WxCpNoticeSaveReqVO updateReqVO) { |
| | | WxCpNoticeDO notice = validateNoticeExists(updateReqVO.getId()); |
| | | if (Objects.equals(notice.getStatus(), WxCpNoticeStatusEnum.REVOKED.getStatus())) { |
| | | throw exception(NOTICE_CANNOT_UPDATE); |
| | | } |
| | | WxCpNoticeDO updateObj = BeanUtils.toBean(updateReqVO, WxCpNoticeDO.class); |
| | | // ç¶æä¸æ¨éä¿¡æ¯ç±ç³»ç»ç»´æ¤ï¼ä¸å
许å端è¦ç |
| | | updateObj.setStatus(null); |
| | | updateObj.setPushStatus(null); |
| | | updateObj.setPushTime(null); |
| | | wxcpNoticeMapper.updateById(updateObj); |
| | | } |
| | | |
| | | @Override |
| | | public void deleteNotice(Long id) { |
| | | validateNoticeExists(id); |
| | | wxcpNoticeMapper.deleteById(id); |
| | | } |
| | | |
| | | @Override |
| | | public WxCpNoticeDO getNotice(Long id) { |
| | | return wxcpNoticeMapper.selectById(id); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<WxCpNoticeDO> getNoticePage(WxCpNoticePageReqVO pageReqVO) { |
| | | return wxcpNoticeMapper.selectPage(pageReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public void publishNotice(Long id) { |
| | | WxCpNoticeDO notice = validateNoticeExists(id); |
| | | if (Objects.equals(notice.getStatus(), WxCpNoticeStatusEnum.PUBLISHED.getStatus())) { |
| | | return; // å·²åå¸ï¼å¹çè¿å |
| | | } |
| | | if (Objects.equals(notice.getStatus(), WxCpNoticeStatusEnum.REVOKED.getStatus())) { |
| | | throw exception(NOTICE_STATUS_REVOKED); |
| | | } |
| | | wxcpNoticeMapper.updateById(WxCpNoticeDO.builder() |
| | | .id(id).status(WxCpNoticeStatusEnum.PUBLISHED.getStatus()).build()); |
| | | } |
| | | |
| | | @Override |
| | | public void revokeNotice(Long id) { |
| | | validateNoticeExists(id); |
| | | wxcpNoticeMapper.updateById(WxCpNoticeDO.builder() |
| | | .id(id).status(WxCpNoticeStatusEnum.REVOKED.getStatus()).build()); |
| | | } |
| | | |
| | | @Override |
| | | public WxCpPushResultVO pushNotice(WxCpPushReqVO pushReqVO) { |
| | | WxCpNoticeDO notice = validateNoticeExists(pushReqVO.getId()); |
| | | if (!Objects.equals(notice.getStatus(), WxCpNoticeStatusEnum.PUBLISHED.getStatus())) { |
| | | throw exception(NOTICE_STATUS_NOT_PUBLISHED); |
| | | } |
| | | |
| | | Integer scope = pushReqVO.getPushScope() != null ? pushReqVO.getPushScope() : notice.getPushScope(); |
| | | String target = StrUtil.isNotBlank(pushReqVO.getPushTarget()) |
| | | ? pushReqVO.getPushTarget() : notice.getPushTarget(); |
| | | if (scope == null) { |
| | | scope = WxCpPushScopeEnum.ALL.getScope(); |
| | | } |
| | | |
| | | // éç¥ç¨çº¯ææ¬æ¨éï¼æ²¡æå¯è·³è½¬ç详æ
页ï¼è textcard ç±»å强å¶è¦æ± url |
| | | WxCpPushResultVO result = messagePushService.pushText( |
| | | buildPushContent(notice), scope, target); |
| | | |
| | | if (Boolean.TRUE.equals(result.getSuccess())) { |
| | | wxcpNoticeMapper.updateById(WxCpNoticeDO.builder() |
| | | .id(notice.getId()) |
| | | .pushScope(scope) |
| | | .pushTarget(target) |
| | | .pushStatus(1) |
| | | .pushTime(LocalDateTime.now()) |
| | | .build()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * æ¼è£
æ¨éæ£æï¼æ é¢å ç²æ ·å¼ç¨ä¸äºï¼æ¹ç¨ããå
裹ï¼è¿é¿åæªæå¹¶æç¤º |
| | | */ |
| | | private String buildPushContent(WxCpNoticeDO notice) { |
| | | String body = StrUtil.blankToDefault(notice.getContent(), ""); |
| | | String content = "ã" + notice.getTitle() + "ã\n" + body; |
| | | if (content.length() > MAX_PUSH_CONTENT_LENGTH) { |
| | | content = content.substring(0, MAX_PUSH_CONTENT_LENGTH) |
| | | + "\nâ¦â¦ï¼å
容è¿é¿å·²æªæï¼è¯·å°ç³»ç»å
æ¥ç宿´éç¥ï¼"; |
| | | } |
| | | return content; |
| | | } |
| | | |
| | | private WxCpNoticeDO validateNoticeExists(Long id) { |
| | | if (id == null) { |
| | | throw exception(NOTICE_NOT_EXISTS); |
| | | } |
| | | WxCpNoticeDO notice = wxcpNoticeMapper.selectById(id); |
| | | if (notice == null) { |
| | | throw exception(NOTICE_NOT_EXISTS); |
| | | } |
| | | return notice; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.service.notify; |
| | | |
| | | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; |
| | | import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper; |
| | | import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper; |
| | | import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import me.chanjar.weixin.cp.api.WxCpService; |
| | | import me.chanjar.weixin.cp.bean.message.WxCpMessage; |
| | | import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult; |
| | | import org.springframework.scheduling.annotation.Async; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ¶æ¯éç¥æå¡ |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Service |
| | | @Slf4j |
| | | public class WxCpNotifyService { |
| | | |
| | | @Resource |
| | | private WxCpService wxCpService; |
| | | |
| | | @Resource |
| | | private SocialUserBindMapper socialUserBindMapper; |
| | | |
| | | @Resource |
| | | private SocialUserMapper socialUserMapper; |
| | | |
| | | /** |
| | | * åéç»å½éç¥æ¶æ¯å°ä¼ä¸å¾®ä¿¡ |
| | | * |
| | | * @param userId ç³»ç»ç¨æ· ID |
| | | * @param username ç¨æ·ç»å½å |
| | | * @param loginType ç»å½æ¹å¼ï¼å¦ï¼è´¦å·å¯ç ãçä¿¡ãç¤¾äº¤ï¼ |
| | | */ |
| | | @Async |
| | | public void sendLoginNotification(Long userId, String username, String loginType) { |
| | | try { |
| | | // 1. æ¥è¯¢ç¨æ·ç»å®çä¼ä¸å¾®ä¿¡ |
| | | String wxUserId = getWxUserId(userId); |
| | | if (wxUserId == null) { |
| | | log.info("[sendLoginNotification][ç¨æ·({})æªç»å®ä¼ä¸å¾®ä¿¡ï¼è·³è¿éç¥]", userId); |
| | | return; |
| | | } |
| | | |
| | | // 2. æé ææ¬æ¶æ¯ |
| | | // 注æï¼ä¸è½ç¨ TEXTCARD ââ ä¼ä¸å¾®ä¿¡è¦æ± textcard ç url å¿
å¡«ï¼ |
| | | // ç空ä¼è¿å 41010 missing urlï¼å¯¼è´éç¥å
¨é¨åé失败 |
| | | String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); |
| | | WxCpMessage message = WxCpMessage.TEXT() |
| | | .toUser(wxUserId) |
| | | .content(String.format( |
| | | "ãç»å½éç¥ãæ¨çè´¦å·äº %s éè¿ã%sãæ¹å¼ç»å½ç³»ç»ã\nå¦éæ¬äººæä½ï¼è¯·ç«å³ä¿®æ¹å¯ç ã", |
| | | time, loginType)) |
| | | .build(); |
| | | |
| | | // 3. åéæ¶æ¯ |
| | | WxCpMessageSendResult result = wxCpService.getMessageService().send(message); |
| | | if (result.getErrCode() == 0) { |
| | | log.info("[sendLoginNotification][ä¼ä¸å¾®ä¿¡ç»å½éç¥åéæå userId={} wxUserId={}]", userId, wxUserId); |
| | | } else { |
| | | log.warn("[sendLoginNotification][ä¼ä¸å¾®ä¿¡ç»å½éç¥åé失败 userId={} wxUserId={} errCode={} errMsg={}]", |
| | | userId, wxUserId, result.getErrCode(), result.getErrMsg()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[sendLoginNotification][ä¼ä¸å¾®ä¿¡ç»å½éç¥åéå¼å¸¸ userId={}]", userId, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å鿮鿿¬æ¶æ¯å°ä¼ä¸å¾®ä¿¡ |
| | | * |
| | | * @param userId ç³»ç»ç¨æ· ID |
| | | * @param content æ¶æ¯å
容 |
| | | */ |
| | | @Async |
| | | public void sendTextMessage(Long userId, String content) { |
| | | try { |
| | | String wxUserId = getWxUserId(userId); |
| | | if (wxUserId == null) { |
| | | return; |
| | | } |
| | | |
| | | WxCpMessage message = WxCpMessage.TEXT() |
| | | .toUser(wxUserId) |
| | | .content(content) |
| | | .build(); |
| | | |
| | | WxCpMessageSendResult result = wxCpService.getMessageService().send(message); |
| | | if (result.getErrCode() == 0) { |
| | | log.info("[sendTextMessage][ä¼ä¸å¾®ä¿¡ææ¬æ¶æ¯åéæå userId={}]", userId); |
| | | } else { |
| | | log.warn("[sendTextMessage][ä¼ä¸å¾®ä¿¡ææ¬æ¶æ¯åé失败 userId={} errCode={} errMsg={}]", |
| | | userId, result.getErrCode(), result.getErrMsg()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[sendTextMessage][ä¼ä¸å¾®ä¿¡ææ¬æ¶æ¯åéå¼å¸¸ userId={}]", userId, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®ç³»ç»ç¨æ· ID è·åä¼ä¸å¾®ä¿¡ UserId |
| | | * |
| | | * @param userId ç³»ç»ç¨æ· ID |
| | | * @return ä¼ä¸å¾®ä¿¡ UserIdï¼å³ openidï¼ |
| | | */ |
| | | private String getWxUserId(Long userId) { |
| | | // 1. æ¥ç»å®å
³ç³» |
| | | SocialUserBindDO bind = socialUserBindMapper.selectByUserIdAndUserTypeAndSocialType( |
| | | userId, UserTypeEnum.ADMIN.getValue(), SocialTypeEnum.WECHAT_ENTERPRISE.getType()); |
| | | if (bind == null) { |
| | | return null; |
| | | } |
| | | |
| | | // 2. æ¥ç¤¾äº¤ç¨æ·ä¿¡æ¯ |
| | | SocialUserDO socialUser = socialUserMapper.selectById(bind.getSocialUserId()); |
| | | if (socialUser == null) { |
| | | return null; |
| | | } |
| | | |
| | | return socialUser.getOpenid(); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.service.push; |
| | | |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ¶æ¯æ¨é Service æ¥å£ |
| | | * |
| | | * æ½åºæ¥ä¾æ´»å¨ãéç¥çä¸å¡å¤ç¨ï¼é¿å
åå¤é夿¼è£
æ¥æ¶è
ä¸é误å¤ç |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public interface WxCpMessagePushService { |
| | | |
| | | /** |
| | | * æ¨éææ¬å¡çæ¶æ¯ |
| | | * |
| | | * @param title å¡çæ é¢ |
| | | * @param description å¡çæ£æ |
| | | * @param url ç¹å»æé®è·³è½¬å°åï¼textcard ç±»åå¿
å¡« |
| | | * @param btnTxt æé®ææ¡ |
| | | * @param scope æ¨éèå´ï¼è§ {@link cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum} |
| | | * @param target æ¨éç®æ ï¼èå´æ¯å
¨åæ¶å¯ä¼ 空 |
| | | * @return æ¨éç»æï¼ä¸æå¼å¸¸ï¼å¤±è´¥ä¿¡æ¯éè¿è¿åå¼ç»åºï¼ |
| | | */ |
| | | WxCpPushResultVO pushCard(String title, String description, String url, |
| | | String btnTxt, Integer scope, String target); |
| | | |
| | | /** |
| | | * æ¨éçº¯ææ¬æ¶æ¯ |
| | | * |
| | | * @param content ææ¬å
容 |
| | | * @param scope æ¨éèå´ |
| | | * @param target æ¨éç®æ |
| | | * @return æ¨éç»æ |
| | | */ |
| | | WxCpPushResultVO pushText(String content, Integer scope, String target); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.wxcp.service.push; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.module.wxcp.controller.admin.vo.WxCpPushResultVO; |
| | | import cn.iocoder.yudao.module.wxcp.enums.activity.WxCpPushScopeEnum; |
| | | import cn.iocoder.yudao.module.wxcp.framework.config.WxCpProperties; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import me.chanjar.weixin.cp.api.WxCpService; |
| | | import me.chanjar.weixin.cp.bean.message.WxCpMessage; |
| | | import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.util.Objects; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.wxcp.enums.ErrorCodeConstants.ACTIVITY_PUSH_TARGET_EMPTY; |
| | | |
| | | /** |
| | | * ä¼ä¸å¾®ä¿¡æ¶æ¯æ¨é Service å®ç°ç±» |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class WxCpMessagePushServiceImpl implements WxCpMessagePushService { |
| | | |
| | | @Resource |
| | | private WxCpService wxCpService; |
| | | @Resource |
| | | private WxCpProperties wxCpProperties; |
| | | |
| | | @Override |
| | | public WxCpPushResultVO pushCard(String title, String description, String url, |
| | | String btnTxt, Integer scope, String target) { |
| | | // textcard ç±»åç url å¿
å¡«ï¼ç空ä¼ä¸å¾®ä¿¡ä¼è¿å 41010 |
| | | if (StrUtil.isBlank(url)) { |
| | | throw new IllegalArgumentException("æ¨éå¡çæ¶æ¯æ¶ url ä¸è½ä¸ºç©º"); |
| | | } |
| | | WxCpMessage message = WxCpMessage.TEXTCARD() |
| | | .agentId(agentId()) |
| | | .title(title) |
| | | .description(description) |
| | | .url(url) |
| | | .btnTxt(StrUtil.blankToDefault(btnTxt, "æ¥ç详æ
")) |
| | | .build(); |
| | | return doSend(message, scope, target, "card"); |
| | | } |
| | | |
| | | @Override |
| | | public WxCpPushResultVO pushText(String content, Integer scope, String target) { |
| | | WxCpMessage message = WxCpMessage.TEXT() |
| | | .agentId(agentId()) |
| | | .content(content) |
| | | .build(); |
| | | return doSend(message, scope, target, "text"); |
| | | } |
| | | |
| | | /** |
| | | * ç»ä¸å¤çæ¥æ¶è
è§£æãåéä¸é误å
è£
|
| | | */ |
| | | private WxCpPushResultVO doSend(WxCpMessage message, Integer scope, String target, String type) { |
| | | Recipients recipients = resolveRecipients(scope, target); |
| | | message.setToUser(recipients.toUser); |
| | | message.setToParty(recipients.toParty); |
| | | |
| | | WxCpMessageSendResult result; |
| | | try { |
| | | result = wxCpService.getMessageService().send(message); |
| | | } catch (Exception e) { |
| | | // ç½ç»/é
ç½®å¼å¸¸ä¸å䏿ï¼è½¬æå¯è¯»ç»æï¼é¿å
å端æ¿å° 500 ä¸ç¥éåå |
| | | log.error("[doSend][ä¼ä¸å¾®ä¿¡æ¶æ¯æ¨éå¼å¸¸ type={} toUser={} toParty={}]", |
| | | type, recipients.toUser, recipients.toParty, e); |
| | | return new WxCpPushResultVO(false, null, "æ¨éå¼å¸¸ï¼" + e.getMessage(), |
| | | recipients.toUser, recipients.toParty); |
| | | } |
| | | |
| | | if (result.getErrCode() != null && result.getErrCode() == 0) { |
| | | log.info("[doSend][ä¼ä¸å¾®ä¿¡æ¶æ¯æ¨éæå type={} toUser={} toParty={}]", |
| | | type, recipients.toUser, recipients.toParty); |
| | | return new WxCpPushResultVO(true, 0, "æ¨éæå", recipients.toUser, recipients.toParty); |
| | | } |
| | | log.warn("[doSend][ä¼ä¸å¾®ä¿¡æ¶æ¯æ¨é失败 type={} errCode={} errMsg={}]", |
| | | type, result.getErrCode(), result.getErrMsg()); |
| | | return new WxCpPushResultVO(false, result.getErrCode(), |
| | | "ä¼ä¸å¾®ä¿¡è¿åï¼" + result.getErrMsg(), recipients.toUser, recipients.toParty); |
| | | } |
| | | |
| | | /** |
| | | * è§£ææ¨éèå´ä¸ç®æ ï¼è½¬æä¼ä¸å¾®ä¿¡è¦æ±ç | åéæ ¼å¼ |
| | | */ |
| | | private Recipients resolveRecipients(Integer scope, String target) { |
| | | int actualScope = scope != null ? scope : WxCpPushScopeEnum.ALL.getScope(); |
| | | if (Objects.equals(actualScope, WxCpPushScopeEnum.ALL.getScope())) { |
| | | return new Recipients("@all", null); |
| | | } |
| | | if (StrUtil.isBlank(target)) { |
| | | throw exception(ACTIVITY_PUSH_TARGET_EMPTY); |
| | | } |
| | | // 忶容å¿ä¸è±æéå·ä¸ç«çº¿ï¼é¿å
é
ç½®æ¶æè¯¯ |
| | | String normalized = target.replace("ï¼", ",").replace(",", "|").replace(" ", ""); |
| | | if (Objects.equals(actualScope, WxCpPushScopeEnum.PARTY.getScope())) { |
| | | return new Recipients(null, normalized); |
| | | } |
| | | return new Recipients(normalized, null); |
| | | } |
| | | |
| | | private Integer agentId() { |
| | | return StrUtil.isBlank(wxCpProperties.getAgentId()) |
| | | ? null : Integer.valueOf(wxCpProperties.getAgentId()); |
| | | } |
| | | |
| | | private record Recipients(String toUser, String toParty) { |
| | | } |
| | | |
| | | } |
| | |
| | | <version>${revision}</version> |
| | | </dependency> |
| | | |
| | | <!-- ä¼ä¸å¾®ä¿¡æ¨¡å --> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-module-wxcp</artifactId> |
| | | <version>${revision}</version> |
| | | </dependency> |
| | | |
| | | <!-- è´¢å¡ç®¡ç模å --> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |