diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 8406d508bc..0000000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 49330ee16f..ae493b8dad 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,5 @@ rebel.xml application-my.yaml /yudao-ui-app/unpackage/ +.DS_Store **/.DS_Store diff --git a/.image/common/im-feature.png b/.image/common/im-feature.png new file mode 100644 index 0000000000..da3f012801 Binary files /dev/null and b/.image/common/im-feature.png differ diff --git a/.image/common/im-preview-home.png b/.image/common/im-preview-home.png new file mode 100644 index 0000000000..c4790a3246 Binary files /dev/null and b/.image/common/im-preview-home.png differ diff --git a/.image/common/im-preview-manager.png b/.image/common/im-preview-manager.png new file mode 100644 index 0000000000..b8bb057dcf Binary files /dev/null and b/.image/common/im-preview-manager.png differ diff --git a/README.md b/README.md index 6f036fa0cd..2c6a9691ef 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ | 【完整版】[ruoyi-vue-pro](https://gitee.com/zhijiantianya/ruoyi-vue-pro) | [`master`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master-jdk17/) 分支 | | 【精简版】[yudao-boot-mini](https://gitee.com/yudaocode/yudao-boot-mini) | [`master`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master-jdk17/) 分支 | -* 【完整版】:包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、AI 大模型、IoT 物联网 等功能 -* 【精简版】:只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、AI 大模型、IoT 物联网 等功能 +* 【完整版】:包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、IM 即时通讯、AI 大模型、IoT 物联网等功能 +* 【精简版】:只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、IM 即时通讯、AI 大模型、IoT 物联网等功能 可参考 [《迁移文档》](https://doc.iocoder.cn/migrate-module/) ,只需要 5-10 分钟,即可将【完整版】按需迁移到【精简版】 @@ -102,7 +102,7 @@ 团队包含专业的项目经理、架构师、前端工程师、后端工程师、测试工程师、运维工程师,可以提供全流程的外包服务。 -项目可以是商城、SCRM 系统、OA 系统、物流系统、ERP 系统、CMS 系统、HIS 系统、支付系统、IM 聊天、微信公众号、微信小程序等等。 +项目可以是商城、SCRM 系统、OA 系统、物流系统、ERP 系统、CMS 系统、HIS 系统、支付系统、IM 即时通讯、微信公众号、微信小程序等等。 ## 🐼 内置功能 @@ -308,6 +308,19 @@ ![预览图](/.image/common/iot-preview.png) +### IM 即时通讯 + +演示地址(Boot): + +演示地址(Vue3 + Element Plus): + + +![功能图](/.image/common/im-feature.png) + +| 聊天界面 | 聊天管理 | +| --- | --- | +| ![聊天界面](/.image/common/im-preview-home.png) | ![聊天管理](/.image/common/im-preview-manager.png) | + ## 🐨 技术栈 ### 模块 @@ -327,6 +340,7 @@ | `yudao-module-crm` | CRM 系统的 Module 模块 | | `yudao-module-mes` | MES 系统的 Module 模块 | | `yudao-module-wms` | WMS 系统的 Module 模块 | +| `yudao-module-im` | IM 即时通讯的 Module 模块 | | `yudao-module-ai` | AI 大模型的 Module 模块 | | `yudao-module-iot` | IoT 物联网的 Module 模块 | | `yudao-module-mp` | 微信公众号的 Module 模块 | diff --git a/pom.xml b/pom.xml index bd0734948f..cbafbb7c3f 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ + @@ -35,7 +36,7 @@ https://github.com/YunaiV/ruoyi-vue-pro - 2026.04-SNAPSHOT + 2026.05-SNAPSHOT 21 ${java.version} diff --git a/script/livekit-poc/README.md b/script/livekit-poc/README.md new file mode 100644 index 0000000000..104cdb2654 --- /dev/null +++ b/script/livekit-poc/README.md @@ -0,0 +1,32 @@ +# LiveKit Server PoC + +最小可用的 LiveKit Server 自部署验证环境,用于零期 PoC。 + +## 启动 + +```bash +cd tools/livekit-poc +docker compose up -d +bash verify.sh +``` + +## 端口 + +- 7880:HTTP / WebSocket 信令; +- 7881:WebRTC TCP fallback; +- 7882/UDP:WebRTC 媒体; +- macOS / Windows:当前 `docker-compose.yml` 走端口映射模式,webhook URL 用 `host.docker.internal:48080` 让容器访问到宿主机 yudao 后端; +- macOS 上 host network(`network_mode: host`)需要 Docker Desktop 4.34+ 并在 Settings → Resources → Network 勾选「Enable host networking」,老版本静默失败(容器跑得起来但端口完全不通); +- Linux:可以把 `docker-compose.yml` 改成 `network_mode: host` + 删 `ports:` 段,并把 `livekit.yaml` 的 webhook URL 改为 `http://127.0.0.1:48080/admin-api/im/livekit/webhook`。 + +## 凭据 (仅 PoC,勿用于生产) + +- `LIVEKIT_KEYS=devkey: secret-poc-key-min-32-chars-required-here` +- API Key:`devkey` +- API Secret:`secret-poc-key-min-32-chars-required-here` + +生产环境必须改用强随机 secret,并通过 `--config /etc/livekit.yaml` 加载。 + +## 浏览器联调 + +`verify.sh` 跑完会输出一个 `meet.livekit.io` 链接,用两个浏览器(或两台机器)打开同一链接即可看到对方画面。 diff --git a/script/livekit-poc/docker-compose.yml b/script/livekit-poc/docker-compose.yml new file mode 100644 index 0000000000..893daf2d38 --- /dev/null +++ b/script/livekit-poc/docker-compose.yml @@ -0,0 +1,20 @@ +services: + livekit: + image: docker.m.daocloud.io/livekit/livekit-server:latest + container_name: yudao-livekit-dev + restart: unless-stopped + # 端口映射模式 + # macOS / Windows 必走这种方式:Docker Desktop 4.34 以下没有 host network + # Linux 可以改 network_mode: host 省去映射,并把 livekit.yaml 的 webhook url 换成 127.0.0.1 + ports: + - "7880:7880" # HTTP / WebSocket 信令 + - "7881:7881" # WebRTC TCP fallback + - "7882:7882/udp" # WebRTC UDP (dev 模式 UDP mux 单端口) + volumes: + # 挂载 config 文件;webhook 配置在 livekit.yaml 里 + - ./livekit.yaml:/etc/livekit.yaml:ro + command: + - --config + - /etc/livekit.yaml + - --bind + - 0.0.0.0 diff --git a/script/livekit-poc/livekit.yaml b/script/livekit-poc/livekit.yaml new file mode 100644 index 0000000000..0dd255ebf2 --- /dev/null +++ b/script/livekit-poc/livekit.yaml @@ -0,0 +1,30 @@ +# LiveKit Server 本地开发配置(PoC 用,勿用于生产) +# 替代 docker --dev 模式;为支持 webhook 必须用 config 文件而非 env + +keys: + devkey: secret-poc-key-min-32-chars-required-here + +# 端口 +port: 7880 +rtc: + tcp_port: 7881 + udp_port: 7882 + use_external_ip: false + +# Webhook:成员离开 / 房间结束等事件回调到 yudao 后端做业务态兜底清理 +# host.docker.internal 让容器访问宿主机 macOS / Windows 上的 yudao 后端 +# Linux 上 docker compose 可改 network_mode: host,这里同步改成 127.0.0.1 +# api_key 用于签发 JWT,yudao 后端用相同 secret 验证签名 +webhook: + api_key: devkey + urls: + - http://host.docker.internal:48080/admin-api/im/livekit/webhook + +# 房间无人时多久销毁;秒;之前 PoC 默认 300,给低些方便排查 +room: + empty_timeout: 300 + departure_timeout: 20 + +# 开发模式:放宽 secret 长度限制 + 内置 TURN 服务 +development: true +log_level: info diff --git a/script/livekit-poc/verify.sh b/script/livekit-poc/verify.sh new file mode 100755 index 0000000000..9cf9a152ca --- /dev/null +++ b/script/livekit-poc/verify.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# LiveKit Server PoC 验证脚本; +# 用法: bash verify.sh +set -e + +API_KEY="${LIVEKIT_API_KEY:-devkey}" +API_SECRET="${LIVEKIT_API_SECRET:-secret-poc-key-min-32-chars-required-here}" +HOST="${LIVEKIT_HOST:-localhost:7880}" +ROOM="${LIVEKIT_ROOM:-poc-room}" + +ok() { printf "[OK] %s\n" "$1"; } +fail() { printf "[FAIL] %s\n" "$1"; exit 1; } + +echo "==> 1/5 等待 HTTP 端点就绪 (http://${HOST}/)" +for i in $(seq 1 20); do + code=$(curl -s -o /dev/null -w "%{http_code}" "http://${HOST}/" || echo "000") + [ "$code" = "200" ] && { ok "HTTP 200"; break; } + [ $i -eq 20 ] && fail "20 秒内未就绪 (last code=${code})" + sleep 1 +done + +echo "==> 2/5 签发管理 + 客户端权限 Token" +TOKEN=$(API_KEY="$API_KEY" API_SECRET="$API_SECRET" ROOM="$ROOM" python3 - <<'PY' +import json, time, hmac, hashlib, base64, os +def b64u(b): return base64.urlsafe_b64encode(b).rstrip(b'=').decode() +header = b64u(json.dumps({"alg":"HS256","typ":"JWT"}, separators=(',',':')).encode()) +payload = b64u(json.dumps({ + "iss": os.environ["API_KEY"], + "sub": "poc-tester", + "name": "PoC Tester", + "video": { + "roomJoin": True, "room": os.environ["ROOM"], + "canPublish": True, "canSubscribe": True, "canPublishData": True, + "roomCreate": True, "roomList": True, "roomAdmin": True + }, + "exp": int(time.time()) + 3600, + "nbf": int(time.time()) +}, separators=(',',':')).encode()) +sig = b64u(hmac.new(os.environ["API_SECRET"].encode(), + f"{header}.{payload}".encode(), + hashlib.sha256).digest()) +print(f"{header}.{payload}.{sig}") +PY +) +[ -n "$TOKEN" ] || fail "Token 生成失败" +ok "Token 已生成 (${#TOKEN} chars)" + +echo "==> 3/5 创建房间 ${ROOM} (CreateRoom RPC)" +create_resp=$(curl -s -X POST "http://${HOST}/twirp/livekit.RoomService/CreateRoom" \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"${ROOM}\",\"empty_timeout\":300,\"max_participants\":10}") +echo " 响应: $create_resp" +echo "$create_resp" | jq -e '.sid' >/dev/null 2>&1 \ + && ok "房间已创建" \ + || fail "CreateRoom 失败" + +echo "==> 4/5 列出房间 (ListRooms RPC)" +list_resp=$(curl -s -X POST "http://${HOST}/twirp/livekit.RoomService/ListRooms" \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{}') +room_count=$(echo "$list_resp" | jq '.rooms | length' 2>/dev/null || echo "0") +ok "当前房间数: ${room_count}" +echo "$list_resp" | jq '.' + +echo "==> 5/5 删除房间 (DeleteRoom RPC) —— 清理" +del_resp=$(curl -s -X POST "http://${HOST}/twirp/livekit.RoomService/DeleteRoom" \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"room\":\"${ROOM}\"}") +ok "删除响应: $del_resp" + +# 重新签一个仅 client 权限的 token;用于浏览器进会 +CLIENT_TOKEN=$(API_KEY="$API_KEY" API_SECRET="$API_SECRET" ROOM="$ROOM" python3 - <<'PY' +import json, time, hmac, hashlib, base64, os +def b64u(b): return base64.urlsafe_b64encode(b).rstrip(b'=').decode() +header = b64u(json.dumps({"alg":"HS256","typ":"JWT"}, separators=(',',':')).encode()) +payload = b64u(json.dumps({ + "iss": os.environ["API_KEY"], + "sub": "browser-tester", + "name": "Browser", + "video": { + "roomJoin": True, "room": os.environ["ROOM"], + "canPublish": True, "canSubscribe": True, "canPublishData": True + }, + "exp": int(time.time()) + 7200 +}, separators=(',',':')).encode()) +sig = b64u(hmac.new(os.environ["API_SECRET"].encode(), + f"{header}.{payload}".encode(), + hashlib.sha256).digest()) +print(f"{header}.{payload}.{sig}") +PY +) + +echo "" +echo "============================================================" +echo " LiveKit Server 验证通过" +echo "============================================================" +echo " 浏览器测试 (开两个窗口能互通):" +echo " https://meet.livekit.io/?liveKitUrl=ws%3A%2F%2F${HOST}&token=${CLIENT_TOKEN}" +echo "" +echo " 停止服务:" +echo " docker compose -f tools/livekit-poc/docker-compose.yml down" +echo "============================================================" diff --git a/sql/dm/ruoyi-vue-pro-dm8.sql b/sql/dm/ruoyi-vue-pro-dm8.sql index 3cc9a9a175..ff6652b233 100644 --- a/sql/dm/ruoyi-vue-pro-dm8.sql +++ b/sql/dm/ruoyi-vue-pro-dm8.sql @@ -1270,6 +1270,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); @@ -1552,9 +1554,9 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3443, 1, '草稿', '0', 'mes_wm_product_produce_status', 0, 'info', '', '草稿状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3444, 2, '已完成', '4', 'mes_wm_product_produce_status', 0, 'success', '', '已完成状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3445, 3, '已取消', '5', 'mes_wm_product_produce_status', 0, 'danger', '', '已取消状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); COMMIT; SET IDENTITY_INSERT system_dict_data OFF; -- @formatter:on @@ -5617,4 +5619,3 @@ INSERT INTO yudao_demo03_student (id, name, sex, birthday, description, creator, COMMIT; SET IDENTITY_INSERT yudao_demo03_student OFF; -- @formatter:on - diff --git a/sql/highgo/quartz.sql b/sql/highgo/quartz.sql new file mode 100644 index 0000000000..46bb938431 --- /dev/null +++ b/sql/highgo/quartz.sql @@ -0,0 +1,208 @@ +-- https://github.com/quartz-scheduler/quartz/blob/main/quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_postgres.sql +-- Thanks to Patrick Lightbody for submitting this... +-- +-- In your Quartz properties file, you'll need to set +-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate + +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +CREATE TABLE QRTZ_JOB_DETAILS +( + SCHED_NAME VARCHAR(120) NOT NULL, + JOB_NAME VARCHAR(200) NOT NULL, + JOB_GROUP VARCHAR(200) NOT NULL, + DESCRIPTION VARCHAR(250) NULL, + JOB_CLASS_NAME VARCHAR(250) NOT NULL, + IS_DURABLE BOOL NOT NULL, + IS_NONCONCURRENT BOOL NOT NULL, + IS_UPDATE_DATA BOOL NOT NULL, + REQUESTS_RECOVERY BOOL NOT NULL, + JOB_DATA BYTEA NULL, + PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) +); + +CREATE TABLE QRTZ_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + JOB_NAME VARCHAR(200) NOT NULL, + JOB_GROUP VARCHAR(200) NOT NULL, + DESCRIPTION VARCHAR(250) NULL, + NEXT_FIRE_TIME BIGINT NULL, + PREV_FIRE_TIME BIGINT NULL, + PRIORITY INTEGER NULL, + TRIGGER_STATE VARCHAR(16) NOT NULL, + TRIGGER_TYPE VARCHAR(8) NOT NULL, + START_TIME BIGINT NOT NULL, + END_TIME BIGINT NULL, + CALENDAR_NAME VARCHAR(200) NULL, + MISFIRE_INSTR SMALLINT NULL, + JOB_DATA BYTEA NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) + REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP) +); + +CREATE TABLE QRTZ_SIMPLE_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + REPEAT_COUNT BIGINT NOT NULL, + REPEAT_INTERVAL BIGINT NOT NULL, + TIMES_TRIGGERED BIGINT NOT NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_CRON_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + CRON_EXPRESSION VARCHAR(120) NOT NULL, + TIME_ZONE_ID VARCHAR(80), + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_SIMPROP_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13, 4) NULL, + DEC_PROP_2 NUMERIC(13, 4) NULL, + BOOL_PROP_1 BOOL NULL, + BOOL_PROP_2 BOOL NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_BLOB_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + BLOB_DATA BYTEA NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_CALENDARS +( + SCHED_NAME VARCHAR(120) NOT NULL, + CALENDAR_NAME VARCHAR(200) NOT NULL, + CALENDAR BYTEA NOT NULL, + PRIMARY KEY (SCHED_NAME, CALENDAR_NAME) +); + + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_FIRED_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + ENTRY_ID VARCHAR(95) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + INSTANCE_NAME VARCHAR(200) NOT NULL, + FIRED_TIME BIGINT NOT NULL, + SCHED_TIME BIGINT NOT NULL, + PRIORITY INTEGER NOT NULL, + STATE VARCHAR(16) NOT NULL, + JOB_NAME VARCHAR(200) NULL, + JOB_GROUP VARCHAR(200) NULL, + IS_NONCONCURRENT BOOL NULL, + REQUESTS_RECOVERY BOOL NULL, + PRIMARY KEY (SCHED_NAME, ENTRY_ID) +); + +CREATE TABLE QRTZ_SCHEDULER_STATE +( + SCHED_NAME VARCHAR(120) NOT NULL, + INSTANCE_NAME VARCHAR(200) NOT NULL, + LAST_CHECKIN_TIME BIGINT NOT NULL, + CHECKIN_INTERVAL BIGINT NOT NULL, + PRIMARY KEY (SCHED_NAME, INSTANCE_NAME) +); + +CREATE TABLE QRTZ_LOCKS +( + SCHED_NAME VARCHAR(120) NOT NULL, + LOCK_NAME VARCHAR(40) NOT NULL, + PRIMARY KEY (SCHED_NAME, LOCK_NAME) +); + +CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY + ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_J_GRP + ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP); + +CREATE INDEX IDX_QRTZ_T_J + ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_JG + ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_C + ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME); +CREATE INDEX IDX_QRTZ_T_G + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_T_STATE + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_STATE + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_G_STATE + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME + ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE); + +CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME); +CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_FT_J_G + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_JG + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_T_G + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_FT_TG + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); + + +COMMIT; \ No newline at end of file diff --git a/sql/highgo/ruoyi-vue-pro.sql b/sql/highgo/ruoyi-vue-pro.sql new file mode 100644 index 0000000000..6384bc28f7 --- /dev/null +++ b/sql/highgo/ruoyi-vue-pro.sql @@ -0,0 +1,6198 @@ +/* + Yudao Database Transfer Tool + + Source Server Type : MySQL + + Target Server Type : HighGo + + Date: 2026-06-13 20:18:03 +*/ + + +-- ---------------------------- +-- Table structure for dual +-- ---------------------------- +DROP TABLE IF EXISTS dual; +CREATE TABLE dual +( + id int2 +); + +COMMENT ON TABLE dual IS '数据库连接的表'; + +-- ---------------------------- +-- Records of dual +-- ---------------------------- +-- @formatter:off +INSERT INTO dual VALUES (1); +-- @formatter:on + +-- ---------------------------- +-- Table structure for infra_api_access_log +-- ---------------------------- +DROP TABLE IF EXISTS infra_api_access_log; +CREATE TABLE infra_api_access_log ( + id int8 NOT NULL, + trace_id varchar(64) NOT NULL DEFAULT '', + user_id int8 NOT NULL DEFAULT 0, + user_type int2 NOT NULL DEFAULT 0, + application_name varchar(50) NOT NULL, + request_method varchar(16) NOT NULL DEFAULT '', + request_url varchar(255) NOT NULL DEFAULT '', + request_params text NULL, + response_body text NULL, + user_ip varchar(50) NOT NULL, + user_agent varchar(512) NOT NULL, + operate_module varchar(50) NULL DEFAULT NULL, + operate_name varchar(50) NULL DEFAULT NULL, + operate_type int2 NULL DEFAULT 0, + begin_time timestamp NOT NULL, + end_time timestamp NOT NULL, + duration int4 NOT NULL, + result_code int4 NOT NULL DEFAULT 0, + result_msg varchar(512) NULL DEFAULT '', + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_api_access_log ADD CONSTRAINT pk_infra_api_access_log PRIMARY KEY (id); + +CREATE INDEX idx_infra_api_access_log_01 ON infra_api_access_log (create_time); + +COMMENT ON COLUMN infra_api_access_log.id IS '日志主键'; +COMMENT ON COLUMN infra_api_access_log.trace_id IS '链路追踪编号'; +COMMENT ON COLUMN infra_api_access_log.user_id IS '用户编号'; +COMMENT ON COLUMN infra_api_access_log.user_type IS '用户类型'; +COMMENT ON COLUMN infra_api_access_log.application_name IS '应用名'; +COMMENT ON COLUMN infra_api_access_log.request_method IS '请求方法名'; +COMMENT ON COLUMN infra_api_access_log.request_url IS '请求地址'; +COMMENT ON COLUMN infra_api_access_log.request_params IS '请求参数'; +COMMENT ON COLUMN infra_api_access_log.response_body IS '响应结果'; +COMMENT ON COLUMN infra_api_access_log.user_ip IS '用户 IP'; +COMMENT ON COLUMN infra_api_access_log.user_agent IS '浏览器 UA'; +COMMENT ON COLUMN infra_api_access_log.operate_module IS '操作模块'; +COMMENT ON COLUMN infra_api_access_log.operate_name IS '操作名'; +COMMENT ON COLUMN infra_api_access_log.operate_type IS '操作分类'; +COMMENT ON COLUMN infra_api_access_log.begin_time IS '开始请求时间'; +COMMENT ON COLUMN infra_api_access_log.end_time IS '结束请求时间'; +COMMENT ON COLUMN infra_api_access_log.duration IS '执行时长'; +COMMENT ON COLUMN infra_api_access_log.result_code IS '结果码'; +COMMENT ON COLUMN infra_api_access_log.result_msg IS '结果提示'; +COMMENT ON COLUMN infra_api_access_log.creator IS '创建者'; +COMMENT ON COLUMN infra_api_access_log.create_time IS '创建时间'; +COMMENT ON COLUMN infra_api_access_log.updater IS '更新者'; +COMMENT ON COLUMN infra_api_access_log.update_time IS '更新时间'; +COMMENT ON COLUMN infra_api_access_log.deleted IS '是否删除'; +COMMENT ON COLUMN infra_api_access_log.tenant_id IS '租户编号'; +COMMENT ON TABLE infra_api_access_log IS 'API 访问日志表'; + +DROP SEQUENCE IF EXISTS infra_api_access_log_seq; +CREATE SEQUENCE infra_api_access_log_seq + START 1; + +-- ---------------------------- +-- Table structure for infra_api_error_log +-- ---------------------------- +DROP TABLE IF EXISTS infra_api_error_log; +CREATE TABLE infra_api_error_log ( + id int8 NOT NULL, + trace_id varchar(64) NOT NULL, + user_id int8 NOT NULL DEFAULT 0, + user_type int2 NOT NULL DEFAULT 0, + application_name varchar(50) NOT NULL, + request_method varchar(16) NOT NULL, + request_url varchar(255) NOT NULL, + request_params varchar(8000) NOT NULL, + user_ip varchar(50) NOT NULL, + user_agent varchar(512) NOT NULL, + exception_time timestamp NOT NULL, + exception_name varchar(128) NOT NULL DEFAULT '', + exception_message text NOT NULL, + exception_root_cause_message text NOT NULL, + exception_stack_trace text NOT NULL, + exception_class_name varchar(512) NOT NULL, + exception_file_name varchar(512) NOT NULL, + exception_method_name varchar(512) NOT NULL, + exception_line_number int4 NOT NULL, + process_status int2 NOT NULL, + process_time timestamp NULL DEFAULT NULL, + process_user_id int4 NULL DEFAULT 0, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_api_error_log ADD CONSTRAINT pk_infra_api_error_log PRIMARY KEY (id); + +CREATE INDEX idx_infra_api_error_log_01 ON infra_api_error_log (create_time); + +COMMENT ON COLUMN infra_api_error_log.id IS '编号'; +COMMENT ON COLUMN infra_api_error_log.trace_id IS '链路追踪编号'; +COMMENT ON COLUMN infra_api_error_log.user_id IS '用户编号'; +COMMENT ON COLUMN infra_api_error_log.user_type IS '用户类型'; +COMMENT ON COLUMN infra_api_error_log.application_name IS '应用名'; +COMMENT ON COLUMN infra_api_error_log.request_method IS '请求方法名'; +COMMENT ON COLUMN infra_api_error_log.request_url IS '请求地址'; +COMMENT ON COLUMN infra_api_error_log.request_params IS '请求参数'; +COMMENT ON COLUMN infra_api_error_log.user_ip IS '用户 IP'; +COMMENT ON COLUMN infra_api_error_log.user_agent IS '浏览器 UA'; +COMMENT ON COLUMN infra_api_error_log.exception_time IS '异常发生时间'; +COMMENT ON COLUMN infra_api_error_log.exception_name IS '异常名'; +COMMENT ON COLUMN infra_api_error_log.exception_message IS '异常导致的消息'; +COMMENT ON COLUMN infra_api_error_log.exception_root_cause_message IS '异常导致的根消息'; +COMMENT ON COLUMN infra_api_error_log.exception_stack_trace IS '异常的栈轨迹'; +COMMENT ON COLUMN infra_api_error_log.exception_class_name IS '异常发生的类全名'; +COMMENT ON COLUMN infra_api_error_log.exception_file_name IS '异常发生的类文件'; +COMMENT ON COLUMN infra_api_error_log.exception_method_name IS '异常发生的方法名'; +COMMENT ON COLUMN infra_api_error_log.exception_line_number IS '异常发生的方法所在行'; +COMMENT ON COLUMN infra_api_error_log.process_status IS '处理状态'; +COMMENT ON COLUMN infra_api_error_log.process_time IS '处理时间'; +COMMENT ON COLUMN infra_api_error_log.process_user_id IS '处理用户编号'; +COMMENT ON COLUMN infra_api_error_log.creator IS '创建者'; +COMMENT ON COLUMN infra_api_error_log.create_time IS '创建时间'; +COMMENT ON COLUMN infra_api_error_log.updater IS '更新者'; +COMMENT ON COLUMN infra_api_error_log.update_time IS '更新时间'; +COMMENT ON COLUMN infra_api_error_log.deleted IS '是否删除'; +COMMENT ON COLUMN infra_api_error_log.tenant_id IS '租户编号'; +COMMENT ON TABLE infra_api_error_log IS '系统异常日志'; + +DROP SEQUENCE IF EXISTS infra_api_error_log_seq; +CREATE SEQUENCE infra_api_error_log_seq + START 1; + +-- ---------------------------- +-- Table structure for infra_codegen_column +-- ---------------------------- +DROP TABLE IF EXISTS infra_codegen_column; +CREATE TABLE infra_codegen_column ( + id int8 NOT NULL, + table_id int8 NOT NULL, + column_name varchar(200) NOT NULL, + data_type varchar(100) NOT NULL, + column_comment varchar(500) NOT NULL, + nullable bool NOT NULL, + primary_key bool NOT NULL, + ordinal_position int4 NOT NULL, + java_type varchar(32) NOT NULL, + java_field varchar(64) NOT NULL, + dict_type varchar(200) NULL DEFAULT '', + example varchar(64) NULL DEFAULT NULL, + create_operation bool NOT NULL, + update_operation bool NOT NULL, + list_operation bool NOT NULL, + list_operation_condition varchar(32) NOT NULL DEFAULT '=', + list_operation_result bool NOT NULL, + html_type varchar(32) NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_codegen_column ADD CONSTRAINT pk_infra_codegen_column PRIMARY KEY (id); + +CREATE INDEX idx_infra_codegen_column_01 ON infra_codegen_column (table_id); + +COMMENT ON COLUMN infra_codegen_column.id IS '编号'; +COMMENT ON COLUMN infra_codegen_column.table_id IS '表编号'; +COMMENT ON COLUMN infra_codegen_column.column_name IS '字段名'; +COMMENT ON COLUMN infra_codegen_column.data_type IS '字段类型'; +COMMENT ON COLUMN infra_codegen_column.column_comment IS '字段描述'; +COMMENT ON COLUMN infra_codegen_column.nullable IS '是否允许为空'; +COMMENT ON COLUMN infra_codegen_column.primary_key IS '是否主键'; +COMMENT ON COLUMN infra_codegen_column.ordinal_position IS '排序'; +COMMENT ON COLUMN infra_codegen_column.java_type IS 'Java 属性类型'; +COMMENT ON COLUMN infra_codegen_column.java_field IS 'Java 属性名'; +COMMENT ON COLUMN infra_codegen_column.dict_type IS '字典类型'; +COMMENT ON COLUMN infra_codegen_column.example IS '数据示例'; +COMMENT ON COLUMN infra_codegen_column.create_operation IS '是否为 Create 创建操作的字段'; +COMMENT ON COLUMN infra_codegen_column.update_operation IS '是否为 Update 更新操作的字段'; +COMMENT ON COLUMN infra_codegen_column.list_operation IS '是否为 List 查询操作的字段'; +COMMENT ON COLUMN infra_codegen_column.list_operation_condition IS 'List 查询操作的条件类型'; +COMMENT ON COLUMN infra_codegen_column.list_operation_result IS '是否为 List 查询操作的返回字段'; +COMMENT ON COLUMN infra_codegen_column.html_type IS '显示类型'; +COMMENT ON COLUMN infra_codegen_column.creator IS '创建者'; +COMMENT ON COLUMN infra_codegen_column.create_time IS '创建时间'; +COMMENT ON COLUMN infra_codegen_column.updater IS '更新者'; +COMMENT ON COLUMN infra_codegen_column.update_time IS '更新时间'; +COMMENT ON COLUMN infra_codegen_column.deleted IS '是否删除'; +COMMENT ON TABLE infra_codegen_column IS '代码生成表字段定义'; + +DROP SEQUENCE IF EXISTS infra_codegen_column_seq; +CREATE SEQUENCE infra_codegen_column_seq + START 1; + +-- ---------------------------- +-- Table structure for infra_codegen_table +-- ---------------------------- +DROP TABLE IF EXISTS infra_codegen_table; +CREATE TABLE infra_codegen_table ( + id int8 NOT NULL, + data_source_config_id int8 NOT NULL, + scene int2 NOT NULL DEFAULT 1, + table_name varchar(200) NOT NULL DEFAULT '', + table_comment varchar(500) NOT NULL DEFAULT '', + remark varchar(500) NULL DEFAULT NULL, + module_name varchar(30) NOT NULL, + business_name varchar(30) NOT NULL, + class_name varchar(100) NOT NULL DEFAULT '', + class_comment varchar(50) NOT NULL, + author varchar(50) NOT NULL, + template_type int2 NOT NULL DEFAULT 1, + front_type int2 NOT NULL, + parent_menu_id int8 NULL DEFAULT NULL, + master_table_id int8 NULL DEFAULT NULL, + sub_join_column_id int8 NULL DEFAULT NULL, + sub_join_many bool NULL DEFAULT NULL, + tree_parent_column_id int8 NULL DEFAULT NULL, + tree_name_column_id int8 NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_codegen_table ADD CONSTRAINT pk_infra_codegen_table PRIMARY KEY (id); + +COMMENT ON COLUMN infra_codegen_table.id IS '编号'; +COMMENT ON COLUMN infra_codegen_table.data_source_config_id IS '数据源配置的编号'; +COMMENT ON COLUMN infra_codegen_table.scene IS '生成场景'; +COMMENT ON COLUMN infra_codegen_table.table_name IS '表名称'; +COMMENT ON COLUMN infra_codegen_table.table_comment IS '表描述'; +COMMENT ON COLUMN infra_codegen_table.remark IS '备注'; +COMMENT ON COLUMN infra_codegen_table.module_name IS '模块名'; +COMMENT ON COLUMN infra_codegen_table.business_name IS '业务名'; +COMMENT ON COLUMN infra_codegen_table.class_name IS '类名称'; +COMMENT ON COLUMN infra_codegen_table.class_comment IS '类描述'; +COMMENT ON COLUMN infra_codegen_table.author IS '作者'; +COMMENT ON COLUMN infra_codegen_table.template_type IS '模板类型'; +COMMENT ON COLUMN infra_codegen_table.front_type IS '前端类型'; +COMMENT ON COLUMN infra_codegen_table.parent_menu_id IS '父菜单编号'; +COMMENT ON COLUMN infra_codegen_table.master_table_id IS '主表的编号'; +COMMENT ON COLUMN infra_codegen_table.sub_join_column_id IS '子表关联主表的字段编号'; +COMMENT ON COLUMN infra_codegen_table.sub_join_many IS '主表与子表是否一对多'; +COMMENT ON COLUMN infra_codegen_table.tree_parent_column_id IS '树表的父字段编号'; +COMMENT ON COLUMN infra_codegen_table.tree_name_column_id IS '树表的名字字段编号'; +COMMENT ON COLUMN infra_codegen_table.creator IS '创建者'; +COMMENT ON COLUMN infra_codegen_table.create_time IS '创建时间'; +COMMENT ON COLUMN infra_codegen_table.updater IS '更新者'; +COMMENT ON COLUMN infra_codegen_table.update_time IS '更新时间'; +COMMENT ON COLUMN infra_codegen_table.deleted IS '是否删除'; +COMMENT ON TABLE infra_codegen_table IS '代码生成表定义'; + +DROP SEQUENCE IF EXISTS infra_codegen_table_seq; +CREATE SEQUENCE infra_codegen_table_seq + START 1; + +-- ---------------------------- +-- Table structure for infra_config +-- ---------------------------- +DROP TABLE IF EXISTS infra_config; +CREATE TABLE infra_config ( + id int8 NOT NULL, + category varchar(50) NOT NULL, + type int2 NOT NULL, + name varchar(100) NOT NULL DEFAULT '', + config_key varchar(100) NOT NULL DEFAULT '', + value varchar(500) NOT NULL DEFAULT '', + visible bool NOT NULL, + remark varchar(500) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_config ADD CONSTRAINT pk_infra_config PRIMARY KEY (id); + +CREATE INDEX idx_infra_config_01 ON infra_config (config_key); + +COMMENT ON COLUMN infra_config.id IS '参数主键'; +COMMENT ON COLUMN infra_config.category IS '参数分组'; +COMMENT ON COLUMN infra_config.type IS '参数类型'; +COMMENT ON COLUMN infra_config.name IS '参数名称'; +COMMENT ON COLUMN infra_config.config_key IS '参数键名'; +COMMENT ON COLUMN infra_config.value IS '参数键值'; +COMMENT ON COLUMN infra_config.visible IS '是否可见'; +COMMENT ON COLUMN infra_config.remark IS '备注'; +COMMENT ON COLUMN infra_config.creator IS '创建者'; +COMMENT ON COLUMN infra_config.create_time IS '创建时间'; +COMMENT ON COLUMN infra_config.updater IS '更新者'; +COMMENT ON COLUMN infra_config.update_time IS '更新时间'; +COMMENT ON COLUMN infra_config.deleted IS '是否删除'; +COMMENT ON TABLE infra_config IS '参数配置表'; + +-- ---------------------------- +-- Records of infra_config +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO infra_config (id, category, type, name, config_key, value, visible, remark, creator, create_time, updater, update_time, deleted) VALUES (2, 'biz', 1, '用户管理-账号初始密码', 'system.user.init-password', '123456', '0', '初始化密码 123456', 'admin', '2021-01-05 17:03:48', '1', '2024-07-20 17:22:47', '0'); +INSERT INTO infra_config (id, category, type, name, config_key, value, visible, remark, creator, create_time, updater, update_time, deleted) VALUES (7, 'url', 2, 'MySQL 监控的地址', 'url.druid', '', '1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:33:38', '0'); +INSERT INTO infra_config (id, category, type, name, config_key, value, visible, remark, creator, create_time, updater, update_time, deleted) VALUES (8, 'url', 2, 'SkyWalking 监控的地址', 'url.skywalking', '', '1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:57:03', '0'); +INSERT INTO infra_config (id, category, type, name, config_key, value, visible, remark, creator, create_time, updater, update_time, deleted) VALUES (9, 'url', 2, 'Spring Boot Admin 监控的地址', 'url.spring-boot-admin', '', '1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:52:07', '0'); +INSERT INTO infra_config (id, category, type, name, config_key, value, visible, remark, creator, create_time, updater, update_time, deleted) VALUES (10, 'url', 2, 'Swagger 接口文档的地址', 'url.swagger', '', '1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:59:00', '0'); +INSERT INTO infra_config (id, category, type, name, config_key, value, visible, remark, creator, create_time, updater, update_time, deleted) VALUES (12, 'test2', 2, 'test3', 'test4', 'test5', '1', 'test6', '1', '2023-12-03 09:55:16', '1', '2025-04-06 21:00:09', '0'); +INSERT INTO infra_config (id, category, type, name, config_key, value, visible, remark, creator, create_time, updater, update_time, deleted) VALUES (13, '用户管理-账号初始密码', 2, '用户管理-注册开关', 'system.user.register-enabled', 'true', '0', '', '1', '2025-04-26 17:23:41', '1', '2025-04-26 17:23:41', '0'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS infra_config_seq; +CREATE SEQUENCE infra_config_seq + START 14; + +-- ---------------------------- +-- Table structure for infra_data_source_config +-- ---------------------------- +DROP TABLE IF EXISTS infra_data_source_config; +CREATE TABLE infra_data_source_config ( + id int8 NOT NULL, + name varchar(100) NOT NULL DEFAULT '', + url varchar(1024) NOT NULL, + username varchar(255) NOT NULL, + password varchar(255) NOT NULL DEFAULT '', + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_data_source_config ADD CONSTRAINT pk_infra_data_source_config PRIMARY KEY (id); + +COMMENT ON COLUMN infra_data_source_config.id IS '主键编号'; +COMMENT ON COLUMN infra_data_source_config.name IS '参数名称'; +COMMENT ON COLUMN infra_data_source_config.url IS '数据源连接'; +COMMENT ON COLUMN infra_data_source_config.username IS '用户名'; +COMMENT ON COLUMN infra_data_source_config.password IS '密码'; +COMMENT ON COLUMN infra_data_source_config.creator IS '创建者'; +COMMENT ON COLUMN infra_data_source_config.create_time IS '创建时间'; +COMMENT ON COLUMN infra_data_source_config.updater IS '更新者'; +COMMENT ON COLUMN infra_data_source_config.update_time IS '更新时间'; +COMMENT ON COLUMN infra_data_source_config.deleted IS '是否删除'; +COMMENT ON TABLE infra_data_source_config IS '数据源配置表'; + +DROP SEQUENCE IF EXISTS infra_data_source_config_seq; +CREATE SEQUENCE infra_data_source_config_seq + START 1; + +-- ---------------------------- +-- Table structure for infra_file +-- ---------------------------- +DROP TABLE IF EXISTS infra_file; +CREATE TABLE infra_file ( + id int8 NOT NULL, + config_id int8 NULL DEFAULT NULL, + name varchar(256) NULL DEFAULT NULL, + path varchar(512) NOT NULL, + url varchar(1024) NOT NULL, + type varchar(128) NULL DEFAULT NULL, + size int4 NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_file ADD CONSTRAINT pk_infra_file PRIMARY KEY (id); + +COMMENT ON COLUMN infra_file.id IS '文件编号'; +COMMENT ON COLUMN infra_file.config_id IS '配置编号'; +COMMENT ON COLUMN infra_file.name IS '文件名'; +COMMENT ON COLUMN infra_file.path IS '文件路径'; +COMMENT ON COLUMN infra_file.url IS '文件 URL'; +COMMENT ON COLUMN infra_file.type IS '文件类型'; +COMMENT ON COLUMN infra_file.size IS '文件大小'; +COMMENT ON COLUMN infra_file.creator IS '创建者'; +COMMENT ON COLUMN infra_file.create_time IS '创建时间'; +COMMENT ON COLUMN infra_file.updater IS '更新者'; +COMMENT ON COLUMN infra_file.update_time IS '更新时间'; +COMMENT ON COLUMN infra_file.deleted IS '是否删除'; +COMMENT ON TABLE infra_file IS '文件表'; + +DROP SEQUENCE IF EXISTS infra_file_seq; +CREATE SEQUENCE infra_file_seq + START 1; + +-- ---------------------------- +-- Table structure for infra_file_config +-- ---------------------------- +DROP TABLE IF EXISTS infra_file_config; +CREATE TABLE infra_file_config ( + id int8 NOT NULL, + name varchar(63) NOT NULL, + storage int2 NOT NULL, + remark varchar(255) NULL DEFAULT NULL, + master bool NOT NULL, + config varchar(4096) NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_file_config ADD CONSTRAINT pk_infra_file_config PRIMARY KEY (id); + +COMMENT ON COLUMN infra_file_config.id IS '编号'; +COMMENT ON COLUMN infra_file_config.name IS '配置名'; +COMMENT ON COLUMN infra_file_config.storage IS '存储器'; +COMMENT ON COLUMN infra_file_config.remark IS '备注'; +COMMENT ON COLUMN infra_file_config.master IS '是否为主配置'; +COMMENT ON COLUMN infra_file_config.config IS '存储配置'; +COMMENT ON COLUMN infra_file_config.creator IS '创建者'; +COMMENT ON COLUMN infra_file_config.create_time IS '创建时间'; +COMMENT ON COLUMN infra_file_config.updater IS '更新者'; +COMMENT ON COLUMN infra_file_config.update_time IS '更新时间'; +COMMENT ON COLUMN infra_file_config.deleted IS '是否删除'; +COMMENT ON TABLE infra_file_config IS '文件配置表'; + +-- ---------------------------- +-- Records of infra_file_config +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (4, '数据库(示例)', 1, '我是数据库', '0', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.db.DBFileClientConfig","domain":"http://127.0.0.1:48080"}', '1', '2022-03-15 23:56:24', '1', '2026-05-17 14:05:15', '0'); +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (22, '七牛存储器(示例)', 20, '请换成你自己的密钥!!!', '1', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig","endpoint":"s3.cn-south-1.qiniucs.com","domain":"http://test.yudao.iocoder.cn","bucket":"ruoyi-vue-pro","accessKey":"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS","accessSecret":"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY","enablePathStyleAccess":false,"enablePublicAccess":true}', '1', '2024-01-13 22:11:12', '1', '2026-05-17 14:05:15', '0'); +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (24, '腾讯云存储(示例)', 20, '请换成你的密钥!!!', '0', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig","endpoint":"https://cos.ap-shanghai.myqcloud.com","domain":"http://tengxun-oss.iocoder.cn","bucket":"aoteman-1255880240","accessKey":"AKIDAF6WSh1uiIjwqtrOsGSN3WryqTM6cTMt","accessSecret":"X","enablePathStyleAccess":false,"enablePublicAccess":true}', '1', '2024-11-09 16:03:22', '1', '2026-05-17 14:05:15', '0'); +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (25, '阿里云存储(示例)', 20, '', '0', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig","endpoint":"oss-cn-beijing.aliyuncs.com","domain":"http://ali-oss.iocoder.cn","bucket":"yunai-aoteman","accessKey":"LTAI5tEQLgnDyjh3WpNcdMKA","accessSecret":"X","enablePathStyleAccess":false,"enablePublicAccess":true}', '1', '2024-11-09 16:47:08', '1', '2026-05-17 14:05:15', '0'); +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (26, '火山云存储(示例)', 20, '', '0', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig","endpoint":"tos-s3-cn-beijing.volces.com","domain":null,"bucket":"yunai","accessKey":"AKLTZjc3Zjc4MzZmMjU3NDk0ZTgxYmIyMmFkNTIwMDI1ZGE","accessSecret":"X==","enablePathStyleAccess":false,"enablePublicAccess":true}', '1', '2024-11-09 16:56:42', '1', '2026-05-17 14:05:15', '0'); +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (27, '华为云存储(示例)', 20, '', '0', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig","endpoint":"obs.cn-east-3.myhuaweicloud.com","domain":"","bucket":"yudao","accessKey":"PVDONDEIOTW88LF8DC4U","accessSecret":"X","enablePathStyleAccess":false,"enablePublicAccess":true}', '1', '2024-11-09 17:18:41', '1', '2026-05-17 14:05:15', '0'); +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (28, 'MinIO 存储(示例)', 20, '', '0', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig","endpoint":"http://127.0.0.1:9000","domain":"http://127.0.0.1:9000/yudao","bucket":"yudao","accessKey":"admin","accessSecret":"password","enablePathStyleAccess":false,"enablePublicAccess":true}', '1', '2024-11-09 17:43:10', '1', '2026-05-17 14:05:15', '0'); +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (29, '本地存储(示例)', 10, 'mac/linux 使用 /,windows 使用 \', '0', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig","basePath":"/Users/yunai/tmp/file","domain":"http://127.0.0.1:48080"}', '1', '2025-05-02 11:25:45', '1', '2026-05-17 14:05:15', '0'); +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (30, 'SFTP 存储(示例)', 12, '', '0', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.sftp.SftpFileClientConfig","basePath":"/upload","domain":"http://127.0.0.1:48080","host":"127.0.0.1","port":2222,"username":"foo","password":"pass"}', '1', '2025-05-02 16:34:10', '1', '2026-05-17 14:05:15', '0'); +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (34, '七牛云存储【私有】(示例)', 20, '请换成你自己的密钥!!!', '0', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig","endpoint":"s3.cn-south-1.qiniucs.com","domain":"http://t151glocd.hn-bkt.clouddn.com","bucket":"ruoyi-vue-pro-private","accessKey":"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS","accessSecret":"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY","enablePathStyleAccess":false,"enablePublicAccess":false}', '1', '2025-08-17 21:22:00', '1', '2026-05-17 14:05:15', '0'); +INSERT INTO infra_file_config (id, name, storage, remark, master, config, creator, create_time, updater, update_time, deleted) VALUES (35, '1', 20, '1', '0', '{"@class":"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig","endpoint":"http://www.baidu.com","domain":"http://www.xxx.com","bucket":"1","accessKey":"2","accessSecret":"3","enablePathStyleAccess":false,"enablePublicAccess":false,"region":"1"}', '1', '2025-10-02 14:32:12', '1', '2026-05-17 14:05:15', '0'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS infra_file_config_seq; +CREATE SEQUENCE infra_file_config_seq + START 36; + +-- ---------------------------- +-- Table structure for infra_file_content +-- ---------------------------- +DROP TABLE IF EXISTS infra_file_content; +CREATE TABLE infra_file_content ( + id int8 NOT NULL, + config_id int8 NOT NULL, + path varchar(512) NOT NULL, + content bytea NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_file_content ADD CONSTRAINT pk_infra_file_content PRIMARY KEY (id); + +CREATE INDEX idx_infra_file_content_01 ON infra_file_content (config_id, path); + +COMMENT ON COLUMN infra_file_content.id IS '编号'; +COMMENT ON COLUMN infra_file_content.config_id IS '配置编号'; +COMMENT ON COLUMN infra_file_content.path IS '文件路径'; +COMMENT ON COLUMN infra_file_content.content IS '文件内容'; +COMMENT ON COLUMN infra_file_content.creator IS '创建者'; +COMMENT ON COLUMN infra_file_content.create_time IS '创建时间'; +COMMENT ON COLUMN infra_file_content.updater IS '更新者'; +COMMENT ON COLUMN infra_file_content.update_time IS '更新时间'; +COMMENT ON COLUMN infra_file_content.deleted IS '是否删除'; +COMMENT ON TABLE infra_file_content IS '文件表'; + +DROP SEQUENCE IF EXISTS infra_file_content_seq; +CREATE SEQUENCE infra_file_content_seq + START 1; + +-- ---------------------------- +-- Table structure for infra_job +-- ---------------------------- +DROP TABLE IF EXISTS infra_job; +CREATE TABLE infra_job ( + id int8 NOT NULL, + name varchar(32) NOT NULL, + status int2 NOT NULL, + handler_name varchar(64) NOT NULL, + handler_param varchar(255) NULL DEFAULT NULL, + cron_expression varchar(32) NOT NULL, + retry_count int4 NOT NULL DEFAULT 0, + retry_interval int4 NOT NULL DEFAULT 0, + monitor_timeout int4 NOT NULL DEFAULT 0, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_job ADD CONSTRAINT pk_infra_job PRIMARY KEY (id); + +COMMENT ON COLUMN infra_job.id IS '任务编号'; +COMMENT ON COLUMN infra_job.name IS '任务名称'; +COMMENT ON COLUMN infra_job.status IS '任务状态'; +COMMENT ON COLUMN infra_job.handler_name IS '处理器的名字'; +COMMENT ON COLUMN infra_job.handler_param IS '处理器的参数'; +COMMENT ON COLUMN infra_job.cron_expression IS 'CRON 表达式'; +COMMENT ON COLUMN infra_job.retry_count IS '重试次数'; +COMMENT ON COLUMN infra_job.retry_interval IS '重试间隔'; +COMMENT ON COLUMN infra_job.monitor_timeout IS '监控超时时间'; +COMMENT ON COLUMN infra_job.creator IS '创建者'; +COMMENT ON COLUMN infra_job.create_time IS '创建时间'; +COMMENT ON COLUMN infra_job.updater IS '更新者'; +COMMENT ON COLUMN infra_job.update_time IS '更新时间'; +COMMENT ON COLUMN infra_job.deleted IS '是否删除'; +COMMENT ON TABLE infra_job IS '定时任务表'; + +-- ---------------------------- +-- Records of infra_job +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (5, '支付通知 Job', 2, 'payNotifyJob', NULL, '* * * * * ?', 0, 0, 0, '1', '2021-10-27 08:34:42', '1', '2024-09-12 13:32:48', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (17, '支付订单同步 Job', 2, 'payOrderSyncJob', NULL, '0 0/1 * * * ?', 0, 0, 0, '1', '2023-07-22 14:36:26', '1', '2023-07-22 15:39:08', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (18, '支付订单过期 Job', 2, 'payOrderExpireJob', NULL, '0 0/1 * * * ?', 0, 0, 0, '1', '2023-07-22 15:36:23', '1', '2023-07-22 15:39:54', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (19, '退款订单的同步 Job', 2, 'payRefundSyncJob', NULL, '0 0/1 * * * ?', 0, 0, 0, '1', '2023-07-23 21:03:44', '1', '2023-07-23 21:09:00', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (21, 'Mall 交易订单的自动过期 Job', 2, 'tradeOrderAutoCancelJob', '', '0 * * * * ?', 3, 0, 0, '1', '2023-09-25 23:43:26', '1', '2025-10-02 11:08:34', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (22, 'Mall 交易订单的自动收货 Job', 2, 'tradeOrderAutoReceiveJob', '', '0 * * * * ?', 3, 0, 0, '1', '2023-09-26 19:23:53', '1', '2025-10-02 11:08:36', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (23, 'Mall 交易订单的自动评论 Job', 2, 'tradeOrderAutoCommentJob', '', '0 * * * * ?', 3, 0, 0, '1', '2023-09-26 23:38:29', '1', '2025-10-02 11:08:38', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (24, 'Mall 佣金解冻 Job', 2, 'brokerageRecordUnfreezeJob', '', '0 * * * * ?', 3, 0, 0, '1', '2023-09-28 22:01:46', '1', '2025-10-02 11:08:04', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (25, '访问日志清理 Job', 2, 'accessLogCleanJob', '', '0 0 0 * * ?', 3, 0, 0, '1', '2023-10-03 10:59:41', '1', '2023-10-03 11:01:10', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (26, '错误日志清理 Job', 2, 'errorLogCleanJob', '', '0 0 0 * * ?', 3, 0, 0, '1', '2023-10-03 11:00:43', '1', '2023-10-03 11:01:12', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (27, '任务日志清理 Job', 2, 'jobLogCleanJob', '', '0 0 0 * * ?', 3, 0, 0, '1', '2023-10-03 11:01:33', '1', '2024-09-12 13:40:34', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (33, 'demoJob', 2, 'demoJob', '', '0 * * * * ?', 1, 1, 0, '1', '2024-10-27 19:38:46', '1', '2025-05-10 18:13:54', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (35, '转账订单的同步 Job', 2, 'payTransferSyncJob', '', '0 * * * * ?', 0, 0, 0, '1', '2025-05-10 17:35:54', '1', '2025-05-10 18:13:52', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (36, 'IoT 设备离线检查 Job', 2, 'iotDeviceOfflineCheckJob', '', '0 * * * * ?', 0, 0, 0, '1', '2025-07-03 23:48:44', '"1"', '2025-07-03 23:48:47', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (37, 'IoT OTA 升级推送 Job', 2, 'iotOtaUpgradeJob', '', '0 * * * * ?', 0, 0, 0, '1', '2025-07-03 23:49:07', '"1"', '2025-07-03 23:49:13', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (38, 'Mall 拼团过期 Job', 2, 'combinationRecordExpireJob', '', '0 * * * * ?', 0, 0, 0, '1', '2025-10-02 11:07:11', '1', '2025-10-02 11:07:14', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (39, 'Mall 优惠券过期 Job', 2, 'couponExpireJob', '', '0 * * * * ?', 0, 0, 0, '1', '2025-10-02 11:07:34', '1', '2025-10-02 11:07:37', '0'); +INSERT INTO infra_job (id, name, status, handler_name, handler_param, cron_expression, retry_count, retry_interval, monitor_timeout, creator, create_time, updater, update_time, deleted) VALUES (40, 'Mall 商品统计 Job', 2, 'productStatisticsJob', '', '0 0 0 * * ?', 0, 0, 0, '1', '2025-11-22 18:51:25', '1', '2025-11-22 18:56:21', '0'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS infra_job_seq; +CREATE SEQUENCE infra_job_seq + START 41; + +-- ---------------------------- +-- Table structure for infra_job_log +-- ---------------------------- +DROP TABLE IF EXISTS infra_job_log; +CREATE TABLE infra_job_log ( + id int8 NOT NULL, + job_id int8 NOT NULL, + handler_name varchar(64) NOT NULL, + handler_param varchar(255) NULL DEFAULT NULL, + execute_index int2 NOT NULL DEFAULT 1, + begin_time timestamp NOT NULL, + end_time timestamp NULL DEFAULT NULL, + duration int4 NULL DEFAULT NULL, + status int2 NOT NULL, + result varchar(4000) NULL DEFAULT '', + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE infra_job_log ADD CONSTRAINT pk_infra_job_log PRIMARY KEY (id); + +CREATE INDEX idx_infra_job_log_01 ON infra_job_log (job_id); +CREATE INDEX idx_infra_job_log_02 ON infra_job_log (create_time); + +COMMENT ON COLUMN infra_job_log.id IS '日志编号'; +COMMENT ON COLUMN infra_job_log.job_id IS '任务编号'; +COMMENT ON COLUMN infra_job_log.handler_name IS '处理器的名字'; +COMMENT ON COLUMN infra_job_log.handler_param IS '处理器的参数'; +COMMENT ON COLUMN infra_job_log.execute_index IS '第几次执行'; +COMMENT ON COLUMN infra_job_log.begin_time IS '开始执行时间'; +COMMENT ON COLUMN infra_job_log.end_time IS '结束执行时间'; +COMMENT ON COLUMN infra_job_log.duration IS '执行时长'; +COMMENT ON COLUMN infra_job_log.status IS '任务状态'; +COMMENT ON COLUMN infra_job_log.result IS '结果数据'; +COMMENT ON COLUMN infra_job_log.creator IS '创建者'; +COMMENT ON COLUMN infra_job_log.create_time IS '创建时间'; +COMMENT ON COLUMN infra_job_log.updater IS '更新者'; +COMMENT ON COLUMN infra_job_log.update_time IS '更新时间'; +COMMENT ON COLUMN infra_job_log.deleted IS '是否删除'; +COMMENT ON TABLE infra_job_log IS '定时任务日志表'; + +DROP SEQUENCE IF EXISTS infra_job_log_seq; +CREATE SEQUENCE infra_job_log_seq + START 1; + +-- ---------------------------- +-- Table structure for system_dept +-- ---------------------------- +DROP TABLE IF EXISTS system_dept; +CREATE TABLE system_dept ( + id int8 NOT NULL, + name varchar(30) NOT NULL DEFAULT '', + parent_id int8 NOT NULL DEFAULT 0, + sort int4 NOT NULL DEFAULT 0, + leader_user_id int8 NULL DEFAULT NULL, + phone varchar(11) NULL DEFAULT NULL, + email varchar(50) NULL DEFAULT NULL, + status int2 NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_dept ADD CONSTRAINT pk_system_dept PRIMARY KEY (id); + +COMMENT ON COLUMN system_dept.id IS '部门id'; +COMMENT ON COLUMN system_dept.name IS '部门名称'; +COMMENT ON COLUMN system_dept.parent_id IS '父部门id'; +COMMENT ON COLUMN system_dept.sort IS '显示顺序'; +COMMENT ON COLUMN system_dept.leader_user_id IS '负责人'; +COMMENT ON COLUMN system_dept.phone IS '联系电话'; +COMMENT ON COLUMN system_dept.email IS '邮箱'; +COMMENT ON COLUMN system_dept.status IS '部门状态(0正常 1停用)'; +COMMENT ON COLUMN system_dept.creator IS '创建者'; +COMMENT ON COLUMN system_dept.create_time IS '创建时间'; +COMMENT ON COLUMN system_dept.updater IS '更新者'; +COMMENT ON COLUMN system_dept.update_time IS '更新时间'; +COMMENT ON COLUMN system_dept.deleted IS '是否删除'; +COMMENT ON COLUMN system_dept.tenant_id IS '租户编号'; +COMMENT ON TABLE system_dept IS '部门表'; + +-- ---------------------------- +-- Records of system_dept +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (100, '芋道源码', 0, 0, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2026-01-04 18:01:12', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (101, '深圳总公司', 100, 1, 104, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2025-03-29 15:49:55', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (102, '长沙分公司', 100, 2, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', '2021-12-15 05:01:40', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (103, '研发部门', 101, 1, 104, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2026-01-04 18:01:24', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (104, '市场部门', 101, 2, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', '2021-12-15 05:01:38', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (105, '测试部门', 101, 3, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2022-05-16 20:25:15', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (106, '财务部门', 101, 4, 103, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '103', '2022-01-15 21:32:22', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (107, '运维部门', 101, 5, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2023-12-02 09:28:22', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (108, '市场部门', 102, 1, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2022-02-16 08:35:45', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (109, '财务部门', 102, 2, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '', '2021-12-15 05:01:29', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (110, '新部门', 0, 1, NULL, NULL, NULL, 0, '110', '2022-02-23 20:46:30', '110', '2022-02-23 20:46:30', '0', 121); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (111, '顶级部门', 0, 1, NULL, NULL, NULL, 0, '113', '2022-03-07 21:44:50', '113', '2022-03-07 21:44:50', '0', 122); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (112, '产品部门', 101, 100, 1, NULL, NULL, 1, '1', '2023-12-02 09:45:13', '1', '2023-12-02 09:45:31', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (113, '支持部门', 102, 3, 104, NULL, NULL, 1, '1', '2023-12-02 09:47:38', '1', '2025-03-29 15:00:56', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (116, '某个子部门', 0, 1, NULL, NULL, NULL, 0, '1', '2025-12-08 14:51:12', '1', '2025-12-08 14:51:12', '0', 1); +INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (117, '某个子部门 2', 0, 2, NULL, NULL, NULL, 0, '1', '2025-12-08 14:51:25', '1', '2025-12-08 14:51:25', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_dept_seq; +CREATE SEQUENCE system_dept_seq + START 118; + +-- ---------------------------- +-- Table structure for system_dict_data +-- ---------------------------- +DROP TABLE IF EXISTS system_dict_data; +CREATE TABLE system_dict_data ( + id int8 NOT NULL, + sort int4 NOT NULL DEFAULT 0, + label varchar(100) NOT NULL DEFAULT '', + value varchar(100) NOT NULL DEFAULT '', + dict_type varchar(100) NOT NULL DEFAULT '', + status int2 NOT NULL DEFAULT 0, + color_type varchar(100) NULL DEFAULT '', + css_class varchar(100) NULL DEFAULT '', + remark varchar(500) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_dict_data ADD CONSTRAINT pk_system_dict_data PRIMARY KEY (id); + +COMMENT ON COLUMN system_dict_data.id IS '字典编码'; +COMMENT ON COLUMN system_dict_data.sort IS '字典排序'; +COMMENT ON COLUMN system_dict_data.label IS '字典标签'; +COMMENT ON COLUMN system_dict_data.value IS '字典键值'; +COMMENT ON COLUMN system_dict_data.dict_type IS '字典类型'; +COMMENT ON COLUMN system_dict_data.status IS '状态(0正常 1停用)'; +COMMENT ON COLUMN system_dict_data.color_type IS '颜色类型'; +COMMENT ON COLUMN system_dict_data.css_class IS 'css 样式'; +COMMENT ON COLUMN system_dict_data.remark IS '备注'; +COMMENT ON COLUMN system_dict_data.creator IS '创建者'; +COMMENT ON COLUMN system_dict_data.create_time IS '创建时间'; +COMMENT ON COLUMN system_dict_data.updater IS '更新者'; +COMMENT ON COLUMN system_dict_data.update_time IS '更新时间'; +COMMENT ON COLUMN system_dict_data.deleted IS '是否删除'; +COMMENT ON TABLE system_dict_data IS '字典数据表'; + +-- ---------------------------- +-- Records of system_dict_data +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1, 1, '男', '1', 'system_user_sex', 0, 'primary', 'A', '性别男', 'admin', '2021-01-05 17:03:48', '1', '2025-12-10 13:19:26', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2, 2, '女', '2', 'system_user_sex', 0, 'success', '', '性别女', 'admin', '2021-01-05 17:03:48', '1', '2023-11-15 23:30:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (8, 1, '正常', '1', 'infra_job_status', 0, 'success', '', '正常状态', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 19:33:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (9, 2, '暂停', '2', 'infra_job_status', 0, 'danger', '', '停用状态', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 19:33:45', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (12, 1, '系统内置', '1', 'infra_config_type', 0, 'danger', '', '参数类型 - 系统内置', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 19:06:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (13, 2, '自定义', '2', 'infra_config_type', 0, 'primary', '', '参数类型 - 自定义', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 19:06:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (14, 1, '通知', '1', 'system_notice_type', 0, 'success', '', '通知', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 13:05:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (15, 2, '公告', '2', 'system_notice_type', 0, 'info', '', '公告', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 13:06:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (16, 0, '其它', '0', 'infra_operate_type', 0, 'default', '', '其它操作', 'admin', '2021-01-05 17:03:48', '1', '2024-03-14 12:44:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (17, 1, '查询', '1', 'infra_operate_type', 0, 'info', '', '查询操作', 'admin', '2021-01-05 17:03:48', '1', '2024-03-14 12:44:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (18, 2, '新增', '2', 'infra_operate_type', 0, 'primary', '', '新增操作', 'admin', '2021-01-05 17:03:48', '1', '2024-03-14 12:44:21', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (19, 3, '修改', '3', 'infra_operate_type', 0, 'warning', '', '修改操作', 'admin', '2021-01-05 17:03:48', '1', '2024-03-14 12:44:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (20, 4, '删除', '4', 'infra_operate_type', 0, 'danger', '', '删除操作', 'admin', '2021-01-05 17:03:48', '1', '2024-03-14 12:44:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (22, 5, '导出', '5', 'infra_operate_type', 0, 'default', '', '导出操作', 'admin', '2021-01-05 17:03:48', '1', '2024-03-14 12:44:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (23, 6, '导入', '6', 'infra_operate_type', 0, 'default', '', '导入操作', 'admin', '2021-01-05 17:03:48', '1', '2024-03-14 12:44:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (27, 1, '开启', '0', 'common_status', 0, 'primary', '', '开启状态', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 08:00:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (28, 2, '关闭', '1', 'common_status', 0, 'info', '', '关闭状态', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 08:00:44', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (29, 1, '目录', '1', 'system_menu_type', 0, '', '', '目录', 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:43:45', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (30, 2, '菜单', '2', 'system_menu_type', 0, '', '', '菜单', 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:43:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (31, 3, '按钮', '3', 'system_menu_type', 0, '', '', '按钮', 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:43:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (32, 1, '内置', '1', 'system_role_type', 0, 'danger', '', '内置角色', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 13:02:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (33, 2, '自定义', '2', 'system_role_type', 0, 'primary', '', '自定义角色', 'admin', '2021-01-05 17:03:48', '1', '2022-02-16 13:02:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (34, 1, '全部数据权限', '1', 'system_data_scope', 0, '', '', '全部数据权限', 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:47:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (35, 2, '指定部门数据权限', '2', 'system_data_scope', 0, '', '', '指定部门数据权限', 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:47:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (36, 3, '本部门数据权限', '3', 'system_data_scope', 0, '', '', '本部门数据权限', 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:47:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (37, 4, '本部门及以下数据权限', '4', 'system_data_scope', 0, '', '', '本部门及以下数据权限', 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:47:21', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (38, 5, '仅本人数据权限', '5', 'system_data_scope', 0, '', '', '仅本人数据权限', 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:47:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (39, 0, '成功', '0', 'system_login_result', 0, 'success', '', '登陆结果 - 成功', '', '2021-01-18 06:17:36', '1', '2022-02-16 13:23:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (40, 10, '账号或密码不正确', '10', 'system_login_result', 0, 'primary', '', '登陆结果 - 账号或密码不正确', '', '2021-01-18 06:17:54', '1', '2022-02-16 13:24:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (41, 20, '用户被禁用', '20', 'system_login_result', 0, 'warning', '', '登陆结果 - 用户被禁用', '', '2021-01-18 06:17:54', '1', '2022-02-16 13:23:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (42, 30, '验证码不存在', '30', 'system_login_result', 0, 'info', '', '登陆结果 - 验证码不存在', '', '2021-01-18 06:17:54', '1', '2022-02-16 13:24:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (43, 31, '验证码不正确', '31', 'system_login_result', 0, 'info', '', '登陆结果 - 验证码不正确', '', '2021-01-18 06:17:54', '1', '2022-02-16 13:24:11', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (44, 100, '未知异常', '100', 'system_login_result', 0, 'danger', '', '登陆结果 - 未知异常', '', '2021-01-18 06:17:54', '1', '2022-02-16 13:24:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (45, 1, '是', 'true', 'infra_boolean_string', 0, 'danger', '', 'Boolean 是否类型 - 是', '', '2021-01-19 03:20:55', '1', '2022-03-15 23:01:45', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (46, 1, '否', 'false', 'infra_boolean_string', 0, 'info', '', 'Boolean 是否类型 - 否', '', '2021-01-19 03:20:55', '1', '2022-03-15 23:09:45', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (50, 1, '单表(增删改查)', '1', 'infra_codegen_template_type', 0, '', '', NULL, '', '2021-02-05 07:09:06', '', '2022-03-10 16:33:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (51, 2, '树表(增删改查)', '2', 'infra_codegen_template_type', 0, '', '', NULL, '', '2021-02-05 07:14:46', '', '2022-03-10 16:33:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (53, 0, '初始化中', '0', 'infra_job_status', 0, 'primary', '', NULL, '', '2021-02-07 07:46:49', '1', '2022-02-16 19:33:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (57, 0, '运行中', '0', 'infra_job_log_status', 0, 'primary', '', 'RUNNING', '', '2021-02-08 10:04:24', '1', '2022-02-16 19:07:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (58, 1, '成功', '1', 'infra_job_log_status', 0, 'success', '', NULL, '', '2021-02-08 10:06:57', '1', '2022-02-16 19:07:52', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (59, 2, '失败', '2', 'infra_job_log_status', 0, 'warning', '', '失败', '', '2021-02-08 10:07:38', '1', '2022-02-16 19:07:56', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (60, 1, '会员', '1', 'user_type', 0, 'primary', '', NULL, '', '2021-02-26 00:16:27', '1', '2022-02-16 10:22:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (61, 2, '管理员', '2', 'user_type', 0, 'success', '', NULL, '', '2021-02-26 00:16:34', '1', '2025-04-06 18:37:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (62, 0, '未处理', '0', 'infra_api_error_log_process_status', 0, 'primary', '', NULL, '', '2021-02-26 07:07:19', '1', '2022-02-16 20:14:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (63, 1, '已处理', '1', 'infra_api_error_log_process_status', 0, 'success', '', NULL, '', '2021-02-26 07:07:26', '1', '2022-02-16 20:14:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (64, 2, '已忽略', '2', 'infra_api_error_log_process_status', 0, 'danger', '', NULL, '', '2021-02-26 07:07:34', '1', '2022-02-16 20:14:14', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (66, 1, '阿里云', 'ALIYUN', 'system_sms_channel_code', 0, 'primary', '', NULL, '1', '2021-04-05 01:05:26', '1', '2024-07-22 22:23:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (67, 1, '验证码', '1', 'system_sms_template_type', 0, 'warning', '', NULL, '1', '2021-04-05 21:50:57', '1', '2022-02-16 12:48:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (68, 2, '通知', '2', 'system_sms_template_type', 0, 'primary', '', NULL, '1', '2021-04-05 21:51:08', '1', '2022-02-16 12:48:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (69, 0, '营销', '3', 'system_sms_template_type', 0, 'danger', '', NULL, '1', '2021-04-05 21:51:15', '1', '2022-02-16 12:48:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (70, 0, '初始化', '0', 'system_sms_send_status', 0, 'primary', '', NULL, '1', '2021-04-11 20:18:33', '1', '2022-02-16 10:26:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (71, 1, '发送成功', '10', 'system_sms_send_status', 0, 'success', '', NULL, '1', '2021-04-11 20:18:43', '1', '2022-02-16 10:25:56', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (72, 2, '发送失败', '20', 'system_sms_send_status', 0, 'danger', '', NULL, '1', '2021-04-11 20:18:49', '1', '2022-02-16 10:26:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (73, 3, '不发送', '30', 'system_sms_send_status', 0, 'info', '', NULL, '1', '2021-04-11 20:19:44', '1', '2022-02-16 10:26:10', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (74, 0, '等待结果', '0', 'system_sms_receive_status', 0, 'primary', '', NULL, '1', '2021-04-11 20:27:43', '1', '2022-02-16 10:28:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (75, 1, '接收成功', '10', 'system_sms_receive_status', 0, 'success', '', NULL, '1', '2021-04-11 20:29:25', '1', '2022-02-16 10:28:28', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (76, 2, '接收失败', '20', 'system_sms_receive_status', 0, 'danger', '', NULL, '1', '2021-04-11 20:29:31', '1', '2022-02-16 10:28:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (77, 0, '调试(钉钉)', 'DEBUG_DING_TALK', 'system_sms_channel_code', 0, 'info', '', NULL, '1', '2021-04-13 00:20:37', '1', '2022-02-16 10:10:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (80, 100, '账号登录', '100', 'system_login_type', 0, 'primary', '', '账号登录', '1', '2021-10-06 00:52:02', '1', '2022-02-16 13:11:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (81, 101, '社交登录', '101', 'system_login_type', 0, 'info', '', '社交登录', '1', '2021-10-06 00:52:17', '1', '2022-02-16 13:11:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (83, 200, '主动登出', '200', 'system_login_type', 0, 'primary', '', '主动登出', '1', '2021-10-06 00:52:58', '1', '2022-02-16 13:11:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (85, 202, '强制登出', '202', 'system_login_type', 0, 'danger', '', '强制退出', '1', '2021-10-06 00:53:41', '1', '2022-02-16 13:11:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (86, 0, '病假', '1', 'bpm_oa_leave_type', 0, 'primary', '', NULL, '1', '2021-09-21 22:35:28', '1', '2022-02-16 10:00:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (87, 1, '事假', '2', 'bpm_oa_leave_type', 0, 'info', '', NULL, '1', '2021-09-21 22:36:11', '1', '2022-02-16 10:00:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (88, 2, '婚假', '3', 'bpm_oa_leave_type', 0, 'warning', '', NULL, '1', '2021-09-21 22:36:38', '1', '2022-02-16 10:00:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (112, 0, '微信 Wap 网站支付', 'wx_wap', 'pay_channel_code', 0, 'success', '', '微信 Wap 网站支付', '1', '2023-07-19 20:08:06', '1', '2023-07-19 20:09:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (113, 1, '微信公众号支付', 'wx_pub', 'pay_channel_code', 0, 'success', '', '微信公众号支付', '1', '2021-12-03 10:40:24', '1', '2023-07-19 20:08:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (114, 2, '微信小程序支付', 'wx_lite', 'pay_channel_code', 0, 'success', '', '微信小程序支付', '1', '2021-12-03 10:41:06', '1', '2023-07-19 20:08:50', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (115, 3, '微信 App 支付', 'wx_app', 'pay_channel_code', 0, 'success', '', '微信 App 支付', '1', '2021-12-03 10:41:20', '1', '2023-07-19 20:08:56', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (116, 10, '支付宝 PC 网站支付', 'alipay_pc', 'pay_channel_code', 0, 'primary', '', '支付宝 PC 网站支付', '1', '2021-12-03 10:42:09', '1', '2023-07-19 20:09:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (117, 11, '支付宝 Wap 网站支付', 'alipay_wap', 'pay_channel_code', 0, 'primary', '', '支付宝 Wap 网站支付', '1', '2021-12-03 10:42:26', '1', '2023-07-19 20:09:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (118, 12, '支付宝 App 支付', 'alipay_app', 'pay_channel_code', 0, 'primary', '', '支付宝 App 支付', '1', '2021-12-03 10:42:55', '1', '2023-07-19 20:09:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (119, 14, '支付宝扫码支付', 'alipay_qr', 'pay_channel_code', 0, 'primary', '', '支付宝扫码支付', '1', '2021-12-03 10:43:10', '1', '2023-07-19 20:09:28', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (120, 10, '通知成功', '10', 'pay_notify_status', 0, 'success', '', '通知成功', '1', '2021-12-03 11:02:41', '1', '2023-07-19 10:08:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (121, 20, '通知失败', '20', 'pay_notify_status', 0, 'danger', '', '通知失败', '1', '2021-12-03 11:02:59', '1', '2023-07-19 10:08:21', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (122, 0, '等待通知', '0', 'pay_notify_status', 0, 'info', '', '未通知', '1', '2021-12-03 11:03:10', '1', '2023-07-19 10:08:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (123, 10, '支付成功', '10', 'pay_order_status', 0, 'success', '', '支付成功', '1', '2021-12-03 11:18:29', '1', '2023-07-19 18:04:28', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (124, 30, '支付关闭', '30', 'pay_order_status', 0, 'info', '', '支付关闭', '1', '2021-12-03 11:18:42', '1', '2023-07-19 18:05:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (125, 0, '等待支付', '0', 'pay_order_status', 0, 'info', '', '未支付', '1', '2021-12-03 11:18:18', '1', '2023-07-19 18:04:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (600, 5, '首页', '1', 'promotion_banner_position', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', '2023-10-11 07:45:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (601, 4, '秒杀活动页', '2', 'promotion_banner_position', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', '2023-10-11 07:45:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (602, 3, '砍价活动页', '3', 'promotion_banner_position', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', '2023-10-11 07:45:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (603, 2, '限时折扣页', '4', 'promotion_banner_position', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', '2023-10-11 07:45:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (604, 1, '满减送页', '5', 'promotion_banner_position', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', '2023-10-11 07:45:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1118, 0, '等待退款', '0', 'pay_refund_status', 0, 'info', '', '等待退款', '1', '2021-12-10 16:44:59', '1', '2023-07-19 10:14:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1119, 20, '退款失败', '20', 'pay_refund_status', 0, 'danger', '', '退款失败', '1', '2021-12-10 16:45:10', '1', '2023-07-19 10:15:10', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1124, 10, '退款成功', '10', 'pay_refund_status', 0, 'success', '', '退款成功', '1', '2021-12-10 16:46:26', '1', '2023-07-19 10:15:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1127, 1, '审批中', '1', 'bpm_process_instance_status', 0, 'default', '', '流程实例的状态 - 进行中', '1', '2022-01-07 23:47:22', '1', '2024-03-16 16:11:45', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1128, 2, '审批通过', '2', 'bpm_process_instance_status', 0, 'success', '', '流程实例的状态 - 已完成', '1', '2022-01-07 23:47:49', '1', '2024-03-16 16:11:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1129, 1, '审批中', '1', 'bpm_task_status', 0, 'primary', '', '流程实例的结果 - 处理中', '1', '2022-01-07 23:48:32', '1', '2024-03-08 22:41:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1130, 2, '审批通过', '2', 'bpm_task_status', 0, 'success', '', '流程实例的结果 - 通过', '1', '2022-01-07 23:48:45', '1', '2024-03-08 22:41:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1131, 3, '审批不通过', '3', 'bpm_task_status', 0, 'danger', '', '流程实例的结果 - 不通过', '1', '2022-01-07 23:48:55', '1', '2024-03-08 22:41:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1132, 4, '已取消', '4', 'bpm_task_status', 0, 'info', '', '流程实例的结果 - 撤销', '1', '2022-01-07 23:49:06', '1', '2024-03-08 22:41:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1133, 10, '流程表单', '10', 'bpm_model_form_type', 0, '', '', '流程的表单类型 - 流程表单', '103', '2022-01-11 23:51:30', '103', '2022-01-11 23:51:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1134, 20, '业务表单', '20', 'bpm_model_form_type', 0, '', '', '流程的表单类型 - 业务表单', '103', '2022-01-11 23:51:47', '103', '2022-01-11 23:51:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1135, 10, '角色', '10', 'bpm_task_candidate_strategy', 0, 'info', '', '任务分配规则的类型 - 角色', '103', '2022-01-12 23:21:22', '1', '2024-03-06 02:53:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1136, 20, '部门的成员', '20', 'bpm_task_candidate_strategy', 0, 'primary', '', '任务分配规则的类型 - 部门的成员', '103', '2022-01-12 23:21:47', '1', '2024-03-06 02:53:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1137, 21, '部门的负责人', '21', 'bpm_task_candidate_strategy', 0, 'primary', '', '任务分配规则的类型 - 部门的负责人', '103', '2022-01-12 23:33:36', '1', '2024-03-06 02:53:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1138, 30, '用户', '30', 'bpm_task_candidate_strategy', 0, 'info', '', '任务分配规则的类型 - 用户', '103', '2022-01-12 23:34:02', '1', '2024-03-06 02:53:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1139, 40, '用户组', '40', 'bpm_task_candidate_strategy', 0, 'warning', '', '任务分配规则的类型 - 用户组', '103', '2022-01-12 23:34:21', '1', '2024-03-06 02:53:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1140, 60, '流程表达式', '60', 'bpm_task_candidate_strategy', 0, 'danger', '', '任务分配规则的类型 - 流程表达式', '103', '2022-01-12 23:34:43', '1', '2024-03-06 02:53:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1141, 22, '岗位', '22', 'bpm_task_candidate_strategy', 0, 'success', '', '任务分配规则的类型 - 岗位', '103', '2022-01-14 18:41:55', '1', '2024-03-06 02:53:21', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1145, 1, '管理后台', '1', 'infra_codegen_scene', 0, '', '', '代码生成的场景枚举 - 管理后台', '1', '2022-02-02 13:15:06', '1', '2022-03-10 16:32:59', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1146, 2, '用户 APP', '2', 'infra_codegen_scene', 0, '', '', '代码生成的场景枚举 - 用户 APP', '1', '2022-02-02 13:15:19', '1', '2022-03-10 16:33:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1150, 1, '数据库', '1', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:25:28', '1', '2022-03-15 00:25:28', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1151, 10, '本地磁盘', '10', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:25:41', '1', '2022-03-15 00:25:56', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1152, 11, 'FTP 服务器', '11', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:26:06', '1', '2022-03-15 00:26:10', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1153, 12, 'SFTP 服务器', '12', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:26:22', '1', '2022-03-15 00:26:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1154, 20, 'S3 对象存储', '20', 'infra_file_storage', 0, 'default', '', NULL, '1', '2022-03-15 00:26:31', '1', '2022-03-15 00:26:45', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1155, 103, '短信登录', '103', 'system_login_type', 0, 'default', '', NULL, '1', '2022-05-09 23:57:58', '1', '2022-05-09 23:58:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1156, 1, 'password', 'password', 'system_oauth2_grant_type', 0, 'default', '', '密码模式', '1', '2022-05-12 00:22:05', '1', '2022-05-11 16:26:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1157, 2, 'authorization_code', 'authorization_code', 'system_oauth2_grant_type', 0, 'primary', '', '授权码模式', '1', '2022-05-12 00:22:59', '1', '2022-05-11 16:26:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1158, 3, 'implicit', 'implicit', 'system_oauth2_grant_type', 0, 'success', '', '简化模式', '1', '2022-05-12 00:23:40', '1', '2022-05-11 16:26:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1159, 4, 'client_credentials', 'client_credentials', 'system_oauth2_grant_type', 0, 'default', '', '客户端模式', '1', '2022-05-12 00:23:51', '1', '2022-05-11 16:26:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1160, 5, 'refresh_token', 'refresh_token', 'system_oauth2_grant_type', 0, 'info', '', '刷新模式', '1', '2022-05-12 00:24:02', '1', '2022-05-11 16:26:11', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1162, 1, '销售中', '1', 'product_spu_status', 0, 'success', '', '商品 SPU 状态 - 销售中', '1', '2022-10-24 21:19:47', '1', '2022-10-24 21:20:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1163, 0, '仓库中', '0', 'product_spu_status', 0, 'info', '', '商品 SPU 状态 - 仓库中', '1', '2022-10-24 21:20:54', '1', '2022-10-24 21:21:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1164, 0, '回收站', '-1', 'product_spu_status', 0, 'default', '', '商品 SPU 状态 - 回收站', '1', '2022-10-24 21:21:11', '1', '2022-10-24 21:21:11', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1165, 1, '满减', '1', 'promotion_discount_type', 0, 'success', '', '优惠类型 - 满减', '1', '2022-11-01 12:46:41', '1', '2022-11-01 12:50:11', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1166, 2, '折扣', '2', 'promotion_discount_type', 0, 'primary', '', '优惠类型 - 折扣', '1', '2022-11-01 12:46:51', '1', '2022-11-01 12:50:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1167, 1, '固定日期', '1', 'promotion_coupon_template_validity_type', 0, 'default', '', '优惠劵模板的有限期类型 - 固定日期', '1', '2022-11-02 00:07:34', '1', '2022-11-04 00:07:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1168, 2, '领取之后', '2', 'promotion_coupon_template_validity_type', 0, 'default', '', '优惠劵模板的有限期类型 - 领取之后', '1', '2022-11-02 00:07:54', '1', '2022-11-04 00:07:52', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1169, 1, '通用劵', '1', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 全部商品参与', '1', '2022-11-02 00:28:22', '1', '2023-09-28 00:27:42', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1170, 2, '商品劵', '2', 'promotion_product_scope', 0, 'default', '', '营销的商品范围 - 指定商品参与', '1', '2022-11-02 00:28:34', '1', '2023-09-28 00:27:44', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1171, 1, '未使用', '1', 'promotion_coupon_status', 0, 'primary', '', '优惠劵的状态 - 已领取', '1', '2022-11-04 00:15:08', '1', '2023-10-03 12:54:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1172, 2, '已使用', '2', 'promotion_coupon_status', 0, 'success', '', '优惠劵的状态 - 已使用', '1', '2022-11-04 00:15:21', '1', '2022-11-04 19:16:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1173, 3, '已过期', '3', 'promotion_coupon_status', 0, 'info', '', '优惠劵的状态 - 已过期', '1', '2022-11-04 00:15:43', '1', '2022-11-04 19:16:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1174, 1, '直接领取', '1', 'promotion_coupon_take_type', 0, 'primary', '', '优惠劵的领取方式 - 直接领取', '1', '2022-11-04 19:13:00', '1', '2022-11-04 19:13:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1175, 2, '指定发放', '2', 'promotion_coupon_take_type', 0, 'success', '', '优惠劵的领取方式 - 指定发放', '1', '2022-11-04 19:13:13', '1', '2022-11-04 19:14:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1176, 10, '未开始', '10', 'promotion_activity_status', 0, 'primary', '', '促销活动的状态枚举 - 未开始', '1', '2022-11-04 22:54:49', '1', '2022-11-04 22:55:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1177, 20, '进行中', '20', 'promotion_activity_status', 0, 'success', '', '促销活动的状态枚举 - 进行中', '1', '2022-11-04 22:55:06', '1', '2022-11-04 22:55:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1178, 30, '已结束', '30', 'promotion_activity_status', 0, 'info', '', '促销活动的状态枚举 - 已结束', '1', '2022-11-04 22:55:41', '1', '2022-11-04 22:55:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1179, 40, '已关闭', '40', 'promotion_activity_status', 0, 'warning', '', '促销活动的状态枚举 - 已关闭', '1', '2022-11-04 22:56:10', '1', '2022-11-04 22:56:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1180, 10, '满 N 元', '10', 'promotion_condition_type', 0, 'primary', '', '营销的条件类型 - 满 N 元', '1', '2022-11-04 22:59:45', '1', '2022-11-04 22:59:45', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1181, 20, '满 N 件', '20', 'promotion_condition_type', 0, 'success', '', '营销的条件类型 - 满 N 件', '1', '2022-11-04 23:00:02', '1', '2022-11-04 23:00:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1182, 10, '申请售后', '10', 'trade_after_sale_status', 0, 'primary', '', '交易售后状态 - 申请售后', '1', '2022-11-19 20:53:33', '1', '2022-11-19 20:54:42', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1183, 20, '商品待退货', '20', 'trade_after_sale_status', 0, 'primary', '', '交易售后状态 - 商品待退货', '1', '2022-11-19 20:54:36', '1', '2022-11-19 20:58:58', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1184, 30, '商家待收货', '30', 'trade_after_sale_status', 0, 'primary', '', '交易售后状态 - 商家待收货', '1', '2022-11-19 20:56:56', '1', '2022-11-19 20:59:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1185, 40, '等待退款', '40', 'trade_after_sale_status', 0, 'primary', '', '交易售后状态 - 等待退款', '1', '2022-11-19 20:59:54', '1', '2022-11-19 21:00:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1186, 50, '退款成功', '50', 'trade_after_sale_status', 0, 'default', '', '交易售后状态 - 退款成功', '1', '2022-11-19 21:00:33', '1', '2022-11-19 21:00:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1187, 61, '买家取消', '61', 'trade_after_sale_status', 0, 'info', '', '交易售后状态 - 买家取消', '1', '2022-11-19 21:01:29', '1', '2022-11-19 21:01:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1188, 62, '商家拒绝', '62', 'trade_after_sale_status', 0, 'info', '', '交易售后状态 - 商家拒绝', '1', '2022-11-19 21:02:17', '1', '2022-11-19 21:02:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1189, 63, '商家拒收货', '63', 'trade_after_sale_status', 0, 'info', '', '交易售后状态 - 商家拒收货', '1', '2022-11-19 21:02:37', '1', '2022-11-19 21:03:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1190, 10, '售中退款', '10', 'trade_after_sale_type', 0, 'success', '', '交易售后的类型 - 售中退款', '1', '2022-11-19 21:05:05', '1', '2022-11-19 21:38:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1191, 20, '售后退款', '20', 'trade_after_sale_type', 0, 'primary', '', '交易售后的类型 - 售后退款', '1', '2022-11-19 21:05:32', '1', '2022-11-19 21:38:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1192, 10, '仅退款', '10', 'trade_after_sale_way', 0, 'primary', '', '交易售后的方式 - 仅退款', '1', '2022-11-19 21:39:19', '1', '2022-11-19 21:39:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1193, 20, '退货退款', '20', 'trade_after_sale_way', 0, 'success', '', '交易售后的方式 - 退货退款', '1', '2022-11-19 21:39:38', '1', '2022-11-19 21:39:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1194, 10, '微信小程序', '10', 'terminal', 0, 'default', '', '终端 - 微信小程序', '1', '2022-12-10 10:51:11', '1', '2022-12-10 10:51:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1195, 20, 'H5 网页', '20', 'terminal', 0, 'default', '', '终端 - H5 网页', '1', '2022-12-10 10:51:30', '1', '2022-12-10 10:51:59', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1196, 11, '微信公众号', '11', 'terminal', 0, 'default', '', '终端 - 微信公众号', '1', '2022-12-10 10:54:16', '1', '2022-12-10 10:52:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1197, 31, '苹果 App', '31', 'terminal', 0, 'default', '', '终端 - 苹果 App', '1', '2022-12-10 10:54:42', '1', '2022-12-10 10:52:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1198, 32, '安卓 App', '32', 'terminal', 0, 'default', '', '终端 - 安卓 App', '1', '2022-12-10 10:55:02', '1', '2022-12-10 10:59:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1199, 0, '普通订单', '0', 'trade_order_type', 0, 'default', '', '交易订单的类型 - 普通订单', '1', '2022-12-10 16:34:14', '1', '2022-12-10 16:34:14', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1200, 1, '秒杀订单', '1', 'trade_order_type', 0, 'default', '', '交易订单的类型 - 秒杀订单', '1', '2022-12-10 16:34:26', '1', '2022-12-10 16:34:26', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1201, 2, '砍价订单', '2', 'trade_order_type', 0, 'default', '', '交易订单的类型 - 拼团订单', '1', '2022-12-10 16:34:36', '1', '2024-09-07 14:18:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1202, 3, '拼团订单', '3', 'trade_order_type', 0, 'default', '', '交易订单的类型 - 砍价订单', '1', '2022-12-10 16:34:48', '1', '2024-09-07 14:18:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1203, 0, '待支付', '0', 'trade_order_status', 0, 'default', '', '交易订单状态 - 待支付', '1', '2022-12-10 16:49:29', '1', '2022-12-10 16:49:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1204, 10, '待发货', '10', 'trade_order_status', 0, 'primary', '', '交易订单状态 - 待发货', '1', '2022-12-10 16:49:53', '1', '2022-12-10 16:51:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1205, 20, '已发货', '20', 'trade_order_status', 0, 'primary', '', '交易订单状态 - 已发货', '1', '2022-12-10 16:50:13', '1', '2022-12-10 16:51:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1206, 30, '已完成', '30', 'trade_order_status', 0, 'success', '', '交易订单状态 - 已完成', '1', '2022-12-10 16:50:30', '1', '2022-12-10 16:51:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1207, 40, '已取消', '40', 'trade_order_status', 0, 'danger', '', '交易订单状态 - 已取消', '1', '2022-12-10 16:50:50', '1', '2022-12-10 16:51:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1208, 0, '未售后', '0', 'trade_order_item_after_sale_status', 0, 'info', '', '交易订单项的售后状态 - 未售后', '1', '2022-12-10 20:58:42', '1', '2022-12-10 20:59:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1209, 10, '售后中', '10', 'trade_order_item_after_sale_status', 0, 'primary', '', '交易订单项的售后状态 - 售后中', '1', '2022-12-10 20:59:21', '1', '2024-07-21 17:01:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1210, 20, '已退款', '20', 'trade_order_item_after_sale_status', 0, 'success', '', '交易订单项的售后状态 - 已退款', '1', '2022-12-10 20:59:46', '1', '2024-07-21 17:01:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1211, 1, '完全匹配', '1', 'mp_auto_reply_request_match', 0, 'primary', '', '公众号自动回复的请求关键字匹配模式 - 完全匹配', '1', '2023-01-16 23:30:39', '1', '2023-01-16 23:31:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1212, 2, '半匹配', '2', 'mp_auto_reply_request_match', 0, 'success', '', '公众号自动回复的请求关键字匹配模式 - 半匹配', '1', '2023-01-16 23:30:55', '1', '2023-01-16 23:31:10', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1213, 1, '文本', 'text', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 文本', '1', '2023-01-17 22:17:32', '1', '2023-01-17 22:17:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1214, 2, '图片', 'image', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 图片', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:19:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1215, 3, '语音', 'voice', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 语音', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:20:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1216, 4, '视频', 'video', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 视频', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:21:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1217, 5, '小视频', 'shortvideo', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 小视频', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:19:59', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1218, 6, '图文', 'news', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 图文', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:22:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1219, 7, '音乐', 'music', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 音乐', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:22:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1220, 8, '地理位置', 'location', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 地理位置', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:23:51', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1221, 9, '链接', 'link', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 链接', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:24:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1222, 10, '事件', 'event', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 事件', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:24:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1223, 0, '初始化', '0', 'system_mail_send_status', 0, 'primary', '', '邮件发送状态 - 初始化\n', '1', '2023-01-26 09:53:49', '1', '2023-01-26 16:36:14', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1224, 10, '发送成功', '10', 'system_mail_send_status', 0, 'success', '', '邮件发送状态 - 发送成功', '1', '2023-01-26 09:54:28', '1', '2023-01-26 16:36:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1225, 20, '发送失败', '20', 'system_mail_send_status', 0, 'danger', '', '邮件发送状态 - 发送失败', '1', '2023-01-26 09:54:50', '1', '2023-01-26 16:36:26', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1226, 30, '不发送', '30', 'system_mail_send_status', 0, 'info', '', '邮件发送状态 - 不发送', '1', '2023-01-26 09:55:06', '1', '2023-01-26 16:36:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1227, 1, '通知公告', '1', 'system_notify_template_type', 0, 'primary', '', '站内信模版的类型 - 通知公告', '1', '2023-01-28 10:35:59', '1', '2023-01-28 10:35:59', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1228, 2, '系统消息', '2', 'system_notify_template_type', 0, 'success', '', '站内信模版的类型 - 系统消息', '1', '2023-01-28 10:36:20', '1', '2023-01-28 10:36:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1230, 13, '支付宝条码支付', 'alipay_bar', 'pay_channel_code', 0, 'primary', '', '支付宝条码支付', '1', '2023-02-18 23:32:24', '1', '2023-07-19 20:09:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1231, 10, 'Vue2 Element UI 标准模版', '10', 'infra_codegen_front_type', 0, '', '', '', '1', '2023-04-13 00:03:55', '1', '2023-04-13 00:03:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1232, 20, 'Vue3 Element Plus 标准模版', '20', 'infra_codegen_front_type', 0, '', '', '', '1', '2023-04-13 00:04:08', '1', '2023-04-13 00:04:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1234, 30, 'Vben2.0 Ant Design Schema 模版', '30', 'infra_codegen_front_type', 1, '', '', '', '1', '2023-04-13 00:04:26', '1', '2025-07-27 10:55:14', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1244, 0, '按件', '1', 'trade_delivery_express_charge_mode', 0, '', '', '', '1', '2023-05-21 22:46:40', '1', '2023-05-21 22:46:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1245, 1, '按重量', '2', 'trade_delivery_express_charge_mode', 0, '', '', '', '1', '2023-05-21 22:46:58', '1', '2023-05-21 22:46:58', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1246, 2, '按体积', '3', 'trade_delivery_express_charge_mode', 0, '', '', '', '1', '2023-05-21 22:47:18', '1', '2023-05-21 22:47:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1335, 11, '订单积分抵扣', '11', 'member_point_biz_type', 0, '', '', '', '1', '2023-06-10 12:15:27', '1', '2023-10-11 07:41:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1336, 1, '签到', '1', 'member_point_biz_type', 0, '', '', '', '1', '2023-06-10 12:15:48', '1', '2023-08-20 11:59:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1341, 20, '已退款', '20', 'pay_order_status', 0, 'danger', '', '已退款', '1', '2023-07-19 18:05:37', '1', '2023-07-19 18:05:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1342, 21, '请求成功,但是结果失败', '21', 'pay_notify_status', 0, 'warning', '', '请求成功,但是结果失败', '1', '2023-07-19 18:10:47', '1', '2023-07-19 18:11:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1343, 22, '请求失败', '22', 'pay_notify_status', 0, 'warning', '', NULL, '1', '2023-07-19 18:11:05', '1', '2023-07-19 18:11:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1344, 4, '微信扫码支付', 'wx_native', 'pay_channel_code', 0, 'success', '', '微信扫码支付', '1', '2023-07-19 20:07:47', '1', '2023-07-19 20:09:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1345, 5, '微信条码支付', 'wx_bar', 'pay_channel_code', 0, 'success', '', '微信条码支付\n', '1', '2023-07-19 20:08:06', '1', '2023-07-19 20:09:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1346, 1, '支付单', '1', 'pay_notify_type', 0, 'primary', '', '支付单', '1', '2023-07-20 12:23:17', '1', '2023-07-20 12:23:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1347, 2, '退款单', '2', 'pay_notify_type', 0, 'danger', '', NULL, '1', '2023-07-20 12:23:26', '1', '2023-07-20 12:23:26', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1348, 20, '模拟支付', 'mock', 'pay_channel_code', 0, 'default', '', '模拟支付', '1', '2023-07-29 11:10:51', '1', '2023-07-29 03:14:10', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1349, 12, '订单积分抵扣(整单取消)', '12', 'member_point_biz_type', 0, '', '', '', '1', '2023-08-20 12:00:03', '1', '2023-10-11 07:42:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1350, 0, '管理员调整', '0', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', '2023-08-22 12:41:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1351, 1, '邀新奖励', '1', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', '2023-08-22 12:41:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1352, 11, '下单奖励', '11', 'member_experience_biz_type', 0, 'success', '', NULL, '', '2023-08-22 12:41:01', '1', '2023-10-11 07:45:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1353, 12, '下单奖励(整单取消)', '12', 'member_experience_biz_type', 0, 'warning', '', NULL, '', '2023-08-22 12:41:01', '1', '2023-10-11 07:45:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1354, 4, '签到奖励', '4', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', '2023-08-22 12:41:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1355, 5, '抽奖奖励', '5', 'member_experience_biz_type', 0, '', '', NULL, '', '2023-08-22 12:41:01', '', '2023-08-22 12:41:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1356, 1, '快递发货', '1', 'trade_delivery_type', 0, '', '', '', '1', '2023-08-23 00:04:55', '1', '2023-08-23 00:04:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1357, 2, '用户自提', '2', 'trade_delivery_type', 0, '', '', '', '1', '2023-08-23 00:05:05', '1', '2023-08-23 00:05:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1358, 3, '品类劵', '3', 'promotion_product_scope', 0, 'default', '', '', '1', '2023-09-01 23:43:07', '1', '2023-09-28 00:27:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1359, 1, '人人分销', '1', 'brokerage_enabled_condition', 0, '', '', '所有用户都可以分销', '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1360, 2, '指定分销', '2', 'brokerage_enabled_condition', 0, '', '', '仅可后台手动设置推广员', '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1361, 1, '首次绑定', '1', 'brokerage_bind_mode', 0, '', '', '只要用户没有推广人,随时都可以绑定推广关系', '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1362, 2, '注册绑定', '2', 'brokerage_bind_mode', 0, '', '', '仅新用户注册时才能绑定推广关系', '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1363, 3, '覆盖绑定', '3', 'brokerage_bind_mode', 0, '', '', '如果用户已经有推广人,推广人会被变更', '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1364, 1, '钱包', '1', 'brokerage_withdraw_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1365, 2, '银行卡', '2', 'brokerage_withdraw_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1366, 3, '微信收款码', '3', 'brokerage_withdraw_type', 0, '', '', '手动打款', '', '2023-09-28 02:46:05', '1', '2025-05-10 08:24:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1367, 4, '支付宝收款码', '4', 'brokerage_withdraw_type', 0, '', '', '手动打款', '', '2023-09-28 02:46:05', '1', '2025-05-10 08:24:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1368, 1, '订单返佣', '1', 'brokerage_record_biz_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1369, 2, '申请提现', '2', 'brokerage_record_biz_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1370, 3, '申请提现驳回', '3', 'brokerage_record_biz_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1371, 0, '待结算', '0', 'brokerage_record_status', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1372, 1, '已结算', '1', 'brokerage_record_status', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1373, 2, '已取消', '2', 'brokerage_record_status', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1374, 0, '审核中', '0', 'brokerage_withdraw_status', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1375, 10, '审核通过', '10', 'brokerage_withdraw_status', 0, 'success', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1376, 11, '提现成功', '11', 'brokerage_withdraw_status', 0, 'success', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1377, 20, '审核不通过', '20', 'brokerage_withdraw_status', 0, 'danger', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1378, 21, '提现失败', '21', 'brokerage_withdraw_status', 0, 'danger', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1379, 0, '工商银行', '0', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1380, 1, '建设银行', '1', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1381, 2, '农业银行', '2', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1382, 3, '中国银行', '3', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1383, 4, '交通银行', '4', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1384, 5, '招商银行', '5', 'brokerage_bank_name', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1385, 21, '钱包', 'wallet', 'pay_channel_code', 0, 'primary', '', '', '1', '2023-10-01 21:46:19', '1', '2023-10-01 21:48:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1386, 1, '砍价中', '1', 'promotion_bargain_record_status', 0, 'default', '', '', '1', '2023-10-05 10:41:26', '1', '2023-10-05 10:41:26', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1387, 2, '砍价成功', '2', 'promotion_bargain_record_status', 0, 'success', '', '', '1', '2023-10-05 10:41:39', '1', '2023-10-05 10:41:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1388, 3, '砍价失败', '3', 'promotion_bargain_record_status', 0, 'warning', '', '', '1', '2023-10-05 10:41:57', '1', '2023-10-05 10:41:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1389, 0, '拼团中', '0', 'promotion_combination_record_status', 0, '', '', '', '1', '2023-10-08 07:24:44', '1', '2024-10-13 10:08:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1390, 1, '拼团成功', '1', 'promotion_combination_record_status', 0, 'success', '', '', '1', '2023-10-08 07:24:56', '1', '2024-10-13 10:08:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1391, 2, '拼团失败', '2', 'promotion_combination_record_status', 0, 'warning', '', '', '1', '2023-10-08 07:25:11', '1', '2024-10-13 10:08:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1392, 2, '管理员修改', '2', 'member_point_biz_type', 0, 'default', '', '', '1', '2023-10-11 07:41:34', '1', '2023-10-11 07:41:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1393, 13, '订单积分抵扣(单个退款)', '13', 'member_point_biz_type', 0, '', '', '', '1', '2023-10-11 07:42:29', '1', '2023-10-11 07:42:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1394, 21, '订单积分奖励', '21', 'member_point_biz_type', 0, 'default', '', '', '1', '2023-10-11 07:42:44', '1', '2023-10-11 07:42:44', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1395, 22, '订单积分奖励(整单取消)', '22', 'member_point_biz_type', 0, 'default', '', '', '1', '2023-10-11 07:42:55', '1', '2023-10-11 07:43:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1396, 23, '订单积分奖励(单个退款)', '23', 'member_point_biz_type', 0, 'default', '', '', '1', '2023-10-11 07:43:16', '1', '2023-10-11 07:43:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1397, 13, '下单奖励(单个退款)', '13', 'member_experience_biz_type', 0, 'warning', '', '', '1', '2023-10-11 07:45:24', '1', '2023-10-11 07:45:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1398, 5, '网上转账', '5', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:24', '1', '2023-10-18 21:55:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1399, 6, '支付宝', '6', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:38', '1', '2023-10-18 21:55:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1400, 7, '微信支付', '7', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:53', '1', '2023-10-18 21:55:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1401, 8, '其他', '8', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:56:06', '1', '2023-10-18 21:56:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1402, 1, 'IT', '1', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:02:15', '1', '2024-02-18 23:30:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1403, 2, '金融业', '2', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:02:29', '1', '2024-02-18 23:30:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1404, 3, '房地产', '3', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:02:41', '1', '2024-02-18 23:30:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1405, 4, '商业服务', '4', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:02:54', '1', '2024-02-18 23:30:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1406, 5, '运输/物流', '5', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:03:03', '1', '2024-02-18 23:31:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1407, 6, '生产', '6', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:03:13', '1', '2024-02-18 23:31:08', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1408, 7, '政府', '7', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:03:27', '1', '2024-02-18 23:31:13', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1409, 8, '文化传媒', '8', 'crm_customer_industry', 0, 'default', '', '', '1', '2023-10-28 23:03:37', '1', '2024-02-18 23:31:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1422, 1, 'A (重点客户)', '1', 'crm_customer_level', 0, 'primary', '', '', '1', '2023-10-28 23:07:13', '1', '2023-10-28 23:07:13', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1423, 2, 'B (普通客户)', '2', 'crm_customer_level', 0, 'info', '', '', '1', '2023-10-28 23:07:35', '1', '2023-10-28 23:07:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1424, 3, 'C (非优先客户)', '3', 'crm_customer_level', 0, 'default', '', '', '1', '2023-10-28 23:07:53', '1', '2023-10-28 23:07:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1425, 1, '促销', '1', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:08:29', '1', '2023-10-28 23:08:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1426, 2, '搜索引擎', '2', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:08:39', '1', '2023-10-28 23:08:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1427, 3, '广告', '3', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:08:47', '1', '2023-10-28 23:08:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1428, 4, '转介绍', '4', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:08:58', '1', '2023-10-28 23:08:58', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1429, 5, '线上注册', '5', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:09:12', '1', '2023-10-28 23:09:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1430, 6, '线上咨询', '6', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:09:22', '1', '2023-10-28 23:09:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1431, 7, '预约上门', '7', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:09:39', '1', '2023-10-28 23:09:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1432, 8, '陌拜', '8', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:10:04', '1', '2023-10-28 23:10:04', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1433, 9, '电话咨询', '9', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:10:18', '1', '2023-10-28 23:10:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1434, 10, '邮件咨询', '10', 'crm_customer_source', 0, 'default', '', '', '1', '2023-10-28 23:10:33', '1', '2023-10-28 23:10:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1435, 10, 'Gitee', '10', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:04:42', '1', '2023-11-04 13:04:42', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1436, 20, '钉钉', '20', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:04:54', '1', '2023-11-04 13:04:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1437, 30, '企业微信', '30', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:09', '1', '2023-11-04 13:05:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1438, 31, '微信公众平台', '31', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:18', '1', '2023-11-04 13:05:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1439, 32, '微信开放平台', '32', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:30', '1', '2023-11-04 13:05:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1440, 34, '微信小程序', '34', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1441, 1, '上架', '1', 'crm_product_status', 0, 'success', '', '', '1', '2023-10-30 21:49:34', '1', '2023-10-30 21:49:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1442, 0, '下架', '0', 'crm_product_status', 0, 'success', '', '', '1', '2023-10-30 21:49:13', '1', '2023-10-30 21:49:13', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1443, 15, '子表', '15', 'infra_codegen_template_type', 0, 'default', '', '', '1', '2023-11-13 23:06:16', '1', '2023-11-13 23:06:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1444, 10, '主表(标准模式)', '10', 'infra_codegen_template_type', 0, 'default', '', '', '1', '2023-11-14 12:32:49', '1', '2023-11-14 12:32:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1445, 11, '主表(ERP 模式)', '11', 'infra_codegen_template_type', 0, 'default', '', '', '1', '2023-11-14 12:33:05', '1', '2023-11-14 12:33:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1446, 12, '主表(内嵌模式)', '12', 'infra_codegen_template_type', 0, '', '', '', '1', '2023-11-14 12:33:31', '1', '2023-11-14 12:33:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1447, 1, '负责人', '1', 'crm_permission_level', 0, 'default', '', '', '1', '2023-11-30 09:53:12', '1', '2023-11-30 09:53:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1448, 2, '只读', '2', 'crm_permission_level', 0, '', '', '', '1', '2023-11-30 09:53:29', '1', '2023-11-30 09:53:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1449, 3, '读写', '3', 'crm_permission_level', 0, '', '', '', '1', '2023-11-30 09:53:36', '1', '2023-11-30 09:53:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1450, 0, '未提交', '0', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:56:59', '1', '2023-11-30 18:56:59', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1451, 10, '审批中', '10', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:10', '1', '2023-11-30 18:57:10', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1452, 20, '审核通过', '20', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:24', '1', '2023-11-30 18:57:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1453, 30, '审核不通过', '30', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:32', '1', '2023-11-30 18:57:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1454, 40, '已取消', '40', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:42', '1', '2023-11-30 18:57:42', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1456, 1, '支票', '1', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:29', '1', '2023-10-18 21:54:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1457, 2, '现金', '2', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:41', '1', '2023-10-18 21:54:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1458, 3, '邮政汇款', '3', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:53', '1', '2023-10-18 21:54:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1459, 4, '电汇', '4', 'crm_receivable_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:07', '1', '2023-10-18 21:55:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1461, 1, '个', '1', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:02:26', '1', '2023-12-05 23:02:26', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1462, 2, '块', '2', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:02:34', '1', '2023-12-05 23:02:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1463, 3, '只', '3', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:02:57', '1', '2023-12-05 23:02:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1464, 4, '把', '4', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:05', '1', '2023-12-05 23:03:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1465, 5, '枚', '5', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:14', '1', '2023-12-05 23:03:14', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1466, 6, '瓶', '6', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:20', '1', '2023-12-05 23:03:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1467, 7, '盒', '7', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:30', '1', '2023-12-05 23:03:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1468, 8, '台', '8', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:41', '1', '2023-12-05 23:03:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1469, 9, '吨', '9', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:03:48', '1', '2023-12-05 23:03:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1470, 10, '千克', '10', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:04:03', '1', '2023-12-05 23:04:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1471, 11, '米', '11', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:04:12', '1', '2023-12-05 23:04:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1472, 12, '箱', '12', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:04:25', '1', '2023-12-05 23:04:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1473, 13, '套', '13', 'crm_product_unit', 0, '', '', '', '1', '2023-12-05 23:04:34', '1', '2023-12-05 23:04:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1474, 1, '打电话', '1', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:48:20', '1', '2024-01-15 20:48:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1475, 2, '发短信', '2', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:48:31', '1', '2024-01-15 20:48:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1476, 3, '上门拜访', '3', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:49:07', '1', '2024-01-15 20:49:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1477, 4, '微信沟通', '4', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:49:15', '1', '2024-01-15 20:49:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1482, 4, '转账失败', '20', 'pay_transfer_status', 0, 'warning', '', '', '1', '2023-10-28 16:24:16', '1', '2025-05-08 12:59:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1483, 3, '转账成功', '10', 'pay_transfer_status', 0, 'success', '', '', '1', '2023-10-28 16:23:50', '1', '2025-05-08 12:58:58', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1484, 2, '转账进行中', '5', 'pay_transfer_status', 0, 'info', '', '', '1', '2023-10-28 16:23:12', '1', '2025-05-08 12:58:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1485, 1, '等待转账', '0', 'pay_transfer_status', 0, 'default', '', '', '1', '2023-10-28 16:21:43', '1', '2023-10-28 16:23:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1486, 10, '其它入库', '10', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-05 18:07:25', '1', '2024-02-05 18:07:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1487, 11, '其它入库(作废)', '11', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-05 18:08:07', '1', '2024-02-05 19:20:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1488, 20, '其它出库', '20', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-05 18:08:51', '1', '2024-02-05 18:08:51', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1489, 21, '其它出库(作废)', '21', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-05 18:09:00', '1', '2024-02-05 19:20:10', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1490, 10, '未审核', '10', 'erp_audit_status', 0, 'default', '', '', '1', '2024-02-06 00:00:21', '1', '2024-02-06 00:00:21', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1491, 20, '已审核', '20', 'erp_audit_status', 0, 'success', '', '', '1', '2024-02-06 00:00:35', '1', '2024-02-06 00:00:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1492, 30, '调拨入库', '30', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-07 20:34:19', '1', '2024-02-07 12:36:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1493, 31, '调拨入库(作废)', '31', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-07 20:34:29', '1', '2024-02-07 20:37:11', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1494, 32, '调拨出库', '32', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-07 20:34:38', '1', '2024-02-07 12:36:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1495, 33, '调拨出库(作废)', '33', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-07 20:34:49', '1', '2024-02-07 20:37:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1496, 40, '盘盈入库', '40', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-08 08:53:00', '1', '2024-02-08 08:53:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1497, 41, '盘盈入库(作废)', '41', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-08 08:53:39', '1', '2024-02-16 19:40:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1498, 42, '盘亏出库', '42', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-08 08:54:16', '1', '2024-02-08 08:54:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1499, 43, '盘亏出库(作废)', '43', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-08 08:54:31', '1', '2024-02-16 19:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1500, 50, '销售出库', '50', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-11 21:47:25', '1', '2024-02-11 21:50:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1501, 51, '销售出库(作废)', '51', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-11 21:47:37', '1', '2024-02-11 21:51:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1502, 60, '销售退货入库', '60', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-12 06:51:05', '1', '2024-02-12 06:51:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1503, 61, '销售退货入库(作废)', '61', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-12 06:51:18', '1', '2024-02-12 06:51:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1504, 70, '采购入库', '70', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-16 13:10:02', '1', '2024-02-16 13:10:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1505, 71, '采购入库(作废)', '71', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-16 13:10:10', '1', '2024-02-16 19:40:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1506, 80, '采购退货出库', '80', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-16 13:10:17', '1', '2024-02-16 13:10:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1507, 81, '采购退货出库(作废)', '81', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-16 13:10:26', '1', '2024-02-16 19:40:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1509, 3, '审批不通过', '3', 'bpm_process_instance_status', 0, 'danger', '', '', '1', '2024-03-16 16:12:06', '1', '2024-03-16 16:12:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1510, 4, '已取消', '4', 'bpm_process_instance_status', 0, 'warning', '', '', '1', '2024-03-16 16:12:22', '1', '2024-03-16 16:12:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1511, 5, '已退回', '5', 'bpm_task_status', 0, 'warning', '', '', '1', '2024-03-16 19:10:46', '1', '2024-03-08 22:41:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1512, 6, '委派中', '6', 'bpm_task_status', 0, 'primary', '', '', '1', '2024-03-17 10:06:22', '1', '2024-03-08 22:41:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1513, 7, '审批通过中', '7', 'bpm_task_status', 0, 'success', '', '', '1', '2024-03-17 10:06:47', '1', '2024-03-08 22:41:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1514, 0, '待审批', '0', 'bpm_task_status', 0, 'info', '', '', '1', '2024-03-17 10:07:11', '1', '2024-03-08 22:41:42', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1515, 35, '发起人自选', '35', 'bpm_task_candidate_strategy', 0, '', '', '', '1', '2024-03-22 19:45:16', '1', '2024-03-22 19:45:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1516, 1, '执行监听器', 'execution', 'bpm_process_listener_type', 0, 'primary', '', '', '1', '2024-03-23 12:54:03', '1', '2024-03-23 19:14:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1517, 1, '任务监听器', 'task', 'bpm_process_listener_type', 0, 'success', '', '', '1', '2024-03-23 12:54:13', '1', '2024-03-23 19:14:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1526, 1, 'Java 类', 'class', 'bpm_process_listener_value_type', 0, 'primary', '', '', '1', '2024-03-23 15:08:45', '1', '2024-03-23 19:14:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1527, 2, '表达式', 'expression', 'bpm_process_listener_value_type', 0, 'success', '', '', '1', '2024-03-23 15:09:06', '1', '2024-03-23 19:14:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1528, 3, '代理表达式', 'delegateExpression', 'bpm_process_listener_value_type', 0, 'info', '', '', '1', '2024-03-23 15:11:23', '1', '2024-03-23 19:14:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1529, 1, '天', '1', 'date_interval', 0, '', '', '', '1', '2024-03-29 22:50:26', '1', '2024-03-29 22:50:26', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1530, 2, '周', '2', 'date_interval', 0, '', '', '', '1', '2024-03-29 22:50:36', '1', '2024-03-29 22:50:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1531, 3, '月', '3', 'date_interval', 0, '', '', '', '1', '2024-03-29 22:50:46', '1', '2024-03-29 22:50:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1532, 4, '季度', '4', 'date_interval', 0, '', '', '', '1', '2024-03-29 22:51:01', '1', '2024-03-29 22:51:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1533, 5, '年', '5', 'date_interval', 0, '', '', '', '1', '2024-03-29 22:51:07', '1', '2024-03-29 22:51:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1534, 1, '赢单', '1', 'crm_business_end_status_type', 0, 'success', '', '', '1', '2024-04-13 23:26:57', '1', '2024-04-13 23:26:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1535, 2, '输单', '2', 'crm_business_end_status_type', 0, 'primary', '', '', '1', '2024-04-13 23:27:31', '1', '2024-04-13 23:27:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1536, 3, '无效', '3', 'crm_business_end_status_type', 0, 'info', '', '', '1', '2024-04-13 23:27:59', '1', '2024-04-13 23:27:59', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1537, 1, 'OpenAI', 'OpenAI', 'ai_platform', 0, '', '', '', '1', '2024-05-09 22:33:47', '1', '2024-05-09 22:58:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1538, 2, 'Ollama', 'Ollama', 'ai_platform', 0, '', '', '', '1', '2024-05-17 23:02:55', '1', '2024-05-17 23:02:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1539, 3, '文心一言', 'YiYan', 'ai_platform', 0, '', '', '', '1', '2024-05-18 09:24:20', '1', '2024-05-18 09:29:01', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1540, 4, '讯飞星火', 'XingHuo', 'ai_platform', 0, '', '', '', '1', '2024-05-18 10:08:56', '1', '2024-05-18 10:08:56', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1541, 5, '通义千问', 'TongYi', 'ai_platform', 0, '', '', '', '1', '2024-05-18 10:32:29', '1', '2024-07-06 15:42:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1542, 6, 'StableDiffusion', 'StableDiffusion', 'ai_platform', 0, '', '', '', '1', '2024-06-01 15:09:31', '1', '2024-06-01 15:10:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1543, 10, '进行中', '10', 'ai_image_status', 0, 'primary', '', '', '1', '2024-06-26 20:51:41', '1', '2024-06-26 20:52:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1544, 20, '已完成', '20', 'ai_image_status', 0, 'success', '', '', '1', '2024-06-26 20:52:07', '1', '2024-06-26 20:52:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1545, 30, '已失败', '30', 'ai_image_status', 0, 'warning', '', '', '1', '2024-06-26 20:52:25', '1', '2024-06-26 20:52:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1546, 7, 'Midjourney', 'Midjourney', 'ai_platform', 0, '', '', '', '1', '2024-06-26 22:14:46', '1', '2024-06-26 22:14:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1547, 10, '进行中', '10', 'ai_music_status', 0, 'primary', '', '', '1', '2024-06-27 22:45:22', '1', '2024-06-28 00:56:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1548, 20, '已完成', '20', 'ai_music_status', 0, 'success', '', '', '1', '2024-06-27 22:45:33', '1', '2024-06-28 00:56:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1549, 30, '已失败', '30', 'ai_music_status', 0, 'danger', '', '', '1', '2024-06-27 22:45:44', '1', '2024-06-28 00:56:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1550, 1, '歌词模式', '1', 'ai_generate_mode', 0, '', '', '', '1', '2024-06-27 22:46:31', '1', '2024-06-28 01:22:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1551, 2, '描述模式', '2', 'ai_generate_mode', 0, '', '', '', '1', '2024-06-27 22:46:37', '1', '2024-06-28 01:22:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1552, 8, 'Suno', 'Suno', 'ai_platform', 0, '', '', '', '1', '2024-06-29 09:13:36', '1', '2024-06-29 09:13:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1553, 9, 'DeepSeek', 'DeepSeek', 'ai_platform', 0, '', '', '', '1', '2024-07-06 12:04:30', '1', '2024-07-06 12:05:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1554, 13, '智谱', 'ZhiPu', 'ai_platform', 0, '', '', '', '1', '2024-07-06 18:00:35', '1', '2025-02-24 20:18:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1555, 4, '长', '4', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:49:03', '1', '2024-07-07 15:49:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1556, 5, '段落', '5', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:49:54', '1', '2024-07-07 15:49:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1557, 6, '文章', '6', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:05', '1', '2024-07-07 15:50:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1558, 7, '博客文章', '7', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:23', '1', '2024-07-07 15:50:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1559, 8, '想法', '8', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:31', '1', '2024-07-07 15:50:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1560, 9, '大纲', '9', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:37', '1', '2024-07-07 15:50:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1561, 1, '自动', '1', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:06', '1', '2024-07-07 15:51:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1562, 2, '友善', '2', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:19', '1', '2024-07-07 15:51:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1563, 3, '随意', '3', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:27', '1', '2024-07-07 15:51:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1564, 4, '友好', '4', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:37', '1', '2024-07-07 15:51:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1565, 5, '专业', '5', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:49', '1', '2024-07-07 15:52:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1566, 6, '诙谐', '6', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:52:15', '1', '2024-07-07 15:52:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1567, 7, '有趣', '7', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:52:24', '1', '2024-07-07 15:52:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1568, 8, '正式', '8', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:54:33', '1', '2024-07-07 15:54:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1570, 1, '自动', '1', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:19:34', '1', '2024-07-07 15:19:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1571, 2, '电子邮件', '2', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:19:50', '1', '2024-07-07 15:49:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1572, 3, '消息', '3', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:20:01', '1', '2024-07-07 15:49:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1573, 4, '评论', '4', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:20:13', '1', '2024-07-07 15:49:45', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1574, 1, '自动', '1', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:44:18', '1', '2024-07-07 15:44:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1575, 2, '中文', '2', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:44:28', '1', '2024-07-07 15:44:28', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1576, 3, '英文', '3', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:44:37', '1', '2024-07-07 15:44:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1577, 4, '韩语', '4', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:46:28', '1', '2024-07-07 15:46:28', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1578, 5, '日语', '5', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:46:44', '1', '2024-07-07 15:46:44', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1579, 1, '自动', '1', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:48:34', '1', '2024-07-07 15:48:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1580, 2, '短', '2', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:48:44', '1', '2024-07-07 15:48:44', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1581, 3, '中等', '3', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:48:52', '1', '2024-07-07 15:48:52', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1584, 1, '撰写', '1', 'ai_write_type', 0, '', '', '', '1', '2024-07-10 21:26:00', '1', '2024-07-10 21:26:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1585, 2, '回复', '2', 'ai_write_type', 0, '', '', '', '1', '2024-07-10 21:26:06', '1', '2024-07-10 21:26:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1586, 2, '腾讯云', 'TENCENT', 'system_sms_channel_code', 0, '', '', '', '1', '2024-07-22 22:23:16', '1', '2024-07-22 22:23:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1587, 3, '华为云', 'HUAWEI', 'system_sms_channel_code', 0, '', '', '', '1', '2024-07-22 22:23:46', '1', '2024-07-22 22:23:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1588, 1, 'OpenAI 微软', 'AzureOpenAI', 'ai_platform', 0, '', '', '', '1', '2024-08-10 14:07:41', '1', '2024-08-10 14:07:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1589, 10, 'BPMN 设计器', '10', 'bpm_model_type', 0, 'primary', '', '', '1', '2024-08-26 15:22:17', '1', '2024-08-26 16:46:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1590, 20, 'SIMPLE 设计器', '20', 'bpm_model_type', 0, 'success', '', '', '1', '2024-08-26 15:22:27', '1', '2024-08-26 16:45:58', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1591, 4, '七牛云', 'QINIU', 'system_sms_channel_code', 0, '', '', '', '1', '2024-08-31 08:45:03', '1', '2024-08-31 08:45:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1592, 3, '新人券', '3', 'promotion_coupon_take_type', 0, 'info', '', '新人注册后,自动发放', '1', '2024-09-03 11:57:16', '1', '2024-09-03 11:57:28', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1593, 5, '微信零钱', '5', 'brokerage_withdraw_type', 0, '', '', 'API 打款', '1', '2024-10-13 11:06:48', '1', '2025-05-10 08:24:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1683, 10, '字节豆包', 'DouBao', 'ai_platform', 0, '', '', '', '1', '2025-02-23 19:51:40', '1', '2025-02-23 19:52:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1684, 11, '腾讯混元', 'HunYuan', 'ai_platform', 0, '', '', '', '1', '2025-02-23 20:58:04', '1', '2025-02-23 20:58:04', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1685, 12, '硅基流动', 'SiliconFlow', 'ai_platform', 0, '', '', '', '1', '2025-02-24 20:19:09', '1', '2025-02-24 20:19:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1686, 1, '聊天', '1', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:26:34', '1', '2025-03-03 12:26:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1687, 2, '图像', '2', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:27:23', '1', '2025-03-03 12:27:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1688, 3, '音频', '3', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:27:51', '1', '2025-03-03 12:27:51', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1689, 4, '视频', '4', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:28:03', '1', '2025-03-03 12:28:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1690, 5, '向量', '5', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:28:15', '1', '2025-03-03 12:28:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1691, 6, '重排', '6', 'ai_model_type', 0, '', '', '', '1', '2025-03-03 12:28:26', '1', '2025-03-03 12:28:26', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1692, 14, 'MiniMax', 'MiniMax', 'ai_platform', 0, '', '', '', '1', '2025-03-11 20:04:51', '1', '2025-03-11 20:04:51', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1693, 15, '月之暗面', 'Moonshot', 'ai_platform', 0, '', '', '', '1', '2025-03-11 20:05:08', '1', '2025-11-24 07:17:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2002, 0, '直连设备', '0', 'iot_product_device_type', 0, 'default', '', '', '1', '2024-08-10 11:54:58', '1', '2025-03-17 09:28:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2003, 2, '网关设备', '2', 'iot_product_device_type', 0, 'default', '', '', '1', '2024-08-10 11:55:08', '1', '2025-03-17 09:28:28', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2004, 1, '网关子设备', '1', 'iot_product_device_type', 0, 'default', '', '', '1', '2024-08-10 11:55:20', '1', '2025-03-17 09:28:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2005, 1, '已发布', '1', 'iot_product_status', 0, 'success', '', '', '1', '2024-08-10 12:10:33', '1', '2025-03-17 09:28:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2006, 0, '开发中', '0', 'iot_product_status', 0, 'default', '', '', '1', '2024-08-10 14:19:18', '1', '2025-03-17 09:28:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2009, 0, 'Wi-Fi', '0', 'iot_net_type', 0, '', '', '', '1', '2024-09-06 22:04:47', '1', '2025-03-17 09:28:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2010, 1, '移动网络', '1', 'iot_net_type', 0, '', '', '', '1', '2024-09-06 22:05:14', '1', '2025-06-12 23:27:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2011, 2, '以太网', '2', 'iot_net_type', 0, '', '', '', '1', '2024-09-06 22:05:35', '1', '2025-03-17 09:28:51', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2012, 3, '其他', '3', 'iot_net_type', 0, '', '', '', '1', '2024-09-06 22:05:52', '1', '2025-03-17 09:28:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2018, 0, '未激活', '0', 'iot_device_state', 0, '', '', '', '1', '2024-09-21 08:13:34', '1', '2025-03-17 09:29:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2019, 1, '在线', '1', 'iot_device_state', 0, '', '', '', '1', '2024-09-21 08:13:48', '1', '2025-03-17 09:29:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2020, 2, '离线', '2', 'iot_device_state', 0, '', '', '', '1', '2024-09-21 08:13:59', '1', '2025-03-17 09:29:14', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2021, 1, '属性', '1', 'iot_thing_model_type', 0, '', '', '', '1', '2024-09-29 20:03:01', '1', '2025-03-17 09:29:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2022, 2, '服务', '2', 'iot_thing_model_type', 0, '', '', '', '1', '2024-09-29 20:03:11', '1', '2025-03-17 09:29:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2023, 3, '事件', '3', 'iot_thing_model_type', 0, '', '', '', '1', '2024-09-29 20:03:20', '1', '2025-03-17 09:29:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2030, 1, '升每分钟', 'L/min', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:34:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2031, 2, '毫克每千克', 'mg/kg', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:34:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2032, 3, '浊度', 'NTU', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:34:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2033, 4, 'PH值', 'pH', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:34:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2034, 5, '土壤EC值', 'dS/m', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:34:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2035, 6, '太阳总辐射', 'W/㎡', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:36:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2036, 7, '降雨量', 'mm/hour', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:36:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2037, 8, '乏', 'var', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:36:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2038, 9, '厘泊', 'cP', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:36:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2039, 10, '饱和度', 'aw', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:11', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2040, 11, '个', 'pcs', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2041, 12, '厘斯', 'cst', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2042, 13, '巴', 'bar', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2043, 14, '纳克每升', 'ppt', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2044, 15, '十亿分之一', 'ppb', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2026-04-05 15:53:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2045, 16, '微西每厘米', 'uS/cm', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2046, 17, '牛顿每库仑', 'N/C', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2047, 18, '伏特每米', 'V/m', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2048, 19, '滴速', 'ml/min', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2049, 20, '毫米汞柱', 'mmHg', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2050, 21, '血糖', 'mmol/L', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:37:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2051, 22, '毫米每秒', 'mm/s', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:38:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2052, 23, '转每米', 'turn/m', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2026-04-05 15:53:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2053, 24, '次', 'count', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:38:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2054, 25, '档', 'gear', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:38:11', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2055, 26, '步', 'stepCount', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:38:13', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2056, 27, '标准立方米每小时', 'Nm3/h', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:38:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2057, 28, '千伏', 'kV', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:38:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2058, 29, '千伏安', 'kVA', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:38:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2060, 30, '千乏', 'kVar', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2061, 31, '微瓦每平方厘米', 'uw/cm2', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2062, 32, '只', '只', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2063, 33, '相对湿度', '%RH', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2064, 34, '立方米每秒', 'm³/s', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2065, 35, '公斤每秒', 'kg/s', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2066, 36, '转每分钟', 'r/min', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2067, 37, '吨每小时', 't/h', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2068, 38, '千卡每小时', 'KCL/h', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2069, 39, '升每秒', 'L/s', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2070, 40, '兆帕', 'MPa', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2026-04-05 15:53:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2071, 41, '立方米每小时', 'm³/h', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2072, 42, '千乏时', 'kvarh', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2073, 43, '微克每升', 'μg/L', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2074, 44, '千卡路里', 'kcal', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2075, 45, '吉字节', 'GB', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2076, 46, '兆字节', 'MB', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2077, 47, '千字节', 'KB', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2078, 48, '字节', 'B', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2079, 49, '微克每平方分米每天', 'μg/(d㎡·d)', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2080, 50, '无', '', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2081, 51, '百万分率', 'ppm', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2082, 52, '像素', 'pixel', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2083, 53, '照度', 'Lux', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2084, 54, '重力加速度', 'grav', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2085, 55, '分贝', 'dB', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2086, 56, '百分比', '%', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2087, 57, '流明', 'lm', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2088, 58, '比特', 'bit', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2089, 59, '克每毫升', 'g/mL', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2090, 60, '克每升', 'g/L', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2091, 61, '毫克每升', 'mg/L', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2092, 62, '微克每立方米', 'μg/m³', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2093, 63, '毫克每立方米', 'mg/m³', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2094, 64, '克每立方米', 'g/m³', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2095, 65, '千克每立方米', 'kg/m³', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2096, 66, '纳法', 'nF', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2097, 67, '皮法', 'pF', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2098, 68, '微法', 'μF', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2099, 69, '法拉', 'F', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2100, 70, '欧姆', 'Ω', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2101, 71, '微安', 'μA', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2102, 72, '毫安', 'mA', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2103, 73, '千安', 'kA', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2104, 74, '安培', 'A', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2105, 75, '毫伏', 'mV', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2106, 76, '伏特', 'V', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2107, 77, '毫秒', 'ms', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2108, 78, '秒', 's', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2109, 79, '分钟', 'min', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2110, 80, '小时', 'h', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2111, 81, '日', 'day', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2112, 82, '周', 'week', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2113, 83, '月', 'month', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2114, 84, '年', 'year', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2115, 85, '节', 'kn', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2116, 86, '千米每小时', 'km/h', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2117, 87, '米每秒', 'm/s', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2118, 88, '角秒', '″', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2026-04-05 15:53:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2119, 89, '分', '′', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2120, 90, '度', '°', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2121, 91, '弧度', 'rad', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2122, 92, '赫兹', 'Hz', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2123, 93, '微瓦', 'μW', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2124, 94, '毫瓦', 'mW', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2125, 95, '千瓦特', 'kW', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2126, 96, '瓦特', 'W', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2127, 97, '卡路里', 'cal', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2128, 98, '千瓦时', 'kW·h', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2129, 99, '瓦时', 'Wh', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2130, 100, '电子伏', 'eV', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2131, 101, '千焦', 'kJ', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2132, 102, '焦耳', 'J', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2133, 103, '华氏度', '℉', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2134, 104, '开尔文', 'K', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2135, 105, '吨', 't', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2136, 106, '摄氏度', '°C', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2137, 107, '毫帕', '1e-3Pa', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2026-04-05 15:53:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2138, 108, '百帕', 'hPa', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2139, 109, '千帕', 'kPa', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2140, 110, '帕斯卡', 'Pa', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2141, 111, '毫克', 'mg', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2142, 112, '克', 'g', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2143, 113, '千克', 'kg', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2144, 114, '牛', 'N', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2145, 115, '毫升', 'mL', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2146, 116, '升', 'L', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2147, 117, '立方毫米', 'mm³', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2148, 118, '立方厘米', 'cm³', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2149, 119, '立方千米', 'km³', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2150, 120, '立方米', 'm³', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2151, 121, '公顷', 'h㎡', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2152, 122, '平方厘米', 'c㎡', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2153, 123, '平方毫米', 'm㎡', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2154, 124, '平方千米', 'k㎡', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2155, 125, '平方米', '㎡', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2156, 126, '纳米', 'nm', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2157, 127, '微米', 'μm', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2158, 128, '毫米', 'mm', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2159, 129, '厘米', 'cm', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2160, 130, '分米', 'dm', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2161, 131, '千米', 'km', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2162, 132, '米', 'm', 'iot_thing_model_unit', 0, '', '', '', '1', '2024-12-13 11:08:41', '1', '2025-03-17 09:40:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2165, 1, 'HTTP', '1', 'iot_data_sink_type_enum', 0, 'default', '', '', '1', '2025-03-09 12:39:54', '1', '2025-06-24 12:44:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2166, 2, 'TCP', '2', 'iot_data_sink_type_enum', 0, 'default', '', '', '1', '2025-03-09 12:40:06', '1', '2025-06-24 12:44:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2167, 3, 'WebSocket', '3', 'iot_data_sink_type_enum', 0, 'default', '', '', '1', '2025-03-09 12:40:24', '1', '2025-06-24 12:44:45', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2168, 10, 'MQTT', '10', 'iot_data_sink_type_enum', 0, 'default', '', '', '1', '2025-03-09 12:40:37', '1', '2025-06-24 12:44:44', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2169, 20, 'Database', '20', 'iot_data_sink_type_enum', 0, 'default', '', '', '1', '2025-03-09 12:41:05', '1', '2025-06-24 12:44:44', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2170, 21, 'Redis Stream', '21', 'iot_data_sink_type_enum', 0, 'default', '', '', '1', '2025-03-09 12:41:18', '1', '2025-06-24 12:44:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2171, 30, 'RocketMQ', '30', 'iot_data_sink_type_enum', 0, 'default', '', '', '1', '2025-03-09 12:41:30', '1', '2025-06-24 12:44:42', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2172, 31, 'RabbitMQ', '31', 'iot_data_sink_type_enum', 0, 'default', '', '', '1', '2025-03-09 12:41:47', '1', '2025-06-24 12:44:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2173, 32, 'Kafka', '32', 'iot_data_sink_type_enum', 0, 'default', '', '', '1', '2025-03-09 12:41:59', '1', '2025-06-24 12:44:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2174, 1, '设备上下线变更', '1', 'iot_rule_scene_trigger_type_enum', 0, 'primary', '', '', '1', '2025-03-20 15:00:01', '"1"', '2025-07-06 10:28:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2175, 2, '物模型属性上报', '2', 'iot_rule_scene_trigger_type_enum', 0, 'primary', '', '', '1', '2025-03-20 15:00:09', '"1"', '2025-07-06 10:28:22', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2176, 1, '设备状态', 'state', 'iot_device_message_type_enum', 0, 'primary', '', '', '1', '2025-03-20 15:24:58', '1', '2025-03-20 15:24:58', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2177, 2, '设备属性', 'property', 'iot_device_message_type_enum', 0, 'primary', '', '', '1', '2025-03-20 15:25:09', '1', '2025-03-20 15:25:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2178, 3, '设备事件', 'event', 'iot_device_message_type_enum', 0, 'primary', '', '', '1', '2025-03-20 15:25:23', '1', '2025-03-20 15:25:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2179, 4, '设备服务', 'service', 'iot_device_message_type_enum', 0, 'primary', '', '', '1', '2025-03-20 15:25:39', '1', '2025-03-20 15:25:39', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2180, 5, '设备配置', 'config', 'iot_device_message_type_enum', 0, 'primary', '', '', '1', '2025-03-20 15:25:51', '1', '2025-03-20 15:25:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2181, 6, '设备 OTA', 'ota', 'iot_device_message_type_enum', 0, 'primary', '', '', '1', '2025-03-20 15:26:17', '1', '2025-03-20 15:26:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2182, 7, '设备注册', 'register', 'iot_device_message_type_enum', 0, 'primary', '', '', '1', '2025-03-20 15:26:35', '1', '2025-03-20 15:26:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2183, 8, '设备拓扑', 'topology', 'iot_device_message_type_enum', 0, 'primary', '', '', '1', '2025-03-20 15:26:46', '1', '2025-03-20 15:26:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2184, 1, '设备属性设置', '1', 'iot_rule_scene_action_type_enum', 0, 'primary', '', '', '1', '2025-03-28 15:27:12', '"1"', '2025-07-06 10:37:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2185, 2, '设备服务调用', '2', 'iot_rule_scene_action_type_enum', 0, 'primary', '', '', '1', '2025-03-28 15:27:25', '"1"', '2025-07-06 10:37:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (2186, 100, '告警触发', '100', 'iot_rule_scene_action_type_enum', 0, 'primary', '', '', '1', '2025-03-28 15:27:35', '"1"', '2025-07-06 10:37:50', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3000, 16, '百川智能', 'BaiChuan', 'ai_platform', 0, '', '', '', '1', '2025-03-23 12:15:46', '1', '2025-03-23 12:15:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3001, 40, 'Vben5.0 Ant Design Schema 模版', '40', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-04-23 21:47:47', '1', '2025-09-04 23:25:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3002, 6, '支付宝余额', '6', 'brokerage_withdraw_type', 0, '', '', 'API 打款', '1', '2025-05-10 08:24:49', '1', '2025-05-10 08:24:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3004, 3, 'WARN', '3', 'iot_alert_level', 0, 'warning', '', '', '1', '2025-06-27 20:32:22', '1', '2025-06-27 20:34:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3005, 1, 'INFO', '1', 'iot_alert_level', 0, 'primary', '', '', '1', '2025-06-27 20:33:28', '1', '2025-06-27 20:34:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3006, 5, 'ERROR', '5', 'iot_alert_level', 0, 'danger', '', '', '1', '2025-06-27 20:33:50', '1', '2025-06-27 20:33:50', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3007, 1, '短信', '1', 'iot_alert_receive_type', 0, '', '', '', '1', '2025-06-27 22:49:30', '1', '2025-06-27 22:49:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3008, 2, '邮箱', '2', 'iot_alert_receive_type', 0, '', '', '', '1', '2025-06-27 22:49:39', '1', '2025-06-27 22:50:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3009, 3, '站内信', '3', 'iot_alert_receive_type', 0, '', '', '', '1', '2025-06-27 22:50:20', '1', '2025-06-27 22:50:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3010, 1, '全部设备', '1', 'iot_ota_task_device_scope', 0, '', '', '', '1', '2025-07-02 09:43:09', '1', '2025-07-02 09:43:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3011, 2, '指定设备', '2', 'iot_ota_task_device_scope', 0, '', '', '', '1', '2025-07-02 09:43:15', '1', '2025-07-02 09:43:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3012, 10, '进行中', '10', 'iot_ota_task_status', 0, 'primary', '', '', '1', '2025-07-02 09:44:01', '"1"', '2025-07-02 09:44:21', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3013, 20, '已结束', '20', 'iot_ota_task_status', 0, 'success', '', '', '1', '2025-07-02 09:44:14', '"1"', '2025-07-02 23:56:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3014, 30, '已取消', '30', 'iot_ota_task_status', 0, 'danger', '', '', '1', '2025-07-02 09:44:36', '1', '2025-07-02 09:44:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3015, 0, '待推送', '0', 'iot_ota_task_record_status', 0, '', '', '', '1', '2025-07-02 09:45:16', '1', '2025-07-02 09:45:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3016, 10, '已推送', '10', 'iot_ota_task_record_status', 0, '', '', '', '1', '2025-07-02 09:45:25', '1', '2025-07-02 09:45:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3017, 20, '升级中', '20', 'iot_ota_task_record_status', 0, 'primary', '', '', '1', '2025-07-02 09:45:37', '1', '2025-07-02 09:45:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3018, 30, '升级成功', '30', 'iot_ota_task_record_status', 0, 'success', '', '', '1', '2025-07-02 09:45:47', '1', '2025-07-02 09:45:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3019, 40, '升级失败', '40', 'iot_ota_task_record_status', 0, 'danger', '', '', '1', '2025-07-02 09:46:02', '1', '2025-07-02 09:46:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3020, 50, '升级取消', '50', 'iot_ota_task_record_status', 0, 'warning', '', '', '1', '2025-07-02 09:46:09', '"1"', '2025-07-02 09:46:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3024, 3, '设备事件上报', '3', 'iot_rule_scene_trigger_type_enum', 0, '', '', '', '1', '2025-07-06 10:28:29', '1', '2025-07-06 10:28:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3025, 4, '设备服务调用', '4', 'iot_rule_scene_trigger_type_enum', 0, '', '', '', '1', '2025-07-06 10:28:35', '1', '2025-07-06 10:28:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3026, 100, '定时触发', '100', 'iot_rule_scene_trigger_type_enum', 0, '', '', '', '1', '2025-07-06 10:28:48', '1', '2025-07-06 10:28:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3027, 101, '告警恢复', '101', 'iot_rule_scene_action_type_enum', 0, '', '', '', '1', '2025-07-06 10:37:57', '1', '2025-07-06 10:37:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3028, 2, 'Anthropic', 'Anthropic', 'ai_platform', 0, '', '', '', '1', '2025-08-21 22:54:24', '1', '2025-08-21 22:57:58', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3029, 2, '谷歌 Gemini', 'Gemini', 'ai_platform', 0, '', '', '', '1', '2025-08-22 22:39:35', '1', '2025-08-22 22:44:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3030, 1, '文件系统', 'filesystem', 'ai_mcp_client_name', 0, '', '', '', '1', '2025-08-28 13:58:43', '1', '2025-08-28 21:19:42', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3031, 41, 'Vben5.0 Ant Design 标准模版', '41', 'infra_codegen_front_type', 0, '', '', '', '1', '2025-09-04 23:26:07', '1', '2025-09-04 23:26:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3032, 50, 'Vben5.0 Element Plus Schema 模版', '50', 'infra_codegen_front_type', 0, '', '', '', '1', '2025-09-04 23:26:38', '1', '2025-09-04 23:26:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3033, 51, 'Vben5.0 Element Plus 标准模版', '51', 'infra_codegen_front_type', 0, '', '', '', '1', '2025-09-04 23:26:49', '1', '2025-09-04 23:26:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', '2026-05-23 13:52:25', '1', '2026-05-23 13:52:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', '2026-05-23 13:52:25', '1', '2026-05-23 13:52:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3043, 4, 'MQTT', 'mqtt', 'iot_protocol_type', 0, 'success', '', 'MQTT 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3044, 5, 'EMQX', 'emqx', 'iot_protocol_type', 0, 'success', '', 'EMQX 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3045, 6, 'CoAP', 'coap', 'iot_protocol_type', 0, '', '', 'CoAP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3046, 7, 'Modbus TCP Server', 'modbus_tcp_server', 'iot_protocol_type', 0, '', '', 'Modbus TCP Server 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-12 15:16:45', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3047, 0, 'JSON', 'json', 'iot_serialize_type', 0, 'success', '', 'JSON 格式', '1', '2026-02-04 00:33:19', '1', '2026-02-04 00:33:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3048, 1, '二进制', 'binary', 'iot_serialize_type', 0, 'warning', '', '二进制格式', '1', '2026-02-04 00:33:19', '1', '2026-02-04 00:33:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3049, 8, 'Modbus TCP Client', 'modbus_tcp_client', 'iot_protocol_type', 0, '', '', 'Modbus TCP Client 协议', '1', '2026-02-08 18:29:46', '1', '2026-02-12 15:16:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3050, 2, '边缘采集', '2', 'iot_modbus_mode', 0, 'success', '', '设备主动上报数据,无需轮询', '1', '2025-06-12 22:56:06', '1', '2026-02-09 13:03:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3051, 1, 'Modbus TCP', '1', 'iot_modbus_frame_format', 0, 'default', '', 'MBAP 头部格式', '1', '2025-06-12 22:56:06', '1', '2025-06-12 22:56:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3052, 2, 'Modbus RTU', '2', 'iot_modbus_frame_format', 0, 'warning', '', 'CRC16 校验格式', '1', '2025-06-12 22:56:06', '1', '2025-06-12 22:56:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3053, 1, '云端轮询', '1', 'iot_modbus_mode', 0, 'primary', '', '网关主动轮询读取设备寄存器', '1', '2025-06-12 22:56:06', '1', '2025-06-12 22:56:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3054, 1, '企业客户', '1', 'mes_client_type', 0, 'primary', '', '', '1', '2026-02-15 14:38:25', '1', '2026-02-15 14:38:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3055, 2, '个人', '2', 'mes_client_type', 0, 'success', '', '', '1', '2026-02-15 14:38:25', '1', '2026-02-15 14:38:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3056, 1, '优质供应商', 'A', 'mes_vendor_level', 0, 'success', '', '', '1', '2026-02-15 15:59:15', '1', '2026-02-15 15:59:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3057, 2, '正常', 'B', 'mes_vendor_level', 0, 'primary', '', '', '1', '2026-02-15 15:59:15', '1', '2026-02-15 15:59:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3058, 3, '重点关注', 'C', 'mes_vendor_level', 0, 'warning', '', '', '1', '2026-02-15 15:59:15', '1', '2026-02-15 15:59:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3059, 4, '劣质供应商', 'D', 'mes_vendor_level', 0, 'danger', '', '', '1', '2026-02-15 15:59:15', '1', '2026-02-15 15:59:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3060, 5, '黑名单', 'E', 'mes_vendor_level', 0, 'info', '', '', '1', '2026-02-15 15:59:15', '1', '2026-02-15 15:59:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3061, 1, '假期', '2', 'mes_cal_holiday_type', 0, 'success', '', '', '1', '2026-02-16 07:35:58', '1', '2026-02-16 11:20:42', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3062, 2, '工作日', '1', 'mes_cal_holiday_type', 0, 'primary', '', '', '1', '2026-02-16 07:35:58', '1', '2026-02-16 11:20:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3063, 1, '在库', '1', 'mes_tm_tool_status', 0, 'success', '', '', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3064, 2, '领用中', '2', 'mes_tm_tool_status', 0, 'primary', '', '', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3065, 3, '维修中', '3', 'mes_tm_tool_status', 0, 'warning', '', '', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3066, 4, '报废', '4', 'mes_tm_tool_status', 0, 'danger', '', '', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3067, 1, '定期维护', '1', 'mes_tm_mainten_type', 0, 'primary', '', '', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3068, 2, '按使用次数维护', '2', 'mes_tm_mainten_type', 0, 'success', '', '', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3069, 1, '停机', '1', 'mes_dv_machinery_status', 0, 'success', '', '', '1', '2026-02-17 01:00:06', '1', '2026-02-17 03:28:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3070, 2, '生产中', '2', 'mes_dv_machinery_status', 0, 'info', '', '', '1', '2026-02-17 01:00:06', '1', '2026-02-17 03:28:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3071, 3, '维护中', '3', 'mes_dv_machinery_status', 0, 'danger', '', '', '1', '2026-02-17 01:00:06', '1', '2026-02-17 03:28:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3072, 1, '尺寸', '1', 'mes_indicator_type', 0, '', '', '', '1', '2026-02-17 02:18:18', '1', '2026-04-09 14:38:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3073, 2, '外观', '2', 'mes_indicator_type', 0, '', '', '', '1', '2026-02-17 02:18:18', '1', '2026-04-09 14:38:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3074, 3, '重量', '3', 'mes_indicator_type', 0, '', '', '', '1', '2026-02-17 02:18:18', '1', '2026-04-09 14:38:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3075, 4, '性能', '4', 'mes_indicator_type', 0, '', '', '', '1', '2026-02-17 02:18:18', '1', '2026-04-09 14:38:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3076, 5, '成分', '5', 'mes_indicator_type', 0, '', '', '', '1', '2026-02-17 02:18:18', '1', '2026-04-09 14:38:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3077, 1, '致命缺陷', '1', 'mes_defect_level', 0, 'danger', '', '', '1', '2026-02-17 02:18:18', '1', '2026-02-21 12:21:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3078, 2, '严重缺陷', '2', 'mes_defect_level', 0, 'warning', '', '', '1', '2026-02-17 02:18:18', '1', '2026-02-21 12:21:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3079, 3, '轻微缺陷', '3', 'mes_defect_level', 0, 'info', '', '', '1', '2026-02-17 02:18:18', '1', '2026-02-21 12:21:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3080, 1, '单白班', '1', 'mes_cal_shift_type', 0, 'primary', '', '', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3081, 2, '两班倒', '2', 'mes_cal_shift_type', 0, 'success', '', '', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3082, 3, '三班倒', '3', 'mes_cal_shift_type', 0, 'warning', '', '', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3083, 1, '按季度', '1', 'mes_cal_shift_method', 0, '', '', '', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3084, 2, '按月', '2', 'mes_cal_shift_method', 0, '', '', '', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3085, 3, '按周', '3', 'mes_cal_shift_method', 0, '', '', '', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3086, 4, '按天', '4', 'mes_cal_shift_method', 0, '', '', '', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3089, 0, '草稿', '0', 'mes_cal_plan_status', 0, 'info', '', '', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3090, 1, '已确认', '1', 'mes_cal_plan_status', 0, 'success', '', '', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3100, 0, '草稿', '0', 'mes_pro_work_order_status', 0, 'info', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3101, 1, '已确认', '1', 'mes_pro_work_order_status', 0, 'primary', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3102, 2, '已完成', '2', 'mes_pro_work_order_status', 0, 'success', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3103, 3, '已取消', '3', 'mes_pro_work_order_status', 0, 'warning', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3104, 1, '客户订单', '1', 'mes_pro_work_order_source_type', 0, 'primary', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3105, 2, '库存备货', '2', 'mes_pro_work_order_source_type', 0, 'success', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3106, 1, '自行生产', '1', 'mes_pro_work_order_type', 0, 'primary', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3107, 2, '代工', '2', 'mes_pro_work_order_type', 0, 'warning', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3108, 3, '采购', '3', 'mes_pro_work_order_type', 0, 'info', '', '', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3121, 1, 'IQC(来料检验)', '1', 'mes_qc_type', 0, 'primary', '', '来料质量检验', '1', '2026-02-18 14:12:05', '1', '2026-02-18 14:12:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3122, 2, 'IPQC(过程检验)', '2', 'mes_qc_type', 0, 'warning', '', '生产制程质量检验', '1', '2026-02-18 14:12:05', '1', '2026-03-24 15:21:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3123, 3, 'OQC(出货检验)', '3', 'mes_qc_type', 0, 'success', '', '出货质量检验', '1', '2026-02-18 14:12:05', '1', '2026-02-18 14:12:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3124, 4, 'RQC(退料检验)', '4', 'mes_qc_type', 0, 'danger', '', '退货质量检验', '1', '2026-02-18 14:12:05', '1', '2026-03-24 15:22:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3125, 0, '开始-开始(SS)', '0', 'mes_pro_link_type', 0, 'default', '', '前序开始后,后序可以开始', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3126, 1, '结束-结束(FF)', '1', 'mes_pro_link_type', 0, 'default', '', '前序结束后,后序才能结束', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3127, 2, '开始-结束(SF)', '2', 'mes_pro_link_type', 0, 'default', '', '前序开始后,后序才能结束', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3128, 3, '结束-开始(FS)', '3', 'mes_pro_link_type', 0, 'default', '', '前序结束后,后序才能开始', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3129, 1, '分钟', 'MINUTE', 'mes_time_unit_type', 0, 'default', '', '', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3130, 2, '小时', 'HOUR', 'mes_time_unit_type', 0, 'default', '', '', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3131, 3, '天', 'DAY', 'mes_time_unit_type', 0, 'default', '', '', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3137, 1, '设备点检', '1', 'mes_dv_subject_type', 0, 'info', '', '', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3138, 2, '设备保养', '2', 'mes_dv_subject_type', 0, 'success', '', '', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3139, 1, '待保养', '0', 'mes_mainten_record_status', 0, 'info', '', NULL, 'admin', '2026-02-20 02:59:55', '1', '2026-04-16 05:32:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3140, 2, '已完成', '4', 'mes_mainten_record_status', 0, 'success', '', NULL, 'admin', '2026-02-20 02:59:55', '1', '2026-04-16 05:32:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3141, 1, '正常', '1', 'mes_mainten_status', 0, 'success', '', NULL, 'admin', '2026-02-20 02:59:55', 'admin', '2026-02-20 02:59:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3142, 2, '异常', '0', 'mes_mainten_status', 0, 'danger', '', NULL, 'admin', '2026-02-20 02:59:55', 'admin', '2026-02-20 02:59:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3143, 1, '天', '1', 'mes_dv_cycle_type', 0, 'default', '', '', '1', '2026-02-20 07:11:43', '1', '2026-02-20 07:11:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3144, 2, '周', '2', 'mes_dv_cycle_type', 0, 'default', '', '', '1', '2026-02-20 07:11:43', '1', '2026-02-20 07:11:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3145, 3, '月', '3', 'mes_dv_cycle_type', 0, 'default', '', '', '1', '2026-02-20 07:11:43', '1', '2026-02-20 07:11:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3146, 4, '年', '4', 'mes_dv_cycle_type', 0, 'default', '', '', '1', '2026-02-20 07:11:43', '1', '2026-02-20 07:11:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3147, 0, '草稿', '0', 'mes_dv_check_plan_status', 0, 'info', '', '', '1', '2026-02-20 07:11:43', '1', '2026-02-20 07:11:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3148, 1, '已启用', '1', 'mes_dv_check_plan_status', 0, 'success', '', '', '1', '2026-02-20 07:11:43', '1', '2026-02-20 07:11:43', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3149, 1, '待点检', '10', 'mes_dv_check_record_status', 0, 'info', '', NULL, 'admin', '2026-02-20 09:46:19', 'admin', '2026-02-20 09:46:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3150, 2, '已完成', '20', 'mes_dv_check_record_status', 0, 'success', '', NULL, 'admin', '2026-02-20 09:46:19', 'admin', '2026-02-20 09:46:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3151, 1, '正常', '1', 'mes_dv_check_result', 0, 'success', '', NULL, 'admin', '2026-02-20 09:46:19', 'admin', '2026-02-20 09:46:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3152, 2, '异常', '2', 'mes_dv_check_result', 0, 'danger', '', NULL, 'admin', '2026-02-20 09:46:19', 'admin', '2026-02-20 09:46:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3157, 1, '修复成功', '1', 'mes_dv_repair_result', 0, 'success', '', '', '1', '2026-02-20 10:56:24', '1', '2026-02-20 10:56:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3158, 2, '报废', '2', 'mes_dv_repair_result', 0, 'danger', '', '', '1', '2026-02-20 10:56:24', '1', '2026-02-20 10:56:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3161, 1, '校验通过', '1', 'mes_qc_check_result', 0, 'success', '', '', '1', '2026-02-20 11:23:35', '1', '2026-02-20 16:15:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3162, 2, '校验不通过', '2', 'mes_qc_check_result', 0, 'danger', '', '', '1', '2026-02-20 11:23:35', '1', '2026-02-20 16:15:52', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3166, 0, '未处置', '0', 'mes_pro_andon_status', 0, 'danger', '', '', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3167, 1, '已处置', '1', 'mes_pro_andon_status', 0, 'success', '', '', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3168, 1, '一级', '1', 'mes_pro_andon_level', 0, 'danger', '', '', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3169, 2, '二级', '2', 'mes_pro_andon_level', 0, 'warning', '', '', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3170, 3, '三级', '3', 'mes_pro_andon_level', 0, 'info', '', '', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3171, 0, '草稿', '0', 'mes_pro_feedback_status', 0, 'info', '', '', '1', '2026-02-21 00:50:32', '1', '2026-02-21 00:50:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3172, 2, '审批中', '2', 'mes_pro_feedback_status', 0, 'primary', '', '', '1', '2026-02-21 00:50:32', '1', '2026-03-19 00:51:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3173, 3, '待检验', '3', 'mes_pro_feedback_status', 0, 'warning', '', '', '1', '2026-02-21 00:50:32', '1', '2026-03-19 00:51:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3174, 4, '已完成', '4', 'mes_pro_feedback_status', 0, 'success', '', '', '1', '2026-02-21 00:50:32', '1', '2026-03-19 00:51:54', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3176, 1, '自行报工', '1', 'mes_pro_feedback_type', 0, 'primary', '', '', '1', '2026-02-21 00:50:32', '1', '2026-02-21 00:50:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3177, 2, '统一报工', '2', 'mes_pro_feedback_type', 0, 'success', '', '', '1', '2026-02-21 00:50:32', '1', '2026-02-21 00:50:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3178, 1, 'PC', 'PC', 'mes_pro_feedback_channel', 0, 'primary', '', '', '1', '2026-02-21 00:50:32', '1', '2026-02-21 00:50:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3179, 2, 'APP', 'APP', 'mes_pro_feedback_channel', 0, 'success', '', '', '1', '2026-02-21 00:50:32', '1', '2026-02-21 00:50:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3180, 3, 'PDA', 'PDA', 'mes_pro_feedback_channel', 0, 'info', '', '', '1', '2026-02-21 00:50:32', '1', '2026-02-21 00:50:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3181, 1, '浮点', '1', 'mes_qc_result_type', 0, 'primary', '', '', '1', '2026-02-21 13:37:17', '1', '2026-02-21 13:37:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3182, 2, '整数', '2', 'mes_qc_result_type', 0, 'success', '', '', '1', '2026-02-21 13:37:17', '1', '2026-02-21 13:37:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3183, 3, '文本', '3', 'mes_qc_result_type', 0, 'info', '', '', '1', '2026-02-21 13:37:17', '1', '2026-02-21 13:37:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3184, 4, '字典', '4', 'mes_qc_result_type', 0, 'warning', '', '', '1', '2026-02-21 13:37:17', '1', '2026-02-21 13:37:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3185, 5, '文件', '5', 'mes_qc_result_type', 0, 'danger', '', '', '1', '2026-02-21 13:37:17', '1', '2026-02-21 13:37:17', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3186, 1, '生产退料', '1', 'mes_rqc_type', 0, 'default', '', '生产退料检验', '1', '2026-02-22 06:44:09', '1', '2026-02-22 06:44:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3187, 2, '销售退货', '2', 'mes_rqc_type', 0, 'default', '', '销售退货检验', '1', '2026-02-22 06:44:09', '1', '2026-02-22 06:44:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3188, 1, '自制工序检验', '1', 'mes_ipqc_type', 0, 'primary', '', '', '1', '2026-02-22 07:01:04', '1', '2026-02-22 07:01:04', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3189, 2, '首检', '2', 'mes_ipqc_type', 0, 'success', '', '', '1', '2026-02-22 07:01:04', '1', '2026-02-22 07:01:04', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3190, 3, '巡检', '3', 'mes_ipqc_type', 0, 'warning', '', '', '1', '2026-02-22 07:01:04', '1', '2026-02-22 07:01:04', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3191, 4, '自检', '4', 'mes_ipqc_type', 0, 'info', '', '', '1', '2026-02-22 07:01:04', '1', '2026-02-22 07:01:04', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3192, 5, '成品检验', '5', 'mes_ipqc_type', 0, 'danger', '', '', '1', '2026-02-22 07:01:04', '1', '2026-02-22 07:01:04', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3205, 0, '草稿', '0', 'mes_wm_arrival_notice_status', 0, 'info', '', '', '1', '2026-02-22 14:53:18', '1', '2026-02-22 14:53:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3206, 2, '待质检', '2', 'mes_wm_arrival_notice_status', 0, 'warning', '', '', '1', '2026-02-22 14:53:18', '1', '2026-02-26 05:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3207, 3, '待入库', '3', 'mes_wm_arrival_notice_status', 0, 'success', '', '', '1', '2026-02-22 14:53:18', '1', '2026-02-26 05:24:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3208, 4, '已完成', '4', 'mes_wm_arrival_notice_status', 0, 'primary', '', '', '1', '2026-02-22 14:53:18', '1', '2026-02-26 05:24:52', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3209, 0, '草稿', '0', 'mes_wm_item_receipt_status', 0, 'info', '', '', '1', '2026-02-22 14:54:05', '1', '2026-02-22 14:54:05', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3210, 1, '待上架', '2', 'mes_wm_item_receipt_status', 0, 'warning', '', '', '1', '2026-02-22 14:54:05', '1', '2026-02-26 08:03:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3211, 2, '待执行入库', '3', 'mes_wm_item_receipt_status', 0, 'success', '', '', '1', '2026-02-22 14:54:05', '1', '2026-02-26 08:03:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3212, 3, '已完成', '4', 'mes_wm_item_receipt_status', 0, 'primary', '', '', '1', '2026-02-22 14:54:05', '1', '2026-02-26 08:03:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3213, 4, '已取消', '5', 'mes_wm_item_receipt_status', 0, 'danger', '', '', '1', '2026-02-22 14:54:05', '1', '2026-02-26 08:03:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3214, 1, '草稿', '0', 'mes_order_status', 0, 'info', '', '', '1', '2026-02-23 21:16:03', '1', '2026-02-23 21:16:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3215, 2, '已确认', '1', 'mes_order_status', 0, 'primary', '', '', '1', '2026-02-23 21:16:03', '1', '2026-02-23 21:16:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3216, 3, '审批中', '2', 'mes_order_status', 0, 'warning', '', '', '1', '2026-02-23 21:16:03', '1', '2026-02-23 21:16:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3217, 4, '已审批', '3', 'mes_order_status', 0, 'success', '', '', '1', '2026-02-23 21:16:03', '1', '2026-02-23 21:16:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3218, 5, '已完成', '4', 'mes_order_status', 0, 'success', '', '', '1', '2026-02-23 21:16:03', '1', '2026-02-23 21:16:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3219, 6, '已取消', '5', 'mes_order_status', 0, 'danger', '', '', '1', '2026-02-23 21:16:03', '1', '2026-02-23 21:16:03', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3220, 1, '草稿', '0', 'mes_wm_issue_status', 0, 'info', '', '草稿状态,未完成', '1', '2026-02-26 15:54:25', '1', '2026-02-26 15:54:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3221, 2, '已完成', '4', 'mes_wm_issue_status', 0, 'success', '', '已完成出库', '1', '2026-02-26 15:54:25', '1', '2026-02-26 15:54:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3222, 1, '草稿', '0', 'mes_wm_product_issue_status', 0, 'info', '', '草稿状态,可编辑', '1', '2026-02-26 16:39:12', '1', '2026-03-23 13:18:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3223, 2, '待拣货', '2', 'mes_wm_product_issue_status', 0, 'warning', '', '审批中,可执行拣货', '1', '2026-02-26 16:39:12', '1', '2026-03-23 13:18:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3224, 3, '待执行领出', '3', 'mes_wm_product_issue_status', 0, 'primary', '', '已审批,拣货完成', '1', '2026-02-26 16:39:12', '1', '2026-03-23 13:18:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3225, 4, '已完成', '4', 'mes_wm_product_issue_status', 0, 'success', '', '已完成出库', '1', '2026-02-26 16:39:12', '1', '2026-03-23 13:18:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3226, 5, '已取消', '5', 'mes_wm_product_issue_status', 0, 'success', '', '已完成出库', '1', '2026-02-26 16:39:12', '1', '2026-03-23 13:18:02', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3232, 1, '草稿', '0', 'mes_wm_return_issue_status', 0, 'info', '', '草稿状态,可编辑', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:28:24', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3233, 2, '待检验', '1', 'mes_wm_return_issue_status', 0, 'default', '', '已确认,等待质检', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:28:28', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3234, 3, '待上架', '2', 'mes_wm_return_issue_status', 0, 'warning', '', '检验完成,等待仓库上架', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:28:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3235, 4, '待执行退料', '3', 'mes_wm_return_issue_status', 0, 'primary', '', '上架完成,等待执行退料操作', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:28:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3236, 5, '已完成', '4', 'mes_wm_return_issue_status', 0, 'success', '', '退料执行完成,库存已更新', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:28:37', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3237, 6, '已取消', '5', 'mes_wm_return_issue_status', 0, 'danger', '', '已取消', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:28:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3238, 1, '余料退料', '1', 'mes_wm_return_issue_type', 0, 'success', '', '余料退回,直接合格', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:27:47', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3239, 2, '不良退料', '2', 'mes_wm_return_issue_type', 0, 'danger', '', '不良品退回', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:27:49', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3240, 3, '其他退料', '3', 'mes_wm_return_issue_type', 0, 'info', '', '其他原因退料', '1', '2026-02-28 14:11:12', '1', '2026-02-28 14:27:55', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3241, 1, '待检', '0', 'mes_wm_quality_status', 0, 'warning', '', '待检状态', '1', '2026-02-28 15:00:53', '1', '2026-02-28 15:00:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3242, 2, '合格', '1', 'mes_wm_quality_status', 0, 'success', '', '合格状态', '1', '2026-02-28 15:00:53', '1', '2026-02-28 15:00:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3243, 3, '不合格', '2', 'mes_wm_quality_status', 0, 'danger', '', '不合格状态', '1', '2026-02-28 15:00:53', '1', '2026-02-28 15:00:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3244, 1, '草稿', '0', 'mes_wm_product_receipt_status', 0, 'info', '', '草稿状态', '1', '2026-03-01 06:03:07', '1', '2026-03-01 06:03:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3245, 2, '待上架', '2', 'mes_wm_product_receipt_status', 0, 'primary', '', '待上架', '1', '2026-03-01 06:03:07', '1', '2026-03-01 06:03:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3246, 3, '待执行入库', '3', 'mes_wm_product_receipt_status', 0, 'warning', '', '待执行入库', '1', '2026-03-01 06:03:07', '1', '2026-03-01 06:03:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3247, 4, '已完成', '4', 'mes_wm_product_receipt_status', 0, 'success', '', '已完成', '1', '2026-03-01 06:03:07', '1', '2026-03-01 06:03:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3248, 5, '已取消', '5', 'mes_wm_product_receipt_status', 0, 'danger', '', '已取消', '1', '2026-03-01 06:03:07', '1', '2026-03-01 06:03:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3252, 1, '草稿', '0', 'mes_wm_product_sales_status', 0, 'info', '', '草稿状态', '1', '2026-03-02 08:55:11', '1', '2026-03-02 08:55:11', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3253, 3, '待拣货', '2', 'mes_wm_product_sales_status', 0, 'warning', '', '待拣货状态', '1', '2026-03-02 08:55:11', '1', '2026-03-27 11:44:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3254, 4, '待出库', '3', 'mes_wm_product_sales_status', 0, 'primary', '', '待出库状态', '1', '2026-03-02 08:55:11', '1', '2026-03-27 11:44:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3255, 5, '已完成', '4', 'mes_wm_product_sales_status', 0, 'success', '', '已完成状态', '1', '2026-03-02 08:55:11', '1', '2026-03-27 11:44:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3256, 6, '已取消', '5', 'mes_wm_product_sales_status', 0, 'danger', '', '已取消状态', '1', '2026-03-02 08:55:11', '1', '2026-03-27 11:44:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3272, 1, '草稿', '0', 'mes_wm_misc_receipt_status', 0, 'info', '', '草稿状态', '1', '2026-03-03 07:33:41', '1', '2026-03-03 07:33:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3273, 2, '待执行入库', '3', 'mes_wm_misc_receipt_status', 0, 'primary', '', '待执行入库状态', '1', '2026-03-03 07:33:41', '1', '2026-03-03 07:37:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3274, 3, '已完成', '4', 'mes_wm_misc_receipt_status', 0, 'success', '', '已完成状态', '1', '2026-03-03 07:33:41', '1', '2026-03-03 07:33:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3275, 4, '已取消', '5', 'mes_wm_misc_receipt_status', 0, 'danger', '', '已取消状态', '1', '2026-03-03 07:33:41', '1', '2026-03-03 07:33:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3277, 1, '库存调整', '1', 'mes_wm_misc_receipt_type', 0, 'primary', '', '库存调整入库', '1', '2026-03-03 07:34:33', '1', '2026-03-03 07:34:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3278, 1, '库存调整', '1', 'mes_wm_misc_issue_type', 0, 'primary', '', '库存调整出库', '1', '2026-03-03 07:34:33', '1', '2026-03-03 07:34:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3279, 2, '报废出库', '2', 'mes_wm_misc_issue_type', 0, 'danger', '', '报废出库', '1', '2026-03-03 07:36:13', '1', '2026-03-03 07:36:13', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3280, 1, '草稿', '0', 'mes_wm_outsource_receipt_status', 0, 'info', '', '草稿状态', '1', '2026-03-03 14:03:57', '1', '2026-03-03 14:03:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3281, 2, '待检验', '1', 'mes_wm_outsource_receipt_status', 0, 'warning', '', '已确认,等待质检', '1', '2026-03-03 14:03:57', '1', '2026-03-03 14:03:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3282, 3, '待上架', '2', 'mes_wm_outsource_receipt_status', 0, 'primary', '', '检验完成,等待仓库上架', '1', '2026-03-03 14:03:57', '1', '2026-03-03 14:03:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3283, 4, '待执行入库', '3', 'mes_wm_outsource_receipt_status', 0, 'warning', '', '上架完成,等待执行入库操作', '1', '2026-03-03 14:03:57', '1', '2026-03-03 14:03:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3284, 5, '已完成', '4', 'mes_wm_outsource_receipt_status', 0, 'success', '', '入库执行完成,库存已更新', '1', '2026-03-03 14:03:57', '1', '2026-03-03 14:03:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3285, 6, '已取消', '5', 'mes_wm_outsource_receipt_status', 0, 'danger', '', '已取消', '1', '2026-03-03 14:03:57', '1', '2026-03-03 14:03:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3286, 1, '草稿', '0', 'mes_wm_outsource_issue_status', 0, 'info', '', '草稿状态,可编辑、删除、执行出库', '1', '2026-03-03 16:31:00', '1', '2026-03-03 16:31:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3287, 2, '待拣货', '2', 'mes_wm_outsource_issue_status', 0, 'warning', '', '待拣货状态', '1', '2026-03-03 16:31:00', '1', '2026-03-03 16:31:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3288, 3, '待执行出库', '3', 'mes_wm_outsource_issue_status', 0, 'primary', '', '待执行出库状态', '1', '2026-03-03 16:31:00', '1', '2026-03-03 16:31:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3289, 4, '已完成', '4', 'mes_wm_outsource_issue_status', 0, 'success', '', '已完成,库存已扣减', '1', '2026-03-03 16:31:00', '1', '2026-03-03 16:31:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3290, 5, '已取消', '5', 'mes_wm_outsource_issue_status', 0, 'danger', '', '已取消状态', '1', '2026-03-03 16:31:00', '1', '2026-03-03 16:31:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3301, 1, '输入字符', '1', 'mes_md_auto_code_part_type', 0, 'default', '', '输入字符', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3302, 2, '当前日期', '2', 'mes_md_auto_code_part_type', 0, 'primary', '', '当前日期时间', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3303, 3, '固定字符', '3', 'mes_md_auto_code_part_type', 0, 'success', '', '固定字符', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3304, 4, '流水号', '4', 'mes_md_auto_code_part_type', 0, 'warning', '', '流水号', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3305, 1, '左补齐', '1', 'mes_md_auto_code_padded_method', 0, 'primary', '', '左补齐', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3306, 2, '右补齐', '2', 'mes_md_auto_code_padded_method', 0, 'success', '', '右补齐', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3307, 1, '按年', '1', 'mes_md_auto_code_cycle_method', 0, 'default', '', '按年循环', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3308, 2, '按月', '2', 'mes_md_auto_code_cycle_method', 0, 'primary', '', '按月循环', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3309, 3, '按天', '3', 'mes_md_auto_code_cycle_method', 0, 'success', '', '按天循环', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3310, 4, '按小时', '4', 'mes_md_auto_code_cycle_method', 0, 'warning', '', '按小时循环', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3311, 5, '按分钟', '5', 'mes_md_auto_code_cycle_method', 0, 'danger', '', '按分钟循环', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3312, 10, '按传入字符', '10', 'mes_md_auto_code_cycle_method', 0, 'info', '', '按传入字符循环', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3313, 1, '二维码', '1', 'mes_wm_barcode_format', 0, 'primary', '', 'QR_CODE', '1', '2026-03-05 14:37:20', '1', '2026-03-06 13:18:21', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3314, 2, 'EAN13 商品条码', '2', 'mes_wm_barcode_format', 0, 'success', '', 'EAN13', '1', '2026-03-05 14:37:20', '1', '2026-03-06 13:18:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3315, 3, 'CODE39 工业条码', '3', 'mes_wm_barcode_format', 0, 'info', '', 'CODE39', '1', '2026-03-05 14:37:20', '1', '2026-03-06 13:18:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3316, 4, 'UPC-A 美国商品码', '4', 'mes_wm_barcode_format', 0, 'warning', '', 'UPC_A', '1', '2026-03-05 14:37:20', '1', '2026-03-06 13:18:28', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3318, 3, '库位', '104', 'mes_wm_barcode_biz_type', 0, 'default', '', 'AREA', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3319, 4, '装箱单', '105', 'mes_wm_barcode_biz_type', 0, 'default', '', 'PACKAGE', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3320, 5, '库存', '106', 'mes_wm_barcode_biz_type', 0, 'default', '', 'STOCK', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3321, 6, '批次', '107', 'mes_wm_barcode_biz_type', 0, 'default', '', 'BATCH', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3322, 7, '流转卡', '300', 'mes_wm_barcode_biz_type', 0, 'primary', '', 'PROCARD', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3323, 8, '工单', '301', 'mes_wm_barcode_biz_type', 0, 'primary', '', 'WORKORDER', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3324, 9, '流转单', '302', 'mes_wm_barcode_biz_type', 0, 'primary', '', 'TRANSORDER', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3325, 10, '设备', '400', 'mes_wm_barcode_biz_type', 0, 'success', '', 'MACHINERY', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3327, 12, '产品物料', '600', 'mes_wm_barcode_biz_type', 0, 'info', '', 'ITEM', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3328, 13, '供应商', '601', 'mes_wm_barcode_biz_type', 0, 'info', '', 'VENDOR', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3329, 14, '工作站', '602', 'mes_wm_barcode_biz_type', 0, 'info', '', 'WORKSTATION', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3330, 15, '车间', '603', 'mes_wm_barcode_biz_type', 0, 'info', '', 'WORKSHOP', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3331, 16, '人员', '604', 'mes_wm_barcode_biz_type', 0, 'info', '', 'USER', '1', '2026-03-05 14:37:20', '1', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3351, 1, '仓库', '102', 'mes_wm_barcode_biz_type', 0, '', '', NULL, '', '2026-03-07 06:22:27', '', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3352, 2, '库区', '103', 'mes_wm_barcode_biz_type', 0, '', '', NULL, '', '2026-03-07 06:22:27', '', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3353, 11, '工具', '500', 'mes_wm_barcode_biz_type', 0, '', '', NULL, '', '2026-03-07 06:22:27', '', '2026-03-07 06:22:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3354, 17, '客户', '605', 'mes_wm_barcode_biz_type', 0, '', '', NULL, '', '2026-03-07 06:22:27', '', '2026-03-07 06:25:19', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3355, 1, '草稿', '0', 'mes_wm_package_status', 0, 'info', '', '草稿状态,可编辑', '1', '2026-03-08 02:05:46', '1', '2026-03-08 02:05:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3356, 2, '已完成', '4', 'mes_wm_package_status', 0, 'success', '', '装箱已完成', '1', '2026-03-08 02:05:46', '1', '2026-03-08 02:05:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3357, 1, '草稿', '0', 'mes_wm_transfer_status', 0, 'info', '', '草稿状态,可编辑', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3358, 2, '待确认', '1', 'mes_wm_transfer_status', 0, 'warning', '', '外部调拨待确认到货', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3359, 3, '待上架', '2', 'mes_wm_transfer_status', 0, 'primary', '', '待维护目标库位明细', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3360, 4, '待执行', '3', 'mes_wm_transfer_status', 0, 'success', '', '目标库位已分配,待执行调拨', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3361, 5, '已完成', '4', 'mes_wm_transfer_status', 0, 'success', '', '调拨已完成', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3362, 6, '已取消', '5', 'mes_wm_transfer_status', 0, 'danger', '', '调拨已取消', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3363, 1, '内部调拨', '1', 'mes_wm_transfer_type', 0, 'success', '', '内部仓储调拨', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3364, 2, '外部调拨', '2', 'mes_wm_transfer_type', 0, 'warning', '', '外部配送/外部收货调拨', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3365, 1, '静态盘点', '1', 'mes_wm_stock_taking_type', 0, 'primary', '', '静态盘点', '1', '2026-03-09 00:00:00', '1', '2026-04-05 15:02:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3366, 2, '动态盘点', '2', 'mes_wm_stock_taking_type', 0, 'success', '', '动态盘点', '1', '2026-03-09 00:00:00', '1', '2026-04-05 15:02:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3367, 1, '仓库', '102', 'mes_wm_stock_taking_plan_param_type', 0, 'primary', '', '按仓库盘点', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3368, 2, '库区', '103', 'mes_wm_stock_taking_plan_param_type', 0, 'success', '', '按库区盘点', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3369, 3, '库位', '104', 'mes_wm_stock_taking_plan_param_type', 0, 'info', '', '按库位盘点', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3370, 4, '物料', '600', 'mes_wm_stock_taking_plan_param_type', 0, 'warning', '', '按物料盘点', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3371, 5, '批次', '107', 'mes_wm_stock_taking_plan_param_type', 0, 'danger', '', '按批次盘点', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3372, 1, '草稿', '0', 'mes_wm_stock_taking_task_status', 0, 'info', '', '草稿', '1', '2026-03-09 00:00:00', '1', '2026-04-05 15:02:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3373, 2, '审批中', '2', 'mes_wm_stock_taking_task_status', 0, 'primary', '', '盘点任务审批中', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3374, 3, '已完成', '4', 'mes_wm_stock_taking_task_status', 0, 'success', '', '已完成', '1', '2026-03-09 00:00:00', '1', '2026-04-05 15:02:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3375, 4, '已取消', '5', 'mes_wm_stock_taking_task_status', 0, 'danger', '', '已取消', '1', '2026-03-09 00:00:00', '1', '2026-04-05 15:02:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3377, 1, '正常', '1', 'mes_wm_stock_taking_task_line_status', 0, 'success', '', '正常', '1', '2026-03-09 00:00:00', '1', '2026-04-05 15:02:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3378, 2, '盘盈', '2', 'mes_wm_stock_taking_task_line_status', 0, 'primary', '', '盘盈', '1', '2026-03-09 00:00:00', '1', '2026-04-05 15:02:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3379, 3, '盘亏', '3', 'mes_wm_stock_taking_task_line_status', 0, 'danger', '', '盘亏', '1', '2026-03-09 00:00:00', '1', '2026-04-05 15:02:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3380, 6, '质量状态', '900', 'mes_wm_stock_taking_plan_param_type', 0, 'default', '', '按质量状态盘点', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3381, 1, '物料', 'ITEM', 'mes_md_item_or_product', 0, 'info', '', '', '1', '2026-03-15 01:55:06', '1', '2026-03-15 01:55:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3382, 2, '产品', 'PRODUCT', 'mes_md_item_or_product', 0, 'success', '', '', '1', '2026-03-15 01:55:06', '1', '2026-03-15 01:55:06', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3383, 1, '草稿', '0', 'mes_wm_item_consume_status', 0, 'info', '', '草稿状态', '1', '2026-03-19 15:06:23', '1', '2026-03-19 15:06:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3384, 2, '已完成', '4', 'mes_wm_item_consume_status', 0, 'success', '', '已完成', '1', '2026-03-19 15:06:23', '1', '2026-03-19 15:06:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3385, 1, '到货通知单', '100', 'mes_qc_source_doc_type', 0, 'primary', '', 'IQC', '1', '2026-03-26 13:01:09', '1', '2026-03-26 13:01:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3386, 2, '外协入库单', '121', 'mes_qc_source_doc_type', 0, 'warning', '', 'IQC', '1', '2026-03-26 13:01:09', '1', '2026-03-26 13:01:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3387, 3, '生产报工', '304', 'mes_qc_source_doc_type', 0, 'success', '', 'IPQC', '1', '2026-03-26 13:01:09', '1', '2026-03-26 13:01:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3388, 4, '销售出库单', '118', 'mes_qc_source_doc_type', 0, 'info', '', 'OQC', '1', '2026-03-26 13:01:09', '1', '2026-03-26 13:01:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3389, 5, '生产退料单', '116', 'mes_qc_source_doc_type', 0, 'danger', '', 'RQC', '1', '2026-03-26 13:01:09', '1', '2026-03-26 13:01:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3390, 6, '销售退货单', '119', 'mes_qc_source_doc_type', 0, 'default', '', 'RQC', '1', '2026-03-26 13:01:09', '1', '2026-03-26 13:01:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3397, 2, '待检测', '1', 'mes_wm_product_sales_status', 0, 'warning', '', 'OQC 检验中', '1', '2026-03-27 11:44:48', '1', '2026-03-27 11:44:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3398, 0, '草稿', '0', 'mes_wm_return_vendor_status', 0, 'info', '', NULL, '', '2026-03-29 13:49:57', '', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3399, 1, '待拣货', '2', 'mes_wm_return_vendor_status', 0, 'primary', '', NULL, '', '2026-03-29 13:49:57', '', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3400, 2, '待执行退货', '3', 'mes_wm_return_vendor_status', 0, 'warning', '', NULL, '', '2026-03-29 13:49:57', '', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3401, 3, '已完成', '4', 'mes_wm_return_vendor_status', 0, 'success', '', NULL, '', '2026-03-29 13:49:57', '', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3402, 4, '已取消', '5', 'mes_wm_return_vendor_status', 0, 'danger', '', NULL, '', '2026-03-29 13:49:57', '', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3403, 1, '草稿', '0', 'mes_wm_sales_notice_status', 0, 'info', '', '草稿状态,可以修改和删除', '1', '2026-03-30 08:54:30', '1', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3404, 2, '待出库', '3', 'mes_wm_sales_notice_status', 0, 'success', '', '已提交状态,不可修改和删除', '1', '2026-03-30 08:54:30', '1', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3405, 3, '已完成', '4', 'mes_wm_sales_notice_status', 0, '', '', NULL, '1', '2026-03-30 10:02:10', '1', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3406, 1, '草稿', '0', 'mes_wm_misc_issue_status', 0, 'info', '', '草稿状态', '1', '2026-03-30 15:00:18', '1', '2026-03-30 15:00:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3407, 2, '待出库', '3', 'mes_wm_misc_issue_status', 0, 'warning', '', '待出库状态', '1', '2026-03-30 15:00:18', '1', '2026-03-30 15:00:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3408, 3, '已完成', '4', 'mes_wm_misc_issue_status', 0, 'success', '', '执行出库后的状态', '1', '2026-03-30 15:00:18', '1', '2026-03-30 15:00:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3409, 4, '已取消', '5', 'mes_wm_misc_issue_status', 0, 'danger', '', '已取消状态', '1', '2026-03-30 15:00:18', '1', '2026-03-30 15:00:18', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3415, 1, '注塑', '1', 'mes_cal_calendar_type', 0, 'primary', '', '', '1', '2026-04-01 15:23:14', '1', '2026-04-01 16:08:31', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3416, 2, '机加工', '2', 'mes_cal_calendar_type', 0, 'success', '', '', '1', '2026-04-01 15:23:14', '1', '2026-04-01 16:08:32', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3417, 3, '组装', '3', 'mes_cal_calendar_type', 0, 'warning', '', '', '1', '2026-04-01 15:23:14', '1', '2026-04-01 16:08:33', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3418, 4, '仓库', '4', 'mes_cal_calendar_type', 0, 'danger', '', '', '1', '2026-04-01 15:23:14', '1', '2026-04-01 16:08:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3419, 0, '草稿', '0', 'mes_dv_repair_status', 0, 'info', '', '', '1', '2026-04-03 17:20:23', '1', '2026-04-03 17:20:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3420, 1, '维修中', '1', 'mes_dv_repair_status', 0, 'primary', '', '', '1', '2026-04-03 17:20:23', '1', '2026-04-03 17:20:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3421, 2, '待验收', '2', 'mes_dv_repair_status', 0, 'warning', '', '', '1', '2026-04-03 17:20:23', '1', '2026-04-03 17:20:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3422, 3, '已确认', '4', 'mes_dv_repair_status', 0, 'success', '', '', '1', '2026-04-03 17:20:23', '1', '2026-04-03 17:20:23', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3423, 0, '草稿', '0', 'mes_wm_return_sales_status', 0, 'info', '', '', '1', '2026-04-03 17:20:25', '1', '2026-04-03 17:20:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3424, 1, '待检验', '1', 'mes_wm_return_sales_status', 0, 'warning', '', '', '1', '2026-04-03 17:20:25', '1', '2026-04-03 17:20:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3425, 2, '待执行', '2', 'mes_wm_return_sales_status', 0, 'warning', '', '', '1', '2026-04-03 17:20:25', '1', '2026-04-03 17:20:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3426, 3, '待上架', '3', 'mes_wm_return_sales_status', 0, 'primary', '', '', '1', '2026-04-03 17:20:25', '1', '2026-04-03 17:20:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3427, 4, '已完成', '4', 'mes_wm_return_sales_status', 0, 'success', '', '', '1', '2026-04-03 17:20:25', '1', '2026-04-03 17:20:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3428, 5, '已取消', '5', 'mes_wm_return_sales_status', 0, 'danger', '', '', '1', '2026-04-03 17:20:25', '1', '2026-04-03 17:20:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3436, 1, '上工', '1', 'mes_pro_work_record_type', 0, 'success', '', '', '1', '2026-04-05 14:07:27', '1', '2026-04-05 14:07:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3437, 2, '下工', '2', 'mes_pro_work_record_type', 0, 'danger', '', '', '1', '2026-04-05 14:07:27', '1', '2026-04-05 14:07:27', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3443, 1, '草稿', '0', 'mes_wm_product_produce_status', 0, 'info', '', '草稿状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3444, 2, '已完成', '4', 'mes_wm_product_produce_status', 0, 'success', '', '已完成状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3445, 3, '已取消', '5', 'mes_wm_product_produce_status', 0, 'danger', '', '已取消状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3520, 1, '未读', '0', 'im_private_message_status', 0, 'warning', '', '私聊=未读,群聊=正常', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 15:14:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3521, 2, '已撤回', '2', 'im_private_message_status', 0, 'danger', '', 'RECALL', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 15:14:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3522, 3, '已读', '3', 'im_private_message_status', 0, 'success', '', 'READ(仅私聊)', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 15:14:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3525, 1, '正常', '0', 'im_group_message_status', 0, 'success', '', '群聊正常(初始状态)', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3526, 2, '已撤回', '2', 'im_group_message_status', 0, 'danger', '', '群聊已撤回', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3530, 1, '不需要回执', '0', 'im_group_message_receipt_status', 0, 'info', '', 'NO_RECEIPT', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3531, 2, '待完成', '1', 'im_group_message_receipt_status', 0, 'warning', '', 'PENDING', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3532, 3, '已完成', '2', 'im_group_message_receipt_status', 0, 'success', '', 'DONE', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3540, 1, '正常', '0', 'im_friend_status', 0, 'success', '', '正常好友关系', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3541, 2, '已删除', '1', 'im_friend_status', 0, 'danger', '', '已删除好友关系', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3550, 1, '正常', '0', 'im_group_status', 0, 'success', '', '群正常', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3551, 2, '已解散', '1', 'im_group_status', 0, 'info', '', '群已解散', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3552, 1, '群主', '1', 'im_group_member_role', 0, 'primary', '', NULL, '1', '2026-05-02 02:14:12', '1', '2026-05-02 02:14:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3553, 2, '管理员', '2', 'im_group_member_role', 0, 'warning', '', NULL, '1', '2026-05-02 02:14:12', '1', '2026-05-02 02:14:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3554, 3, '普通成员', '3', 'im_group_member_role', 0, 'info', '', NULL, '1', '2026-05-02 02:14:12', '1', '2026-05-02 02:14:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3555, 1, '搜索', '1', 'im_friend_add_source', 0, 'default', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-05 11:46:57', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3556, 2, '群聊', '2', 'im_friend_add_source', 0, 'default', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-05 11:46:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3557, 3, '扫码', '3', 'im_friend_add_source', 0, 'default', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-05 11:46:50', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3558, 4, '名片', '4', 'im_friend_add_source', 0, 'default', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-05 11:46:48', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3559, 1, '等待验证', '0', 'im_friend_request_handle_result', 0, 'warning', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3560, 2, '已添加', '1', 'im_friend_request_handle_result', 0, 'success', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3561, 3, '已拒绝', '2', 'im_friend_request_handle_result', 0, 'info', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3562, 101, '文本', '101', 'im_message_type', 0, '', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3563, 102, '图片', '102', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3564, 103, '语音', '103', 'im_message_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3565, 104, '视频', '104', 'im_message_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3566, 105, '文件', '105', 'im_message_type', 0, 'info', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3568, 2101, '撤回', '2101', 'im_message_type', 0, 'danger', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3569, 2200, '回执', '2200', 'im_message_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3570, 2201, '已读', '2201', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3571, 1501, '群创建', '1501', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3572, 1502, '群信息变更', '1502', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3573, 1503, '入群申请', '1503', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3574, 1504, '成员退群', '1504', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3575, 1505, '入群申请通过', '1505', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3576, 1506, '入群申请拒绝', '1506', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3577, 1507, '群主转让', '1507', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3578, 1508, '成员被移出', '1508', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3579, 1509, '成员加入', '1509', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3580, 1510, '自由进群', '1510', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3581, 1511, '群解散', '1511', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3582, 1512, '成员禁言', '1512', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3583, 1513, '成员取消禁言', '1513', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3584, 1514, '全群禁言', '1514', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3585, 1515, '全群取消禁言', '1515', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3586, 1516, '成员昵称变更', '1516', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3587, 1517, '添加管理员', '1517', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3588, 1518, '撤销管理员', '1518', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3589, 1519, '群公告变更', '1519', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3590, 1520, '群名变更', '1520', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3591, 1531, '群消息置顶', '1531', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3592, 1532, '群消息取消置顶', '1532', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3593, 1533, '群封禁变更', '1533', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3594, 1204, '新增好友', '1204', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 13:26:53', 'admin', '2026-05-05 13:26:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3595, 1205, '好友被删除', '1205', 'im_message_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 13:26:53', 'admin', '2026-05-05 13:26:53', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3596, 1, '搜索', '1', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3597, 2, '邀请', '2', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3598, 3, '扫码', '3', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3599, 4, '分享链接', '4', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3600, 1, '未处理', '0', 'im_group_request_handle_result', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3601, 2, '同意', '1', 'im_group_request_handle_result', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3602, 3, '拒绝', '2', 'im_group_request_handle_result', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061051, 1, '客户', '1', 'merchant_type', 0, 'primary', '', '客户', '1', '2026-05-10 15:26:09', '1', '2026-05-10 15:26:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061052, 2, '供应商', '2', 'merchant_type', 0, 'success', '', '供应商', '1', '2026-05-10 15:26:09', '1', '2026-05-10 15:26:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061053, 3, '客户/供应商', '3', 'merchant_type', 0, 'warning', '', '客户/供应商', '1', '2026-05-10 15:26:09', '1', '2026-05-10 15:26:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061061, 1, '入库单', '1', 'wms_order_type', 0, 'success', '', '', '1', '2026-05-10 17:51:46', '1', '2026-05-14 08:14:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061062, 2, '出库单', '2', 'wms_order_type', 0, 'danger', '', '', '1', '2026-05-10 17:51:46', '1', '2026-05-14 08:14:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061063, 3, '移库单', '3', 'wms_order_type', 0, 'primary', '', '', '1', '2026-05-10 17:51:46', '1', '2026-05-14 08:14:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061064, 4, '盘库单', '4', 'wms_order_type', 0, 'warning', '', '', '1', '2026-05-10 17:51:46', '1', '2026-05-14 08:14:09', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061071, 1, '草稿', '0', 'wms_order_status', 0, 'info', '', '草稿', '1', '2026-05-12 13:40:29', '1', '2026-05-12 13:40:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061072, 2, '已完成', '4', 'wms_order_status', 0, 'success', '', '已完成', '1', '2026-05-12 13:40:29', '1', '2026-05-12 13:40:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061073, 3, '已作废', '5', 'wms_order_status', 0, 'danger', '', '已作废', '1', '2026-05-12 13:40:29', '1', '2026-05-12 13:40:29', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061081, 1, '生产入库', '100', 'wms_receipt_order_type', 0, 'success', '', '', '1', '2026-05-11 11:21:49', '1', '2026-05-14 02:21:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061082, 2, '采购入库', '101', 'wms_receipt_order_type', 0, 'primary', '', '', '1', '2026-05-11 11:21:49', '1', '2026-05-14 02:21:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061083, 3, '退货入库', '102', 'wms_receipt_order_type', 0, 'warning', '', '', '1', '2026-05-11 11:21:49', '1', '2026-05-14 02:21:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061084, 4, '归还入库', '103', 'wms_receipt_order_type', 0, 'info', '', '', '1', '2026-05-13 16:02:33', '1', '2026-05-14 02:21:20', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061091, 1, '退货出库', '200', 'wms_shipment_order_type', 0, 'warning', '', '退货出库', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061092, 2, '销售出库', '201', 'wms_shipment_order_type', 0, 'primary', '', '销售出库', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061093, 3, '生产出库', '202', 'wms_shipment_order_type', 0, 'success', '', '生产出库', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061096, 1, '语音', '1', 'im_rtc_call_media_type', 0, '', '', '语音通话', 'admin', '2026-05-16 11:34:50', 'admin', '2026-05-16 11:34:50', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061097, 2, '视频', '2', 'im_rtc_call_media_type', 0, '', '', '视频通话', 'admin', '2026-05-16 11:34:50', 'admin', '2026-05-16 11:34:50', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061098, 1, '私聊', '1', 'im_rtc_call_conversation_type', 0, 'primary', '', '一对一私聊通话', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061099, 2, '群聊', '2', 'im_rtc_call_conversation_type', 0, 'success', '', '群内多人通话', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061100, 1, '创建', '10', 'im_rtc_call_status', 0, 'info', '', '通话已创建,等待接通', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061101, 2, '进行中', '20', 'im_rtc_call_status', 0, 'primary', '', '已有人接通,通话中', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061102, 3, '已结束', '30', 'im_rtc_call_status', 0, 'success', '', '通话结束', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061103, 1, '通话结束', '1', 'im_rtc_call_end_reason', 0, 'success', '', '接通后任一方主动挂断', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061104, 2, '已拒绝', '2', 'im_rtc_call_end_reason', 0, 'warning', '', '被叫接通前点拒接', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061105, 3, '已取消', '3', 'im_rtc_call_end_reason', 0, 'info', '', '主叫接通前主动取消', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061106, 4, '无人接听', '4', 'im_rtc_call_end_reason', 0, 'info', '', '振铃超时未接通', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061107, 5, '对方正忙', '5', 'im_rtc_call_end_reason', 0, 'warning', '', '对方在另一通话中', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061108, 6, '通话异常', '9', 'im_rtc_call_end_reason', 0, 'danger', '', '网络中断、设备失败等', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061109, 1, '发起人', '1', 'im_rtc_participant_role', 0, 'primary', '', '通话发起者', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061110, 2, '被邀请者', '2', 'im_rtc_participant_role', 0, 'info', '', '被邀请加入', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061111, 3, '主动加入者', '3', 'im_rtc_participant_role', 0, 'success', '', '群通话场景,旁观者主动加入', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061112, 1, '邀请中', '10', 'im_rtc_participant_status', 0, 'info', '', '已发出 invite,等待响应', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061113, 2, '已加入', '20', 'im_rtc_participant_status', 0, 'primary', '', '已接通并进入房间', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061114, 3, '已拒绝', '30', 'im_rtc_participant_status', 0, 'warning', '', '接通前点拒接', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061115, 4, '未应答', '40', 'im_rtc_participant_status', 0, 'info', '', '通话已结束仍未应答', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061116, 5, '已离开', '50', 'im_rtc_participant_status', 0, 'success', '', '接通后挂断 / 离开', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061117, 1610, '通话开始', '1610', 'im_message_type', 0, 'info', '', '入消息流;私聊定向通知,群聊全员广播', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061118, 1611, '通话结束', '1611', 'im_message_type', 0, 'info', '', '入消息流;私聊准气泡,群聊系统 tip', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061119, 125, '素材', '125', 'im_message_type', 0, 'success', '', '频道运营推送的图文卡片消息', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061120, 1, '富文本', '1', 'im_channel_material_type', 0, 'primary', '', '', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1061121, 2, '外链', '2', 'im_channel_material_type', 0, 'info', '', '', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', '0'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_dict_data_seq; +CREATE SEQUENCE system_dict_data_seq + START 1061122; + +-- ---------------------------- +-- Table structure for system_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS system_dict_type; +CREATE TABLE system_dict_type ( + id int8 NOT NULL, + name varchar(100) NOT NULL DEFAULT '', + type varchar(100) NOT NULL DEFAULT '', + status int2 NOT NULL DEFAULT 0, + remark varchar(500) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + deleted_time timestamp NULL DEFAULT NULL +); + +ALTER TABLE system_dict_type ADD CONSTRAINT pk_system_dict_type PRIMARY KEY (id); + +COMMENT ON COLUMN system_dict_type.id IS '字典主键'; +COMMENT ON COLUMN system_dict_type.name IS '字典名称'; +COMMENT ON COLUMN system_dict_type.type IS '字典类型'; +COMMENT ON COLUMN system_dict_type.status IS '状态(0正常 1停用)'; +COMMENT ON COLUMN system_dict_type.remark IS '备注'; +COMMENT ON COLUMN system_dict_type.creator IS '创建者'; +COMMENT ON COLUMN system_dict_type.create_time IS '创建时间'; +COMMENT ON COLUMN system_dict_type.updater IS '更新者'; +COMMENT ON COLUMN system_dict_type.update_time IS '更新时间'; +COMMENT ON COLUMN system_dict_type.deleted IS '是否删除'; +COMMENT ON COLUMN system_dict_type.deleted_time IS '删除时间'; +COMMENT ON TABLE system_dict_type IS '字典类型表'; + +-- ---------------------------- +-- Records of system_dict_type +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1, '用户性别', 'system_user_sex', 0, NULL, 'admin', '2021-01-05 17:03:48', '1', '2022-05-16 20:29:32', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (6, '参数类型', 'infra_config_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:36:54', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (7, '通知类型', 'system_notice_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:35:26', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (9, '操作类型', 'infra_operate_type', 0, NULL, 'admin', '2021-01-05 17:03:48', '1', '2024-03-14 12:44:01', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (10, '系统状态', 'common_status', 0, NULL, 'admin', '2021-01-05 17:03:48', '', '2022-02-01 16:21:28', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (11, 'Boolean 是否类型', 'infra_boolean_string', 0, 'boolean 转是否', '', '2021-01-19 03:20:08', '', '2022-02-01 16:37:10', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (104, '登陆结果', 'system_login_result', 0, '登陆结果', '', '2021-01-18 06:17:11', '', '2022-02-01 16:36:00', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (106, '代码生成模板类型', 'infra_codegen_template_type', 0, NULL, '', '2021-02-05 07:08:06', '1', '2022-05-16 20:26:50', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (107, '定时任务状态', 'infra_job_status', 0, NULL, '', '2021-02-07 07:44:16', '', '2022-02-01 16:51:11', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (108, '定时任务日志状态', 'infra_job_log_status', 0, NULL, '', '2021-02-08 10:03:51', '', '2022-02-01 16:50:43', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (109, '用户类型', 'user_type', 0, NULL, '', '2021-02-26 00:15:51', '', '2021-02-26 00:15:51', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (110, 'API 异常数据的处理状态', 'infra_api_error_log_process_status', 0, NULL, '', '2021-02-26 07:07:01', '', '2022-02-01 16:50:53', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (111, '短信渠道编码', 'system_sms_channel_code', 0, NULL, '1', '2021-04-05 01:04:50', '1', '2022-02-16 02:09:08', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (112, '短信模板的类型', 'system_sms_template_type', 0, NULL, '1', '2021-04-05 21:50:43', '1', '2022-02-01 16:35:06', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (113, '短信发送状态', 'system_sms_send_status', 0, NULL, '1', '2021-04-11 20:18:03', '1', '2022-02-01 16:35:09', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (114, '短信接收状态', 'system_sms_receive_status', 0, NULL, '1', '2021-04-11 20:27:14', '1', '2022-02-01 16:35:14', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (116, '登陆日志的类型', 'system_login_type', 0, '登陆日志的类型', '1', '2021-10-06 00:50:46', '1', '2022-02-01 16:35:56', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (117, 'OA 请假类型', 'bpm_oa_leave_type', 0, NULL, '1', '2021-09-21 22:34:33', '1', '2022-01-22 10:41:37', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (130, '支付渠道编码类型', 'pay_channel_code', 0, '支付渠道的编码', '1', '2021-12-03 10:35:08', '1', '2023-07-10 10:11:39', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (131, '支付回调状态', 'pay_notify_status', 0, '支付回调状态(包括退款回调)', '1', '2021-12-03 10:53:29', '1', '2023-07-19 18:09:43', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (132, '支付订单状态', 'pay_order_status', 0, '支付订单状态', '1', '2021-12-03 11:17:50', '1', '2021-12-03 11:17:50', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (134, '退款订单状态', 'pay_refund_status', 0, '退款订单状态', '1', '2021-12-10 16:42:50', '1', '2023-07-19 10:13:17', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (139, '流程实例的状态', 'bpm_process_instance_status', 0, '流程实例的状态', '1', '2022-01-07 23:46:42', '1', '2022-01-07 23:46:42', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (140, '流程实例的结果', 'bpm_task_status', 0, '流程实例的结果', '1', '2022-01-07 23:48:10', '1', '2024-03-08 22:42:03', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (141, '流程的表单类型', 'bpm_model_form_type', 0, '流程的表单类型', '103', '2022-01-11 23:50:45', '103', '2022-01-11 23:50:45', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (142, '任务分配规则的类型', 'bpm_task_candidate_strategy', 0, 'BPM 任务的候选人的策略', '103', '2022-01-12 23:21:04', '103', '2024-03-06 02:53:59', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (144, '代码生成的场景枚举', 'infra_codegen_scene', 0, '代码生成的场景枚举', '1', '2022-02-02 13:14:45', '1', '2022-03-10 16:33:46', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (145, '角色类型', 'system_role_type', 0, '角色类型', '1', '2022-02-16 13:01:46', '1', '2022-02-16 13:01:46', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (146, '文件存储器', 'infra_file_storage', 0, '文件存储器', '1', '2022-03-15 00:24:38', '1', '2022-03-15 00:24:38', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (147, 'OAuth 2.0 授权类型', 'system_oauth2_grant_type', 0, 'OAuth 2.0 授权类型(模式)', '1', '2022-05-12 00:20:52', '1', '2022-05-11 16:25:49', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (149, '商品 SPU 状态', 'product_spu_status', 0, '商品 SPU 状态', '1', '2022-10-24 21:19:04', '1', '2022-10-24 21:19:08', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (150, '优惠类型', 'promotion_discount_type', 0, '优惠类型', '1', '2022-11-01 12:46:06', '1', '2022-11-01 12:46:06', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (151, '优惠劵模板的有限期类型', 'promotion_coupon_template_validity_type', 0, '优惠劵模板的有限期类型', '1', '2022-11-02 00:06:20', '1', '2022-11-04 00:08:26', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (152, '营销的商品范围', 'promotion_product_scope', 0, '营销的商品范围', '1', '2022-11-02 00:28:01', '1', '2022-11-02 00:28:01', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (153, '优惠劵的状态', 'promotion_coupon_status', 0, '优惠劵的状态', '1', '2022-11-04 00:14:49', '1', '2022-11-04 00:14:49', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (154, '优惠劵的领取方式', 'promotion_coupon_take_type', 0, '优惠劵的领取方式', '1', '2022-11-04 19:12:27', '1', '2022-11-04 19:12:27', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (155, '促销活动的状态', 'promotion_activity_status', 0, '促销活动的状态', '1', '2022-11-04 22:54:23', '1', '2022-11-04 22:54:23', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (156, '营销的条件类型', 'promotion_condition_type', 0, '营销的条件类型', '1', '2022-11-04 22:59:23', '1', '2022-11-04 22:59:23', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (157, '交易售后状态', 'trade_after_sale_status', 0, '交易售后状态', '1', '2022-11-19 20:52:56', '1', '2022-11-19 20:52:56', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (158, '交易售后的类型', 'trade_after_sale_type', 0, '交易售后的类型', '1', '2022-11-19 21:04:09', '1', '2022-11-19 21:04:09', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (159, '交易售后的方式', 'trade_after_sale_way', 0, '交易售后的方式', '1', '2022-11-19 21:39:04', '1', '2022-11-19 21:39:04', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (160, '终端', 'terminal', 0, '终端', '1', '2022-12-10 10:50:50', '1', '2022-12-10 10:53:11', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (161, '交易订单的类型', 'trade_order_type', 0, '交易订单的类型', '1', '2022-12-10 16:33:54', '1', '2022-12-10 16:33:54', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (162, '交易订单的状态', 'trade_order_status', 0, '交易订单的状态', '1', '2022-12-10 16:48:44', '1', '2022-12-10 16:48:44', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (163, '交易订单项的售后状态', 'trade_order_item_after_sale_status', 0, '交易订单项的售后状态', '1', '2022-12-10 20:58:08', '1', '2022-12-10 20:58:08', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (164, '公众号自动回复的请求关键字匹配模式', 'mp_auto_reply_request_match', 0, '公众号自动回复的请求关键字匹配模式', '1', '2023-01-16 23:29:56', '1', '2023-01-16 23:29:56', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (165, '公众号的消息类型', 'mp_message_type', 0, '公众号的消息类型', '1', '2023-01-17 22:17:09', '1', '2023-01-17 22:17:09', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (166, '邮件发送状态', 'system_mail_send_status', 0, '邮件发送状态', '1', '2023-01-26 09:53:13', '1', '2023-01-26 09:53:13', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (167, '站内信模版的类型', 'system_notify_template_type', 0, '站内信模版的类型', '1', '2023-01-28 10:35:10', '1', '2023-01-28 10:35:10', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (168, '代码生成的前端类型', 'infra_codegen_front_type', 0, '', '1', '2023-04-12 23:57:52', '1', '2023-04-12 23:57:52', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (170, '快递计费方式', 'trade_delivery_express_charge_mode', 0, '用于商城交易模块配送管理', '1', '2023-05-21 22:45:03', '1', '2023-05-21 22:45:03', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (171, '积分业务类型', 'member_point_biz_type', 0, '', '1', '2023-06-10 12:15:00', '1', '2023-06-28 13:48:20', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (173, '支付通知类型', 'pay_notify_type', 0, NULL, '1', '2023-07-20 12:23:03', '1', '2023-07-20 12:23:03', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (174, '会员经验业务类型', 'member_experience_biz_type', 0, NULL, '', '2023-08-22 12:41:01', '', '2023-08-22 12:41:01', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (175, '交易配送类型', 'trade_delivery_type', 0, '', '1', '2023-08-23 00:03:14', '1', '2023-08-23 00:03:14', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (176, '分佣模式', 'brokerage_enabled_condition', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (177, '分销关系绑定模式', 'brokerage_bind_mode', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (178, '佣金提现类型', 'brokerage_withdraw_type', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (179, '佣金记录业务类型', 'brokerage_record_biz_type', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (180, '佣金记录状态', 'brokerage_record_status', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (181, '佣金提现状态', 'brokerage_withdraw_status', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (182, '佣金提现银行', 'brokerage_bank_name', 0, NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (183, '砍价记录的状态', 'promotion_bargain_record_status', 0, '', '1', '2023-10-05 10:41:08', '1', '2023-10-05 10:41:08', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (184, '拼团记录的状态', 'promotion_combination_record_status', 0, '', '1', '2023-10-08 07:24:25', '1', '2023-10-08 07:24:25', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (185, '回款-回款方式', 'crm_receivable_return_type', 0, '回款-回款方式', '1', '2023-10-18 21:54:10', '1', '2023-10-18 21:54:10', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (186, 'CRM 客户行业', 'crm_customer_industry', 0, 'CRM 客户所属行业', '1', '2023-10-28 22:57:07', '1', '2024-02-18 23:30:22', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (187, '客户等级', 'crm_customer_level', 0, 'CRM 客户等级', '1', '2023-10-28 22:59:12', '1', '2023-10-28 15:11:16', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (188, '客户来源', 'crm_customer_source', 0, 'CRM 客户来源', '1', '2023-10-28 23:00:34', '1', '2023-10-28 15:11:16', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (600, 'Banner 位置', 'promotion_banner_position', 0, '', '1', '2023-10-08 07:24:25', '1', '2023-11-04 13:04:02', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (601, '社交类型', 'system_social_type', 0, '', '1', '2023-11-04 13:03:54', '1', '2023-11-04 13:03:54', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (604, '产品状态', 'crm_product_status', 0, '', '1', '2023-10-30 21:47:59', '1', '2023-10-30 21:48:45', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (605, 'CRM 数据权限的级别', 'crm_permission_level', 0, '', '1', '2023-11-30 09:51:59', '1', '2023-11-30 09:51:59', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (606, 'CRM 审批状态', 'crm_audit_status', 0, '', '1', '2023-11-30 18:56:23', '1', '2023-11-30 18:56:23', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (607, 'CRM 产品单位', 'crm_product_unit', 0, '', '1', '2023-12-05 23:01:51', '1', '2023-12-05 23:01:51', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (608, 'CRM 跟进方式', 'crm_follow_up_type', 0, '', '1', '2024-01-15 20:48:05', '1', '2024-01-15 20:48:05', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (610, '转账订单状态', 'pay_transfer_status', 0, '', '1', '2023-10-28 16:18:32', '1', '2023-10-28 16:18:32', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (611, 'ERP 库存明细的业务类型', 'erp_stock_record_biz_type', 0, 'ERP 库存明细的业务类型', '1', '2024-02-05 18:07:02', '1', '2024-02-05 18:07:02', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (612, 'ERP 审批状态', 'erp_audit_status', 0, '', '1', '2024-02-06 00:00:07', '1', '2024-02-06 00:00:07', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (613, 'BPM 监听器类型', 'bpm_process_listener_type', 0, '', '1', '2024-03-23 12:52:24', '1', '2024-03-09 15:54:28', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (615, 'BPM 监听器值类型', 'bpm_process_listener_value_type', 0, '', '1', '2024-03-23 13:00:31', '1', '2024-03-23 13:00:31', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (616, '时间间隔', 'date_interval', 0, '', '1', '2024-03-29 22:50:09', '1', '2024-03-29 22:50:09', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (619, 'CRM 商机结束状态类型', 'crm_business_end_status_type', 0, '', '1', '2024-04-13 23:23:00', '1', '2024-04-13 23:23:00', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (620, 'AI 模型平台', 'ai_platform', 0, '', '1', '2024-05-09 22:27:38', '1', '2024-05-09 22:27:38', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (621, 'AI 绘画状态', 'ai_image_status', 0, '', '1', '2024-06-26 20:51:23', '1', '2024-06-26 20:51:23', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (622, 'AI 音乐状态', 'ai_music_status', 0, '', '1', '2024-06-27 22:45:07', '1', '2024-06-28 00:56:27', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (623, 'AI 音乐生成模式', 'ai_generate_mode', 0, '', '1', '2024-06-27 22:46:21', '1', '2024-06-28 01:22:29', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (624, '写作语气', 'ai_write_tone', 0, '', '1', '2024-07-07 15:19:02', '1', '2024-07-07 15:19:02', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (625, '写作语言', 'ai_write_language', 0, '', '1', '2024-07-07 15:18:52', '1', '2024-07-07 15:18:52', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (626, '写作长度', 'ai_write_length', 0, '', '1', '2024-07-07 15:18:41', '1', '2024-07-07 15:18:41', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (627, '写作格式', 'ai_write_format', 0, '', '1', '2024-07-07 15:14:34', '1', '2024-07-07 15:14:34', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (628, 'AI 写作类型', 'ai_write_type', 0, '', '1', '2024-07-10 21:25:29', '1', '2024-07-10 21:25:29', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (629, 'BPM 流程模型类型', 'bpm_model_type', 0, '', '1', '2024-08-26 15:21:43', '1', '2024-08-26 15:21:43', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (640, 'AI 模型类型', 'ai_model_type', 0, '', '1', '2025-03-03 12:24:07', '1', '2025-03-03 12:24:07', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1001, 'IoT 产品设备类型', 'iot_product_device_type', 0, '', '1', '2024-08-10 11:54:30', '1', '2025-03-17 09:25:08', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1002, 'IoT 产品状态', 'iot_product_status', 0, '', '1', '2024-08-10 12:06:09', '1', '2025-03-17 09:25:10', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1004, 'IoT 联网方式', 'iot_net_type', 0, '', '1', '2024-09-06 22:04:13', '1', '2025-03-17 09:25:14', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1006, 'IoT 设备状态', 'iot_device_state', 0, '', '1', '2024-09-21 08:12:55', '1', '2025-03-17 09:25:19', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1007, 'IoT 物模型功能类型', 'iot_thing_model_type', 0, '', '1', '2024-09-29 20:02:36', '1', '2025-03-17 09:25:24', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1011, 'IoT 物模型单位', 'iot_thing_model_unit', 0, '', '1', '2024-12-25 17:36:46', '1', '2025-03-17 09:25:35', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1013, 'IoT 数据流转目的的类型枚举', 'iot_data_sink_type_enum', 0, '', '1', '2025-03-09 12:39:36', '1', '2025-06-24 12:45:24', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1014, 'IoT 场景流转的触发类型枚举', 'iot_rule_scene_trigger_type_enum', 0, '', '1', '2025-03-20 14:59:44', '1', '2025-03-20 14:59:44', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1015, 'IoT 设备消息类型枚举', 'iot_device_message_type_enum', 0, '', '1', '2025-03-20 15:01:15', '1', '2025-03-20 15:01:15', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1016, 'IoT 规则场景的触发类型枚举', 'iot_rule_scene_action_type_enum', 0, '', '1', '2025-03-28 15:26:54', '1', '2025-03-28 15:29:13', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1017, 'MES 物料消耗记录状态', 'mes_wm_item_consume_status', 0, 'MES 物料消耗记录状态', '1', '2026-03-19 15:06:23', '1', '2026-03-19 15:06:23', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2001, 'IoT 告警级别', 'iot_alert_level', 0, '', '1', '2025-06-27 20:30:57', '1', '2025-06-27 20:30:57', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2002, 'IoT 告警', 'iot_alert_receive_type', 0, '', '1', '2025-06-27 22:49:19', '1', '2025-06-27 22:49:19', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2003, 'IoT 固件设备范围', 'iot_ota_task_device_scope', 0, '', '1', '2025-07-02 09:42:49', '1', '2025-07-02 09:42:49', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2004, 'IoT 固件升级任务状态', 'iot_ota_task_status', 0, '', '1', '2025-07-02 09:43:43', '1', '2025-07-02 09:43:43', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2005, 'IoT 固件升级记录状态', 'iot_ota_task_record_status', 0, '', '1', '2025-07-02 09:45:02', '1', '2025-07-02 09:45:02', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2007, 'AI MCP 客户端名字', 'ai_mcp_client_name', 0, '', '1', '2025-08-28 13:57:40', '1', '2025-08-28 13:57:40', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2008, 'IoT 协议类型', 'iot_protocol_type', 0, 'IoT 设备接入协议类型', '1', '2026-02-04 00:31:33', '1', '2026-02-04 00:31:33', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2009, 'IoT 序列化类型', 'iot_serialize_type', 0, 'IoT 设备消息序列化类型', '1', '2026-02-04 00:33:16', '1', '2026-02-04 00:33:16', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2010, 'IoT Modbus 工作模式', 'iot_modbus_mode', 0, 'Modbus 设备数据采集模式', '1', '2025-06-12 22:55:46', '1', '2025-06-12 22:55:46', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2011, 'IoT Modbus 帧格式', 'iot_modbus_frame_format', 0, 'Modbus 数据帧协议格式', '1', '2025-06-12 22:55:46', '1', '2025-06-12 22:55:46', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2012, 'MES 客户类型', 'mes_client_type', 0, '', '1', '2026-02-15 14:38:25', '1', '2026-02-15 14:38:25', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2013, 'MES 供应商级别', 'mes_vendor_level', 0, '', '1', '2026-02-15 15:59:15', '1', '2026-02-15 15:59:15', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2014, 'MES 假期类型', 'mes_cal_holiday_type', 0, 'MES 日历排班 - 假期类型(HOLIDAY=假期,WORKDAY=工作日)', '1', '2026-02-16 07:35:58', '1', '2026-02-16 07:35:58', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2015, 'MES 工具状态', 'mes_tm_tool_status', 0, 'MES 工具管理 - 工具状态(1=在库,2=领用中,3=维修中,4=报废)', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2016, 'MES 保养维护类型', 'mes_tm_mainten_type', 0, 'MES 工具管理 - 保养维护类型(1=定期维护,2=按使用次数维护)', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2017, 'MES 设备状态', 'mes_dv_machinery_status', 0, 'MES 设备管理 - 设备状态(1=运行中,2=停机,3=故障)', '1', '2026-02-17 01:00:06', '1', '2026-02-17 01:00:06', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2018, 'MES 检测项类型', 'mes_indicator_type', 0, '', '1', '2026-02-17 02:16:22', '1', '2026-02-21 15:25:04', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2019, 'MES 缺陷等级', 'mes_defect_level', 0, '', '1', '2026-02-17 02:16:22', '1', '2026-02-17 02:16:22', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2020, 'MES 轮班方式', 'mes_cal_shift_type', 0, 'MES 日历排班 - 轮班方式', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2021, 'MES 倒班方式', 'mes_cal_shift_method', 0, 'MES 日历排班 - 倒班方式', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2022, 'MES 班组类型', 'mes_cal_calendar_type', 0, 'MES 日历排班 - 班组类型', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2023, 'MES 排班计划状态', 'mes_cal_plan_status', 0, 'MES 日历排班 - 排班计划状态', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2026, 'MES 检测种类', 'mes_qc_type', 0, 'IQC/IPQC/OQC/RQC', '1', '2026-02-17 08:34:40', '1', '2026-02-17 08:34:40', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2027, 'MES 生产工单状态', 'mes_pro_work_order_status', 0, 'MES 生产管理 - 工单状态(0=草稿,1=已确认,2=已完成,3=已取消)', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2028, 'MES 工单来源类型', 'mes_pro_work_order_source_type', 0, 'MES 生产管理 - 工单来源类型(1=客户订单,2=库存备货)', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2029, 'MES 工单类型', 'mes_pro_work_order_type', 0, 'MES 生产管理 - 工单类型(1=自行生产,2=代工,3=采购)', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2036, 'MES 工序关系类型', 'mes_pro_link_type', 0, '工艺路线中工序之间的关系类型', '1', '2026-02-19 04:24:53', '1', '2026-04-05 15:05:07', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2037, 'MES 时间单位', 'mes_time_unit_type', 0, '生产时间的计量单位', '1', '2026-02-19 04:24:53', '1', '2026-04-05 15:04:57', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2038, 'MES 生产任务状态', 'mes_pro_task_status', 0, 'MES 生产管理 - 任务状态(0=草稿,1=进行中,2=暂停,3=已完成,4=已取消)', '1', '2026-02-19 15:25:27', '1', '2026-02-19 15:25:27', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2039, 'MES 点检保养项目类型', 'mes_dv_subject_type', 0, 'MES 设备管理 - 点检保养项目类型(1=设备点检,2=设备保养)', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2040, 'MES 保养记录状态', 'mes_mainten_record_status', 0, NULL, 'admin', '2026-02-20 02:59:55', 'admin', '2026-02-20 02:59:55', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2041, 'MES 保养结果', 'mes_mainten_status', 0, NULL, 'admin', '2026-02-20 02:59:55', 'admin', '2026-02-20 02:59:55', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2042, 'MES 点检保养周期类型', 'mes_dv_cycle_type', 0, 'MES 设备管理 - 点检保养周期类型(1=天,2=周,3=月,4=年)', '1', '2026-02-20 07:11:43', '1', '2026-02-20 07:11:43', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2043, 'MES 点检保养方案状态', 'mes_dv_check_plan_status', 0, 'MES 设备管理 - 点检保养方案状态(0=草稿,1=已启用)', '1', '2026-02-20 07:11:43', '1', '2026-02-20 07:11:43', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2044, 'MES 点检记录状态', 'mes_dv_check_record_status', 0, NULL, 'admin', '2026-02-20 09:46:19', 'admin', '2026-02-20 09:46:19', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2045, 'MES 点检结果', 'mes_dv_check_result', 0, NULL, 'admin', '2026-02-20 09:46:19', 'admin', '2026-02-20 09:46:19', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2046, 'MES 维修工单状态', 'mes_dv_repair_status', 0, 'MES 设备管理 - 维修工单状态(10=待维修,20=维修中,30=已完成,40=已验收)', '1', '2026-02-20 10:56:24', '1', '2026-02-20 10:56:24', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2047, 'MES 维修结果', 'mes_dv_repair_result', 0, 'MES 设备管理 - 维修结果(1=修复成功,2=报废)', '1', '2026-02-20 10:56:24', '1', '2026-02-20 10:56:24', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2049, 'MES 检测结果', 'mes_qc_check_result', 0, '来料检验的最终结果判定', '1', '2026-02-20 11:23:35', '1', '2026-02-20 11:23:35', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2050, 'MES 来源单据类型', 'mes_qc_source_doc_type', 0, 'IQC 来料检验的来源单据类型', '1', '2026-02-20 11:23:35', '1', '2026-02-20 11:23:35', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2051, 'MES 安灯处置状态', 'mes_pro_andon_status', 0, 'MES 生产管理 - 安灯处置状态(0=未处置,1=已处置)', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2052, 'MES 安灯级别', 'mes_pro_andon_level', 0, 'MES 生产管理 - 安灯级别(1=一级,2=二级,3=三级)', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2053, 'MES 生产报工状态', 'mes_pro_feedback_status', 0, 'MES 生产管理 - 报工状态(0=草稿,1=审批中,2=待检验,3=已完成,4=已取消)', '1', '2026-02-21 00:50:32', '1', '2026-02-21 00:50:32', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2054, 'MES 生产报工类型', 'mes_pro_feedback_type', 0, 'MES 生产管理 - 报工类型(1=自行报工,2=统一报工)', '1', '2026-02-21 00:50:32', '1', '2026-02-21 00:50:32', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2055, 'MES 生产报工途径', 'mes_pro_feedback_channel', 0, 'MES 生产管理 - 报工途径(PC/APP/PDA)', '1', '2026-02-21 00:50:32', '1', '2026-02-21 00:50:32', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2056, 'MES 质检值类型', 'mes_qc_result_type', 0, '检验结果明细的值类型:浮点/整数/文本/字典/文件', '1', '2026-02-21 13:37:17', '1', '2026-02-21 13:37:17', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2057, 'MES 退货检验类型', 'mes_rqc_type', 0, 'MES 退货检验类型', '1', '2026-02-22 06:43:18', '1', '2026-02-22 06:43:18', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2062, 'MES IPQC 检验类型', 'mes_ipqc_type', 0, 'IPQC 过程检验的检验类型', '1', '2026-02-22 07:01:04', '1', '2026-02-22 07:01:04', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2066, 'MES 到货通知单状态', 'mes_wm_arrival_notice_status', 0, 'MES 到货通知单状态', '1', '2026-02-22 14:53:18', '1', '2026-02-22 14:53:18', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2067, 'MES 采购入库单状态', 'mes_wm_item_receipt_status', 0, 'MES 采购入库单状态', '1', '2026-02-22 14:54:05', '1', '2026-02-22 14:54:05', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2068, 'MES 单据状态', 'mes_order_status', 0, '', '1', '2026-02-23 21:16:03', '1', '2026-02-23 21:17:37', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2069, 'MES 领料出库单状态', 'mes_wm_product_issue_status', 0, 'MES 领料出库单状态', '1', '2026-02-26 16:39:44', '1', '2026-04-05 15:05:11', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2073, 'MES 生产退料单状态', 'mes_wm_return_issue_status', 0, 'MES 生产退料单状态', '1', '2026-02-28 14:11:09', '1', '2026-04-05 15:05:14', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2074, 'MES 生产退料类型', 'mes_wm_return_issue_type', 0, 'MES 生产退料类型', '1', '2026-02-28 14:11:09', '1', '2026-04-05 15:05:16', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2075, 'MES 质量状态', 'mes_wm_quality_status', 0, 'MES 质量状态(待检/合格/不合格)', '1', '2026-02-28 15:00:53', '1', '2026-02-28 15:00:53', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2100, 'MES 产品入库单状态', 'mes_wm_product_receipt_status', 0, 'MES 产品入库单状态', '1', '2026-03-01 06:03:04', '1', '2026-04-05 15:05:49', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2102, 'MES 销售出库单状态', 'mes_wm_product_sales_status', 0, 'MES 销售出库单状态', '1', '2026-03-02 08:55:11', '1', '2026-04-05 15:05:18', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2105, 'MES 杂项入库类型', 'mes_wm_misc_receipt_type', 0, '杂项入库类型', '1', '2026-03-03 07:18:12', '1', '2026-04-05 15:05:23', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2106, 'MES 杂项入库状态', 'mes_wm_misc_receipt_status', 0, '杂项入库状态', '1', '2026-03-03 07:18:12', '1', '2026-04-05 15:05:25', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2109, 'MES 杂项出库类型', 'mes_wm_misc_issue_type', 0, 'MES 杂项出库类型', '1', '2026-03-03 07:34:33', '1', '2026-03-03 07:34:33', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2110, 'MES 外协入库单状态', 'mes_wm_outsource_receipt_status', 0, 'MES 外协入库单状态', '1', '2026-03-03 14:03:20', '1', '2026-03-03 14:03:20', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2112, 'MES 外协发料单状态', 'mes_wm_outsource_issue_status', 0, 'MES 外协发料单状态', '1', '2026-03-03 16:30:56', '1', '2026-04-05 15:05:47', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2113, 'MES 编码规则分段类型', 'mes_md_auto_code_part_type', 0, 'MES 编码规则分段类型', '1', '2026-03-04 14:45:46', '1', '2026-03-04 15:24:40', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2115, 'MES 编码规则补齐方式', 'mes_md_auto_code_padded_method', 0, 'MES 编码规则补齐方式', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2116, 'MES 编码规则循环方式', 'mes_md_auto_code_cycle_method', 0, 'MES 编码规则循环方式', '1', '2026-03-04 14:46:22', '1', '2026-03-04 15:24:40', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2117, 'MES 条码格式', 'mes_wm_barcode_format', 0, 'MES 条码格式', '1', '2026-03-05 14:37:20', '1', '2026-04-05 15:05:27', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2118, 'MES 条码业务类型', 'mes_wm_barcode_biz_type', 0, 'MES 条码业务类型', '1', '2026-03-05 14:37:20', '1', '2026-04-05 15:05:29', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2121, 'MES 装箱单状态', 'mes_wm_package_status', 0, 'MES 装箱单状态', '1', '2026-03-08 02:05:46', '1', '2026-04-05 15:05:35', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2122, 'MES 调拨单状态', 'mes_wm_transfer_status', 0, 'MES 调拨单状态', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2123, 'MES 调拨类型', 'mes_wm_transfer_type', 0, 'MES 调拨类型', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2124, 'MES 盘点类型', 'mes_wm_stock_taking_type', 0, 'MES 盘点类型', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2125, 'MES 盘点方案参数类型', 'mes_wm_stock_taking_plan_param_type', 0, 'MES 盘点方案参数类型', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2126, 'MES 盘点任务状态', 'mes_wm_stock_taking_task_status', 0, 'MES 盘点任务状态', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2127, 'MES 盘点任务行状态', 'mes_wm_stock_taking_task_line_status', 0, 'MES 盘点任务行状态', '1', '2026-03-09 00:00:00', '1', '2026-04-05 15:02:18', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2129, 'MES 物料产品标识', 'mes_md_item_or_product', 0, '物料分类:物料(ITEM) / 产品(PRODUCT)', '1', '2026-03-15 01:55:06', '1', '2026-03-15 01:55:06', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2130, 'MES 供应商退货单状态', 'mes_wm_return_vendor_status', 0, '采购退货单状态', '', '2026-03-29 13:49:57', '', '2026-04-05 15:53:46', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2131, 'MES 发货通知单状态', 'mes_wm_sales_notice_status', 0, 'MES 发货通知单状态', '1', '2026-03-30 08:54:30', '1', '2026-04-05 15:53:46', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2132, 'MES 杂项出库单状态', 'mes_wm_misc_issue_status', 0, '杂项出库单状态', '1', '2026-03-30 15:00:18', '1', '2026-04-05 15:05:41', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2133, 'MES 销售退货单状态', 'mes_wm_return_sales_status', 0, 'MES 销售退货单状态枚举', '1', '2026-04-03 17:20:25', '1', '2026-04-05 15:05:39', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2135, 'MES 上下工状态类型', 'mes_pro_work_record_type', 0, 'MES 上下工状态类型', '1', '2026-04-05 14:07:27', '1', '2026-04-05 14:07:27', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2138, 'MES 生产入库单状态', 'mes_wm_product_produce_status', 0, 'MES 生产入库单状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0', '1970-01-01 00:00:00'); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2200, 'IM 消息类型', 'im_message_type', 0, '对应 ImMessageTypeEnum', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2201, 'IM 私聊消息状态', 'im_private_message_status', 0, '对应 ImMessageStatusEnum;私聊 0=未读 / 2=已撤回 / 3=已读', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 15:14:36', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2202, 'IM 群消息回执状态', 'im_group_message_receipt_status', 0, '对应 ImGroupMessageReceiptStatusEnum', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2203, 'IM 好友状态', 'im_friend_status', 0, '0=正常 / 1=已删除', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2204, 'IM 群状态', 'im_group_status', 0, '0=正常 / 1=已解散', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2205, 'IM 群聊消息状态', 'im_group_message_status', 0, '对应 ImMessageStatusEnum;群聊 0=正常 / 2=已撤回(无未读概念)', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2206, 'IM 群成员角色', 'im_group_member_role', 0, NULL, '1', '2026-05-02 02:14:12', '1', '2026-05-02 02:14:12', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2207, 'IM 好友添加来源', 'im_friend_add_source', 0, NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2208, 'IM 好友申请处理结果', 'im_friend_request_handle_result', 0, NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2209, 'IM 加群来源', 'im_group_add_source', 0, NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (2210, 'IM 加群申请处理结果', 'im_group_request_handle_result', 0, NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061050, '往来企业类型', 'merchant_type', 0, 'WMS 往来企业类型', '1', '2026-05-10 15:26:09', '1', '2026-05-10 15:26:09', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061060, 'WMS 单据类型', 'wms_order_type', 0, 'WMS 单据类型', '1', '2026-05-10 17:51:46', '1', '2026-05-14 08:14:09', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061070, 'WMS 单据状态', 'wms_order_status', 0, 'WMS 单据状态', '1', '2026-05-12 13:40:29', '1', '2026-05-12 13:40:29', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061080, '入库单类型', 'wms_receipt_order_type', 0, 'WMS 入库单类型', '1', '2026-05-11 11:21:49', '1', '2026-05-12 13:40:29', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061090, '出库单类型', 'wms_shipment_order_type', 0, 'WMS 出库单类型', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061092, 'IM 通话媒体类型', 'im_rtc_call_media_type', 0, NULL, 'admin', '2026-05-16 11:34:50', 'admin', '2026-05-16 11:34:50', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061093, 'IM 通话会话类型', 'im_rtc_call_conversation_type', 0, '1=私聊;2=群聊', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061094, 'IM 通话状态', 'im_rtc_call_status', 0, '10=创建;20=进行中;30=已结束', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061095, 'IM 通话结束原因', 'im_rtc_call_end_reason', 0, '1=通话结束;2=已拒绝;3=已取消;4=无人接听;5=对方正忙;9=通话异常', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061096, 'IM 通话参与角色', 'im_rtc_participant_role', 0, '1=发起人;2=被邀请者;3=主动加入者', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061097, 'IM 通话参与状态', 'im_rtc_participant_status', 0, '10=邀请中;20=已加入;30=已拒绝;40=未应答;50=已离开', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '0', NULL); +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time) VALUES (1061098, 'IM 频道素材内容类型', 'im_channel_material_type', 0, '1=站内富文本 / 2=外链', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', '0', NULL); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_dict_type_seq; +CREATE SEQUENCE system_dict_type_seq + START 1061099; + +-- ---------------------------- +-- Table structure for system_login_log +-- ---------------------------- +DROP TABLE IF EXISTS system_login_log; +CREATE TABLE system_login_log ( + id int8 NOT NULL, + log_type int8 NOT NULL, + trace_id varchar(64) NOT NULL DEFAULT '', + user_id int8 NOT NULL DEFAULT 0, + user_type int2 NOT NULL DEFAULT 0, + username varchar(50) NOT NULL DEFAULT '', + result int2 NOT NULL, + user_ip varchar(50) NOT NULL, + user_agent varchar(512) NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_login_log ADD CONSTRAINT pk_system_login_log PRIMARY KEY (id); + +CREATE INDEX idx_system_login_log_01 ON system_login_log (username); +CREATE INDEX idx_system_login_log_02 ON system_login_log (create_time); + +COMMENT ON COLUMN system_login_log.id IS '访问ID'; +COMMENT ON COLUMN system_login_log.log_type IS '日志类型'; +COMMENT ON COLUMN system_login_log.trace_id IS '链路追踪编号'; +COMMENT ON COLUMN system_login_log.user_id IS '用户编号'; +COMMENT ON COLUMN system_login_log.user_type IS '用户类型'; +COMMENT ON COLUMN system_login_log.username IS '用户账号'; +COMMENT ON COLUMN system_login_log.result IS '登陆结果'; +COMMENT ON COLUMN system_login_log.user_ip IS '用户 IP'; +COMMENT ON COLUMN system_login_log.user_agent IS '浏览器 UA'; +COMMENT ON COLUMN system_login_log.creator IS '创建者'; +COMMENT ON COLUMN system_login_log.create_time IS '创建时间'; +COMMENT ON COLUMN system_login_log.updater IS '更新者'; +COMMENT ON COLUMN system_login_log.update_time IS '更新时间'; +COMMENT ON COLUMN system_login_log.deleted IS '是否删除'; +COMMENT ON COLUMN system_login_log.tenant_id IS '租户编号'; +COMMENT ON TABLE system_login_log IS '系统访问记录'; + +DROP SEQUENCE IF EXISTS system_login_log_seq; +CREATE SEQUENCE system_login_log_seq + START 1; + +-- ---------------------------- +-- Table structure for system_mail_account +-- ---------------------------- +DROP TABLE IF EXISTS system_mail_account; +CREATE TABLE system_mail_account ( + id int8 NOT NULL, + mail varchar(255) NOT NULL, + username varchar(255) NOT NULL, + password varchar(255) NOT NULL, + host varchar(255) NOT NULL, + port int4 NOT NULL, + ssl_enable bool NOT NULL DEFAULT '0', + starttls_enable bool NOT NULL DEFAULT '0', + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_mail_account ADD CONSTRAINT pk_system_mail_account PRIMARY KEY (id); + +COMMENT ON COLUMN system_mail_account.id IS '主键'; +COMMENT ON COLUMN system_mail_account.mail IS '邮箱'; +COMMENT ON COLUMN system_mail_account.username IS '用户名'; +COMMENT ON COLUMN system_mail_account.password IS '密码'; +COMMENT ON COLUMN system_mail_account.host IS 'SMTP 服务器域名'; +COMMENT ON COLUMN system_mail_account.port IS 'SMTP 服务器端口'; +COMMENT ON COLUMN system_mail_account.ssl_enable IS '是否开启 SSL'; +COMMENT ON COLUMN system_mail_account.starttls_enable IS '是否开启 STARTTLS'; +COMMENT ON COLUMN system_mail_account.creator IS '创建者'; +COMMENT ON COLUMN system_mail_account.create_time IS '创建时间'; +COMMENT ON COLUMN system_mail_account.updater IS '更新者'; +COMMENT ON COLUMN system_mail_account.update_time IS '更新时间'; +COMMENT ON COLUMN system_mail_account.deleted IS '是否删除'; +COMMENT ON TABLE system_mail_account IS '邮箱账号表'; + +-- ---------------------------- +-- Records of system_mail_account +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_mail_account (id, mail, username, password, host, port, ssl_enable, starttls_enable, creator, create_time, updater, update_time, deleted) VALUES (1, '7684413@qq.com', '7684413@qq.com', '1234576', '127.0.0.1', 8080, '0', '0', '1', '2023-01-25 17:39:52', '1', '2025-04-04 16:34:40', '0'); +INSERT INTO system_mail_account (id, mail, username, password, host, port, ssl_enable, starttls_enable, creator, create_time, updater, update_time, deleted) VALUES (2, 'ydym_test@163.com', 'ydym_test@163.com', 'WBZTEINMIFVRYSOE', 'smtp.163.com', 465, '1', '0', '1', '2023-01-26 01:26:03', '1', '2025-12-20 18:09:32', '0'); +INSERT INTO system_mail_account (id, mail, username, password, host, port, ssl_enable, starttls_enable, creator, create_time, updater, update_time, deleted) VALUES (3, '76854114@qq.com', '3335', '11234', 'yunai1.cn', 466, '0', '0', '1', '2023-01-27 15:06:38', '1', '2023-01-27 07:08:36', '1'); +INSERT INTO system_mail_account (id, mail, username, password, host, port, ssl_enable, starttls_enable, creator, create_time, updater, update_time, deleted) VALUES (4, '7685413x@qq.com', '2', '3', '4', 5, '1', '0', '1', '2023-04-12 23:05:06', '1', '2023-04-12 15:05:11', '1'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_mail_account_seq; +CREATE SEQUENCE system_mail_account_seq + START 5; + +-- ---------------------------- +-- Table structure for system_mail_log +-- ---------------------------- +DROP TABLE IF EXISTS system_mail_log; +CREATE TABLE system_mail_log ( + id int8 NOT NULL, + user_id int8 NULL DEFAULT NULL, + user_type int2 NULL DEFAULT NULL, + to_mails varchar(1024) NOT NULL, + cc_mails varchar(1024) NULL DEFAULT NULL, + bcc_mails varchar(1024) NULL DEFAULT NULL, + account_id int8 NOT NULL, + from_mail varchar(255) NOT NULL, + template_id int8 NOT NULL, + template_code varchar(63) NOT NULL, + template_nickname varchar(255) NULL DEFAULT NULL, + template_title varchar(255) NOT NULL, + template_content text NOT NULL, + template_params varchar(255) NOT NULL, + send_status int2 NOT NULL DEFAULT 0, + send_time timestamp NULL DEFAULT NULL, + send_message_id varchar(255) NULL DEFAULT NULL, + send_exception varchar(4096) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_mail_log ADD CONSTRAINT pk_system_mail_log PRIMARY KEY (id); + +COMMENT ON COLUMN system_mail_log.id IS '编号'; +COMMENT ON COLUMN system_mail_log.user_id IS '用户编号'; +COMMENT ON COLUMN system_mail_log.user_type IS '用户类型'; +COMMENT ON COLUMN system_mail_log.to_mails IS '接收邮箱地址'; +COMMENT ON COLUMN system_mail_log.cc_mails IS '抄送邮箱地址'; +COMMENT ON COLUMN system_mail_log.bcc_mails IS '密送邮箱地址'; +COMMENT ON COLUMN system_mail_log.account_id IS '邮箱账号编号'; +COMMENT ON COLUMN system_mail_log.from_mail IS '发送邮箱地址'; +COMMENT ON COLUMN system_mail_log.template_id IS '模板编号'; +COMMENT ON COLUMN system_mail_log.template_code IS '模板编码'; +COMMENT ON COLUMN system_mail_log.template_nickname IS '模版发送人名称'; +COMMENT ON COLUMN system_mail_log.template_title IS '邮件标题'; +COMMENT ON COLUMN system_mail_log.template_content IS '邮件内容'; +COMMENT ON COLUMN system_mail_log.template_params IS '邮件参数'; +COMMENT ON COLUMN system_mail_log.send_status IS '发送状态'; +COMMENT ON COLUMN system_mail_log.send_time IS '发送时间'; +COMMENT ON COLUMN system_mail_log.send_message_id IS '发送返回的消息 ID'; +COMMENT ON COLUMN system_mail_log.send_exception IS '发送异常'; +COMMENT ON COLUMN system_mail_log.creator IS '创建者'; +COMMENT ON COLUMN system_mail_log.create_time IS '创建时间'; +COMMENT ON COLUMN system_mail_log.updater IS '更新者'; +COMMENT ON COLUMN system_mail_log.update_time IS '更新时间'; +COMMENT ON COLUMN system_mail_log.deleted IS '是否删除'; +COMMENT ON TABLE system_mail_log IS '邮件日志表'; + +DROP SEQUENCE IF EXISTS system_mail_log_seq; +CREATE SEQUENCE system_mail_log_seq + START 1; + +-- ---------------------------- +-- Table structure for system_mail_template +-- ---------------------------- +DROP TABLE IF EXISTS system_mail_template; +CREATE TABLE system_mail_template ( + id int8 NOT NULL, + name varchar(63) NOT NULL, + code varchar(63) NOT NULL, + account_id int8 NOT NULL, + nickname varchar(255) NULL DEFAULT NULL, + title varchar(255) NOT NULL, + content varchar(10240) NOT NULL, + params varchar(255) NOT NULL, + status int2 NOT NULL, + remark varchar(255) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_mail_template ADD CONSTRAINT pk_system_mail_template PRIMARY KEY (id); + +COMMENT ON COLUMN system_mail_template.id IS '编号'; +COMMENT ON COLUMN system_mail_template.name IS '模板名称'; +COMMENT ON COLUMN system_mail_template.code IS '模板编码'; +COMMENT ON COLUMN system_mail_template.account_id IS '发送的邮箱账号编号'; +COMMENT ON COLUMN system_mail_template.nickname IS '发送人名称'; +COMMENT ON COLUMN system_mail_template.title IS '模板标题'; +COMMENT ON COLUMN system_mail_template.content IS '模板内容'; +COMMENT ON COLUMN system_mail_template.params IS '参数数组'; +COMMENT ON COLUMN system_mail_template.status IS '开启状态'; +COMMENT ON COLUMN system_mail_template.remark IS '备注'; +COMMENT ON COLUMN system_mail_template.creator IS '创建者'; +COMMENT ON COLUMN system_mail_template.create_time IS '创建时间'; +COMMENT ON COLUMN system_mail_template.updater IS '更新者'; +COMMENT ON COLUMN system_mail_template.update_time IS '更新时间'; +COMMENT ON COLUMN system_mail_template.deleted IS '是否删除'; +COMMENT ON TABLE system_mail_template IS '邮件模版表'; + +-- ---------------------------- +-- Records of system_mail_template +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_mail_template (id, name, code, account_id, nickname, title, content, params, status, remark, creator, create_time, updater, update_time, deleted) VALUES (13, '后台用户短信登录', 'admin-sms-login', 1, '奥特曼', '你猜我猜', '

您的验证码是{code},名字是{name}

', '["code","name"]', 0, '3', '1', '2021-10-11 08:10:00', '1', '2023-12-02 19:51:14', '0'); +INSERT INTO system_mail_template (id, name, code, account_id, nickname, title, content, params, status, remark, creator, create_time, updater, update_time, deleted) VALUES (14, '测试模版', 'test_01', 2, '芋艿', '一个标题', '

你是 {key01} 吗?


是的话,赶紧 {key02} 一下!

', '["key01","key02"]', 0, NULL, '1', '2023-01-26 01:27:40', '1', '2025-07-26 21:48:45', '0'); +INSERT INTO system_mail_template (id, name, code, account_id, nickname, title, content, params, status, remark, creator, create_time, updater, update_time, deleted) VALUES (15, '3', '2', 2, '7', '4', '

45

', '[]', 1, '80', '1', '2023-01-27 15:50:35', '1', '2025-07-26 21:47:49', '1'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_mail_template_seq; +CREATE SEQUENCE system_mail_template_seq + START 16; + +-- ---------------------------- +-- Table structure for system_menu +-- ---------------------------- +DROP TABLE IF EXISTS system_menu; +CREATE TABLE system_menu ( + id int8 NOT NULL, + name varchar(50) NOT NULL, + permission varchar(100) NOT NULL DEFAULT '', + type int2 NOT NULL, + sort int4 NOT NULL DEFAULT 0, + parent_id int8 NOT NULL DEFAULT 0, + path varchar(200) NULL DEFAULT '', + icon varchar(100) NULL DEFAULT '#', + component varchar(255) NULL DEFAULT NULL, + component_name varchar(255) NULL DEFAULT NULL, + status int2 NOT NULL DEFAULT 0, + visible bool NOT NULL DEFAULT '1', + keep_alive bool NOT NULL DEFAULT '1', + always_show bool NOT NULL DEFAULT '1', + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_menu ADD CONSTRAINT pk_system_menu PRIMARY KEY (id); + +COMMENT ON COLUMN system_menu.id IS '菜单ID'; +COMMENT ON COLUMN system_menu.name IS '菜单名称'; +COMMENT ON COLUMN system_menu.permission IS '权限标识'; +COMMENT ON COLUMN system_menu.type IS '菜单类型'; +COMMENT ON COLUMN system_menu.sort IS '显示顺序'; +COMMENT ON COLUMN system_menu.parent_id IS '父菜单ID'; +COMMENT ON COLUMN system_menu.path IS '路由地址'; +COMMENT ON COLUMN system_menu.icon IS '菜单图标'; +COMMENT ON COLUMN system_menu.component IS '组件路径'; +COMMENT ON COLUMN system_menu.component_name IS '组件名'; +COMMENT ON COLUMN system_menu.status IS '菜单状态'; +COMMENT ON COLUMN system_menu.visible IS '是否可见'; +COMMENT ON COLUMN system_menu.keep_alive IS '是否缓存'; +COMMENT ON COLUMN system_menu.always_show IS '是否总是显示'; +COMMENT ON COLUMN system_menu.creator IS '创建者'; +COMMENT ON COLUMN system_menu.create_time IS '创建时间'; +COMMENT ON COLUMN system_menu.updater IS '更新者'; +COMMENT ON COLUMN system_menu.update_time IS '更新时间'; +COMMENT ON COLUMN system_menu.deleted IS '是否删除'; +COMMENT ON TABLE system_menu IS '菜单权限表'; + +-- ---------------------------- +-- Records of system_menu +-- ---------------------------- +-- @formatter:off +BEGIN; +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 (1, '系统管理', '', 1, 10, 0, '/system', 'ep:tools', NULL, NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2025-03-15 21:30:27', '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 (2, '基础设施', '', 1, 20, 0, '/infra', 'ep:monitor', NULL, NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-03-01 08:28:40', '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 (5, 'OA 示例', '', 1, 40, 1185, 'oa', 'fa:road', NULL, NULL, 0, '1', '1', '1', 'admin', '2021-09-20 16:26:19', '1', '2024-02-29 12:38:13', '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 (100, '用户管理', 'system:user:list', 2, 1, 1, 'user', 'ep:avatar', 'system/user/index', 'SystemUser', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2026-01-01 18:43:01', '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 (101, '角色管理', '', 2, 2, 1, 'role', 'ep:user', 'system/role/index', 'SystemRole', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2026-01-05 19:30:33', '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 (102, '菜单管理', '', 2, 3, 1, 'menu', 'ep:menu', 'system/menu/index', 'SystemMenu', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:03:50', '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 (103, '部门管理', '', 2, 4, 1, 'dept', 'fa:address-card', 'system/dept/index', 'SystemDept', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:06:28', '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 (104, '岗位管理', '', 2, 5, 1, 'post', 'fa:address-book-o', 'system/post/index', 'SystemPost', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:06:39', '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 (105, '字典管理', '', 2, 6, 1, 'dict', 'ep:collection', 'system/dict/index', 'SystemDictType', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:07:12', '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 (106, '配置管理', '', 2, 8, 2, 'config', 'fa:connectdevelop', 'infra/config/index', 'InfraConfig', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:02:45', '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 (107, '通知公告', '', 2, 4, 2739, 'notice', 'ep:takeaway-box', 'system/notice/index', 'SystemNotice', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-22 23:56:17', '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 (108, '审计日志', '', 1, 9, 1, 'log', 'ep:document-copy', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:08:30', '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 (109, '令牌管理', '', 2, 2, 1261, 'token', 'fa:key', 'system/oauth2/token/index', 'SystemTokenClient', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:13:48', '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 (110, '定时任务', '', 2, 7, 2, 'job', 'fa-solid:tasks', 'infra/job/index', 'InfraJob', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 08:57:36', '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 (111, 'MySQL 监控', '', 2, 1, 2740, 'druid', 'fa-solid:box', 'infra/druid/index', 'InfraDruid', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:05:58', '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 (112, 'Java 监控', '', 2, 3, 2740, 'admin-server', 'ep:coffee-cup', 'infra/server/index', 'InfraAdminServer', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:06:57', '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 (113, 'Redis 监控', '', 2, 2, 2740, 'redis', 'fa:reddit-square', 'infra/redis/index', 'InfraRedis', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:06:09', '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 (114, '表单构建', 'infra:build:list', 2, 2, 2, 'build', 'fa:wpforms', 'infra/build/index', 'InfraBuild', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 08:51:35', '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 (115, '代码生成', 'infra:codegen:query', 2, 1, 2, 'codegen', 'ep:document-copy', 'infra/codegen/index', 'InfraCodegen', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 08:51:06', '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 (116, 'API 接口', 'infra:swagger:list', 2, 3, 2, 'swagger', 'fa:fighter-jet', 'infra/swagger/index', 'InfraSwagger', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:01:24', '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 (500, '操作日志', '', 2, 1, 108, 'operate-log', 'ep:position', 'system/operatelog/index', 'SystemOperateLog', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:09:59', '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 (501, '登录日志', '', 2, 2, 108, 'login-log', 'ep:promotion', 'system/loginlog/index', 'SystemLoginLog', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:10:29', '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 (1001, '用户查询', 'system:user:query', 3, 1, 100, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1002, '用户新增', 'system:user:create', 3, 2, 100, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1003, '用户修改', 'system:user:update', 3, 3, 100, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1004, '用户删除', 'system:user:delete', 3, 4, 100, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1005, '用户导出', 'system:user:export', 3, 5, 100, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1006, '用户导入', 'system:user:import', 3, 6, 100, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1007, '重置密码', 'system:user:update-password', 3, 7, 100, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1008, '角色查询', 'system:role:query', 3, 1, 101, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1009, '角色新增', 'system:role:create', 3, 2, 101, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1010, '角色修改', 'system:role:update', 3, 3, 101, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1011, '角色删除', 'system:role:delete', 3, 4, 101, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1012, '角色导出', 'system:role:export', 3, 5, 101, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1013, '菜单查询', 'system:menu:query', 3, 1, 102, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1014, '菜单新增', 'system:menu:create', 3, 2, 102, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1015, '菜单修改', 'system:menu:update', 3, 3, 102, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1016, '菜单删除', 'system:menu:delete', 3, 4, 102, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1017, '部门查询', 'system:dept:query', 3, 1, 103, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1018, '部门新增', 'system:dept:create', 3, 2, 103, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1019, '部门修改', 'system:dept:update', 3, 3, 103, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1020, '部门删除', 'system:dept:delete', 3, 4, 103, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1021, '岗位查询', 'system:post:query', 3, 1, 104, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1022, '岗位新增', 'system:post:create', 3, 2, 104, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1023, '岗位修改', 'system:post:update', 3, 3, 104, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1024, '岗位删除', 'system:post:delete', 3, 4, 104, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1025, '岗位导出', 'system:post:export', 3, 5, 104, '', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1026, '字典查询', 'system:dict:query', 3, 1, 105, '#', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1027, '字典新增', 'system:dict:create', 3, 2, 105, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1028, '字典修改', 'system:dict:update', 3, 3, 105, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1029, '字典删除', 'system:dict:delete', 3, 4, 105, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1030, '字典导出', 'system:dict:export', 3, 5, 105, '#', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1031, '配置查询', 'infra:config:query', 3, 1, 106, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1032, '配置新增', 'infra:config:create', 3, 2, 106, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1033, '配置修改', 'infra:config:update', 3, 3, 106, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1034, '配置删除', 'infra:config:delete', 3, 4, 106, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1035, '配置导出', 'infra:config:export', 3, 5, 106, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1036, '公告查询', 'system:notice:query', 3, 1, 107, '#', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1037, '公告新增', 'system:notice:create', 3, 2, 107, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1038, '公告修改', 'system:notice:update', 3, 3, 107, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1039, '公告删除', 'system:notice:delete', 3, 4, 107, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1040, '操作查询', 'system:operate-log:query', 3, 1, 500, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1042, '日志导出', 'system:operate-log:export', 3, 2, 500, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1043, '登录查询', 'system:login-log:query', 3, 1, 501, '#', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1045, '日志导出', 'system:login-log:export', 3, 3, 501, '#', '#', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1046, '令牌列表', 'system:oauth2-token:page', 3, 1, 109, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-05-09 23:54:42', '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 (1048, '令牌删除', 'system:oauth2-token:delete', 3, 2, 109, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-05-09 23:54:53', '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 (1050, '任务新增', 'infra:job:create', 3, 2, 110, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1051, '任务修改', 'infra:job:update', 3, 3, 110, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1052, '任务删除', 'infra:job:delete', 3, 4, 110, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1053, '状态修改', 'infra:job:update', 3, 5, 110, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1054, '任务导出', 'infra:job:export', 3, 7, 110, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', '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 (1056, '生成修改', 'infra:codegen:update', 3, 2, 115, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1057, '生成删除', 'infra:codegen:delete', 3, 3, 115, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1058, '导入代码', 'infra:codegen:create', 3, 2, 115, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1059, '预览代码', 'infra:codegen:preview', 3, 4, 115, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1060, '生成代码', 'infra:codegen:download', 3, 5, 115, '', '', '', NULL, 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', '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 (1063, '设置角色菜单权限', 'system:permission:assign-role-menu', 3, 6, 101, '', '', '', NULL, 0, '1', '1', '1', '', '2021-01-06 17:53:44', '', '2022-04-20 17:03:10', '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 (1064, '设置角色数据权限', 'system:permission:assign-role-data-scope', 3, 7, 101, '', '', '', NULL, 0, '1', '1', '1', '', '2021-01-06 17:56:31', '', '2022-04-20 17:03:10', '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 (1065, '设置用户角色', 'system:permission:assign-user-role', 3, 8, 101, '', '', '', NULL, 0, '1', '1', '1', '', '2021-01-07 10:23:28', '', '2022-04-20 17:03:10', '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 (1066, '获得 Redis 监控信息', 'infra:redis:get-monitor-info', 3, 1, 113, '', '', '', NULL, 0, '1', '1', '1', '', '2021-01-26 01:02:31', '', '2022-04-20 17:03:10', '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 (1067, '获得 Redis Key 列表', 'infra:redis:get-key-list', 3, 2, 113, '', '', '', NULL, 0, '1', '1', '1', '', '2021-01-26 01:02:52', '', '2022-04-20 17:03:10', '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 (1070, '代码生成案例', '', 1, 1, 2, 'demo', 'ep:aim', 'infra/testDemo/index', NULL, 0, '1', '1', '1', '', '2021-02-06 12:42:49', '1', '2023-11-15 23:45:53', '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 (1075, '任务触发', 'infra:job:trigger', 3, 8, 110, '', '', '', NULL, 0, '1', '1', '1', '', '2021-02-07 13:03:10', '', '2022-04-20 17:03:10', '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 (1077, '链路追踪', '', 2, 4, 2740, 'skywalking', 'fa:eye', 'infra/skywalking/index', 'InfraSkyWalking', 0, '1', '1', '1', '', '2021-02-08 20:41:31', '1', '2024-04-23 00:07:15', '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 (1078, '访问日志', '', 2, 1, 1083, 'api-access-log', 'ep:place', 'infra/apiAccessLog/index', 'InfraApiAccessLog', 0, '1', '1', '1', '', '2021-02-26 01:32:59', '1', '2024-02-29 08:54:57', '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 (1082, '日志导出', 'infra:api-access-log:export', 3, 2, 1078, '', '', '', NULL, 0, '1', '1', '1', '', '2021-02-26 01:32:59', '1', '2022-04-20 17:03:10', '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 (1083, 'API 日志', '', 2, 4, 2, 'log', 'fa:tasks', NULL, NULL, 0, '1', '1', '1', '', '2021-02-26 02:18:24', '1', '2024-04-22 23:58:36', '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 (1084, '错误日志', 'infra:api-error-log:query', 2, 2, 1083, 'api-error-log', 'ep:warning-filled', 'infra/apiErrorLog/index', 'InfraApiErrorLog', 0, '1', '1', '1', '', '2021-02-26 07:53:20', '1', '2024-02-29 08:55:17', '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 (1085, '日志处理', 'infra:api-error-log:update-status', 3, 2, 1084, '', '', '', NULL, 0, '1', '1', '1', '', '2021-02-26 07:53:20', '1', '2022-04-20 17:03:10', '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 (1086, '日志导出', 'infra:api-error-log:export', 3, 3, 1084, '', '', '', NULL, 0, '1', '1', '1', '', '2021-02-26 07:53:20', '1', '2022-04-20 17:03:10', '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 (1087, '任务查询', 'infra:job:query', 3, 1, 110, '', '', '', NULL, 0, '1', '1', '1', '1', '2021-03-10 01:26:19', '1', '2022-04-20 17:03:10', '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 (1088, '日志查询', 'infra:api-access-log:query', 3, 1, 1078, '', '', '', NULL, 0, '1', '1', '1', '1', '2021-03-10 01:28:04', '1', '2022-04-20 17:03:10', '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 (1089, '日志查询', 'infra:api-error-log:query', 3, 1, 1084, '', '', '', NULL, 0, '1', '1', '1', '1', '2021-03-10 01:29:09', '1', '2022-04-20 17:03:10', '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 (1090, '文件列表', '', 2, 5, 1243, 'file', 'ep:upload-filled', 'infra/file/index', 'InfraFile', 0, '1', '1', '1', '', '2021-03-12 20:16:20', '1', '2024-02-29 08:53:02', '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 (1091, '文件查询', 'infra:file:query', 3, 1, 1090, '', '', '', NULL, 0, '1', '1', '1', '', '2021-03-12 20:16:20', '', '2022-04-20 17:03:10', '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 (1092, '文件删除', 'infra:file:delete', 3, 4, 1090, '', '', '', NULL, 0, '1', '1', '1', '', '2021-03-12 20:16:20', '', '2022-04-20 17:03:10', '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 (1093, '短信管理', '', 1, 1, 2739, 'sms', 'ep:message', NULL, NULL, 0, '1', '1', '1', '1', '2021-04-05 01:10:16', '1', '2024-04-22 23:56:03', '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 (1094, '短信渠道', '', 2, 0, 1093, 'sms-channel', 'fa:stack-exchange', 'system/sms/channel/index', 'SystemSmsChannel', 0, '1', '1', '1', '', '2021-04-01 11:07:15', '1', '2024-02-29 01:15:54', '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 (1095, '短信渠道查询', 'system:sms-channel:query', 3, 1, 1094, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', '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 (1096, '短信渠道创建', 'system:sms-channel:create', 3, 2, 1094, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', '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 (1097, '短信渠道更新', 'system:sms-channel:update', 3, 3, 1094, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', '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 (1098, '短信渠道删除', 'system:sms-channel:delete', 3, 4, 1094, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-01 11:07:15', '', '2022-04-20 17:03:10', '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 (1100, '短信模板', '', 2, 1, 1093, 'sms-template', 'ep:connection', 'system/sms/template/index', 'SystemSmsTemplate', 0, '1', '1', '1', '', '2021-04-01 17:35:17', '1', '2024-02-29 01:16:18', '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 (1101, '短信模板查询', 'system:sms-template:query', 3, 1, 1100, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', '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 (1102, '短信模板创建', 'system:sms-template:create', 3, 2, 1100, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', '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 (1103, '短信模板更新', 'system:sms-template:update', 3, 3, 1100, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', '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 (1104, '短信模板删除', 'system:sms-template:delete', 3, 4, 1100, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', '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 (1105, '短信模板导出', 'system:sms-template:export', 3, 5, 1100, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-01 17:35:17', '', '2022-04-20 17:03:10', '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 (1106, '发送测试短信', 'system:sms-template:send-sms', 3, 6, 1100, '', '', '', NULL, 0, '1', '1', '1', '1', '2021-04-11 00:26:40', '1', '2022-04-20 17:03:10', '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 (1107, '短信日志', '', 2, 2, 1093, 'sms-log', 'fa:edit', 'system/sms/log/index', 'SystemSmsLog', 0, '1', '1', '1', '', '2021-04-11 08:37:05', '1', '2024-02-29 08:49:02', '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 (1108, '短信日志查询', 'system:sms-log:query', 3, 1, 1107, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-11 08:37:05', '', '2022-04-20 17:03:10', '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 (1109, '短信日志导出', 'system:sms-log:export', 3, 5, 1107, '', '', '', NULL, 0, '1', '1', '1', '', '2021-04-11 08:37:05', '', '2022-04-20 17:03:10', '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 (1117, '支付管理', '', 1, 30, 0, '/pay', 'ep:money', NULL, NULL, 0, '1', '1', '1', '1', '2021-12-25 16:43:41', '1', '2024-02-29 08:58:38', '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 (1118, '请假查询', '', 2, 0, 5, 'leave', 'fa:leanpub', 'bpm/oa/leave/index', 'BpmOALeave', 0, '1', '1', '1', '', '2021-09-20 08:51:03', '1', '2024-02-29 12:38:21', '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 (1119, '请假申请查询', 'bpm:oa-leave:query', 3, 1, 1118, '', '', '', NULL, 0, '1', '1', '1', '', '2021-09-20 08:51:03', '1', '2022-04-20 17:03:10', '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 (1120, '请假申请创建', 'bpm:oa-leave:create', 3, 2, 1118, '', '', '', NULL, 0, '1', '1', '1', '', '2021-09-20 08:51:03', '1', '2022-04-20 17:03:10', '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 (1126, '应用信息', '', 2, 1, 1117, 'app', 'fa:apple', 'pay/app/index', 'PayApp', 0, '1', '1', '1', '', '2021-11-10 01:13:30', '1', '2024-02-29 08:59:55', '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 (1127, '支付应用信息查询', 'pay:app:query', 3, 1, 1126, '', '', '', NULL, 0, '1', '1', '1', '', '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', '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 (1128, '支付应用信息创建', 'pay:app:create', 3, 2, 1126, '', '', '', NULL, 0, '1', '1', '1', '', '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', '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 (1129, '支付应用信息更新', 'pay:app:update', 3, 3, 1126, '', '', '', NULL, 0, '1', '1', '1', '', '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', '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 (1130, '支付应用信息删除', 'pay:app:delete', 3, 4, 1126, '', '', '', NULL, 0, '1', '1', '1', '', '2021-11-10 01:13:31', '', '2022-04-20 17:03:10', '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 (1132, '秘钥解析', 'pay:channel:parsing', 3, 6, 1129, '', '', '', NULL, 0, '1', '1', '1', '1', '2021-11-08 15:15:47', '1', '2022-04-20 17:03:10', '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 (1133, '支付商户信息查询', 'pay:merchant:query', 3, 1, 1132, '', '', '', NULL, 0, '1', '1', '1', '', '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', '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 (1134, '支付商户信息创建', 'pay:merchant:create', 3, 2, 1132, '', '', '', NULL, 0, '1', '1', '1', '', '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', '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 (1135, '支付商户信息更新', 'pay:merchant:update', 3, 3, 1132, '', '', '', NULL, 0, '1', '1', '1', '', '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', '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 (1136, '支付商户信息删除', 'pay:merchant:delete', 3, 4, 1132, '', '', '', NULL, 0, '1', '1', '1', '', '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', '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 (1137, '支付商户信息导出', 'pay:merchant:export', 3, 5, 1132, '', '', '', NULL, 0, '1', '1', '1', '', '2021-11-10 01:13:41', '', '2022-04-20 17:03:10', '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 (1138, '租户列表', '', 2, 0, 1224, 'list', 'ep:house', 'system/tenant/index', 'SystemTenant', 0, '1', '1', '1', '', '2021-12-14 12:31:43', '1', '2024-02-29 01:01:10', '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 (1139, '租户查询', 'system:tenant:query', 3, 1, 1138, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', '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 (1140, '租户创建', 'system:tenant:create', 3, 2, 1138, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', '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 (1141, '租户更新', 'system:tenant:update', 3, 3, 1138, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', '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 (1142, '租户删除', 'system:tenant:delete', 3, 4, 1138, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', '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 (1143, '租户导出', 'system:tenant:export', 3, 5, 1138, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-14 12:31:44', '', '2022-04-20 17:03:10', '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 (1150, '秘钥解析', '', 3, 6, 1129, '', '', '', NULL, 0, '1', '1', '1', '1', '2021-11-08 15:15:47', '1', '2022-04-20 17:03:10', '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 (1161, '退款订单', '', 2, 3, 1117, 'refund', 'fa:registered', 'pay/refund/index', 'PayRefund', 0, '1', '1', '1', '', '2021-12-25 08:29:07', '1', '2024-02-29 08:59:20', '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 (1162, '退款订单查询', 'pay:refund:query', 3, 1, 1161, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', '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 (1166, '退款订单导出', 'pay:refund:export', 3, 5, 1161, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', '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 (1173, '支付订单', '', 2, 2, 1117, 'order', 'fa:cc-paypal', 'pay/order/index', 'PayOrder', 0, '1', '1', '1', '', '2021-12-25 08:49:43', '1', '2024-02-29 08:59:43', '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 (1174, '支付订单查询', 'pay:order:query', 3, 1, 1173, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', '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 (1178, '支付订单导出', 'pay:order:export', 3, 5, 1173, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', '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 (1185, '工作流程', '', 1, 50, 0, '/bpm', 'fa:medium', NULL, NULL, 0, '1', '1', '1', '1', '2021-12-30 20:26:36', '1', '2024-02-29 12:43:43', '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 (1186, '流程管理', '', 1, 10, 1185, 'manager', 'fa:dedent', NULL, NULL, 0, '1', '1', '1', '1', '2021-12-30 20:28:30', '1', '2024-02-29 12:36:02', '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 (1187, '流程表单', '', 2, 2, 1186, 'form', 'fa:hdd-o', 'bpm/form/index', 'BpmForm', 0, '1', '1', '1', '', '2021-12-30 12:38:22', '1', '2024-03-19 12:25:25', '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 (1188, '表单查询', 'bpm:form:query', 3, 1, 1187, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', '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 (1189, '表单创建', 'bpm:form:create', 3, 2, 1187, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', '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 (1190, '表单更新', 'bpm:form:update', 3, 3, 1187, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', '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 (1191, '表单删除', 'bpm:form:delete', 3, 4, 1187, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', '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 (1192, '表单导出', 'bpm:form:export', 3, 5, 1187, '', '', '', NULL, 0, '1', '1', '1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', '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 (1193, '流程模型', '', 2, 1, 1186, 'model', 'fa-solid:project-diagram', 'bpm/model/index', 'BpmModel', 0, '1', '1', '1', '1', '2021-12-31 23:24:58', '1', '2024-03-19 12:25:19', '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 (1194, '模型查询', 'bpm:model:query', 3, 1, 1193, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-03 19:01:10', '1', '2022-04-20 17:03:10', '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 (1195, '模型创建', 'bpm:model:create', 3, 2, 1193, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-03 19:01:24', '1', '2022-04-20 17:03:10', '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 (1197, '模型更新', 'bpm:model:update', 3, 4, 1193, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-03 19:02:28', '1', '2022-04-20 17:03:10', '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 (1198, '模型删除', 'bpm:model:delete', 3, 5, 1193, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-03 19:02:43', '1', '2022-04-20 17:03:10', '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 (1199, '模型发布', 'bpm:model:deploy', 3, 6, 1193, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-03 19:03:24', '1', '2022-04-20 17:03:10', '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 (1200, '审批中心', '', 2, 20, 1185, 'task', 'fa:tasks', NULL, NULL, 0, '1', '1', '1', '1', '2022-01-07 23:51:48', '1', '2024-03-21 00:33:15', '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 (1201, '我的流程', '', 2, 1, 1200, 'my', 'fa-solid:book', 'bpm/processInstance/index', 'BpmProcessInstanceMy', 0, '1', '1', '1', '', '2022-01-07 15:53:44', '1', '2024-03-21 23:52:12', '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 (1202, '流程实例的查询', 'bpm:process-instance:query', 3, 1, 1201, '', '', '', NULL, 0, '1', '1', '1', '', '2022-01-07 15:53:44', '1', '2022-04-20 17:03:10', '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 (1207, '待办任务', '', 2, 10, 1200, 'todo', 'fa:slack', 'bpm/task/todo/index', 'BpmTodoTask', 0, '1', '1', '1', '1', '2022-01-08 10:33:37', '1', '2024-02-29 12:37:39', '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 (1208, '已办任务', '', 2, 20, 1200, 'done', 'fa:delicious', 'bpm/task/done/index', 'BpmDoneTask', 0, '1', '1', '1', '1', '2022-01-08 10:34:13', '1', '2024-02-29 12:37:54', '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 (1209, '用户分组', '', 2, 4, 1186, 'user-group', 'fa:user-secret', 'bpm/group/index', 'BpmUserGroup', 0, '1', '1', '1', '', '2022-01-14 02:14:20', '1', '2024-03-21 23:55:29', '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 (1210, '用户组查询', 'bpm:user-group:query', 3, 1, 1209, '', '', '', NULL, 0, '1', '1', '1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', '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 (1211, '用户组创建', 'bpm:user-group:create', 3, 2, 1209, '', '', '', NULL, 0, '1', '1', '1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', '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 (1212, '用户组更新', 'bpm:user-group:update', 3, 3, 1209, '', '', '', NULL, 0, '1', '1', '1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', '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 (1213, '用户组删除', 'bpm:user-group:delete', 3, 4, 1209, '', '', '', NULL, 0, '1', '1', '1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', '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 (1215, '流程定义查询', 'bpm:process-definition:query', 3, 10, 1193, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-23 00:21:43', '1', '2022-04-20 17:03:10', '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 (1216, '流程任务分配规则查询', 'bpm:task-assign-rule:query', 3, 20, 1193, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-23 00:26:53', '1', '2022-04-20 17:03:10', '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 (1217, '流程任务分配规则创建', 'bpm:task-assign-rule:create', 3, 21, 1193, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-23 00:28:15', '1', '2022-04-20 17:03:10', '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 (1218, '流程任务分配规则更新', 'bpm:task-assign-rule:update', 3, 22, 1193, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-23 00:28:41', '1', '2022-04-20 17:03:10', '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 (1219, '流程实例的创建', 'bpm:process-instance:create', 3, 2, 1201, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-23 00:36:15', '1', '2022-04-20 17:03:10', '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 (1220, '流程实例的取消', 'bpm:process-instance:cancel', 3, 3, 1201, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-23 00:36:33', '1', '2022-04-20 17:03:10', '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 (1221, '流程任务的查询', 'bpm:task:query', 3, 1, 1207, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-23 00:38:52', '1', '2022-04-20 17:03:10', '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 (1222, '流程任务的更新', 'bpm:task:update', 3, 2, 1207, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-01-23 00:39:24', '1', '2022-04-20 17:03:10', '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 (1224, '租户管理', '', 2, 0, 1, 'tenant', 'fa-solid:house-user', NULL, NULL, 0, '1', '1', '1', '1', '2022-02-20 01:41:13', '1', '2024-02-29 00:59:29', '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 (1225, '租户套餐', '', 2, 0, 1224, 'package', 'fa:bars', 'system/tenantPackage/index', 'SystemTenantPackage', 0, '1', '1', '1', '', '2022-02-19 17:44:06', '1', '2024-02-29 01:01:43', '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 (1226, '租户套餐查询', 'system:tenant-package:query', 3, 1, 1225, '', '', '', NULL, 0, '1', '1', '1', '', '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', '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 (1227, '租户套餐创建', 'system:tenant-package:create', 3, 2, 1225, '', '', '', NULL, 0, '1', '1', '1', '', '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', '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 (1228, '租户套餐更新', 'system:tenant-package:update', 3, 3, 1225, '', '', '', NULL, 0, '1', '1', '1', '', '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', '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 (1229, '租户套餐删除', 'system:tenant-package:delete', 3, 4, 1225, '', '', '', NULL, 0, '1', '1', '1', '', '2022-02-19 17:44:06', '', '2022-04-20 17:03:10', '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 (1237, '文件配置', '', 2, 0, 1243, 'file-config', 'fa-solid:file-signature', 'infra/fileConfig/index', 'InfraFileConfig', 0, '1', '1', '1', '', '2022-03-15 14:35:28', '1', '2024-02-29 08:52:54', '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 (1238, '文件配置查询', 'infra:file-config:query', 3, 1, 1237, '', '', '', NULL, 0, '1', '1', '1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', '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 (1239, '文件配置创建', 'infra:file-config:create', 3, 2, 1237, '', '', '', NULL, 0, '1', '1', '1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', '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 (1240, '文件配置更新', 'infra:file-config:update', 3, 3, 1237, '', '', '', NULL, 0, '1', '1', '1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', '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 (1241, '文件配置删除', 'infra:file-config:delete', 3, 4, 1237, '', '', '', NULL, 0, '1', '1', '1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', '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 (1242, '文件配置导出', 'infra:file-config:export', 3, 5, 1237, '', '', '', NULL, 0, '1', '1', '1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', '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 (1243, '文件管理', '', 2, 6, 2, 'file', 'ep:files', NULL, '', 0, '1', '1', '1', '1', '2022-03-16 23:47:40', '1', '2024-04-23 00:02:11', '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 (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'ep:avatar', NULL, NULL, 0, '1', '1', '1', '1', '2022-04-23 01:03:15', '1', '2025-04-29 17:45:38', '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 (1255, '数据源配置', '', 2, 1, 2, 'data-source-config', 'ep:data-analysis', 'infra/dataSourceConfig/index', 'InfraDataSourceConfig', 0, '1', '1', '1', '', '2022-04-27 14:37:32', '1', '2024-02-29 08:51:25', '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 (1256, '数据源配置查询', 'infra:data-source-config:query', 3, 1, 1255, '', '', '', NULL, 0, '1', '1', '1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', '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 (1257, '数据源配置创建', 'infra:data-source-config:create', 3, 2, 1255, '', '', '', NULL, 0, '1', '1', '1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', '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 (1258, '数据源配置更新', 'infra:data-source-config:update', 3, 3, 1255, '', '', '', NULL, 0, '1', '1', '1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', '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 (1259, '数据源配置删除', 'infra:data-source-config:delete', 3, 4, 1255, '', '', '', NULL, 0, '1', '1', '1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', '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 (1260, '数据源配置导出', 'infra:data-source-config:export', 3, 5, 1255, '', '', '', NULL, 0, '1', '1', '1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', '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 (1261, 'OAuth 2.0', '', 2, 10, 1, 'oauth2', 'fa:dashcube', NULL, NULL, 0, '1', '1', '1', '1', '2022-05-09 23:38:17', '1', '2024-02-29 01:12:08', '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 (1263, '应用管理', '', 2, 0, 1261, 'oauth2/application', 'fa:hdd-o', 'system/oauth2/client/index', 'SystemOAuth2Client', 0, '1', '1', '1', '', '2022-05-10 16:26:33', '1', '2024-02-29 01:13:14', '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 (1264, '客户端查询', 'system:oauth2-client:query', 3, 1, 1263, '', '', '', NULL, 0, '1', '1', '1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:06', '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 (1265, '客户端创建', 'system:oauth2-client:create', 3, 2, 1263, '', '', '', NULL, 0, '1', '1', '1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:23', '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 (1266, '客户端更新', 'system:oauth2-client:update', 3, 3, 1263, '', '', '', NULL, 0, '1', '1', '1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:28', '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 (1267, '客户端删除', 'system:oauth2-client:delete', 3, 4, 1263, '', '', '', NULL, 0, '1', '1', '1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:33', '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 (1281, '报表管理', '', 2, 40, 0, '/report', 'ep:pie-chart', NULL, NULL, 0, '1', '1', '1', '1', '2022-07-10 20:22:15', '1', '2024-02-29 12:33:03', '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 (1282, '报表设计器', '', 2, 1, 1281, 'jimu-report', 'ep:trend-charts', 'report/jmreport/index', 'JimuReport', 0, '1', '1', '1', '1', '2022-07-10 20:26:36', '1', '2025-05-03 09:57:07', '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 (2000, '商品中心', '', 1, 60, 2362, 'product', 'fa:product-hunt', NULL, NULL, 0, '1', '1', '1', '', '2022-07-29 15:53:53', '1', '2023-09-30 11:52:36', '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 (2002, '商品分类', '', 2, 2, 2000, 'category', 'ep:cellphone', 'mall/product/category/index', 'ProductCategory', 0, '1', '1', '1', '', '2022-07-29 15:53:53', '1', '2023-08-21 10:27:15', '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 (2003, '分类查询', 'product:category:query', 3, 1, 2002, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', '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 (2004, '分类创建', 'product:category:create', 3, 2, 2002, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', '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 (2005, '分类更新', 'product:category:update', 3, 3, 2002, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', '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 (2006, '分类删除', 'product:category:delete', 3, 4, 2002, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', '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 (2008, '商品品牌', '', 2, 3, 2000, 'brand', 'ep:chicken', 'mall/product/brand/index', 'ProductBrand', 0, '1', '1', '1', '', '2022-07-30 13:52:44', '1', '2023-08-21 10:27:28', '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 (2009, '品牌查询', 'product:brand:query', 3, 1, 2008, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', '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 (2010, '品牌创建', 'product:brand:create', 3, 2, 2008, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', '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 (2011, '品牌更新', 'product:brand:update', 3, 3, 2008, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', '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 (2012, '品牌删除', 'product:brand:delete', 3, 4, 2008, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', '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 (2014, '商品列表', '', 2, 1, 2000, 'spu', 'ep:apple', 'mall/product/spu/index', 'ProductSpu', 0, '1', '1', '1', '', '2022-07-30 14:22:58', '1', '2025-10-08 10:36:52', '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 (2015, '商品查询', 'product:spu:query', 3, 1, 2014, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', '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 (2016, '商品创建', 'product:spu:create', 3, 2, 2014, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', '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 (2017, '商品更新', 'product:spu:update', 3, 3, 2014, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', '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 (2018, '商品删除', 'product:spu:delete', 3, 4, 2014, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', '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 (2019, '商品属性', '', 2, 4, 2000, 'property', 'ep:cold-drink', 'mall/product/property/index', 'ProductProperty', 0, '1', '1', '1', '', '2022-08-01 14:55:35', '1', '2023-08-26 11:01:05', '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 (2020, '规格查询', 'product:property:query', 3, 1, 2019, '', '', '', NULL, 0, '1', '1', '1', '', '2022-08-01 14:55:35', '', '2022-12-12 20:26:24', '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 (2021, '规格创建', 'product:property:create', 3, 2, 2019, '', '', '', NULL, 0, '1', '1', '1', '', '2022-08-01 14:55:35', '', '2022-12-12 20:26:30', '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 (2022, '规格更新', 'product:property:update', 3, 3, 2019, '', '', '', NULL, 0, '1', '1', '1', '', '2022-08-01 14:55:35', '', '2022-12-12 20:26:33', '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 (2023, '规格删除', 'product:property:delete', 3, 4, 2019, '', '', '', NULL, 0, '1', '1', '1', '', '2022-08-01 14:55:35', '', '2022-12-12 20:26:37', '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 (2025, 'Banner', '', 2, 100, 2387, 'banner', 'fa:bandcamp', 'mall/promotion/banner/index', NULL, 0, '1', '1', '1', '', '2022-08-01 14:56:14', '1', '2023-10-24 20:20:06', '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 (2026, 'Banner查询', 'promotion:banner:query', 3, 1, 2025, '', '', '', '', 0, '1', '1', '1', '', '2022-08-01 14:56:14', '1', '2023-10-24 20:20:18', '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 (2027, 'Banner创建', 'promotion:banner:create', 3, 2, 2025, '', '', '', '', 0, '1', '1', '1', '', '2022-08-01 14:56:14', '1', '2023-10-24 20:20:23', '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 (2028, 'Banner更新', 'promotion:banner:update', 3, 3, 2025, '', '', '', '', 0, '1', '1', '1', '', '2022-08-01 14:56:14', '1', '2023-10-24 20:20:28', '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 (2029, 'Banner删除', 'promotion:banner:delete', 3, 4, 2025, '', '', '', '', 0, '1', '1', '1', '', '2022-08-01 14:56:14', '1', '2023-10-24 20:20:36', '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 (2030, '营销中心', '', 1, 70, 2362, 'promotion', 'ep:present', NULL, NULL, 0, '1', '1', '1', '1', '2022-10-31 21:25:09', '1', '2023-09-30 11:54:27', '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 (2032, '优惠劵列表', '', 2, 1, 2365, 'template', 'ep:discount', 'mall/promotion/coupon/template/index', 'PromotionCouponTemplate', 0, '1', '1', '1', '', '2022-10-31 22:27:14', '1', '2023-10-03 12:40:06', '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 (2033, '优惠劵模板查询', 'promotion:coupon-template:query', 3, 1, 2032, '', '', '', NULL, 0, '1', '1', '1', '', '2022-10-31 22:27:14', '', '2022-10-31 22:27:14', '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 (2034, '优惠劵模板创建', 'promotion:coupon-template:create', 3, 2, 2032, '', '', '', NULL, 0, '1', '1', '1', '', '2022-10-31 22:27:14', '', '2022-10-31 22:27:14', '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 (2035, '优惠劵模板更新', 'promotion:coupon-template:update', 3, 3, 2032, '', '', '', NULL, 0, '1', '1', '1', '', '2022-10-31 22:27:14', '', '2022-10-31 22:27:14', '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 (2036, '优惠劵模板删除', 'promotion:coupon-template:delete', 3, 4, 2032, '', '', '', NULL, 0, '1', '1', '1', '', '2022-10-31 22:27:14', '', '2022-10-31 22:27:14', '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 (2038, '领取记录', '', 2, 2, 2365, 'list', 'ep:collection-tag', 'mall/promotion/coupon/index', 'PromotionCoupon', 0, '1', '1', '1', '', '2022-11-03 23:21:31', '1', '2023-10-03 12:55:30', '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 (2039, '优惠劵查询', 'promotion:coupon:query', 3, 1, 2038, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-03 23:21:31', '', '2022-11-03 23:21:31', '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 (2040, '优惠劵删除', 'promotion:coupon:delete', 3, 4, 2038, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-03 23:21:31', '', '2022-11-03 23:21:31', '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 (2041, '满减送', '', 2, 10, 2390, 'reward-activity', 'ep:goblet-square-full', 'mall/promotion/rewardActivity/index', 'PromotionRewardActivity', 0, '1', '1', '1', '', '2022-11-04 23:47:49', '1', '2023-10-21 19:24:46', '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 (2042, '满减送活动查询', 'promotion:reward-activity:query', 3, 1, 2041, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-04 23:47:49', '', '2022-11-04 23:47:49', '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 (2043, '满减送活动创建', 'promotion:reward-activity:create', 3, 2, 2041, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-04 23:47:49', '', '2022-11-04 23:47:49', '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 (2044, '满减送活动更新', 'promotion:reward-activity:update', 3, 3, 2041, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-04 23:47:50', '', '2022-11-04 23:47:50', '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 (2045, '满减送活动删除', 'promotion:reward-activity:delete', 3, 4, 2041, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-04 23:47:50', '', '2022-11-04 23:47:50', '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 (2046, '满减送活动关闭', 'promotion:reward-activity:close', 3, 5, 2041, '', '', '', NULL, 0, '1', '1', '1', '1', '2022-11-05 10:42:53', '1', '2022-11-05 10:42:53', '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 (2047, '限时折扣', '', 2, 7, 2390, 'discount-activity', 'ep:timer', 'mall/promotion/discountActivity/index', 'PromotionDiscountActivity', 0, '1', '1', '1', '', '2022-11-05 17:12:15', '1', '2023-10-21 19:24:21', '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 (2048, '限时折扣活动查询', 'promotion:discount-activity:query', 3, 1, 2047, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-05 17:12:15', '', '2022-11-05 17:12:15', '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 (2049, '限时折扣活动创建', 'promotion:discount-activity:create', 3, 2, 2047, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-05 17:12:15', '', '2022-11-05 17:12:15', '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 (2050, '限时折扣活动更新', 'promotion:discount-activity:update', 3, 3, 2047, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-05 17:12:16', '', '2022-11-05 17:12:16', '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 (2051, '限时折扣活动删除', 'promotion:discount-activity:delete', 3, 4, 2047, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-05 17:12:16', '', '2022-11-05 17:12:16', '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 (2052, '限时折扣活动关闭', 'promotion:discount-activity:close', 3, 5, 2047, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-05 17:12:16', '', '2022-11-05 17:12:16', '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 (2059, '秒杀商品', '', 2, 2, 2209, 'activity', 'ep:basketball', 'mall/promotion/seckill/activity/index', 'PromotionSeckillActivity', 0, '1', '1', '1', '', '2022-11-06 22:24:49', '1', '2023-06-24 18:57:25', '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 (2060, '秒杀活动查询', 'promotion:seckill-activity:query', 3, 1, 2059, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-06 22:24:49', '', '2022-11-06 22:24:49', '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 (2061, '秒杀活动创建', 'promotion:seckill-activity:create', 3, 2, 2059, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-06 22:24:49', '', '2022-11-06 22:24:49', '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 (2062, '秒杀活动更新', 'promotion:seckill-activity:update', 3, 3, 2059, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-06 22:24:49', '', '2022-11-06 22:24:49', '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 (2063, '秒杀活动删除', 'promotion:seckill-activity:delete', 3, 4, 2059, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-06 22:24:49', '', '2022-11-06 22:24:49', '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 (2066, '秒杀时段', '', 2, 1, 2209, 'config', 'ep:baseball', 'mall/promotion/seckill/config/index', 'PromotionSeckillConfig', 0, '1', '1', '1', '', '2022-11-15 19:46:50', '1', '2023-06-24 18:57:14', '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 (2067, '秒杀时段查询', 'promotion:seckill-config:query', 3, 1, 2066, '', '', '', '', 0, '1', '1', '1', '', '2022-11-15 19:46:51', '1', '2023-06-24 17:50:25', '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 (2068, '秒杀时段创建', 'promotion:seckill-config:create', 3, 2, 2066, '', '', '', '', 0, '1', '1', '1', '', '2022-11-15 19:46:51', '1', '2023-06-24 17:48:39', '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 (2069, '秒杀时段更新', 'promotion:seckill-config:update', 3, 3, 2066, '', '', '', '', 0, '1', '1', '1', '', '2022-11-15 19:46:51', '1', '2023-06-24 17:50:29', '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 (2070, '秒杀时段删除', 'promotion:seckill-config:delete', 3, 4, 2066, '', '', '', '', 0, '1', '1', '1', '', '2022-11-15 19:46:51', '1', '2023-06-24 17:50:32', '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 (2072, '订单中心', '', 1, 65, 2362, 'trade', 'ep:eleme', NULL, NULL, 0, '1', '1', '1', '1', '2022-11-19 18:57:19', '1', '2023-09-30 11:54:07', '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 (2073, '售后退款', '', 2, 2, 2072, 'after-sale', 'ep:refrigerator', 'mall/trade/afterSale/index', 'TradeAfterSale', 0, '1', '1', '1', '', '2022-11-19 20:15:32', '1', '2023-10-01 21:42:21', '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 (2074, '售后查询', 'trade:after-sale:query', 3, 1, 2073, '', '', '', NULL, 0, '1', '1', '1', '', '2022-11-19 20:15:33', '1', '2022-12-10 21:04:29', '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 (2075, '秒杀活动关闭', 'promotion:seckill-activity:close', 3, 5, 2059, '', '', '', '', 0, '1', '1', '1', '1', '2022-11-28 20:20:15', '1', '2023-10-03 18:34:28', '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 (2076, '订单列表', '', 2, 1, 2072, 'order', 'ep:list', 'mall/trade/order/index', 'TradeOrder', 0, '1', '1', '1', '1', '2022-12-10 21:05:44', '1', '2023-10-01 21:42:08', '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 (2083, '地区管理', '', 2, 14, 1, 'area', 'fa:map-marker', 'system/area/index', 'SystemArea', 0, '1', '1', '1', '1', '2022-12-23 17:35:05', '1', '2024-02-29 08:50:28', '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 (2084, '公众号管理', '', 1, 100, 0, '/mp', 'ep:compass', NULL, NULL, 0, '1', '1', '1', '1', '2023-01-01 20:11:04', '1', '2024-02-29 12:39:30', '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 (2085, '账号管理', '', 2, 1, 2084, 'account', 'fa:user', 'mp/account/index', 'MpAccount', 0, '1', '1', '1', '1', '2023-01-01 20:13:31', '1', '2024-02-29 12:42:10', '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 (2086, '新增账号', 'mp:account:create', 3, 1, 2085, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-01 20:21:40', '1', '2023-01-07 17:32:53', '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 (2087, '修改账号', 'mp:account:update', 3, 2, 2085, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-07 17:32:46', '1', '2023-01-07 17:32:46', '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 (2088, '查询账号', 'mp:account:query', 3, 0, 2085, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-07 17:33:07', '1', '2023-01-07 17:33:07', '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 (2089, '删除账号', 'mp:account:delete', 3, 3, 2085, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-07 17:33:21', '1', '2023-01-07 17:33:21', '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 (2090, '生成二维码', 'mp:account:qr-code', 3, 4, 2085, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-07 17:33:58', '1', '2023-01-07 17:33:58', '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 (2091, '清空 API 配额', 'mp:account:clear-quota', 3, 5, 2085, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-07 18:20:32', '1', '2023-01-07 18:20:59', '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 (2092, '数据统计', 'mp:statistics:query', 2, 2, 2084, 'statistics', 'ep:trend-charts', 'mp/statistics/index', 'MpStatistics', 0, '1', '1', '1', '1', '2023-01-07 20:17:36', '1', '2024-02-29 12:42:21', '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 (2093, '标签管理', '', 2, 3, 2084, 'tag', 'ep:collection-tag', 'mp/tag/index', 'MpTag', 0, '1', '1', '1', '1', '2023-01-08 11:37:32', '1', '2024-02-29 12:42:29', '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 (2094, '查询标签', 'mp:tag:query', 3, 0, 2093, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-08 11:59:03', '1', '2023-01-08 11:59:03', '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 (2095, '新增标签', 'mp:tag:create', 3, 1, 2093, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-08 11:59:23', '1', '2023-01-08 11:59:23', '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 (2096, '修改标签', 'mp:tag:update', 3, 2, 2093, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-08 11:59:41', '1', '2023-01-08 11:59:41', '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 (2097, '删除标签', 'mp:tag:delete', 3, 3, 2093, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-08 12:00:04', '1', '2023-01-08 12:00:13', '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 (2098, '同步标签', 'mp:tag:sync', 3, 4, 2093, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-08 12:00:29', '1', '2023-01-08 12:00:29', '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 (2099, '粉丝管理', '', 2, 4, 2084, 'user', 'fa:user-secret', 'mp/user/index', 'MpUser', 0, '1', '1', '1', '1', '2023-01-08 16:51:20', '1', '2024-02-29 12:42:39', '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 (2100, '查询粉丝', 'mp:user:query', 3, 0, 2099, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-08 17:16:59', '1', '2023-01-08 17:17:23', '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 (2101, '修改粉丝', 'mp:user:update', 3, 1, 2099, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-08 17:17:11', '1', '2023-01-08 17:17:11', '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 (2102, '同步粉丝', 'mp:user:sync', 3, 2, 2099, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-08 17:17:40', '1', '2023-01-08 17:17:40', '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 (2103, '消息管理', '', 2, 5, 2084, 'message', 'ep:message', 'mp/message/index', 'MpMessage', 0, '1', '1', '1', '1', '2023-01-08 18:44:19', '1', '2024-02-29 12:42:50', '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 (2104, '图文发表记录', '', 2, 10, 2084, 'free-publish', 'ep:edit-pen', 'mp/freePublish/index', 'MpFreePublish', 0, '1', '1', '1', '1', '2023-01-13 00:30:50', '1', '2024-02-29 12:43:31', '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 (2105, '查询发布列表', 'mp:free-publish:query', 3, 1, 2104, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-13 07:19:17', '1', '2023-01-13 07:19:17', '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 (2106, '发布草稿', 'mp:free-publish:submit', 3, 2, 2104, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-13 07:19:46', '1', '2023-01-13 07:19:46', '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 (2107, '删除发布记录', 'mp:free-publish:delete', 3, 3, 2104, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-13 07:20:01', '1', '2023-01-13 07:20:01', '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 (2108, '图文草稿箱', '', 2, 9, 2084, 'draft', 'ep:edit', 'mp/draft/index', 'MpDraft', 0, '1', '1', '1', '1', '2023-01-13 07:40:21', '1', '2024-02-29 12:43:26', '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 (2109, '新建草稿', 'mp:draft:create', 3, 1, 2108, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-13 23:15:30', '1', '2023-01-13 23:15:44', '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 (2110, '修改草稿', 'mp:draft:update', 3, 2, 2108, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-14 10:08:47', '1', '2023-01-14 10:08:47', '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 (2111, '查询草稿', 'mp:draft:query', 3, 0, 2108, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-14 10:09:01', '1', '2023-01-14 10:09:01', '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 (2112, '删除草稿', 'mp:draft:delete', 3, 3, 2108, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-14 10:09:19', '1', '2023-01-14 10:09:19', '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 (2113, '素材管理', '', 2, 8, 2084, 'material', 'ep:basketball', 'mp/material/index', 'MpMaterial', 0, '1', '1', '1', '1', '2023-01-14 14:12:07', '1', '2024-02-29 12:43:18', '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 (2114, '上传临时素材', 'mp:material:upload-temporary', 3, 1, 2113, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-14 15:33:55', '1', '2023-01-14 15:33:55', '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 (2115, '上传永久素材', 'mp:material:upload-permanent', 3, 2, 2113, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-14 15:34:14', '1', '2023-01-14 15:34:14', '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 (2116, '删除素材', 'mp:material:delete', 3, 3, 2113, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-14 15:35:37', '1', '2023-01-14 15:35:37', '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 (2117, '上传图文图片', 'mp:material:upload-news-image', 3, 4, 2113, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-14 15:36:31', '1', '2023-01-14 15:36:31', '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 (2118, '查询素材', 'mp:material:query', 3, 5, 2113, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-14 15:39:22', '1', '2023-01-14 15:39:22', '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 (2119, '菜单管理', '', 2, 6, 2084, 'menu', 'ep:menu', 'mp/menu/index', 'MpMenu', 0, '1', '1', '1', '1', '2023-01-14 17:43:54', '1', '2025-04-01 20:21:02', '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 (2120, '自动回复', '', 2, 7, 2084, 'auto-reply', 'fa-solid:republican', 'mp/autoReply/index', 'MpAutoReply', 0, '1', '1', '1', '1', '2023-01-15 22:13:09', '1', '2024-02-29 12:43:10', '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 (2121, '查询回复', 'mp:auto-reply:query', 3, 0, 2120, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-16 22:28:41', '1', '2023-01-16 22:28:41', '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 (2122, '新增回复', 'mp:auto-reply:create', 3, 1, 2120, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-16 22:28:54', '1', '2023-01-16 22:28:54', '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 (2123, '修改回复', 'mp:auto-reply:update', 3, 2, 2120, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-16 22:29:05', '1', '2023-01-16 22:29:05', '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 (2124, '删除回复', 'mp:auto-reply:delete', 3, 3, 2120, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-16 22:29:34', '1', '2023-01-16 22:29:34', '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 (2125, '查询菜单', 'mp:menu:query', 3, 0, 2119, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-17 23:05:41', '1', '2023-01-17 23:05:41', '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 (2126, '保存菜单', 'mp:menu:save', 3, 1, 2119, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-17 23:06:01', '1', '2023-01-17 23:06:01', '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 (2127, '删除菜单', 'mp:menu:delete', 3, 2, 2119, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-17 23:06:16', '1', '2023-01-17 23:06:16', '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 (2128, '查询消息', 'mp:message:query', 3, 0, 2103, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-17 23:07:14', '1', '2023-01-17 23:07:14', '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 (2129, '发送消息', 'mp:message:send', 3, 1, 2103, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-17 23:07:26', '1', '2023-01-17 23:07:26', '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 (2130, '邮箱管理', '', 2, 2, 2739, 'mail', 'fa-solid:mail-bulk', NULL, NULL, 0, '1', '1', '1', '1', '2023-01-25 17:27:44', '1', '2024-04-22 23:56:08', '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 (2131, '邮箱账号', '', 2, 0, 2130, 'mail-account', 'fa:universal-access', 'system/mail/account/index', 'SystemMailAccount', 0, '1', '1', '1', '', '2023-01-25 09:33:48', '1', '2024-02-29 08:48:16', '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 (2132, '账号查询', 'system:mail-account:query', 3, 1, 2131, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', '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 (2133, '账号创建', 'system:mail-account:create', 3, 2, 2131, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', '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 (2134, '账号更新', 'system:mail-account:update', 3, 3, 2131, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', '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 (2135, '账号删除', 'system:mail-account:delete', 3, 4, 2131, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', '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 (2136, '邮件模版', '', 2, 0, 2130, 'mail-template', 'fa:tag', 'system/mail/template/index', 'SystemMailTemplate', 0, '1', '1', '1', '', '2023-01-25 12:05:31', '1', '2024-02-29 08:48:41', '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 (2137, '模版查询', 'system:mail-template:query', 3, 1, 2136, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', '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 (2138, '模版创建', 'system:mail-template:create', 3, 2, 2136, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', '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 (2139, '模版更新', 'system:mail-template:update', 3, 3, 2136, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', '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 (2140, '模版删除', 'system:mail-template:delete', 3, 4, 2136, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', '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 (2141, '邮件记录', '', 2, 0, 2130, 'mail-log', 'fa:edit', 'system/mail/log/index', 'SystemMailLog', 0, '1', '1', '1', '', '2023-01-26 02:16:50', '1', '2024-02-29 08:48:51', '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 (2142, '日志查询', 'system:mail-log:query', 3, 1, 2141, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-26 02:16:50', '', '2023-01-26 02:16:50', '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 (2143, '发送测试邮件', 'system:mail-template:send-mail', 3, 5, 2136, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-26 23:29:15', '1', '2023-01-26 23:29:15', '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 (2144, '站内信管理', '', 1, 3, 2739, 'notify', 'ep:message-box', NULL, NULL, 0, '1', '1', '1', '1', '2023-01-28 10:25:18', '1', '2024-04-22 23:56:12', '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 (2145, '模板管理', '', 2, 0, 2144, 'notify-template', 'fa:archive', 'system/notify/template/index', 'SystemNotifyTemplate', 0, '1', '1', '1', '', '2023-01-28 02:26:42', '1', '2024-02-29 08:49:14', '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 (2146, '站内信模板查询', 'system:notify-template:query', 3, 1, 2145, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-28 02:26:42', '', '2023-01-28 02:26:42', '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 (2147, '站内信模板创建', 'system:notify-template:create', 3, 2, 2145, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-28 02:26:42', '', '2023-01-28 02:26:42', '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 (2148, '站内信模板更新', 'system:notify-template:update', 3, 3, 2145, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-28 02:26:42', '', '2023-01-28 02:26:42', '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 (2149, '站内信模板删除', 'system:notify-template:delete', 3, 4, 2145, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-28 02:26:42', '', '2023-01-28 02:26:42', '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 (2150, '发送测试站内信', 'system:notify-template:send-notify', 3, 5, 2145, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-01-28 10:54:43', '1', '2023-01-28 10:54:43', '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 (2151, '消息记录', '', 2, 0, 2144, 'notify-message', 'fa:edit', 'system/notify/message/index', 'SystemNotifyMessage', 0, '1', '1', '1', '', '2023-01-28 04:28:22', '1', '2024-02-29 08:49:22', '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 (2152, '站内信消息查询', 'system:notify-message:query', 3, 1, 2151, '', '', '', NULL, 0, '1', '1', '1', '', '2023-01-28 04:28:22', '', '2023-01-28 04:28:22', '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 (2153, '大屏设计器', '', 2, 2, 1281, 'go-view', 'fa:area-chart', 'report/goview/index', 'GoView', 0, '1', '1', '1', '1', '2023-02-07 00:03:19', '1', '2025-05-03 09:57:03', '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 (2154, '创建项目', 'report:go-view-project:create', 3, 1, 2153, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-02-07 19:25:14', '1', '2023-02-07 19:25:14', '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 (2155, '更新项目', 'report:go-view-project:update', 3, 2, 2153, '', '', '', '', 0, '1', '1', '1', '1', '2023-02-07 19:25:34', '1', '2024-04-24 20:01:18', '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 (2156, '查询项目', 'report:go-view-project:query', 3, 0, 2153, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-02-07 19:25:53', '1', '2023-02-07 19:25:53', '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 (2157, '使用 SQL 查询数据', 'report:go-view-data:get-by-sql', 3, 3, 2153, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-02-07 19:26:15', '1', '2023-02-07 19:26:15', '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 (2158, '使用 HTTP 查询数据', 'report:go-view-data:get-by-http', 3, 4, 2153, '', '', '', NULL, 0, '1', '1', '1', '1', '2023-02-07 19:26:35', '1', '2023-02-07 19:26:35', '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 (2159, 'Boot 开发文档', '', 1, 1, 0, 'https://doc.iocoder.cn/', 'ep:document', NULL, NULL, 0, '1', '1', '1', '1', '2023-02-10 22:46:28', '1', '2026-01-05 19:31:07', '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 (2160, 'Cloud 开发文档', '', 1, 2, 0, 'https://cloud.iocoder.cn', 'ep:document-copy', NULL, NULL, 0, '1', '1', '1', '1', '2023-02-10 22:47:07', '1', '2023-12-02 21:32:29', '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 (2161, '接入示例', '', 1, 99, 1117, 'demo', 'fa-solid:dragon', 'pay/demo/index', NULL, 0, '1', '1', '1', '', '2023-02-11 14:21:42', '1', '2024-01-18 23:50:00', '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 (2162, '商品导出', 'product:spu:export', 3, 5, 2014, '', '', '', NULL, 0, '1', '1', '1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', '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 (2164, '配送管理', '', 1, 3, 2072, 'delivery', 'ep:shopping-cart', '', '', 0, '1', '1', '1', '1', '2023-05-18 09:18:02', '1', '2023-09-28 10:58:09', '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 (2165, '快递发货', '', 1, 0, 2164, 'express', 'ep:bicycle', '', '', 0, '1', '1', '1', '1', '2023-05-18 09:22:06', '1', '2023-08-30 21:02:49', '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 (2166, '门店自提', '', 1, 1, 2164, 'pick-up-store', 'ep:add-location', '', '', 0, '1', '1', '1', '1', '2023-05-18 09:23:14', '1', '2023-08-30 21:03:21', '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 (2167, '快递公司', '', 2, 0, 2165, 'express', 'ep:compass', 'mall/trade/delivery/express/index', 'Express', 0, '1', '1', '1', '1', '2023-05-18 09:27:21', '1', '2024-11-29 11:20:54', '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 (2168, '快递公司查询', 'trade:delivery:express:query', 3, 1, 2167, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-18 09:37:53', '', '2023-05-18 09:37:53', '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 (2169, '快递公司创建', 'trade:delivery:express:create', 3, 2, 2167, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-18 09:37:53', '', '2023-05-18 09:37:53', '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 (2170, '快递公司更新', 'trade:delivery:express:update', 3, 3, 2167, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-18 09:37:53', '', '2023-05-18 09:37:53', '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 (2171, '快递公司删除', 'trade:delivery:express:delete', 3, 4, 2167, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-18 09:37:53', '', '2023-05-18 09:37:53', '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 (2172, '快递公司导出', 'trade:delivery:express:export', 3, 5, 2167, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-18 09:37:53', '', '2023-05-18 09:37:53', '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 (2173, '运费模版', 'trade:delivery:express-template:query', 2, 1, 2165, 'express-template', 'ep:coordinate', 'mall/trade/delivery/expressTemplate/index', 'ExpressTemplate', 0, '1', '1', '1', '1', '2023-05-20 06:48:10', '1', '2023-08-30 21:03:13', '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 (2174, '快递运费模板查询', 'trade:delivery:express-template:query', 3, 1, 2173, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-20 06:49:53', '', '2023-05-20 06:49:53', '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 (2175, '快递运费模板创建', 'trade:delivery:express-template:create', 3, 2, 2173, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-20 06:49:53', '', '2023-05-20 06:49:53', '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 (2176, '快递运费模板更新', 'trade:delivery:express-template:update', 3, 3, 2173, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-20 06:49:53', '', '2023-05-20 06:49:53', '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 (2177, '快递运费模板删除', 'trade:delivery:express-template:delete', 3, 4, 2173, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-20 06:49:53', '', '2023-05-20 06:49:53', '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 (2178, '快递运费模板导出', 'trade:delivery:express-template:export', 3, 5, 2173, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-20 06:49:53', '', '2023-05-20 06:49:53', '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 (2179, '门店管理', '', 2, 1, 2166, 'pick-up-store', 'ep:basketball', 'mall/trade/delivery/pickUpStore/index', 'PickUpStore', 0, '1', '1', '1', '1', '2023-05-25 10:50:00', '1', '2023-08-30 21:03:28', '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 (2180, '自提门店查询', 'trade:delivery:pick-up-store:query', 3, 1, 2179, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', '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 (2181, '自提门店创建', 'trade:delivery:pick-up-store:create', 3, 2, 2179, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', '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 (2182, '自提门店更新', 'trade:delivery:pick-up-store:update', 3, 3, 2179, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', '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 (2183, '自提门店删除', 'trade:delivery:pick-up-store:delete', 3, 4, 2179, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', '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 (2184, '自提门店导出', 'trade:delivery:pick-up-store:export', 3, 5, 2179, '', '', '', NULL, 0, '1', '1', '1', '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', '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 (2209, '秒杀活动', '', 2, 3, 2030, 'seckill', 'ep:place', '', '', 0, '1', '1', '1', '1', '2023-06-24 17:39:13', '1', '2023-06-24 18:55:15', '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 (2262, '会员中心', '', 1, 55, 0, '/member', 'ep:bicycle', NULL, NULL, 0, '1', '1', '1', '1', '2023-06-10 00:42:03', '1', '2023-08-20 09:23:56', '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 (2275, '会员配置', '', 2, 0, 2262, 'config', 'fa:archive', 'member/config/index', 'MemberConfig', 0, '1', '1', '1', '', '2023-06-10 02:07:44', '1', '2023-10-01 23:41:29', '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 (2276, '会员配置查询', 'member:config:query', 3, 1, 2275, '', '', '', '', 0, '1', '1', '1', '', '2023-06-10 02:07:44', '1', '2024-04-24 19:48:58', '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 (2277, '会员配置保存', 'member:config:save', 3, 2, 2275, '', '', '', '', 0, '1', '1', '1', '', '2023-06-10 02:07:44', '1', '2024-04-24 19:49:28', '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 (2281, '签到配置', '', 2, 2, 2300, 'config', 'ep:calendar', 'member/signin/config/index', 'SignInConfig', 0, '1', '1', '1', '', '2023-06-10 03:26:12', '1', '2023-08-20 19:25:51', '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 (2282, '积分签到规则查询', 'point:sign-in-config:query', 3, 1, 2281, '', '', '', NULL, 0, '1', '1', '1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', '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 (2283, '积分签到规则创建', 'point:sign-in-config:create', 3, 2, 2281, '', '', '', NULL, 0, '1', '1', '1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', '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 (2284, '积分签到规则更新', 'point:sign-in-config:update', 3, 3, 2281, '', '', '', NULL, 0, '1', '1', '1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', '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 (2285, '积分签到规则删除', 'point:sign-in-config:delete', 3, 4, 2281, '', '', '', NULL, 0, '1', '1', '1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', '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 (2287, '会员积分', '', 2, 10, 2262, 'record', 'fa:asterisk', 'member/point/record/index', 'PointRecord', 0, '1', '1', '1', '', '2023-06-10 04:18:50', '1', '2023-10-01 23:42:11', '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 (2288, '用户积分记录查询', 'point:record:query', 3, 1, 2287, '', '', '', NULL, 0, '1', '1', '1', '', '2023-06-10 04:18:50', '', '2023-06-10 04:18:50', '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 (2293, '签到记录', '', 2, 3, 2300, 'record', 'ep:chicken', 'member/signin/record/index', 'SignInRecord', 0, '1', '1', '1', '', '2023-06-10 04:48:22', '1', '2023-08-20 19:26:02', '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 (2294, '用户签到积分查询', 'point:sign-in-record:query', 3, 1, 2293, '', '', '', NULL, 0, '1', '1', '1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', '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 (2297, '用户签到积分删除', 'point:sign-in-record:delete', 3, 4, 2293, '', '', '', NULL, 0, '1', '1', '1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', '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 (2300, '会员签到', '', 1, 11, 2262, 'signin', 'ep:alarm-clock', '', '', 0, '1', '1', '1', '1', '2023-06-27 22:49:53', '1', '2023-08-20 09:23:48', '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 (2301, '回调通知', '', 2, 5, 1117, 'notify', 'ep:mute-notification', 'pay/notify/index', 'PayNotify', 0, '1', '1', '1', '', '2023-07-20 04:41:32', '1', '2024-01-18 23:56:48', '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 (2302, '支付通知查询', 'pay:notify:query', 3, 1, 2301, '', '', '', NULL, 0, '1', '1', '1', '', '2023-07-20 04:41:32', '', '2023-07-20 04:41:32', '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 (2303, '拼团活动', '', 2, 3, 2030, 'combination', 'fa:group', '', '', 0, '1', '1', '1', '1', '2023-08-12 17:19:54', '1', '2023-08-12 17:20:05', '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 (2304, '拼团商品', '', 2, 1, 2303, 'acitivity', 'ep:apple', 'mall/promotion/combination/activity/index', 'PromotionCombinationActivity', 0, '1', '1', '1', '1', '2023-08-12 17:22:03', '1', '2023-08-12 17:22:29', '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 (2305, '拼团活动查询', 'promotion:combination-activity:query', 3, 1, 2304, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-12 17:54:32', '1', '2023-11-24 11:57:40', '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 (2306, '拼团活动创建', 'promotion:combination-activity:create', 3, 2, 2304, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-12 17:54:49', '1', '2023-08-12 17:54:49', '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 (2307, '拼团活动更新', 'promotion:combination-activity:update', 3, 3, 2304, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-12 17:55:04', '1', '2023-08-12 17:55:04', '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 (2308, '拼团活动删除', 'promotion:combination-activity:delete', 3, 4, 2304, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-12 17:55:23', '1', '2023-08-12 17:55:23', '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 (2309, '拼团活动关闭', 'promotion:combination-activity:close', 3, 5, 2304, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-12 17:55:37', '1', '2023-10-06 10:51:57', '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 (2310, '砍价活动', '', 2, 4, 2030, 'bargain', 'ep:box', '', '', 0, '1', '1', '1', '1', '2023-08-13 00:27:25', '1', '2023-08-13 00:27:25', '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 (2311, '砍价商品', '', 2, 1, 2310, 'activity', 'ep:burger', 'mall/promotion/bargain/activity/index', 'PromotionBargainActivity', 0, '1', '1', '1', '1', '2023-08-13 00:28:49', '1', '2023-10-05 01:16:23', '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 (2312, '砍价活动查询', 'promotion:bargain-activity:query', 3, 1, 2311, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-13 00:32:30', '1', '2023-08-13 00:32:30', '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 (2313, '砍价活动创建', 'promotion:bargain-activity:create', 3, 2, 2311, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-13 00:32:44', '1', '2023-08-13 00:32:44', '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 (2314, '砍价活动更新', 'promotion:bargain-activity:update', 3, 3, 2311, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-13 00:32:55', '1', '2023-08-13 00:32:55', '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 (2315, '砍价活动删除', 'promotion:bargain-activity:delete', 3, 4, 2311, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-13 00:34:50', '1', '2023-08-13 00:34:50', '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 (2316, '砍价活动关闭', 'promotion:bargain-activity:close', 3, 5, 2311, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-13 00:35:02', '1', '2023-08-13 00:35:02', '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 (2317, '会员管理', '', 2, 0, 2262, 'user', 'ep:avatar', 'member/user/index', 'MemberUser', 0, '1', '1', '1', '', '2023-08-19 04:12:15', '1', '2023-08-24 00:50:55', '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 (2318, '会员用户查询', 'member:user:query', 3, 1, 2317, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-19 04:12:15', '', '2023-08-19 04:12:15', '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 (2319, '会员用户更新', 'member:user:update', 3, 3, 2317, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-19 04:12:15', '', '2023-08-19 04:12:15', '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 (2320, '会员标签', '', 2, 1, 2262, 'tag', 'ep:collection-tag', 'member/tag/index', 'MemberTag', 0, '1', '1', '1', '', '2023-08-20 01:03:08', '1', '2023-08-20 09:23:19', '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 (2321, '会员标签查询', 'member:tag:query', 3, 1, 2320, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-20 01:03:08', '', '2023-08-20 01:03:08', '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 (2322, '会员标签创建', 'member:tag:create', 3, 2, 2320, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-20 01:03:08', '', '2023-08-20 01:03:08', '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 (2323, '会员标签更新', 'member:tag:update', 3, 3, 2320, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-20 01:03:08', '', '2023-08-20 01:03:08', '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 (2324, '会员标签删除', 'member:tag:delete', 3, 4, 2320, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-20 01:03:08', '', '2023-08-20 01:03:08', '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 (2325, '会员等级', '', 2, 2, 2262, 'level', 'fa:level-up', 'member/level/index', 'MemberLevel', 0, '1', '1', '1', '', '2023-08-22 12:41:01', '1', '2023-08-22 21:47:00', '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 (2326, '会员等级查询', 'member:level:query', 3, 1, 2325, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-22 12:41:02', '', '2023-08-22 12:41:02', '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 (2327, '会员等级创建', 'member:level:create', 3, 2, 2325, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-22 12:41:02', '', '2023-08-22 12:41:02', '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 (2328, '会员等级更新', 'member:level:update', 3, 3, 2325, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-22 12:41:02', '', '2023-08-22 12:41:02', '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 (2329, '会员等级删除', 'member:level:delete', 3, 4, 2325, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-22 12:41:02', '', '2023-08-22 12:41:02', '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 (2330, '会员分组', '', 2, 3, 2262, 'group', 'fa:group', 'member/group/index', 'MemberGroup', 0, '1', '1', '1', '', '2023-08-22 13:50:06', '1', '2023-10-01 23:42:01', '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 (2331, '用户分组查询', 'member:group:query', 3, 1, 2330, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-22 13:50:06', '', '2023-08-22 13:50:06', '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 (2332, '用户分组创建', 'member:group:create', 3, 2, 2330, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-22 13:50:06', '', '2023-08-22 13:50:06', '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 (2333, '用户分组更新', 'member:group:update', 3, 3, 2330, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-22 13:50:06', '', '2023-08-22 13:50:06', '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 (2334, '用户分组删除', 'member:group:delete', 3, 4, 2330, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-22 13:50:06', '', '2023-08-22 13:50:06', '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 (2335, '用户等级修改', 'member:user:update-level', 3, 5, 2317, '', '', '', NULL, 0, '1', '1', '1', '', '2023-08-23 16:49:05', '', '2023-08-23 16:50:48', '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 (2336, '商品评论', '', 2, 5, 2000, 'comment', 'ep:comment', 'mall/product/comment/index', 'ProductComment', 0, '1', '1', '1', '1', '2023-08-26 11:03:00', '1', '2023-08-26 11:03:38', '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 (2337, '评论查询', 'product:comment:query', 3, 1, 2336, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-26 11:04:01', '1', '2023-08-26 11:04:01', '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 (2338, '添加自评', 'product:comment:create', 3, 2, 2336, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-26 11:04:23', '1', '2023-08-26 11:08:18', '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 (2339, '商家回复', 'product:comment:update', 3, 3, 2336, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-26 11:04:37', '1', '2023-08-26 11:04:37', '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 (2340, '显隐评论', 'product:comment:update', 3, 4, 2336, '', '', '', '', 0, '1', '1', '1', '1', '2023-08-26 11:04:55', '1', '2023-08-26 11:04:55', '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 (2341, '优惠劵发送', 'promotion:coupon:send', 3, 2, 2038, '', '', '', '', 0, '1', '1', '1', '1', '2023-09-02 00:03:14', '1', '2023-09-02 00:03:14', '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 (2342, '交易配置', '', 2, 0, 2072, 'config', 'ep:setting', 'mall/trade/config/index', 'TradeConfig', 0, '1', '1', '1', '', '2023-09-28 02:46:22', '1', '2024-02-26 20:30:53', '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 (2343, '交易中心配置查询', 'trade:config:query', 3, 1, 2342, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', '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 (2344, '交易中心配置保存', 'trade:config:save', 3, 2, 2342, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', '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 (2345, '分销管理', '', 1, 4, 2072, 'brokerage', 'fa-solid:project-diagram', '', '', 0, '1', '1', '1', '', '2023-09-28 02:46:22', '1', '2023-09-28 10:58:44', '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 (2346, '分销用户', '', 2, 0, 2345, 'brokerage-user', 'fa-solid:user-tie', 'mall/trade/brokerage/user/index', 'TradeBrokerageUser', 0, '1', '1', '1', '', '2023-09-28 02:46:22', '1', '2024-02-26 20:33:23', '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 (2347, '分销用户查询', 'trade:brokerage-user:query', 3, 1, 2346, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', '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 (2348, '分销用户推广人查询', 'trade:brokerage-user:user-query', 3, 2, 2346, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', '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 (2349, '分销用户推广订单查询', 'trade:brokerage-user:order-query', 3, 3, 2346, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', '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 (2350, '分销用户修改推广资格', 'trade:brokerage-user:update-brokerage-enable', 3, 4, 2346, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', '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 (2351, '修改推广员', 'trade:brokerage-user:update-bind-user', 3, 5, 2346, '', '', '', '', 0, '1', '1', '1', '', '2023-09-28 02:46:22', '1', '2024-12-01 14:33:07', '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 (2352, '清除推广员', 'trade:brokerage-user:clear-bind-user', 3, 6, 2346, '', '', '', '', 0, '1', '1', '1', '', '2023-09-28 02:46:22', '1', '2024-12-01 14:33:14', '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 (2353, '佣金记录', '', 2, 1, 2345, 'brokerage-record', 'fa:money', 'mall/trade/brokerage/record/index', 'TradeBrokerageRecord', 0, '1', '1', '1', '', '2023-09-28 02:46:22', '1', '2024-02-26 20:33:30', '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 (2354, '佣金记录查询', 'trade:brokerage-record:query', 3, 1, 2353, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', '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 (2355, '佣金提现', '', 2, 2, 2345, 'brokerage-withdraw', 'fa:credit-card', 'mall/trade/brokerage/withdraw/index', 'TradeBrokerageWithdraw', 0, '1', '1', '1', '', '2023-09-28 02:46:22', '1', '2024-02-26 20:33:35', '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 (2356, '佣金提现查询', 'trade:brokerage-withdraw:query', 3, 1, 2355, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', '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 (2357, '佣金提现审核', 'trade:brokerage-withdraw:audit', 3, 2, 2355, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-28 02:46:22', '', '2023-09-28 02:46:22', '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 (2358, '统计中心', '', 1, 75, 2362, 'statistics', 'ep:data-line', '', '', 0, '1', '1', '1', '', '2023-09-30 03:22:40', '1', '2023-09-30 11:54:48', '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 (2359, '交易统计', '', 2, 4, 2358, 'trade', 'fa-solid:credit-card', 'mall/statistics/trade/index', 'TradeStatistics', 0, '1', '1', '1', '', '2023-09-30 03:22:40', '1', '2024-02-26 20:42:00', '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 (2360, '交易统计查询', 'statistics:trade:query', 3, 1, 2359, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-30 03:22:40', '', '2023-09-30 03:22:40', '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 (2361, '交易统计导出', 'statistics:trade:export', 3, 2, 2359, '', '', '', NULL, 0, '1', '1', '1', '', '2023-09-30 03:22:40', '', '2023-09-30 03:22:40', '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 (2362, '商城系统', '', 1, 59, 0, '/mall', 'ep:shop', '', '', 0, '1', '1', '1', '1', '2023-09-30 11:52:02', '1', '2023-09-30 11:52:18', '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 (2363, '用户积分修改', 'member:user:update-point', 3, 6, 2317, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-01 14:39:43', '', '2023-10-01 14:39:43', '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 (2364, '用户余额修改', 'pay:wallet:update-balance', 3, 7, 2317, '', '', '', '', 0, '1', '1', '1', '', '2023-10-01 14:39:43', '1', '2024-10-01 09:42:57', '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 (2365, '优惠劵', '', 1, 2, 2030, 'coupon', 'fa-solid:disease', '', '', 0, '1', '1', '1', '1', '2023-10-03 12:39:15', '1', '2023-10-05 00:16:07', '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 (2366, '砍价记录', '', 2, 2, 2310, 'record', 'ep:list', 'mall/promotion/bargain/record/index', 'PromotionBargainRecord', 0, '1', '1', '1', '', '2023-10-05 02:49:06', '1', '2023-10-05 10:50:38', '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 (2367, '砍价记录查询', 'promotion:bargain-record:query', 3, 1, 2366, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-05 02:49:06', '', '2023-10-05 02:49:06', '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 (2368, '助力记录查询', 'promotion:bargain-help:query', 3, 2, 2366, '', '', '', '', 0, '1', '1', '1', '1', '2023-10-05 12:27:49', '1', '2023-10-05 12:27:49', '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 (2369, '拼团记录', 'promotion:combination-record:query', 2, 2, 2303, 'record', 'ep:avatar', 'mall/promotion/combination/record/index.vue', 'PromotionCombinationRecord', 0, '1', '1', '1', '1', '2023-10-08 07:10:22', '1', '2023-10-08 07:34:11', '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 (2374, '会员统计', '', 2, 2, 2358, 'member', 'ep:avatar', 'mall/statistics/member/index', 'MemberStatistics', 0, '1', '1', '1', '', '2023-10-11 04:39:24', '1', '2024-02-26 20:41:46', '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 (2375, '会员统计查询', 'statistics:member:query', 3, 1, 2374, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-11 04:39:24', '', '2023-10-11 04:39:24', '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 (2376, '订单核销', 'trade:order:pick-up', 3, 10, 2076, '', '', '', '', 0, '1', '1', '1', '1', '2023-10-14 17:11:58', '1', '2023-10-14 17:11:58', '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 (2377, '文章分类', '', 2, 0, 2387, 'article/category', 'fa:certificate', 'mall/promotion/article/category/index', 'ArticleCategory', 0, '1', '1', '1', '', '2023-10-16 01:26:18', '1', '2023-10-16 09:38:26', '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 (2378, '分类查询', 'promotion:article-category:query', 3, 1, 2377, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', '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 (2379, '分类创建', 'promotion:article-category:create', 3, 2, 2377, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', '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 (2380, '分类更新', 'promotion:article-category:update', 3, 3, 2377, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', '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 (2381, '分类删除', 'promotion:article-category:delete', 3, 4, 2377, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', '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 (2382, '文章列表', '', 2, 2, 2387, 'article', 'ep:connection', 'mall/promotion/article/index', 'Article', 0, '1', '1', '1', '', '2023-10-16 01:26:18', '1', '2023-10-16 09:41:19', '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 (2383, '文章管理查询', 'promotion:article:query', 3, 1, 2382, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', '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 (2384, '文章管理创建', 'promotion:article:create', 3, 2, 2382, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', '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 (2385, '文章管理更新', 'promotion:article:update', 3, 3, 2382, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', '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 (2386, '文章管理删除', 'promotion:article:delete', 3, 4, 2382, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-16 01:26:18', '', '2023-10-16 01:26:18', '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 (2387, '内容管理', '', 1, 1, 2030, 'content', 'ep:collection', '', '', 0, '1', '1', '1', '1', '2023-10-16 09:37:31', '1', '2023-10-16 09:37:31', '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 (2388, '商城首页', '', 2, 1, 2362, 'home', 'ep:home-filled', 'mall/home/index', 'MallHome', 0, '1', '1', '1', '', '2023-10-16 12:10:33', '', '2023-10-16 12:10:33', '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 (2389, '核销订单', '', 2, 2, 2166, 'pick-up-order', 'ep:list', 'mall/trade/delivery/pickUpOrder/index', 'PickUpOrder', 0, '1', '1', '1', '', '2023-10-19 16:09:51', '', '2023-10-19 16:09:51', '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 (2390, '优惠活动', '', 1, 99, 2030, 'youhui', 'ep:aim', '', '', 0, '1', '1', '1', '1', '2023-10-21 19:23:49', '1', '2023-10-21 19:23:49', '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 (2391, '客户管理', '', 2, 10, 2397, 'customer', 'fa:address-book-o', 'crm/customer/index', 'CrmCustomer', 0, '1', '1', '1', '', '2023-10-29 09:04:21', '1', '2024-02-17 17:13:32', '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 (2392, '客户查询', 'crm:customer:query', 3, 1, 2391, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', '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 (2393, '客户创建', 'crm:customer:create', 3, 2, 2391, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', '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 (2394, '客户更新', 'crm:customer:update', 3, 3, 2391, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', '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 (2395, '客户删除', 'crm:customer:delete', 3, 4, 2391, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', '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 (2396, '客户导出', 'crm:customer:export', 3, 5, 2391, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', '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 (2397, 'CRM 系统', '', 1, 200, 0, '/crm', 'simple-icons:civicrm', '', '', 0, '1', '1', '1', '1', '2023-10-29 17:08:30', '1', '2025-04-19 18:56:38', '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 (2398, '合同管理', '', 2, 50, 2397, 'contract', 'ep:notebook', 'crm/contract/index', 'CrmContract', 0, '1', '1', '1', '', '2023-10-29 10:50:41', '1', '2024-02-17 17:15:09', '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 (2399, '合同查询', 'crm:contract:query', 3, 1, 2398, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', '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 (2400, '合同创建', 'crm:contract:create', 3, 2, 2398, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', '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 (2401, '合同更新', 'crm:contract:update', 3, 3, 2398, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', '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 (2402, '合同删除', 'crm:contract:delete', 3, 4, 2398, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', '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 (2403, '合同导出', 'crm:contract:export', 3, 5, 2398, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', '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 (2404, '线索管理', '', 2, 8, 2397, 'clue', 'fa:pagelines', 'crm/clue/index', 'CrmClue', 0, '1', '1', '1', '', '2023-10-29 11:06:29', '1', '2024-02-17 17:15:41', '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 (2405, '线索查询', 'crm:clue:query', 3, 1, 2404, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', '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 (2406, '线索创建', 'crm:clue:create', 3, 2, 2404, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', '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 (2407, '线索更新', 'crm:clue:update', 3, 3, 2404, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', '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 (2408, '线索删除', 'crm:clue:delete', 3, 4, 2404, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', '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 (2409, '线索导出', 'crm:clue:export', 3, 5, 2404, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', '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 (2410, '商机管理', '', 2, 40, 2397, 'business', 'fa:bus', 'crm/business/index', 'CrmBusiness', 0, '1', '1', '1', '', '2023-10-29 11:12:35', '1', '2024-02-17 17:14:55', '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 (2411, '商机查询', 'crm:business:query', 3, 1, 2410, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', '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 (2412, '商机创建', 'crm:business:create', 3, 2, 2410, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', '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 (2413, '商机更新', 'crm:business:update', 3, 3, 2410, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', '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 (2414, '商机删除', 'crm:business:delete', 3, 4, 2410, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', '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 (2415, '商机导出', 'crm:business:export', 3, 5, 2410, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', '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 (2416, '联系人管理', '', 2, 20, 2397, 'contact', 'fa:address-book-o', 'crm/contact/index', 'CrmContact', 0, '1', '1', '1', '', '2023-10-29 11:14:56', '1', '2024-02-17 17:13:49', '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 (2417, '联系人查询', 'crm:contact:query', 3, 1, 2416, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', '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 (2418, '联系人创建', 'crm:contact:create', 3, 2, 2416, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', '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 (2419, '联系人更新', 'crm:contact:update', 3, 3, 2416, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', '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 (2420, '联系人删除', 'crm:contact:delete', 3, 4, 2416, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', '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 (2421, '联系人导出', 'crm:contact:export', 3, 5, 2416, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', '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 (2422, '回款管理', '', 2, 60, 2397, 'receivable', 'ep:money', 'crm/receivable/index', 'CrmReceivable', 0, '1', '1', '1', '', '2023-10-29 11:18:09', '1', '2024-02-17 17:16:18', '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 (2423, '回款管理查询', 'crm:receivable:query', 3, 1, 2422, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', '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 (2424, '回款管理创建', 'crm:receivable:create', 3, 2, 2422, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', '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 (2425, '回款管理更新', 'crm:receivable:update', 3, 3, 2422, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', '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 (2426, '回款管理删除', 'crm:receivable:delete', 3, 4, 2422, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', '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 (2427, '回款管理导出', 'crm:receivable:export', 3, 5, 2422, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', '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 (2428, '回款计划', '', 2, 61, 2397, 'receivable-plan', 'fa:money', 'crm/receivable/plan/index', 'CrmReceivablePlan', 0, '1', '1', '1', '', '2023-10-29 11:18:09', '1', '2024-02-17 17:16:11', '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 (2429, '回款计划查询', 'crm:receivable-plan:query', 3, 1, 2428, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', '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 (2430, '回款计划创建', 'crm:receivable-plan:create', 3, 2, 2428, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', '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 (2431, '回款计划更新', 'crm:receivable-plan:update', 3, 3, 2428, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', '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 (2432, '回款计划删除', 'crm:receivable-plan:delete', 3, 4, 2428, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', '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 (2433, '回款计划导出', 'crm:receivable-plan:export', 3, 5, 2428, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', '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 (2435, '商城装修', '', 2, 20, 2030, 'diy-template', 'fa6-solid:brush', 'mall/promotion/diy/template/index', '', 0, '1', '1', '1', '', '2023-10-29 14:19:25', '1', '2025-03-15 21:34:33', '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 (2436, '装修模板', '', 2, 1, 2435, 'diy-template', 'fa6-solid:brush', 'mall/promotion/diy/template/index', 'DiyTemplate', 0, '1', '1', '1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', '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 (2437, '装修模板查询', 'promotion:diy-template:query', 3, 1, 2436, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', '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 (2438, '装修模板创建', 'promotion:diy-template:create', 3, 2, 2436, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', '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 (2439, '装修模板更新', 'promotion:diy-template:update', 3, 3, 2436, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', '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 (2440, '装修模板删除', 'promotion:diy-template:delete', 3, 4, 2436, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', '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 (2441, '装修模板使用', 'promotion:diy-template:use', 3, 5, 2436, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', '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 (2442, '装修页面', '', 2, 2, 2435, 'diy-page', 'foundation:page-edit', 'mall/promotion/diy/page/index', 'DiyPage', 0, '1', '1', '1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', '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 (2443, '装修页面查询', 'promotion:diy-page:query', 3, 1, 2442, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 14:19:25', '', '2023-10-29 14:19:25', '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 (2444, '装修页面创建', 'promotion:diy-page:create', 3, 2, 2442, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 14:19:26', '', '2023-10-29 14:19:26', '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 (2445, '装修页面更新', 'promotion:diy-page:update', 3, 3, 2442, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 14:19:26', '', '2023-10-29 14:19:26', '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 (2446, '装修页面删除', 'promotion:diy-page:delete', 3, 4, 2442, '', '', '', NULL, 0, '1', '1', '1', '', '2023-10-29 14:19:26', '', '2023-10-29 14:19:26', '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 (2447, '三方登录', '', 1, 10, 1, 'social', 'fa:rocket', '', '', 0, '1', '1', '1', '1', '2023-11-04 12:12:01', '1', '2024-02-29 01:14:05', '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 (2448, '三方应用', '', 2, 1, 2447, 'client', 'ep:set-up', 'system/social/client/index.vue', 'SocialClient', 0, '1', '1', '1', '1', '2023-11-04 12:17:19', '1', '2024-05-04 19:09:54', '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 (2449, '三方应用查询', 'system:social-client:query', 3, 1, 2448, '', '', '', '', 0, '1', '1', '1', '1', '2023-11-04 12:43:12', '1', '2023-11-04 12:43:33', '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 (2450, '三方应用创建', 'system:social-client:create', 3, 2, 2448, '', '', '', '', 0, '1', '1', '1', '1', '2023-11-04 12:43:58', '1', '2023-11-04 12:43:58', '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 (2451, '三方应用更新', 'system:social-client:update', 3, 3, 2448, '', '', '', '', 0, '1', '1', '1', '1', '2023-11-04 12:44:27', '1', '2023-11-04 12:44:27', '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 (2452, '三方应用删除', 'system:social-client:delete', 3, 4, 2448, '', '', '', '', 0, '1', '1', '1', '1', '2023-11-04 12:44:43', '1', '2023-11-04 12:44:43', '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 (2453, '三方用户', 'system:social-user:query', 2, 2, 2447, 'user', 'ep:avatar', 'system/social/user/index.vue', 'SocialUser', 0, '1', '1', '1', '1', '2023-11-04 14:01:05', '1', '2023-11-04 14:01:05', '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 (2472, '主子表(内嵌)', '', 2, 12, 1070, 'demo03-inner', 'fa:power-off', 'infra/demo/demo03/inner/index', 'Demo03StudentInner', 0, '1', '1', '1', '', '2023-11-13 04:39:51', '1', '2023-11-16 23:53:46', '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 (2478, '单表(增删改查)', '', 2, 1, 1070, 'demo01-contact', 'ep:bicycle', 'infra/demo/demo01/index', 'Demo01Contact', 0, '1', '1', '1', '', '2023-11-15 14:42:30', '1', '2023-11-16 20:34:40', '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 (2479, '示例联系人查询', 'infra:demo01-contact:query', 3, 1, 2478, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', '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 (2480, '示例联系人创建', 'infra:demo01-contact:create', 3, 2, 2478, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', '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 (2481, '示例联系人更新', 'infra:demo01-contact:update', 3, 3, 2478, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', '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 (2482, '示例联系人删除', 'infra:demo01-contact:delete', 3, 4, 2478, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', '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 (2483, '示例联系人导出', 'infra:demo01-contact:export', 3, 5, 2478, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', '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 (2484, '树表(增删改查)', '', 2, 2, 1070, 'demo02-category', 'fa:tree', 'infra/demo/demo02/index', 'Demo02Category', 0, '1', '1', '1', '', '2023-11-16 12:18:27', '1', '2023-11-16 20:35:01', '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 (2485, '示例分类查询', 'infra:demo02-category:query', 3, 1, 2484, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', '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 (2486, '示例分类创建', 'infra:demo02-category:create', 3, 2, 2484, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', '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 (2487, '示例分类更新', 'infra:demo02-category:update', 3, 3, 2484, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', '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 (2488, '示例分类删除', 'infra:demo02-category:delete', 3, 4, 2484, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', '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 (2489, '示例分类导出', 'infra:demo02-category:export', 3, 5, 2484, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', '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 (2490, '主子表(标准)', '', 2, 10, 1070, 'demo03-normal', 'fa:battery-3', 'infra/demo/demo03/normal/index', 'Demo03StudentNormal', 0, '1', '1', '1', '', '2023-11-16 12:53:37', '1', '2023-11-16 23:10:03', '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 (2491, '学生查询', 'infra:demo03-student:query', 3, 1, 2490, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', '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 (2492, '学生创建', 'infra:demo03-student:create', 3, 2, 2490, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', '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 (2493, '学生更新', 'infra:demo03-student:update', 3, 3, 2490, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', '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 (2494, '学生删除', 'infra:demo03-student:delete', 3, 4, 2490, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', '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 (2495, '学生导出', 'infra:demo03-student:export', 3, 5, 2490, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', '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 (2497, '主子表(ERP)', '', 2, 11, 1070, 'demo03-erp', 'ep:calendar', 'infra/demo/demo03/erp/index', 'Demo03StudentERP', 0, '1', '1', '1', '', '2023-11-16 15:50:59', '1', '2023-11-17 13:19:56', '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 (2516, '客户公海配置', '', 2, 0, 2524, 'customer-pool-config', 'ep:data-analysis', 'crm/customer/poolConfig/index', 'CrmCustomerPoolConfig', 0, '1', '1', '1', '', '2023-11-18 13:33:31', '1', '2024-01-03 19:52:06', '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 (2517, '客户公海配置保存', 'crm:customer-pool-config:update', 3, 1, 2516, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-18 13:33:31', '', '2023-11-18 13:33:31', '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 (2518, '客户限制配置', '', 2, 1, 2524, 'customer-limit-config', 'ep:avatar', 'crm/customer/limitConfig/index', 'CrmCustomerLimitConfig', 0, '1', '1', '1', '', '2023-11-18 13:33:53', '1', '2024-02-24 16:43:33', '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 (2519, '客户限制配置查询', 'crm:customer-limit-config:query', 3, 1, 2518, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', '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 (2520, '客户限制配置创建', 'crm:customer-limit-config:create', 3, 2, 2518, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', '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 (2521, '客户限制配置更新', 'crm:customer-limit-config:update', 3, 3, 2518, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', '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 (2522, '客户限制配置删除', 'crm:customer-limit-config:delete', 3, 4, 2518, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', '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 (2523, '客户限制配置导出', 'crm:customer-limit-config:export', 3, 5, 2518, '', '', '', NULL, 0, '1', '1', '1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', '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 (2524, '系统配置', '', 1, 999, 2397, 'config', 'ep:connection', '', '', 0, '1', '1', '1', '1', '2023-11-18 21:58:00', '1', '2024-02-17 17:14:34', '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 (2525, 'WebSocket', '', 2, 5, 2, 'websocket', 'ep:connection', 'infra/webSocket/index', 'InfraWebSocket', 0, '1', '1', '1', '1', '2023-11-23 19:41:55', '1', '2024-04-23 00:02:00', '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 (2526, '产品管理', '', 2, 80, 2397, 'product', 'fa:product-hunt', 'crm/product/index', 'CrmProduct', 0, '1', '1', '1', '1', '2023-12-05 22:45:26', '1', '2024-02-20 20:36:20', '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 (2527, '产品查询', 'crm:product:query', 3, 1, 2526, '', '', '', '', 0, '1', '1', '1', '1', '2023-12-05 22:47:16', '1', '2023-12-05 22:47:16', '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 (2528, '产品创建', 'crm:product:create', 3, 2, 2526, '', '', '', '', 0, '1', '1', '1', '1', '2023-12-05 22:47:41', '1', '2023-12-05 22:47:48', '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 (2529, '产品更新', 'crm:product:update', 3, 3, 2526, '', '', '', '', 0, '1', '1', '1', '1', '2023-12-05 22:48:03', '1', '2023-12-05 22:48:03', '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 (2530, '产品删除', 'crm:product:delete', 3, 4, 2526, '', '', '', '', 0, '1', '1', '1', '1', '2023-12-05 22:48:17', '1', '2023-12-05 22:48:17', '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 (2531, '产品导出', 'crm:product:export', 3, 5, 2526, '', '', '', '', 0, '1', '1', '1', '1', '2023-12-05 22:48:29', '1', '2023-12-05 22:48:29', '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 (2532, '产品分类配置', '', 2, 3, 2524, 'product/category', 'fa-solid:window-restore', 'crm/product/category/index', 'CrmProductCategory', 0, '1', '1', '1', '1', '2023-12-06 12:52:36', '1', '2023-12-06 12:52:51', '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 (2533, '产品分类查询', 'crm:product-category:query', 3, 1, 2532, '', '', '', '', 0, '1', '1', '1', '1', '2023-12-06 12:53:23', '1', '2023-12-06 12:53:23', '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 (2534, '产品分类创建', 'crm:product-category:create', 3, 2, 2532, '', '', '', '', 0, '1', '1', '1', '1', '2023-12-06 12:53:41', '1', '2023-12-06 12:53:41', '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 (2535, '产品分类更新', 'crm:product-category:update', 3, 3, 2532, '', '', '', '', 0, '1', '1', '1', '1', '2023-12-06 12:53:59', '1', '2023-12-06 12:53:59', '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 (2536, '产品分类删除', 'crm:product-category:delete', 3, 4, 2532, '', '', '', '', 0, '1', '1', '1', '1', '2023-12-06 12:54:14', '1', '2023-12-06 12:54:14', '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 (2543, '关联商机', 'crm:contact:create-business', 3, 10, 2416, '', '', '', '', 0, '1', '1', '1', '1', '2024-01-02 17:28:25', '1', '2024-01-02 17:28:25', '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 (2544, '取关商机', 'crm:contact:delete-business', 3, 11, 2416, '', '', '', '', 0, '1', '1', '1', '1', '2024-01-02 17:28:43', '1', '2024-01-02 17:28:51', '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 (2545, '商品统计', '', 2, 3, 2358, 'product', 'fa:product-hunt', 'mall/statistics/product/index', 'ProductStatistics', 0, '1', '1', '1', '', '2023-12-15 18:54:28', '1', '2024-02-26 20:41:52', '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 (2546, '客户公海', '', 2, 30, 2397, 'customer/pool', 'fa-solid:swimming-pool', 'crm/customer/pool/index', 'CrmCustomerPool', 0, '1', '1', '1', '1', '2024-01-15 21:29:34', '1', '2024-02-17 17:14:18', '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 (2547, '订单查询', 'trade:order:query', 3, 1, 2076, '', '', '', '', 0, '1', '1', '1', '1', '2024-01-16 08:52:00', '1', '2024-01-16 08:52:00', '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 (2548, '订单更新', 'trade:order:update', 3, 2, 2076, '', '', '', '', 0, '1', '1', '1', '1', '2024-01-16 08:52:21', '1', '2024-01-16 08:52:21', '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 (2549, '支付&退款案例', '', 2, 1, 2161, 'order', 'fa:paypal', 'pay/demo/order/index', '', 0, '1', '1', '1', '1', '2024-01-18 23:45:00', '1', '2024-01-18 23:47:21', '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 (2550, '提现转账案例', '', 2, 2, 2161, 'transfer', 'fa:transgender-alt', 'pay/demo/withdraw/index', '', 0, '1', '1', '1', '1', '2024-01-18 23:51:16', '1', '2025-05-08 13:04:36', '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 (2551, '钱包管理', '', 1, 4, 1117, 'wallet', 'ep:wallet', '', '', 0, '1', '1', '1', '', '2023-12-29 02:32:54', '1', '2024-02-29 08:58:54', '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 (2552, '充值套餐', '', 2, 2, 2551, 'wallet-recharge-package', 'fa:leaf', 'pay/wallet/rechargePackage/index', 'WalletRechargePackage', 0, '1', '1', '1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', '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 (2553, '钱包充值套餐查询', 'pay:wallet-recharge-package:query', 3, 1, 2552, '', '', '', NULL, 0, '1', '1', '1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', '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 (2554, '钱包充值套餐创建', 'pay:wallet-recharge-package:create', 3, 2, 2552, '', '', '', NULL, 0, '1', '1', '1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', '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 (2555, '钱包充值套餐更新', 'pay:wallet-recharge-package:update', 3, 3, 2552, '', '', '', NULL, 0, '1', '1', '1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', '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 (2556, '钱包充值套餐删除', 'pay:wallet-recharge-package:delete', 3, 4, 2552, '', '', '', NULL, 0, '1', '1', '1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', '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 (2557, '钱包余额', '', 2, 1, 2551, 'wallet-balance', 'fa:leaf', 'pay/wallet/balance/index', 'WalletBalance', 0, '1', '1', '1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', '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 (2558, '钱包余额查询', 'pay:wallet:query', 3, 1, 2557, '', '', '', NULL, 0, '1', '1', '1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', '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 (2559, '转账订单', '', 2, 3, 1117, 'transfer', 'ep:credit-card', 'pay/transfer/index', 'PayTransfer', 0, '1', '1', '1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', '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 (2560, '数据统计', '', 1, 200, 2397, 'statistics', 'ep:data-line', '', '', 0, '1', '1', '1', '1', '2024-01-26 22:50:35', '1', '2024-02-24 20:10:07', '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 (2561, '排行榜', 'crm:statistics-rank:query', 2, 1, 2560, 'ranking', 'fa:area-chart', 'crm/statistics/rank/index', 'CrmStatisticsRank', 0, '1', '1', '1', '1', '2024-01-26 22:52:09', '1', '2024-04-24 19:39:11', '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 (2562, '客户导入', 'crm:customer:import', 3, 6, 2391, '', '', '', '', 0, '1', '1', '1', '1', '2024-02-01 13:09:00', '1', '2024-02-01 13:09:05', '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 (2563, 'ERP 系统', '', 1, 300, 0, '/erp', 'simple-icons:erpnext', '', '', 0, '1', '1', '1', '1', '2024-02-04 15:37:25', '1', '2025-04-19 18:56:15', '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 (2564, '产品管理', '', 1, 40, 2563, 'product', 'fa:product-hunt', '', '', 0, '1', '1', '1', '1', '2024-02-04 15:38:43', '1', '2024-02-04 15:38:43', '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 (2565, '产品信息', '', 2, 0, 2564, 'product', 'fa-solid:apple-alt', 'erp/product/product/index', 'ErpProduct', 0, '1', '1', '1', '', '2024-02-04 07:52:15', '1', '2024-02-05 14:42:11', '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 (2566, '产品查询', 'erp:product:query', 3, 1, 2565, '', '', '', '', 0, '1', '1', '1', '', '2024-02-04 07:52:15', '1', '2024-02-04 17:21:57', '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 (2567, '产品创建', 'erp:product:create', 3, 2, 2565, '', '', '', '', 0, '1', '1', '1', '', '2024-02-04 07:52:15', '1', '2024-02-04 17:22:12', '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 (2568, '产品更新', 'erp:product:update', 3, 3, 2565, '', '', '', '', 0, '1', '1', '1', '', '2024-02-04 07:52:15', '1', '2024-02-04 17:22:16', '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 (2569, '产品删除', 'erp:product:delete', 3, 4, 2565, '', '', '', '', 0, '1', '1', '1', '', '2024-02-04 07:52:15', '1', '2024-02-04 17:22:22', '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 (2570, '产品导出', 'erp:product:export', 3, 5, 2565, '', '', '', '', 0, '1', '1', '1', '', '2024-02-04 07:52:15', '1', '2024-02-04 17:22:26', '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 (2571, '产品分类', '', 2, 1, 2564, 'product-category', 'fa:certificate', 'erp/product/category/index', 'ErpProductCategory', 0, '1', '1', '1', '', '2024-02-04 09:21:04', '1', '2024-02-04 17:24:58', '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 (2572, '分类查询', 'erp:product-category:query', 3, 1, 2571, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', '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 (2573, '分类创建', 'erp:product-category:create', 3, 2, 2571, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', '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 (2574, '分类更新', 'erp:product-category:update', 3, 3, 2571, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', '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 (2575, '分类删除', 'erp:product-category:delete', 3, 4, 2571, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', '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 (2576, '分类导出', 'erp:product-category:export', 3, 5, 2571, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', '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 (2577, '产品单位', '', 2, 2, 2564, 'unit', 'ep:opportunity', 'erp/product/unit/index', 'ErpProductUnit', 0, '1', '1', '1', '', '2024-02-04 11:54:08', '1', '2024-02-04 19:54:37', '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 (2578, '单位查询', 'erp:product-unit:query', 3, 1, 2577, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', '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 (2579, '单位创建', 'erp:product-unit:create', 3, 2, 2577, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', '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 (2580, '单位更新', 'erp:product-unit:update', 3, 3, 2577, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', '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 (2581, '单位删除', 'erp:product-unit:delete', 3, 4, 2577, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', '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 (2582, '单位导出', 'erp:product-unit:export', 3, 5, 2577, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', '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 (2583, '库存管理', '', 1, 30, 2563, 'stock', 'fa:window-restore', '', '', 0, '1', '1', '1', '1', '2024-02-05 00:29:37', '1', '2024-02-05 00:29:37', '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 (2584, '仓库信息', '', 2, 0, 2583, 'warehouse', 'ep:house', 'erp/stock/warehouse/index', 'ErpWarehouse', 0, '1', '1', '1', '', '2024-02-04 17:12:09', '1', '2024-02-05 01:12:53', '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 (2585, '仓库查询', 'erp:warehouse:query', 3, 1, 2584, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', '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 (2586, '仓库创建', 'erp:warehouse:create', 3, 2, 2584, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', '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 (2587, '仓库更新', 'erp:warehouse:update', 3, 3, 2584, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', '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 (2588, '仓库删除', 'erp:warehouse:delete', 3, 4, 2584, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', '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 (2589, '仓库导出', 'erp:warehouse:export', 3, 5, 2584, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', '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 (2590, '产品库存', '', 2, 1, 2583, 'stock', 'ep:coffee', 'erp/stock/stock/index', 'ErpStock', 0, '1', '1', '1', '', '2024-02-05 06:40:50', '1', '2024-02-05 14:42:44', '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 (2591, '库存查询', 'erp:stock:query', 3, 1, 2590, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 06:40:50', '', '2024-02-05 06:40:50', '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 (2592, '库存导出', 'erp:stock:export', 3, 5, 2590, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 06:40:50', '', '2024-02-05 06:40:50', '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 (2593, '出入库明细', '', 2, 2, 2583, 'record', 'fa-solid:blog', 'erp/stock/record/index', 'ErpStockRecord', 0, '1', '1', '1', '', '2024-02-05 10:27:21', '1', '2024-02-06 17:26:11', '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 (2594, '库存明细查询', 'erp:stock-record:query', 3, 1, 2593, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 10:27:21', '', '2024-02-05 10:27:21', '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 (2595, '库存明细导出', 'erp:stock-record:export', 3, 5, 2593, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 10:27:21', '', '2024-02-05 10:27:21', '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 (2596, '其它入库', '', 2, 3, 2583, 'in', 'ep:zoom-in', 'erp/stock/in/index', 'ErpStockIn', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-07 19:06:51', '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 (2597, '其它入库单查询', 'erp:stock-in:query', 3, 1, 2596, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', '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 (2598, '其它入库单创建', 'erp:stock-in:create', 3, 2, 2596, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', '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 (2599, '其它入库单更新', 'erp:stock-in:update', 3, 3, 2596, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', '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 (2600, '其它入库单删除', 'erp:stock-in:delete', 3, 4, 2596, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', '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 (2601, '其它入库单导出', 'erp:stock-in:export', 3, 5, 2596, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', '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 (2602, '采购管理', '', 1, 10, 2563, 'purchase', 'fa:buysellads', '', '', 0, '1', '1', '1', '1', '2024-02-06 16:01:01', '1', '2024-02-06 16:01:23', '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 (2603, '供应商信息', '', 2, 4, 2602, 'supplier', 'fa:superpowers', 'erp/purchase/supplier/index', 'ErpSupplier', 0, '1', '1', '1', '', '2024-02-06 08:21:55', '1', '2024-02-06 16:22:25', '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 (2604, '供应商查询', 'erp:supplier:query', 3, 1, 2603, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', '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 (2605, '供应商创建', 'erp:supplier:create', 3, 2, 2603, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', '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 (2606, '供应商更新', 'erp:supplier:update', 3, 3, 2603, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', '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 (2607, '供应商删除', 'erp:supplier:delete', 3, 4, 2603, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', '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 (2608, '供应商导出', 'erp:supplier:export', 3, 5, 2603, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', '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 (2609, '其它入库单审批', 'erp:stock-in:update-status', 3, 6, 2596, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', '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 (2610, '其它出库', '', 2, 4, 2583, 'out', 'ep:zoom-out', 'erp/stock/out/index', 'ErpStockOut', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-07 19:06:55', '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 (2611, '其它出库单查询', 'erp:stock-out:query', 3, 1, 2610, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:39', '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 (2612, '其它出库单创建', 'erp:stock-out:create', 3, 2, 2610, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:42', '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 (2613, '其它出库单更新', 'erp:stock-out:update', 3, 3, 2610, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:44', '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 (2614, '其它出库单删除', 'erp:stock-out:delete', 3, 4, 2610, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:56', '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 (2615, '其它出库单导出', 'erp:stock-out:export', 3, 5, 2610, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:57', '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 (2616, '其它出库单审批', 'erp:stock-out:update-status', 3, 6, 2610, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:58', '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 (2617, '销售管理', '', 1, 20, 2563, 'sale', 'fa:sellsy', '', '', 0, '1', '1', '1', '1', '2024-02-07 15:12:32', '1', '2024-02-07 15:12:32', '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 (2618, '客户信息', '', 2, 4, 2617, 'customer', 'ep:avatar', 'erp/sale/customer/index', 'ErpCustomer', 0, '1', '1', '1', '', '2024-02-07 07:21:45', '1', '2024-02-07 15:22:25', '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 (2619, '客户查询', 'erp:customer:query', 3, 1, 2618, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', '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 (2620, '客户创建', 'erp:customer:create', 3, 2, 2618, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', '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 (2621, '客户更新', 'erp:customer:update', 3, 3, 2618, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', '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 (2622, '客户删除', 'erp:customer:delete', 3, 4, 2618, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', '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 (2623, '客户导出', 'erp:customer:export', 3, 5, 2618, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', '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 (2624, '库存调拨', '', 2, 5, 2583, 'move', 'ep:folder-remove', 'erp/stock/move/index', 'ErpStockMove', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-16 18:53:55', '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 (2625, '库存调度单查询', 'erp:stock-move:query', 3, 1, 2624, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', '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 (2626, '库存调度单创建', 'erp:stock-move:create', 3, 2, 2624, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', '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 (2627, '库存调度单更新', 'erp:stock-move:update', 3, 3, 2624, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', '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 (2628, '库存调度单删除', 'erp:stock-move:delete', 3, 4, 2624, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', '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 (2629, '库存调度单导出', 'erp:stock-move:export', 3, 5, 2624, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', '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 (2630, '库存调度单审批', 'erp:stock-move:update-status', 3, 6, 2624, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', '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 (2631, '库存盘点', '', 2, 6, 2583, 'check', 'ep:circle-check-filled', 'erp/stock/check/index', 'ErpStockCheck', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-08 08:31:09', '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 (2632, '库存盘点单查询', 'erp:stock-check:query', 3, 1, 2631, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', '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 (2633, '库存盘点单创建', 'erp:stock-check:create', 3, 2, 2631, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', '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 (2634, '库存盘点单更新', 'erp:stock-check:update', 3, 3, 2631, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', '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 (2635, '库存盘点单删除', 'erp:stock-check:delete', 3, 4, 2631, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', '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 (2636, '库存盘点单导出', 'erp:stock-check:export', 3, 5, 2631, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', '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 (2637, '库存盘点单审批', 'erp:stock-check:update-status', 3, 6, 2631, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', '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 (2638, '销售订单', '', 2, 1, 2617, 'order', 'fa:first-order', 'erp/sale/order/index', 'ErpSaleOrder', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-10 21:59:20', '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 (2639, '销售订单查询', 'erp:sale-order:query', 3, 1, 2638, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', '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 (2640, '销售订单创建', 'erp:sale-order:create', 3, 2, 2638, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', '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 (2641, '销售订单更新', 'erp:sale-order:update', 3, 3, 2638, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', '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 (2642, '销售订单删除', 'erp:sale-order:delete', 3, 4, 2638, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', '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 (2643, '销售订单导出', 'erp:sale-order:export', 3, 5, 2638, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', '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 (2644, '销售订单审批', 'erp:sale-order:update-status', 3, 6, 2638, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', '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 (2645, '财务管理', '', 1, 50, 2563, 'finance', 'ep:money', '', '', 0, '1', '1', '1', '1', '2024-02-10 08:05:58', '1', '2024-02-10 08:06:07', '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 (2646, '结算账户', '', 2, 10, 2645, 'account', 'fa:universal-access', 'erp/finance/account/index', 'ErpAccount', 0, '1', '1', '1', '', '2024-02-10 00:15:07', '1', '2024-02-14 08:24:31', '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 (2647, '结算账户查询', 'erp:account:query', 3, 1, 2646, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', '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 (2648, '结算账户创建', 'erp:account:create', 3, 2, 2646, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', '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 (2649, '结算账户更新', 'erp:account:update', 3, 3, 2646, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', '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 (2650, '结算账户删除', 'erp:account:delete', 3, 4, 2646, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', '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 (2651, '结算账户导出', 'erp:account:export', 3, 5, 2646, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', '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 (2652, '销售出库', '', 2, 2, 2617, 'out', 'ep:sold-out', 'erp/sale/out/index', 'ErpSaleOut', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-10 22:02:07', '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 (2653, '销售出库查询', 'erp:sale-out:query', 3, 1, 2652, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', '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 (2654, '销售出库创建', 'erp:sale-out:create', 3, 2, 2652, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', '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 (2655, '销售出库更新', 'erp:sale-out:update', 3, 3, 2652, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', '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 (2656, '销售出库删除', 'erp:sale-out:delete', 3, 4, 2652, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', '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 (2657, '销售出库导出', 'erp:sale-out:export', 3, 5, 2652, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', '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 (2658, '销售出库审批', 'erp:sale-out:update-status', 3, 6, 2652, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', '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 (2659, '销售退货', '', 2, 3, 2617, 'return', 'fa-solid:bone', 'erp/sale/return/index', 'ErpSaleReturn', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-12 06:12:58', '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 (2660, '销售退货查询', 'erp:sale-return:query', 3, 1, 2659, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', '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 (2661, '销售退货创建', 'erp:sale-return:create', 3, 2, 2659, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', '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 (2662, '销售退货更新', 'erp:sale-return:update', 3, 3, 2659, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', '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 (2663, '销售退货删除', 'erp:sale-return:delete', 3, 4, 2659, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', '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 (2664, '销售退货导出', 'erp:sale-return:export', 3, 5, 2659, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', '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 (2665, '销售退货审批', 'erp:sale-return:update-status', 3, 6, 2659, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', '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 (2666, '采购订单', '', 2, 1, 2602, 'order', 'fa-solid:border-all', 'erp/purchase/order/index', 'ErpPurchaseOrder', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-12 08:51:49', '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 (2667, '采购订单查询', 'erp:purchase-order:query', 3, 1, 2666, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', '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 (2668, '采购订单创建', 'erp:purchase-order:create', 3, 2, 2666, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', '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 (2669, '采购订单更新', 'erp:purchase-order:update', 3, 3, 2666, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', '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 (2670, '采购订单删除', 'erp:purchase-order:delete', 3, 4, 2666, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', '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 (2671, '采购订单导出', 'erp:purchase-order:export', 3, 5, 2666, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', '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 (2672, '采购订单审批', 'erp:purchase-order:update-status', 3, 6, 2666, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', '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 (2673, '采购入库', '', 2, 2, 2602, 'in', 'fa-solid:gopuram', 'erp/purchase/in/index', 'ErpPurchaseIn', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-12 11:19:27', '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 (2674, '采购入库查询', 'erp:purchase-in:query', 3, 1, 2673, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', '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 (2675, '采购入库创建', 'erp:purchase-in:create', 3, 2, 2673, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', '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 (2676, '采购入库更新', 'erp:purchase-in:update', 3, 3, 2673, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', '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 (2677, '采购入库删除', 'erp:purchase-in:delete', 3, 4, 2673, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', '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 (2678, '采购入库导出', 'erp:purchase-in:export', 3, 5, 2673, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', '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 (2679, '采购入库审批', 'erp:purchase-in:update-status', 3, 6, 2673, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', '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 (2680, '采购退货', '', 2, 3, 2602, 'return', 'ep:minus', 'erp/purchase/return/index', 'ErpPurchaseReturn', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-12 20:51:02', '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 (2681, '采购退货查询', 'erp:purchase-return:query', 3, 1, 2680, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', '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 (2682, '采购退货创建', 'erp:purchase-return:create', 3, 2, 2680, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', '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 (2683, '采购退货更新', 'erp:purchase-return:update', 3, 3, 2680, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', '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 (2684, '采购退货删除', 'erp:purchase-return:delete', 3, 4, 2680, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', '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 (2685, '采购退货导出', 'erp:purchase-return:export', 3, 5, 2680, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', '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 (2686, '采购退货审批', 'erp:purchase-return:update-status', 3, 6, 2680, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', '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 (2687, '付款单', '', 2, 1, 2645, 'payment', 'ep:caret-right', 'erp/finance/payment/index', 'ErpFinancePayment', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-14 08:24:23', '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 (2688, '付款单查询', 'erp:finance-payment:query', 3, 1, 2687, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', '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 (2689, '付款单创建', 'erp:finance-payment:create', 3, 2, 2687, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', '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 (2690, '付款单更新', 'erp:finance-payment:update', 3, 3, 2687, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', '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 (2691, '付款单删除', 'erp:finance-payment:delete', 3, 4, 2687, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', '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 (2692, '付款单导出', 'erp:finance-payment:export', 3, 5, 2687, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', '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 (2693, '付款单审批', 'erp:finance-payment:update-status', 3, 6, 2687, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', '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 (2694, '收款单', '', 2, 2, 2645, 'receipt', 'ep:expand', 'erp/finance/receipt/index', 'ErpFinanceReceipt', 0, '1', '1', '1', '', '2024-02-05 16:08:56', '1', '2024-02-15 19:35:45', '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 (2695, '收款单查询', 'erp:finance-receipt:query', 3, 1, 2694, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', '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 (2696, '收款单创建', 'erp:finance-receipt:create', 3, 2, 2694, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', '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 (2697, '收款单更新', 'erp:finance-receipt:update', 3, 3, 2694, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', '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 (2698, '收款单删除', 'erp:finance-receipt:delete', 3, 4, 2694, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', '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 (2699, '收款单导出', 'erp:finance-receipt:export', 3, 5, 2694, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', '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 (2700, '收款单审批', 'erp:finance-receipt:update-status', 3, 6, 2694, '', '', '', NULL, 0, '1', '1', '1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', '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 (2701, '待办事项', '', 2, 0, 2397, 'backlog', 'fa-solid:tasks', 'crm/backlog/index', 'CrmBacklog', 0, '1', '1', '1', '1', '2024-02-17 17:17:11', '1', '2024-02-17 17:17:11', '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 (2702, 'ERP 首页', 'erp:statistics:query', 2, 0, 2563, 'home', 'ep:home-filled', 'erp/home/index.vue', 'ErpHome', 0, '1', '1', '1', '1', '2024-02-18 16:49:40', '1', '2024-02-26 21:12:18', '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 (2703, '商机状态配置', '', 2, 4, 2524, 'business-status', 'fa-solid:charging-station', 'crm/business/status/index', 'CrmBusinessStatus', 0, '1', '1', '1', '1', '2024-02-21 20:15:17', '1', '2024-02-21 20:15:17', '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 (2704, '商机状态查询', 'crm:business-status:query', 3, 1, 2703, '', '', '', '', 0, '1', '1', '1', '1', '2024-02-21 20:35:36', '1', '2024-02-21 20:36:06', '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 (2705, '商机状态创建', 'crm:business-status:create', 3, 2, 2703, '', '', '', '', 0, '1', '1', '1', '1', '2024-02-21 20:35:57', '1', '2024-02-21 20:35:57', '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 (2706, '商机状态更新', 'crm:business-status:update', 3, 3, 2703, '', '', '', '', 0, '1', '1', '1', '1', '2024-02-21 20:36:21', '1', '2024-02-21 20:36:21', '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 (2707, '商机状态删除', 'crm:business-status:delete', 3, 4, 2703, '', '', '', '', 0, '1', '1', '1', '1', '2024-02-21 20:36:36', '1', '2024-02-21 20:36:36', '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 (2708, '合同配置', '', 2, 5, 2524, 'contract-config', 'ep:connection', 'crm/contract/config/index', 'CrmContractConfig', 0, '1', '1', '1', '1', '2024-02-24 16:44:40', '1', '2024-02-24 16:44:48', '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 (2709, '客户公海配置查询', 'crm:customer-pool-config:query', 3, 2, 2516, '', '', '', '', 0, '1', '1', '1', '1', '2024-02-24 16:45:19', '1', '2024-02-24 16:45:28', '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 (2710, '合同配置更新', 'crm:contract-config:update', 3, 1, 2708, '', '', '', '', 0, '1', '1', '1', '1', '2024-02-24 16:45:56', '1', '2024-02-24 16:45:56', '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 (2711, '合同配置查询', 'crm:contract-config:query', 3, 2, 2708, '', '', '', '', 0, '1', '1', '1', '1', '2024-02-24 16:46:16', '1', '2024-02-24 16:46:16', '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 (2712, '客户分析', 'crm:statistics-customer:query', 2, 0, 2560, 'customer', 'ep:avatar', 'crm/statistics/customer/index.vue', 'CrmStatisticsCustomer', 0, '1', '1', '1', '1', '2024-03-09 16:43:56', '1', '2024-05-04 20:38:50', '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 (2713, '抄送我的', 'bpm:process-instance-cc:query', 2, 30, 1200, 'copy', 'ep:copy-document', 'bpm/task/copy/index', 'BpmProcessInstanceCopy', 0, '1', '1', '1', '1', '2024-03-17 21:50:23', '1', '2024-04-24 19:55:12', '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 (2714, '流程分类', '', 2, 3, 1186, 'category', 'fa:object-ungroup', 'bpm/category/index', 'BpmCategory', 0, '1', '1', '1', '', '2024-03-08 02:00:51', '1', '2024-03-21 23:51:18', '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 (2715, '分类查询', 'bpm:category:query', 3, 1, 2714, '', '', '', '', 0, '1', '1', '1', '', '2024-03-08 02:00:51', '1', '2024-03-19 14:36:25', '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 (2716, '分类创建', 'bpm:category:create', 3, 2, 2714, '', '', '', '', 0, '1', '1', '1', '', '2024-03-08 02:00:51', '1', '2024-03-19 14:36:31', '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 (2717, '分类更新', 'bpm:category:update', 3, 3, 2714, '', '', '', '', 0, '1', '1', '1', '', '2024-03-08 02:00:51', '1', '2024-03-19 14:36:35', '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 (2718, '分类删除', 'bpm:category:delete', 3, 4, 2714, '', '', '', '', 0, '1', '1', '1', '', '2024-03-08 02:00:51', '1', '2024-03-19 14:36:41', '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 (2720, '发起流程', '', 2, 0, 1200, 'create', 'fa-solid:grin-stars', 'bpm/processInstance/create/index', 'BpmProcessInstanceCreate', 0, '1', '0', '1', '1', '2024-03-19 19:46:05', '1', '2024-03-23 19:03:42', '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 (2721, '流程实例', '', 2, 10, 1186, 'process-instance/manager', 'fa:square', 'bpm/processInstance/manager/index', 'BpmProcessInstanceManager', 0, '1', '1', '1', '1', '2024-03-21 23:57:30', '1', '2024-03-21 23:57:30', '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 (2722, '流程实例的查询(管理员)', 'bpm:process-instance:manager-query', 3, 1, 2721, '', '', '', '', 0, '1', '1', '1', '1', '2024-03-22 08:18:27', '1', '2024-03-22 08:19:05', '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 (2723, '流程实例的取消(管理员)', 'bpm:process-instance:cancel-by-admin', 3, 2, 2721, '', '', '', '', 0, '1', '1', '1', '1', '2024-03-22 08:19:25', '1', '2024-03-22 08:19:25', '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 (2724, '流程任务', '', 2, 11, 1186, 'process-tasnk', 'ep:collection-tag', 'bpm/task/manager/index', 'BpmManagerTask', 0, '1', '1', '1', '1', '2024-03-22 08:43:22', '1', '2024-03-22 08:43:27', '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 (2725, '流程任务的查询(管理员)', 'bpm:task:manager-query', 3, 1, 2724, '', '', '', '', 0, '1', '1', '1', '1', '2024-03-22 08:43:49', '1', '2025-12-23 23:04:44', '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 (2726, '流程监听器', '', 2, 5, 1186, 'process-listener', 'fa:assistive-listening-systems', 'bpm/processListener/index', 'BpmProcessListener', 0, '1', '1', '1', '', '2024-03-09 16:05:34', '1', '2024-03-23 13:13:38', '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 (2727, '流程监听器查询', 'bpm:process-listener:query', 3, 1, 2726, '', '', '', NULL, 0, '1', '1', '1', '', '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', '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 (2728, '流程监听器创建', 'bpm:process-listener:create', 3, 2, 2726, '', '', '', NULL, 0, '1', '1', '1', '', '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', '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 (2729, '流程监听器更新', 'bpm:process-listener:update', 3, 3, 2726, '', '', '', NULL, 0, '1', '1', '1', '', '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', '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 (2730, '流程监听器删除', 'bpm:process-listener:delete', 3, 4, 2726, '', '', '', NULL, 0, '1', '1', '1', '', '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', '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 (2731, '流程表达式', '', 2, 6, 1186, 'process-expression', 'fa:wpexplorer', 'bpm/processExpression/index', 'BpmProcessExpression', 0, '1', '1', '1', '', '2024-03-09 22:35:08', '1', '2024-03-23 19:43:05', '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 (2732, '流程表达式查询', 'bpm:process-expression:query', 3, 1, 2731, '', '', '', NULL, 0, '1', '1', '1', '', '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', '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 (2733, '流程表达式创建', 'bpm:process-expression:create', 3, 2, 2731, '', '', '', NULL, 0, '1', '1', '1', '', '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', '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 (2734, '流程表达式更新', 'bpm:process-expression:update', 3, 3, 2731, '', '', '', NULL, 0, '1', '1', '1', '', '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', '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 (2735, '流程表达式删除', 'bpm:process-expression:delete', 3, 4, 2731, '', '', '', NULL, 0, '1', '1', '1', '', '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', '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 (2736, '员工业绩', 'crm:statistics-performance:query', 2, 3, 2560, 'performance', 'ep:dish-dot', 'crm/statistics/performance/index', 'CrmStatisticsPerformance', 0, '1', '1', '1', '1', '2024-04-05 13:49:20', '1', '2024-04-24 19:42:43', '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 (2737, '客户画像', 'crm:statistics-portrait:query', 2, 4, 2560, 'portrait', 'ep:picture', 'crm/statistics/portrait/index', 'CrmStatisticsPortrait', 0, '1', '1', '1', '1', '2024-04-05 13:57:40', '1', '2024-04-24 19:42:24', '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 (2738, '销售漏斗', 'crm:statistics-funnel:query', 2, 5, 2560, 'funnel', 'ep:grape', 'crm/statistics/funnel/index', 'CrmStatisticsFunnel', 0, '1', '1', '1', '1', '2024-04-13 10:53:26', '1', '2024-04-24 19:39:33', '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 (2739, '消息中心', '', 1, 7, 1, 'messages', 'ep:chat-dot-round', '', '', 0, '1', '1', '1', '1', '2024-04-22 23:54:30', '1', '2024-04-23 09:36:35', '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 (2740, '监控中心', '', 1, 10, 2, 'monitors', 'ep:monitor', '', '', 0, '1', '1', '1', '1', '2024-04-23 00:04:44', '1', '2024-04-23 00:04:44', '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 (2741, '领取公海客户', 'crm:customer:receive', 3, 1, 2546, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:47:45', '1', '2024-04-24 19:47:45', '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 (2742, '分配公海客户', 'crm:customer:distribute', 3, 2, 2546, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:48:05', '1', '2024-04-24 19:48:05', '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 (2743, '商品统计查询', 'statistics:product:query', 3, 1, 2545, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:50:05', '1', '2024-04-24 19:50:05', '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 (2744, '商品统计导出', 'statistics:product:export', 3, 2, 2545, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:50:26', '1', '2024-04-24 19:50:26', '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 (2745, '支付渠道查询', 'pay:channel:query', 3, 10, 1126, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:53:01', '1', '2024-04-24 19:53:01', '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 (2746, '支付渠道创建', 'pay:channel:create', 3, 11, 1126, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:53:18', '1', '2024-04-24 19:53:18', '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 (2747, '支付渠道更新', 'pay:channel:update', 3, 12, 1126, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:53:32', '1', '2024-04-24 19:53:58', '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 (2748, '支付渠道删除', 'pay:channel:delete', 3, 13, 1126, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:54:34', '1', '2024-04-24 19:54:34', '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 (2749, '商品收藏查询', 'product:favorite:query', 3, 10, 2014, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:55:47', '1', '2024-04-24 19:55:47', '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 (2750, '商品浏览查询', 'product:browse-history:query', 3, 20, 2014, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:57:43', '1', '2024-04-24 19:57:43', '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 (2751, '售后同意', 'trade:after-sale:agree', 3, 2, 2073, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:58:40', '1', '2024-04-24 19:58:40', '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 (2752, '售后不同意', 'trade:after-sale:disagree', 3, 3, 2073, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 19:59:03', '1', '2024-04-24 19:59:03', '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 (2753, '售后确认退货', 'trade:after-sale:receive', 3, 4, 2073, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 20:00:07', '1', '2024-04-24 20:00:07', '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 (2754, '售后确认退款', 'trade:after-sale:refund', 3, 5, 2073, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 20:00:24', '1', '2024-04-24 20:00:24', '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 (2755, '删除项目', 'report:go-view-project:delete', 3, 2, 2153, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 20:01:37', '1', '2024-04-24 20:01:37', '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 (2756, '会员等级记录查询', 'member:level-record:query', 3, 10, 2325, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 20:02:32', '1', '2024-04-24 20:02:32', '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 (2757, '会员经验记录查询', 'member:experience-record:query', 3, 11, 2325, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 20:02:51', '1', '2024-04-24 20:02:51', '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 (2758, 'AI 大模型', '', 1, 400, 0, '/ai', 'tabler:ai', '', '', 0, '1', '1', '1', '1', '2024-05-07 15:07:56', '1', '2025-04-19 18:57:05', '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 (2759, 'AI 对话', '', 2, 1, 2758, 'chat', 'ep:message', 'ai/chat/index/index.vue', 'AiChat', 0, '1', '1', '1', '1', '2024-05-07 15:09:14', '1', '2024-07-07 17:15:36', '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 (2760, '控制台', '', 1, 100, 2758, 'console', 'ep:setting', '', '', 0, '1', '1', '1', '1', '2024-05-09 22:39:09', '1', '2024-05-24 23:34:21', '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 (2761, 'API 密钥', '', 2, 0, 2760, 'api-key', 'ep:key', 'ai/model/apiKey/index.vue', 'AiApiKey', 0, '1', '1', '1', '', '2024-05-09 14:52:56', '1', '2024-05-10 22:44:08', '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 (2762, 'API 密钥查询', 'ai:api-key:query', 3, 1, 2761, '', '', '', '', 0, '1', '1', '1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:32', '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 (2763, 'API 密钥创建', 'ai:api-key:create', 3, 2, 2761, '', '', '', '', 0, '1', '1', '1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:26', '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 (2764, 'API 密钥更新', 'ai:api-key:update', 3, 3, 2761, '', '', '', '', 0, '1', '1', '1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:42', '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 (2765, 'API 密钥删除', 'ai:api-key:delete', 3, 4, 2761, '', '', '', '', 0, '1', '1', '1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:48', '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 (2767, '模型配置', '', 2, 0, 2760, 'model', 'fa-solid:abacus', 'ai/model/model/index.vue', 'AiModel', 0, '1', '1', '1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:57:41', '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 (2768, '聊天模型查询', 'ai:model:query', 3, 1, 2767, '', '', '', '', 0, '1', '1', '1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:19:46', '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 (2769, '聊天模型创建', 'ai:model:create', 3, 2, 2767, '', '', '', '', 0, '1', '1', '1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:20:10', '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 (2770, '聊天模型更新', 'ai:model:update', 3, 3, 2767, '', '', '', '', 0, '1', '1', '1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:20:14', '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 (2771, '聊天模型删除', 'ai:model:delete', 3, 4, 2767, '', '', '', '', 0, '1', '1', '1', '', '2024-05-10 14:42:48', '1', '2025-03-03 09:20:27', '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 (2773, '聊天角色', '', 2, 0, 2760, 'chat-role', 'fa:user-secret', 'ai/model/chatRole/index.vue', 'AiChatRole', 0, '1', '1', '1', '', '2024-05-13 12:39:28', '1', '2024-05-13 20:41:45', '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 (2774, '聊天角色查询', 'ai:chat-role:query', 3, 1, 2773, '', '', '', NULL, 0, '1', '1', '1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', '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 (2775, '聊天角色创建', 'ai:chat-role:create', 3, 2, 2773, '', '', '', NULL, 0, '1', '1', '1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', '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 (2776, '聊天角色更新', 'ai:chat-role:update', 3, 3, 2773, '', '', '', NULL, 0, '1', '1', '1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', '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 (2777, '聊天角色删除', 'ai:chat-role:delete', 3, 4, 2773, '', '', '', '', 0, '1', '1', '1', '1', '2024-05-13 21:43:38', '1', '2024-05-13 21:43:38', '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 (2778, '聊天管理', '', 2, 10, 2760, 'chat-conversation', 'ep:chat-square', 'ai/chat/manager/index.vue', 'AiChatManager', 0, '1', '1', '1', '', '2024-05-24 15:39:18', '1', '2024-06-26 21:36:56', '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 (2779, '会话查询', 'ai:chat-conversation:query', 3, 1, 2778, '', '', '', '', 0, '1', '1', '1', '', '2024-05-24 15:39:18', '1', '2024-05-25 08:38:30', '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 (2780, '会话删除', 'ai:chat-conversation:delete', 3, 2, 2778, '', '', '', '', 0, '1', '1', '1', '', '2024-05-24 15:39:18', '1', '2024-05-25 08:38:40', '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 (2781, '消息查询', 'ai:chat-message:query', 3, 11, 2778, '', '', '', '', 0, '1', '1', '1', '1', '2024-05-25 08:38:56', '1', '2024-05-25 08:38:56', '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 (2782, '消息删除', 'ai:chat-message:delete', 3, 12, 2778, '', '', '', '', 0, '1', '1', '1', '1', '2024-05-25 08:39:10', '1', '2024-05-25 08:39:10', '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 (2783, 'AI 绘画', '', 2, 2, 2758, 'image', 'ep:picture-rounded', 'ai/image/index/index.vue', 'AiImage', 0, '1', '1', '1', '1', '2024-05-26 11:45:17', '1', '2024-07-07 17:18:59', '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 (2784, '绘画管理', '', 2, 11, 2760, 'image', 'fa:file-image-o', 'ai/image/manager/index.vue', 'AiImageManager', 0, '1', '1', '1', '', '2024-06-26 13:32:31', '1', '2024-06-26 21:37:13', '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 (2785, '绘画查询', 'ai:image:query', 3, 1, 2784, '', '', '', '', 0, '1', '1', '1', '', '2024-06-26 13:32:31', '1', '2024-06-26 22:21:57', '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 (2786, '绘画删除', 'ai:image:delete', 3, 4, 2784, '', '', '', '', 0, '1', '1', '1', '', '2024-06-26 13:32:31', '1', '2024-06-26 22:22:08', '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 (2787, '绘图更新', 'ai:image:update', 3, 2, 2784, '', '', '', '', 0, '1', '1', '1', '1', '2024-06-26 22:47:56', '1', '2024-08-31 09:21:35', '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 (2788, '音乐管理', '', 2, 12, 2760, 'music', 'fa:music', 'ai/music/manager/index.vue', 'AiMusicManager', 0, '1', '1', '1', '', '2024-06-27 15:03:33', '1', '2024-06-27 23:04:19', '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 (2789, '音乐查询', 'ai:music:query', 3, 1, 2788, '', '', '', NULL, 0, '1', '1', '1', '', '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', '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 (2790, '音乐更新', 'ai:music:update', 3, 3, 2788, '', '', '', NULL, 0, '1', '1', '1', '', '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', '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 (2791, '音乐删除', 'ai:music:delete', 3, 4, 2788, '', '', '', NULL, 0, '1', '1', '1', '', '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', '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 (2792, 'AI 写作', '', 2, 3, 2758, 'write', 'fa-solid:book-reader', 'ai/write/index/index.vue', 'AiWrite', 0, '1', '1', '1', '1', '2024-07-08 09:26:44', '1', '2024-07-16 13:03:06', '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 (2793, '写作管理', '', 2, 13, 2760, 'write', 'fa:bookmark-o', 'ai/write/manager/index.vue', 'AiWriteManager', 0, '1', '1', '1', '', '2024-07-10 13:24:34', '1', '2024-07-10 21:31:59', '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 (2794, 'AI 写作查询', 'ai:write:query', 3, 1, 2793, '', '', '', NULL, 0, '1', '1', '1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', '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 (2795, 'AI 写作删除', 'ai:write:delete', 3, 4, 2793, '', '', '', NULL, 0, '1', '1', '1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', '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 (2796, 'AI 音乐', '', 2, 4, 2758, 'music', 'fa:music', 'ai/music/index/index.vue', 'AiMusic', 0, '1', '1', '1', '1', '2024-07-17 09:21:12', '1', '2024-07-29 21:11:52', '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 (2797, '客服中心', '', 2, 100, 2362, 'kefu', 'fa-solid:user-alt', 'mall/promotion/kefu/index', 'KeFu', 0, '1', '1', '1', '1', '2024-07-17 23:49:05', '1', '2024-07-17 23:49:16', '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 (2798, 'AI 思维导图', '', 2, 6, 2758, 'mind-map', 'fa:sitemap', 'ai/mindmap/index/index.vue', 'AiMindMap', 0, '1', '1', '1', '1', '2024-07-29 21:31:59', '1', '2025-03-02 18:57:31', '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 (2799, '导图管理', '', 2, 14, 2760, 'mind-map', 'fa:map', 'ai/mindmap/manager/index', 'AiMindMapManager', 0, '1', '1', '1', '', '2024-08-10 09:15:09', '1', '2024-08-10 17:24:28', '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 (2800, '思维导图查询', 'ai:mind-map:query', 3, 1, 2799, '', '', '', NULL, 0, '1', '1', '1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', '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 (2801, '思维导图删除', 'ai:mind-map:delete', 3, 4, 2799, '', '', '', NULL, 0, '1', '1', '1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', '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 (2802, '会话查询', 'promotion:kefu-conversation:query', 3, 1, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:17:52', '1', '2024-08-31 09:18:52', '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 (2803, '会话更新', 'promotion:kefu-conversation:update', 3, 2, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:18:15', '1', '2024-08-31 09:19:29', '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 (2804, '消息查询', 'promotion:kefu-message:query', 3, 10, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:18:42', '1', '2024-08-31 09:18:42', '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 (2805, '会话删除', 'promotion:kefu-conversation:delete', 3, 3, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:19:51', '1', '2024-08-31 09:20:32', '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 (2806, '消息发送', 'promotion:kefu-message:send', 3, 12, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:20:06', '1', '2024-08-31 09:20:06', '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 (2807, '消息更新', 'promotion:kefu-message:update', 3, 11, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:20:22', '1', '2024-08-31 09:20:22', '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 (2808, '积分商城', '', 2, 5, 2030, 'point-activity', 'ep:bowl', 'mall/promotion/point/activity/index', 'PointActivity', 0, '1', '1', '1', '', '2024-09-21 05:36:42', '1', '2024-09-23 09:14:43', '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 (2809, '积分商城活动查询', 'promotion:point-activity:query', 3, 1, 2808, '', '', '', '', 0, '1', '1', '1', '', '2024-09-21 05:36:42', '1', '2024-09-22 14:49:05', '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 (2810, '积分商城活动创建', 'promotion:point-activity:create', 3, 2, 2808, '', '', '', '', 0, '1', '1', '1', '', '2024-09-21 05:36:42', '1', '2024-09-22 14:49:08', '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 (2811, '积分商城活动更新', 'promotion:point-activity:update', 3, 3, 2808, '', '', '', '', 0, '1', '1', '1', '', '2024-09-21 05:36:42', '1', '2024-09-22 14:49:10', '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 (2812, '积分商城活动删除', 'promotion:point-activity:delete', 3, 4, 2808, '', '', '', '', 0, '1', '1', '1', '', '2024-09-21 05:36:42', '1', '2024-09-22 14:49:12', '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 (2813, '积分商城活动导出', 'promotion:point-activity:export', 3, 5, 2808, '', '', '', '', 0, '1', '1', '1', '', '2024-09-21 05:36:42', '1', '2024-09-22 14:49:27', '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 (2912, '创建推广员', 'trade:brokerage-user:create', 3, 7, 2346, '', '', '', '', 0, '1', '1', '1', '1', '2024-12-01 14:32:39', '1', '2024-12-01 14:32:39', '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 (2913, '流程清理', 'bpm:model:clean', 3, 7, 1193, '', '', '', '', 0, '1', '1', '1', '1', '2025-01-17 19:32:06', '1', '2025-01-17 19:32:06', '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 (2914, '积分商城活动关闭', 'promotion:point-activity:close', 3, 6, 2808, '', '', '', '', 0, '1', '1', '1', '1', '2025-01-23 20:23:34', '1', '2025-01-23 20:23:34', '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 (2915, 'AI 知识库', '', 2, 5, 2758, 'knowledge', 'ep:notebook', 'ai/knowledge/knowledge/index', 'AiKnowledge', 0, '1', '1', '1', '', '2025-02-28 07:04:21', '1', '2025-03-02 18:58:37', '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 (2916, 'AI 知识库查询', 'ai:knowledge:query', 3, 1, 2915, '', '', '', NULL, 0, '1', '1', '1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', '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 (2917, 'AI 知识库创建', 'ai:knowledge:create', 3, 2, 2915, '', '', '', NULL, 0, '1', '1', '1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', '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 (2918, 'AI 知识库更新', 'ai:knowledge:update', 3, 3, 2915, '', '', '', NULL, 0, '1', '1', '1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', '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 (2919, 'AI 知识库删除', 'ai:knowledge:delete', 3, 4, 2915, '', '', '', NULL, 0, '1', '1', '1', '', '2025-02-28 07:04:21', '', '2025-02-28 07:04:21', '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 (2920, '工具管理', '', 2, 0, 2760, 'tool', 'fa-solid:tools', 'ai/model/tool/index.vue', 'AiTool', 0, '1', '1', '1', '', '2025-03-14 11:19:29', '1', '2025-03-14 19:20:18', '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 (2921, '工具查询', 'ai:tool:query', 3, 1, 2920, '', '', '', NULL, 0, '1', '1', '1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', '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 (2922, '工具创建', 'ai:tool:create', 3, 2, 2920, '', '', '', NULL, 0, '1', '1', '1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', '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 (2923, '工具更新', 'ai:tool:update', 3, 3, 2920, '', '', '', NULL, 0, '1', '1', '1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', '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 (2924, '工具删除', 'ai:tool:delete', 3, 4, 2920, '', '', '', NULL, 0, '1', '1', '1', '', '2025-03-14 11:19:29', '', '2025-03-14 11:19:29', '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 (4000, 'IoT 物联网', '', 1, 500, 0, '/iot', 'fa-solid:hdd', '', '', 0, '1', '1', '1', '1', '2024-08-10 09:55:28', '1', '2024-12-07 15:58:34', '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 (4001, '设备接入', '', 1, 2, 4000, 'device', 'ep:platform', '', '', 0, '1', '1', '1', '1', '2024-08-10 09:57:56', '1', '2025-02-27 08:39:49', '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 (4002, '产品管理', '', 2, 1, 4001, 'product', 'fa-solid:tools', 'iot/product/product/index', 'IoTProduct', 0, '1', '1', '1', '', '2024-08-10 02:38:02', '1', '2025-06-15 20:56:06', '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 (4003, '产品查询', 'iot:product:query', 3, 1, 4002, '', '', '', NULL, 0, '1', '1', '1', '', '2024-08-10 02:38:02', '', '2024-12-07 15:55:00', '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 (4004, '产品创建', 'iot:product:create', 3, 2, 4002, '', '', '', NULL, 0, '1', '1', '1', '', '2024-08-10 02:38:02', '', '2024-12-07 15:55:03', '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 (4005, '产品更新', 'iot:product:update', 3, 3, 4002, '', '', '', NULL, 0, '1', '1', '1', '', '2024-08-10 02:38:02', '', '2024-12-07 15:55:05', '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 (4006, '产品删除', 'iot:product:delete', 3, 4, 4002, '', '', '', NULL, 0, '1', '1', '1', '', '2024-08-10 02:38:02', '', '2024-12-07 15:55:06', '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 (4007, '产品导出', 'iot:product:export', 3, 5, 4002, '', '', '', NULL, 0, '1', '1', '1', '', '2024-08-10 02:38:02', '', '2024-12-07 15:55:13', '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 (4008, '设备管理', '', 2, 2, 4001, 'device', 'fa:mobile', 'iot/device/device/index', 'IoTDevice', 0, '1', '1', '1', '', '2024-09-16 18:48:19', '1', '2025-06-15 20:56:10', '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 (4009, '设备查询', 'iot:device:query', 3, 1, 4008, '', '', '', '', 0, '1', '1', '1', '', '2024-09-16 18:48:19', '1', '2024-12-07 15:55:40', '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 (4010, '设备创建', 'iot:device:create', 3, 2, 4008, '', '', '', '', 0, '1', '1', '1', '', '2024-09-16 18:48:19', '1', '2024-12-07 15:55:41', '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 (4011, '设备更新', 'iot:device:update', 3, 3, 4008, '', '', '', '', 0, '1', '1', '1', '', '2024-09-16 18:48:19', '1', '2024-12-07 15:55:42', '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 (4012, '设备删除', 'iot:device:delete', 3, 4, 4008, '', '', '', '', 0, '1', '1', '1', '', '2024-09-16 18:48:19', '1', '2024-12-07 15:55:43', '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 (4013, '设备导出', 'iot:device:export', 3, 5, 4008, '', '', '', '', 0, '1', '1', '1', '', '2024-09-16 18:48:19', '1', '2024-12-07 15:55:44', '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 (4014, '产品分类', '', 2, 3, 4001, 'product-category', 'ep:notebook', 'iot/product/category/index', 'IotProductCategory', 0, '1', '1', '1', '', '2024-12-07 16:01:35', '1', '2025-06-15 20:56:39', '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 (4015, '产品分类查询', 'iot:product-category:query', 3, 1, 4014, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-07 16:01:35', '', '2024-12-07 16:01:35', '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 (4016, '产品分类创建', 'iot:product-category:create', 3, 2, 4014, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-07 16:01:35', '', '2024-12-07 16:01:35', '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 (4017, '产品分类更新', 'iot:product-category:update', 3, 3, 4014, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-07 16:01:35', '', '2024-12-07 16:01:35', '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 (4018, '产品分类删除', 'iot:product-category:delete', 3, 4, 4014, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-07 16:01:35', '', '2024-12-07 16:01:35', '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 (4031, '设备分组', '', 2, 3, 4001, 'device-group', 'fa-solid:layer-group', 'iot/device/group/index', 'IotDeviceGroup', 0, '1', '1', '1', '', '2024-12-14 17:08:29', '1', '2024-12-14 17:09:17', '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 (4032, '设备分组查询', 'iot:device-group:query', 3, 1, 4031, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-14 17:08:29', '', '2024-12-14 17:08:29', '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 (4033, '设备分组创建', 'iot:device-group:create', 3, 2, 4031, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-14 17:08:29', '', '2024-12-14 17:08:29', '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 (4034, '设备分组更新', 'iot:device-group:update', 3, 3, 4031, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-14 17:08:29', '', '2024-12-14 17:08:29', '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 (4035, '设备分组删除', 'iot:device-group:delete', 3, 4, 4031, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-14 17:08:29', '', '2024-12-14 17:08:29', '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 (4036, '设备导入', 'iot:device:import', 3, 6, 4008, '', '', '', '', 0, '1', '1', '1', '1', '2024-12-15 10:35:47', '1', '2024-12-15 10:35:47', '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 (4037, '产品物模型', '', 2, 99, 4001, 'thing-model', 'ep:mostly-cloudy', 'iot/thingmodel/index', 'IoTThingModel', 0, '0', '0', '0', '', '2024-12-16 17:17:50', '1', '2025-06-15 20:56:19', '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 (4038, '产品物模型功能查询', 'iot:thing-model:query', 3, 1, 4037, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-16 17:17:51', '', '2025-03-17 09:14:54', '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 (4039, '产品物模型功能创建', 'iot:thing-model:create', 3, 2, 4037, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-16 17:17:52', '', '2025-03-17 09:14:58', '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 (4040, '产品物模型功能更新', 'iot:thing-model:update', 3, 3, 4037, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-16 17:17:52', '', '2025-03-17 09:15:03', '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 (4041, '产品物模型功能删除', 'iot:thing-model:delete', 3, 4, 4037, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-16 17:17:52', '', '2025-03-17 09:15:06', '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 (4042, '产品物模型功能导出', 'iot:thing-model:export', 3, 5, 4037, '', '', '', NULL, 0, '1', '1', '1', '', '2024-12-16 17:17:53', '', '2025-03-17 09:15:09', '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 (4043, '设备消息发送', 'iot:device:message-send', 3, 12, 4008, '', '', '', '', 0, '1', '1', '1', '1', '2025-01-28 04:40:16', '1', '2025-06-14 14:09:26', '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 (4044, '设备属性查询', 'iot:device:property-query', 3, 10, 4008, '', '', '', '', 0, '1', '1', '1', '1', '2025-01-28 11:52:54', '1', '2025-01-28 11:52:54', '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 (4045, '设备消息查询', 'iot:device:message-query', 3, 11, 4008, '', '', '', '', 0, '1', '1', '1', '1', '2025-01-28 11:53:22', '1', '2025-06-14 11:11:20', '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 (4047, '运维管理', '', 1, 4, 4000, 'operation', 'fa:align-center', '', '', 0, '1', '1', '1', '1', '2025-02-05 22:21:37', '"1"', '2025-06-30 20:12:48', '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 (4048, '规则引擎', '', 1, 3, 4000, 'rule', 'fa-solid:cogs', '', '', 0, '1', '1', '1', '1', '2025-02-11 14:10:54', '1', '2025-02-11 14:10:54', '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 (4049, '场景联动', '', 2, 1, 4048, 'scene', 'ep:link', 'iot/rule/scene/index', 'IoTSceneRule', 0, '1', '1', '1', '1', '2025-02-11 14:12:44', '"1"', '2025-08-09 15:38:32', '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 (4050, 'IoT 首页', '', 2, 1, 4000, 'home', 'ep:home-filled', 'iot/home/index', 'IotHome', 0, '1', '1', '1', '1', '2025-02-27 08:39:35', '1', '2025-06-24 14:22:50', '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 (4051, '数据流转', '', 2, 2, 4048, 'data-rule', 'ep:guide', 'iot/rule/data/index', 'IoTDataRule', 0, '1', '1', '1', '', '2025-03-09 13:47:11', '"1"', '2025-08-09 15:38:37', '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 (4052, '数据流转规则查询', 'iot:data-rule:query', 3, 1, 4051, '', '', '', '', 0, '1', '1', '1', '', '2025-03-09 13:47:11', '1', '2025-06-24 20:48:04', '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 (4053, '数据流转规则创建', 'iot:data-rule:create', 3, 2, 4051, '', '', '', '', 0, '1', '1', '1', '', '2025-03-09 13:47:11', '1', '2025-06-24 20:48:08', '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 (4054, '数据流转规则更新', 'iot:data-rule:update', 3, 3, 4051, '', '', '', '', 0, '1', '1', '1', '', '2025-03-09 13:47:11', '1', '2025-06-24 20:48:11', '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 (4055, '数据流转规则删除', 'iot:data-rule:delete', 3, 4, 4051, '', '', '', '', 0, '1', '1', '1', '', '2025-03-09 13:47:12', '1', '2025-06-24 20:48:15', '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 (5000, 'AI 工作流', '', 2, 5, 2758, 'workflow', 'fa:hand-grab-o', 'ai/workflow/index.vue', 'AiWorkflow', 0, '1', '1', '1', '1', '2025-03-25 09:50:27', '1', '2025-05-03 18:55:12', '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 (5001, 'AI 工作流查询', 'ai:workflow:query', 3, 1, 5000, '', '', '', '', 0, '1', '1', '1', '1', '2025-03-25 09:51:11', '1', '2025-03-25 09:51:11', '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 (5002, 'AI 工作流创建', 'ai:workflow:create', 3, 2, 5000, '', '', '', '', 0, '1', '1', '1', '1', '2025-03-25 09:51:28', '1', '2025-03-25 09:51:28', '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 (5003, 'AI 工作流更新', 'ai:workflow:update', 3, 3, 5000, '', '', '', '', 0, '1', '1', '1', '1', '2025-03-25 09:51:42', '1', '2025-03-25 09:51:42', '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 (5004, 'AI 工作流删除', 'ai:workflow:delete', 3, 4, 5000, '', '', '', '', 0, '1', '1', '1', '1', '2025-03-25 09:51:55', '1', '2025-03-25 09:52:03', '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 (5005, 'AI 工作流测试', 'ai:workflow:test', 3, 5, 5000, '', '', '', '', 0, '1', '1', '1', '1', '2025-03-30 10:29:41', '1', '2025-03-30 10:29:41', '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 (5009, '仪表盘设计器', '', 2, 1, 1281, 'jimu-bi', 'fa:y-combinator', 'report/jmreport/bi', 'JimuBI', 0, '1', '1', '1', '1', '2025-05-03 09:57:15', '1', '2025-05-03 10:02:05', '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 (5010, '租户切换', 'system:tenant:visit', 3, 999, 1138, '', '', '', '', 0, '1', '1', '1', '1', '2025-05-05 15:25:32', '1', '2025-05-05 15:25:32', '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 (5011, '转账订单查询', 'pay:transfer:query', 3, 1, 2559, '', '', '', '', 0, '1', '1', '1', '1', '2025-05-08 12:46:53', '1', '2025-05-08 12:46:53', '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 (5012, '转账订单导出', 'pay:transfer:export', 3, 2, 2559, '', '', '', '', 0, '1', '1', '1', '1', '2025-05-10 17:00:28', '1', '2025-05-10 17:00:28', '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 (5013, '场景联动查询', 'iot:rule-scene:query', 3, 1, 4049, '', '', '', '', 0, '1', '1', '1', '1', '2025-06-20 16:53:01', '1', '2025-06-20 16:53:01', '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 (5014, '场景联动创建', 'iot:rule-scene:create', 3, 2, 4049, '', '', '', '', 0, '1', '1', '1', '1', '2025-06-20 16:54:31', '1', '2025-06-20 16:54:31', '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 (5015, '场景联动更新', 'iot:rule-scene:update', 3, 3, 4049, '', '', '', '', 0, '1', '1', '1', '1', '2025-06-20 16:54:47', '1', '2025-06-20 16:54:47', '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 (5016, '场景联动删除', 'iot:rule-scene:delete', 3, 4, 4049, '', '', '', '', 0, '1', '1', '1', '1', '2025-06-20 16:55:04', '1', '2025-06-20 16:55:27', '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 (5017, '场景联动导出', 'iot:rule-scene:export', 3, 5, 4049, '', '', '', '', 0, '1', '1', '1', '1', '2025-06-20 16:57:56', '1', '2025-06-20 16:57:56', '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 (5018, '数据流转目的查询', 'iot:data-sink:query', 3, 11, 4051, '', '', '', '', 0, '1', '1', '1', '1', '2025-06-24 20:48:40', '1', '2025-06-24 20:48:40', '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 (5019, '数据流转目的创建', 'iot:data-sink:create', 3, 12, 4051, '', '', '', '', 0, '1', '1', '1', '1', '2025-06-24 20:48:57', '1', '2025-06-24 20:48:57', '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 (5020, '数据流转目的更新', 'iot:data-sink:update', 3, 13, 4051, '', '', '', '', 0, '1', '1', '1', '1', '2025-06-24 20:49:10', '1', '2025-06-24 20:49:10', '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 (5021, '数据流转目的删除', 'iot:data-sink:delete', 3, 14, 4051, '', '', '', '', 0, '1', '1', '1', '1', '2025-06-24 20:49:23', '1', '2025-06-24 20:49:23', '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 (5022, '告警配置', '', 2, 1, 5028, 'config', 'fa:connectdevelop', 'iot/alert/config/index', 'IotAlertConfig', 0, '1', '1', '1', '', '2025-06-27 14:28:59', '1', '2025-06-27 22:31:19', '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 (5023, '告警配置查询', 'iot:alert-config:query', 3, 1, 5022, '', '', '', '', 0, '1', '1', '1', '', '2025-06-27 14:28:59', '1', '2025-06-28 16:00:31', '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 (5024, '告警配置创建', 'iot:alert-config:create', 3, 2, 5022, '', '', '', '', 0, '1', '1', '1', '', '2025-06-27 14:28:59', '1', '2025-06-28 16:00:35', '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 (5025, '告警配置更新', 'iot:alert-config:update', 3, 3, 5022, '', '', '', '', 0, '1', '1', '1', '', '2025-06-27 14:28:59', '1', '2025-06-28 16:00:43', '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 (5026, '告警配置删除', 'iot:alert-config:delete', 3, 4, 5022, '', '', '', '', 0, '1', '1', '1', '', '2025-06-27 14:29:00', '1', '2025-06-28 16:00:39', '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 (5028, '告警中心', '', 1, 3, 4000, 'alert', 'fa:soundcloud', '', '', 0, '1', '1', '1', '1', '2025-06-27 22:30:04', '1', '2025-06-27 22:30:19', '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 (5029, '告警记录', '', 2, 2, 5028, 'record', 'fa-solid:record-vinyl', 'iot/alert/record/index', 'IotAlertRecord', 0, '1', '1', '1', '', '2025-06-28 07:59:32', '1', '2025-06-28 16:01:48', '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 (5030, '告警记录查询', 'iot:alert-record:query', 3, 1, 5029, '', '', '', '', 0, '1', '1', '1', '', '2025-06-28 07:59:32', '1', '2025-06-28 16:00:53', '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 (5031, '告警记录处理', 'iot:alert-record:process', 3, 2, 5029, '', '', '', '', 0, '1', '1', '1', '', '2025-06-28 07:59:32', '1', '2025-06-28 16:01:04', '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 (5032, 'OTA 固件', '', 2, 1, 4047, 'ota/firmware', 'fa-solid:award', 'iot/ota/firmware/index', 'IoTOtaFirmware', 0, '1', '1', '1', '', '2025-06-30 07:50:29', '"1"', '2025-06-30 20:13:28', '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 (5033, 'OTA 固件查询', 'iot:ota-firmware:query', 3, 1, 5032, '', '', '', '', 0, '1', '1', '1', '', '2025-06-30 07:50:29', '"1"', '2025-06-30 17:38:12', '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 (5034, 'OTA 固件创建', 'iot:ota-firmware:create', 3, 2, 5032, '', '', '', '', 0, '1', '1', '1', '', '2025-06-30 07:50:29', '"1"', '2025-06-30 17:38:21', '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 (5035, 'OTA 固件更新', 'iot:ota-firmware:update', 3, 3, 5032, '', '', '', '', 0, '1', '1', '1', '', '2025-06-30 07:50:29', '"1"', '2025-06-30 17:38:29', '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 (5036, 'OTA 固件删除', 'iot:ota-firmware:delete', 3, 4, 5032, '', '', '', '', 0, '1', '1', '1', '', '2025-06-30 07:50:29', '"1"', '2025-06-30 17:38:37', '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 (5037, 'OTA 升级任务查询', 'iot:ota-task:query', 3, 11, 5032, '', '', '', '', 0, '1', '1', '1', '1', '2025-07-02 23:56:56', '1', '2026-05-19 08:48:53', '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 (5038, 'OTA 升级任务取消', 'iot:ota-task:cancel', 3, 13, 5032, '', '', '', '', 0, '1', '1', '1', '1', '2025-07-02 23:57:26', '1', '2025-07-02 23:57:26', '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 (5039, 'OTA 升级任务创建', 'iot:ota-task:create', 3, 12, 5032, '', '', '', '', 0, '1', '1', '1', '1', '2025-07-02 23:57:52', '1', '2025-07-02 23:57:52', '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 (5040, 'OTA 升级记录查询', 'iot:ota-task-record:query', 3, 21, 5032, '', '', '', '', 0, '1', '1', '1', '1', '2025-07-02 23:58:30', '1', '2025-07-02 23:58:30', '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 (5041, 'OTA 升级记录取消', 'iot:ota-task-record:cancel', 3, 23, 5032, '', '', '', '', 0, '1', '1', '1', '1', '2025-07-02 23:59:18', '1', '2025-07-02 23:59:18', '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 (5042, '模版消息', '', 2, 5, 2084, 'message-template', 'ep:notebook', 'mp/messageTemplate/index', 'MpMessageTemplate', 0, '1', '1', '1', '1', '2025-11-26 16:45:35', '1', '2025-11-26 18:44:52', '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 (5043, '查询模版消息', 'mp:message-template:query', 3, 1, 5042, '', '', '', '', 0, '1', '1', '1', '1', '2025-11-26 17:00:15', '1', '2025-11-26 18:45:00', '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 (5044, '删除模版消息', 'mp:message-template:delete', 3, 2, 5042, '', '', '', '', 0, '1', '1', '1', '1', '2025-11-26 17:00:31', '1', '2025-11-26 18:45:05', '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 (5045, '同步公众号模板', 'mp:message-template:sync', 3, 3, 5042, '', '', '', '', 0, '1', '1', '1', '1', '2025-11-26 17:00:55', '1', '2025-11-26 17:00:55', '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 (5046, '给粉丝发送模版消息', 'mp:message-template:send', 3, 4, 5042, '', '', '', '', 0, '1', '1', '1', '1', '2025-11-26 17:01:11', '1', '2025-11-26 17:01:11', '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 (5100, 'MES 系统', '', 1, 320, 0, '/mes', 'ep:cpu', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:39:57', '1', '2026-05-09 16:19:51', '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 (5101, '基础数据', '', 1, 10, 5100, 'md', 'ep:data-analysis', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 00:40:13', '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 (5110, '物料产品分类', '', 2, 2, 5101, 'item-type', 'ep:files', 'mes/md/item/type/index', 'MesMdItemType', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 13:58:29', '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 (5111, '分类查询', 'mes:md-item-type:query', 3, 1, 5110, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 00:40:13', '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 (5112, '分类创建', 'mes:md-item-type:create', 3, 2, 5110, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 00:40:13', '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 (5113, '分类更新', 'mes:md-item-type:update', 3, 3, 5110, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 00:40:13', '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 (5114, '分类删除', 'mes:md-item-type:delete', 3, 4, 5110, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 00:40:13', '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 (5120, '物料产品管理', '', 2, 1, 5101, 'item', 'ep:box', 'mes/md/item/index', 'MesMdItem', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 13:58:24', '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 (5121, '物料查询', 'mes:md-item:query', 3, 1, 5120, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 00:40:13', '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 (5122, '物料创建', 'mes:md-item:create', 3, 2, 5120, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 00:40:13', '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 (5123, '物料更新', 'mes:md-item:update', 3, 3, 5120, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 00:40:13', '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 (5124, '物料删除', 'mes:md-item:delete', 3, 4, 5120, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 00:40:13', '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 (5125, '物料导出', 'mes:md-item:export', 3, 5, 5120, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 00:40:13', '1', '2026-02-15 00:40:13', '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 (5126, '物料导入', 'mes:md-item:import', 3, 6, 5120, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 14:13:01', '1', '2026-02-15 14:13:01', '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 (5130, '计量单位', '', 2, 3, 5101, 'unit-measure', 'ep:scale-to-original', 'mes/md/unitmeasure/index', 'MesMdUnitMeasure', 0, '1', '1', '1', '1', '2026-02-15 13:55:01', '1', '2026-02-15 13:55:01', '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 (5131, '单位查询', 'mes:md-unit-measure:query', 3, 1, 5130, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 13:55:16', '1', '2026-02-15 13:55:16', '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 (5132, '单位创建', 'mes:md-unit-measure:create', 3, 2, 5130, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 13:55:16', '1', '2026-02-15 13:55:16', '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 (5133, '单位更新', 'mes:md-unit-measure:update', 3, 3, 5130, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 13:55:16', '1', '2026-02-15 13:55:16', '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 (5134, '单位删除', 'mes:md-unit-measure:delete', 3, 4, 5130, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 13:55:16', '1', '2026-02-15 13:55:16', '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 (5135, '单位导出', 'mes:md-unit-measure:export', 3, 5, 5130, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 13:55:16', '1', '2026-02-15 13:55:16', '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 (5140, '客户管理', '', 2, 4, 5101, 'client', 'ep:user', 'mes/md/client/index', 'MesMdClient', 0, '1', '1', '1', '1', '2026-02-15 15:06:41', '1', '2026-02-15 15:06:41', '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 (5141, '客户查询', 'mes:md-client:query', 3, 1, 5140, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 15:06:41', '1', '2026-02-15 15:06:41', '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 (5142, '客户创建', 'mes:md-client:create', 3, 2, 5140, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 15:06:41', '1', '2026-02-15 15:06:41', '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 (5143, '客户更新', 'mes:md-client:update', 3, 3, 5140, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 15:06:41', '1', '2026-02-15 15:06:41', '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 (5144, '客户删除', 'mes:md-client:delete', 3, 4, 5140, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 15:06:41', '1', '2026-02-15 15:06:41', '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 (5145, '客户导出', 'mes:md-client:export', 3, 5, 5140, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 15:06:41', '1', '2026-02-15 15:06:41', '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 (5146, '客户导入', 'mes:md-client:import', 3, 6, 5140, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 15:06:41', '1', '2026-02-15 15:06:41', '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 (5150, '供应商管理', '', 2, 5, 5101, 'vendor', 'ep:goods', 'mes/md/vendor/index', 'MesMdVendor', 0, '1', '1', '1', '1', '2026-02-15 16:01:11', '1', '2026-02-15 16:01:11', '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 (5151, '供应商查询', 'mes:md-vendor:query', 3, 1, 5150, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:01:11', '1', '2026-02-15 16:01:11', '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 (5152, '供应商创建', 'mes:md-vendor:create', 3, 2, 5150, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:01:11', '1', '2026-02-15 16:01:11', '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 (5153, '供应商更新', 'mes:md-vendor:update', 3, 3, 5150, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:01:11', '1', '2026-02-15 16:01:11', '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 (5154, '供应商删除', 'mes:md-vendor:delete', 3, 4, 5150, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:01:11', '1', '2026-02-15 16:01:11', '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 (5155, '供应商导出', 'mes:md-vendor:export', 3, 5, 5150, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:01:11', '1', '2026-02-15 16:01:11', '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 (5156, '供应商导入', 'mes:md-vendor:import', 3, 6, 5150, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:01:11', '1', '2026-02-15 16:01:11', '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 (5160, '车间设置', '', 2, 6, 5101, 'workstation/workshop', 'ep:office-building', 'mes/md/workstation/workshop/index', 'MesMdWorkshop', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-16 11:17:35', '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 (5161, '车间查询', 'mes:md-workshop:query', 3, 1, 5160, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-15 16:47:47', '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 (5162, '车间创建', 'mes:md-workshop:create', 3, 2, 5160, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-15 16:47:47', '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 (5163, '车间更新', 'mes:md-workshop:update', 3, 3, 5160, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-15 16:47:47', '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 (5164, '车间删除', 'mes:md-workshop:delete', 3, 4, 5160, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-15 16:47:47', '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 (5170, '工作站设置', '', 2, 7, 5101, 'workstation', 'ep:place', 'mes/md/workstation/index', 'MesMdWorkstation', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-16 11:18:34', '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 (5171, '工位查询', 'mes:md-workstation:query', 3, 1, 5170, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-15 16:47:47', '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 (5172, '工位创建', 'mes:md-workstation:create', 3, 2, 5170, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-15 16:47:47', '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 (5173, '工位更新', 'mes:md-workstation:update', 3, 3, 5170, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-15 16:47:47', '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 (5174, '工位删除', 'mes:md-workstation:delete', 3, 4, 5170, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-15 16:47:47', '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 (5175, '工位导出', 'mes:md-workstation:export', 3, 5, 5170, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-15 16:47:47', '1', '2026-02-15 16:47:47', '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 (5190, '编码规则', '', 2, 10, 5101, 'auto-code', 'ep:document', 'mes/md/autocode/index', 'MesAutoCode', 0, '1', '1', '1', '1', '2026-03-04 05:26:04', '1', '2026-04-03 08:31:25', '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 (5191, '编码规则查询', 'mes:auto-code-rule:query', 3, 1, 5190, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-04 05:26:04', '1', '2026-03-04 05:26:04', '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 (5192, '编码规则创建', 'mes:auto-code-rule:create', 3, 2, 5190, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-04 05:26:04', '1', '2026-03-04 05:26:04', '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 (5193, '编码规则更新', 'mes:auto-code-rule:update', 3, 3, 5190, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-04 05:26:04', '1', '2026-03-04 05:26:04', '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 (5194, '编码规则删除', 'mes:auto-code-rule:delete', 3, 4, 5190, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-04 05:26:04', '1', '2026-03-04 05:26:04', '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 (5195, '编码规则导出', 'mes:auto-code-rule:export', 3, 5, 5190, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-04 05:26:04', '1', '2026-03-04 05:26:04', '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 (5200, '排班管理', '', 1, 70, 5100, 'cal', 'ep:calendar', '', '', 0, '1', '1', '1', '1', '2026-02-16 07:35:50', '1', '2026-02-16 15:37:53', '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 (5220, '班组设置', '', 2, 1, 5200, 'team', 'ep:user', 'mes/cal/team/index', 'MesCalTeam', 0, '1', '1', '1', '1', '2026-02-18 01:16:03', '1', '2026-02-18 11:14:31', '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 (5221, '班组查询', 'mes:cal-team:query', 3, 1, 5220, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-18 01:16:03', '1', '2026-02-18 01:16:03', '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 (5222, '班组创建', 'mes:cal-team:create', 3, 2, 5220, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-18 01:16:03', '1', '2026-02-18 01:16:03', '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 (5223, '班组更新', 'mes:cal-team:update', 3, 3, 5220, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-18 01:16:03', '1', '2026-02-18 01:16:03', '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 (5224, '班组删除', 'mes:cal-team:delete', 3, 4, 5220, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-18 01:16:03', '1', '2026-02-18 01:16:03', '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 (5225, '班组导出', 'mes:cal-team:export', 3, 5, 5220, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-18 01:16:03', '1', '2026-02-18 01:16:03', '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 (5230, '排班计划', '', 2, 3, 5200, 'plan', 'ep:memo', 'mes/cal/plan/index', 'MesCalPlan', 0, '1', '1', '1', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '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 (5231, '排班计划查询', 'mes:cal-plan:query', 3, 1, 5230, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '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 (5232, '排班计划创建', 'mes:cal-plan:create', 3, 2, 5230, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '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 (5233, '排班计划更新', 'mes:cal-plan:update', 3, 3, 5230, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '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 (5234, '排班计划删除', 'mes:cal-plan:delete', 3, 4, 5230, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '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 (5235, '排班计划导出', 'mes:cal-plan:export', 3, 5, 5230, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 03:40:09', '1', '2026-02-17 03:40:09', '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 (5250, '节假日设置', '', 2, 3, 5200, 'holiday', 'ep:sunny', 'mes/cal/holiday/index', 'MesCalHoliday', 0, '1', '1', '1', '1', '2026-02-16 07:35:50', '1', '2026-02-16 15:38:25', '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 (5251, '假期查询', 'mes:cal-holiday:query', 3, 1, 5250, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 07:35:50', '1', '2026-02-16 07:35:50', '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 (5252, '假期创建', 'mes:cal-holiday:create', 3, 2, 5250, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 07:35:50', '1', '2026-02-16 07:35:50', '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 (5253, '假期更新', 'mes:cal-holiday:update', 3, 3, 5250, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 07:35:50', '1', '2026-02-16 07:35:50', '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 (5254, '假期删除', 'mes:cal-holiday:delete', 3, 4, 5250, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 07:35:50', '1', '2026-02-16 07:35:50', '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 (5255, '假期导出', 'mes:cal-holiday:export', 3, 5, 5250, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 07:35:50', '1', '2026-02-16 07:35:50', '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 (5260, '排班日历', '', 2, 3, 5200, 'calendar', 'ep:calendar', 'mes/cal/calendar/index', 'MesCalCalendar', 0, '1', '1', '1', '1', '2026-02-19 04:19:33', '1', '2026-02-19 04:19:33', '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 (5261, '排班日历查询', 'mes:cal-team-shift:query', 3, 1, 5260, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 04:19:33', '1', '2026-02-19 04:19:33', '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 (5300, '设备管理', '', 1, 30, 5100, 'dv', 'ep:cpu', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 09:01:18', '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 (5310, '设备类型', '', 2, 1, 5300, 'machinery/type', 'ep:files', 'mes/dv/machinery/type/index', 'MesDvMachineryType', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 09:02:05', '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 (5311, '类型查询', 'mes:dv-machinery-type:query', 3, 1, 5310, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5312, '类型创建', 'mes:dv-machinery-type:create', 3, 2, 5310, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5313, '类型更新', 'mes:dv-machinery-type:update', 3, 3, 5310, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5314, '类型删除', 'mes:dv-machinery-type:delete', 3, 4, 5310, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5315, '类型导出', 'mes:dv-machinery-type:export', 3, 5, 5310, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5320, '设备台账', '', 2, 2, 5300, 'machinery', 'ep:monitor', 'mes/dv/machinery/index', 'MesDvMachinery', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5321, '设备查询', 'mes:dv-machinery:query', 3, 1, 5320, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5322, '设备创建', 'mes:dv-machinery:create', 3, 2, 5320, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5323, '设备更新', 'mes:dv-machinery:update', 3, 3, 5320, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5324, '设备删除', 'mes:dv-machinery:delete', 3, 4, 5320, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5325, '设备导出', 'mes:dv-machinery:export', 3, 5, 5320, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 00:59:58', '1', '2026-02-17 00:59:58', '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 (5326, '设备导入', 'mes:dv-machinery:import', 3, 6, 5320, '', '', '', '', 0, '1', '1', '1', '1', '2026-04-02 15:40:42', '1', '2026-04-02 15:40:42', '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 (5330, '点检保养项目', '', 2, 3, 5300, 'subject', 'ep:document-checked', 'mes/dv/subject/index', 'MesDvSubject', 0, '1', '1', '1', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', '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 (5331, '项目查询', 'mes:dv-subject:query', 3, 1, 5330, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', '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 (5332, '项目创建', 'mes:dv-subject:create', 3, 2, 5330, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', '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 (5333, '项目更新', 'mes:dv-subject:update', 3, 3, 5330, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', '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 (5334, '项目删除', 'mes:dv-subject:delete', 3, 4, 5330, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', '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 (5335, '项目导出', 'mes:dv-subject:export', 3, 5, 5330, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', '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 (5340, '保养记录', '', 2, 6, 5300, 'mainten-record', 'ep:setting', 'mes/dv/maintenrecord/index', 'MesDvMaintenRecord', 0, '1', '1', '1', '', '2026-02-20 02:59:55', '1', '2026-03-02 11:45:39', '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 (5341, '保养记录查询', 'mes:dv-mainten-record:query', 3, 1, 5340, '', '', '', '', 0, '1', '1', '1', '', '2026-02-20 02:59:55', '', '2026-02-20 02:59:55', '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 (5342, '保养记录创建', 'mes:dv-mainten-record:create', 3, 2, 5340, '', '', '', '', 0, '1', '1', '1', '', '2026-02-20 02:59:55', '', '2026-02-20 02:59:55', '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 (5343, '保养记录更新', 'mes:dv-mainten-record:update', 3, 3, 5340, '', '', '', '', 0, '1', '1', '1', '', '2026-02-20 02:59:55', '', '2026-02-20 02:59:55', '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 (5344, '保养记录删除', 'mes:dv-mainten-record:delete', 3, 4, 5340, '', '', '', '', 0, '1', '1', '1', '', '2026-02-20 02:59:55', '', '2026-02-20 02:59:55', '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 (5345, '保养记录导出', 'mes:dv-mainten-record:export', 3, 5, 5340, '', '', '', '', 0, '1', '1', '1', '', '2026-02-20 02:59:55', '', '2026-02-20 02:59:55', '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 (5350, '点检保养方案', '', 2, 4, 5300, 'checkplan', 'ep:calendar', 'mes/dv/checkplan/index', 'MesDvCheckPlan', 0, '1', '1', '1', '1', '2026-02-20 07:13:08', '1', '2026-02-20 07:13:08', '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 (5351, '方案查询', 'mes:dv-check-plan:query', 3, 1, 5350, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 07:13:08', '1', '2026-02-20 07:13:08', '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 (5352, '方案创建', 'mes:dv-check-plan:create', 3, 2, 5350, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 07:13:08', '1', '2026-02-20 07:13:08', '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 (5353, '方案更新', 'mes:dv-check-plan:update', 3, 3, 5350, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 07:13:08', '1', '2026-02-20 07:13:08', '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 (5354, '方案删除', 'mes:dv-check-plan:delete', 3, 4, 5350, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 07:13:08', '1', '2026-02-20 07:13:08', '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 (5355, '方案导出', 'mes:dv-check-plan:export', 3, 5, 5350, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 07:13:08', '1', '2026-02-20 07:13:08', '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 (5360, '点检记录', '', 2, 5, 5300, 'check-record', 'ep:document-checked', 'mes/dv/checkrecord/index', 'MesDvCheckRecord', 0, '1', '1', '1', '', '2026-02-20 09:46:19', '', '2026-03-02 11:45:39', '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 (5361, '点检记录查询', 'mes:dv-check-record:query', 3, 1, 5360, '', '', '', '', 0, '1', '1', '1', '', '2026-02-20 09:46:19', '', '2026-02-20 09:46:19', '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 (5362, '点检记录创建', 'mes:dv-check-record:create', 3, 2, 5360, '', '', '', '', 0, '1', '1', '1', '', '2026-02-20 09:46:19', '', '2026-02-20 09:46:19', '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 (5363, '点检记录更新', 'mes:dv-check-record:update', 3, 3, 5360, '', '', '', '', 0, '1', '1', '1', '', '2026-02-20 09:46:19', '', '2026-02-20 09:46:19', '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 (5364, '点检记录删除', 'mes:dv-check-record:delete', 3, 4, 5360, '', '', '', '', 0, '1', '1', '1', '', '2026-02-20 09:46:19', '', '2026-02-20 09:46:19', '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 (5365, '点检记录导出', 'mes:dv-check-record:export', 3, 5, 5360, '', '', '', '', 0, '1', '1', '1', '', '2026-02-20 09:46:19', '', '2026-02-20 09:46:19', '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 (5366, '工单导出', 'mes:dv-repair:export', 3, 5, 5360, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:07:06', '1', '2026-02-20 11:07:06', '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 (5370, '维修单', '', 2, 7, 5300, 'repair', 'ep:tools', 'mes/dv/repair/index', 'MesDvRepair', 0, '1', '1', '1', '1', '2026-02-20 11:06:28', '1', '2026-02-20 19:10:18', '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 (5371, '工单查询', 'mes:dv-repair:query', 3, 1, 5370, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:06:28', '1', '2026-02-20 11:06:28', '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 (5372, '工单创建', 'mes:dv-repair:create', 3, 2, 5370, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:06:28', '1', '2026-02-20 11:06:28', '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 (5373, '工单更新', 'mes:dv-repair:update', 3, 3, 5370, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:54:20', '1', '2026-02-20 11:54:20', '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 (5374, '工单删除', 'mes:dv-repair:delete', 3, 4, 5370, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:54:15', '1', '2026-02-20 11:54:15', '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 (5375, '工单导出', 'mes:dv-repair:export', 3, 5, 5370, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:54:04', '1', '2026-02-20 11:54:04', '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 (5400, '工具管理', '', 1, 40, 5100, 'tm', 'ep:scissor', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-03-21 14:20:41', '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 (5410, '工具类型设置', '', 2, 1, 5400, 'tool/type', 'ep:files', 'mes/tm/tool/type/index', 'MesTmToolType', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 21:28:41', '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 (5411, '类型查询', 'mes:tm-tool-type:query', 3, 1, 5410, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '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 (5412, '类型创建', 'mes:tm-tool-type:create', 3, 2, 5410, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '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 (5413, '类型更新', 'mes:tm-tool-type:update', 3, 3, 5410, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '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 (5414, '类型删除', 'mes:tm-tool-type:delete', 3, 4, 5410, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '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 (5415, '类型导出', 'mes:tm-tool-type:export', 3, 5, 5410, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '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 (5420, '工装夹具台账', '', 2, 2, 5400, 'tool', 'ep:box', 'mes/tm/tool/index', 'MesTmTool', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 21:29:23', '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 (5421, '工具查询', 'mes:tm-tool:query', 3, 1, 5420, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '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 (5422, '工具创建', 'mes:tm-tool:create', 3, 2, 5420, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '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 (5423, '工具更新', 'mes:tm-tool:update', 3, 3, 5420, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '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 (5424, '工具删除', 'mes:tm-tool:delete', 3, 4, 5420, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '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 (5425, '工具导出', 'mes:tm-tool:export', 3, 5, 5420, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-16 11:10:55', '1', '2026-02-16 11:10:55', '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 (5500, '质量管理', '', 1, 60, 5100, 'qc', 'ep:check', '', '', 0, '1', '1', '1', '1', '2026-02-17 02:18:18', '1', '2026-02-17 14:36:15', '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 (5510, '常见缺陷', '', 2, 1, 5500, 'defect', 'ep:warning', 'mes/qc/defect/index', 'MesQcDefect', 0, '1', '1', '1', '1', '2026-02-17 02:18:18', '1', '2026-03-04 04:29:46', '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 (5511, '缺陷类型查询', 'mes:qc-defect:query', 3, 1, 5510, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 02:18:18', '1', '2026-02-17 02:18:18', '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 (5512, '缺陷类型创建', 'mes:qc-defect:create', 3, 2, 5510, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 02:18:18', '1', '2026-02-17 02:18:18', '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 (5513, '缺陷类型更新', 'mes:qc-defect:update', 3, 3, 5510, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 02:18:18', '1', '2026-02-17 02:18:18', '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 (5514, '缺陷类型删除', 'mes:qc-defect:delete', 3, 4, 5510, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 02:18:18', '1', '2026-02-17 02:18:18', '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 (5515, '缺陷类型导出', 'mes:qc-defect:export', 3, 5, 5510, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 02:18:18', '1', '2026-02-17 02:18:18', '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 (5530, '生产工单', '', 2, 1, 5700, 'work-order', 'ep:document-copy', 'mes/pro/workorder/index', 'MesProWorkOrder', 0, '1', '1', '1', '1', '2026-02-17 11:51:44', '1', '2026-02-17 19:54:36', '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 (5531, '工单查询', 'mes:pro-work-order:query', 3, 1, 5530, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:51:44', '1', '2026-02-17 11:51:44', '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 (5532, '工单创建', 'mes:pro-work-order:create', 3, 2, 5530, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:51:44', '1', '2026-02-17 11:51:44', '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 (5533, '工单更新', 'mes:pro-work-order:update', 3, 3, 5530, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:51:44', '1', '2026-02-17 11:51:44', '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 (5534, '工单删除', 'mes:pro-work-order:delete', 3, 4, 5530, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:51:44', '1', '2026-02-17 11:51:44', '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 (5535, '工单导出', 'mes:pro-work-order:export', 3, 5, 5530, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:51:44', '1', '2026-02-17 11:51:44', '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 (5540, '生产排产', '', 2, 4, 5700, 'task', 'ep:calendar', 'mes/pro/task/index', 'MesProTask', 0, '1', '1', '1', '1', '2026-02-19 15:25:27', '1', '2026-02-20 08:30:27', '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 (5541, '排产查询', 'mes:pro-task:query', 3, 1, 5540, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 15:25:27', '1', '2026-02-19 15:25:27', '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 (5542, '排产创建', 'mes:pro-task:create', 3, 2, 5540, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 15:25:27', '1', '2026-02-19 15:25:27', '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 (5543, '排产更新', 'mes:pro-task:update', 3, 3, 5540, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 15:25:27', '1', '2026-02-19 15:25:27', '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 (5544, '排产删除', 'mes:pro-task:delete', 3, 4, 5540, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 15:25:27', '1', '2026-02-19 15:25:27', '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 (5545, '排产导出', 'mes:pro-task:export', 3, 5, 5540, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 15:25:27', '1', '2026-02-19 15:25:27', '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 (5550, '生产报工', '', 2, 5, 5700, 'feedback', 'ep:finished', 'mes/pro/feedback/index', 'MesProFeedback', 0, '1', '1', '1', '1', '2026-02-21 00:50:22', '1', '2026-02-21 08:55:14', '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 (5551, '报工查询', 'mes:pro-feedback:query', 3, 1, 5550, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:50:22', '1', '2026-02-21 00:50:22', '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 (5552, '报工创建', 'mes:pro-feedback:create', 3, 2, 5550, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:50:22', '1', '2026-02-21 00:50:22', '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 (5553, '报工更新', 'mes:pro-feedback:update', 3, 3, 5550, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:50:22', '1', '2026-02-21 00:50:22', '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 (5554, '报工删除', 'mes:pro-feedback:delete', 3, 4, 5550, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:50:22', '1', '2026-02-21 00:50:22', '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 (5555, '报工导出', 'mes:pro-feedback:export', 3, 5, 5550, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:50:22', '1', '2026-02-21 00:50:22', '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 (5560, '工序流转', '', 2, 6, 5700, 'card', 'ep:postcard', 'mes/pro/card/index', 'MesProCard', 0, '1', '1', '1', '1', '2026-02-21 03:16:35', '1', '2026-02-21 11:18:42', '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 (5561, '流转卡查询', 'mes:pro-card:query', 3, 1, 5560, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 03:16:35', '1', '2026-02-21 03:16:35', '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 (5562, '流转卡创建', 'mes:pro-card:create', 3, 2, 5560, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 03:16:35', '1', '2026-02-21 03:16:35', '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 (5563, '流转卡更新', 'mes:pro-card:update', 3, 3, 5560, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 03:16:35', '1', '2026-02-21 03:16:35', '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 (5564, '流转卡删除', 'mes:pro-card:delete', 3, 4, 5560, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 03:16:35', '1', '2026-02-21 03:16:35', '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 (5565, '流转卡导出', 'mes:pro-card:export', 3, 5, 5560, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 03:16:35', '1', '2026-02-21 03:16:35', '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 (5566, '流转卡完成', 'mes:pro-card:finish', 3, 6, 5560, '', '', '', '', 0, '1', '1', '1', '1', '2026-04-04 12:18:15', '1', '2026-04-04 12:18:15', '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 (5610, '检测项设置', '', 2, 2, 5500, 'indicator', 'ep:data-analysis', 'mes/qc/indicator/index', 'MesQcIndicator', 0, '1', '1', '1', '1', '2026-02-17 08:11:31', '1', '2026-02-17 16:16:07', '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 (5611, '质检指标查询', 'mes:qc-indicator:query', 3, 1, 5610, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 08:11:31', '1', '2026-02-17 08:11:31', '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 (5612, '质检指标创建', 'mes:qc-indicator:create', 3, 2, 5610, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 08:11:31', '1', '2026-02-17 08:11:31', '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 (5613, '质检指标更新', 'mes:qc-indicator:update', 3, 3, 5610, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 08:11:31', '1', '2026-02-17 08:11:31', '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 (5614, '质检指标删除', 'mes:qc-indicator:delete', 3, 4, 5610, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 08:11:31', '1', '2026-02-17 08:11:31', '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 (5615, '质检指标导出', 'mes:qc-indicator:export', 3, 5, 5610, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 08:11:31', '1', '2026-02-17 08:11:31', '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 (5630, '质检方案', '', 2, 3, 5500, 'template', 'ep:document-copy', 'mes/qc/template/index', 'MesQcTemplate', 0, '1', '1', '1', '1', '2026-02-17 08:34:40', '1', '2026-03-04 04:29:46', '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 (5631, '质检方案查询', 'mes:qc-template:query', 3, 1, 5630, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 08:34:40', '1', '2026-02-18 14:12:05', '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 (5632, '质检方案创建', 'mes:qc-template:create', 3, 2, 5630, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 08:34:40', '1', '2026-02-18 14:12:05', '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 (5633, '质检方案更新', 'mes:qc-template:update', 3, 3, 5630, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 08:34:40', '1', '2026-02-18 14:12:05', '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 (5634, '质检方案删除', 'mes:qc-template:delete', 3, 4, 5630, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 08:34:40', '1', '2026-02-18 14:12:05', '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 (5635, '质检方案导出', 'mes:qc-template:export', 3, 5, 5630, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 08:34:40', '1', '2026-02-18 14:12:05', '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 (5640, '来料检验', '', 2, 5, 5500, 'iqc', 'ep:finished', 'mes/qc/iqc/index', 'MesQcIqc', 0, '1', '1', '1', '1', '2026-02-20 11:23:45', '1', '2026-02-24 05:24:01', '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 (5641, '来料检验查询', 'mes:qc-iqc:query', 3, 1, 5640, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:23:45', '1', '2026-02-20 11:23:45', '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 (5642, '来料检验创建', 'mes:qc-iqc:create', 3, 2, 5640, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:23:45', '1', '2026-02-20 11:23:45', '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 (5643, '来料检验更新', 'mes:qc-iqc:update', 3, 3, 5640, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:23:45', '1', '2026-02-20 11:23:45', '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 (5644, '来料检验删除', 'mes:qc-iqc:delete', 3, 4, 5640, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:23:45', '1', '2026-02-20 11:23:45', '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 (5645, '来料检验导出', 'mes:qc-iqc:export', 3, 5, 5640, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 11:23:45', '1', '2026-02-20 11:23:45', '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 (5650, '过程检验', '', 2, 6, 5500, 'ipqc', 'ep:checked', 'mes/qc/ipqc/index', 'MesQcIpqc', 0, '1', '1', '1', '1', '2026-02-22 07:01:08', '1', '2026-02-22 15:02:42', '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 (5651, '过程检验查询', 'mes:qc-ipqc:query', 3, 1, 5650, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 07:01:08', '1', '2026-02-22 07:01:08', '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 (5652, '过程检验创建', 'mes:qc-ipqc:create', 3, 2, 5650, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 07:01:08', '1', '2026-02-22 07:01:08', '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 (5653, '过程检验更新', 'mes:qc-ipqc:update', 3, 3, 5650, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 07:01:08', '1', '2026-02-22 07:01:08', '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 (5654, '过程检验删除', 'mes:qc-ipqc:delete', 3, 4, 5650, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 07:01:08', '1', '2026-02-22 07:01:08', '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 (5655, '过程检验导出', 'mes:qc-ipqc:export', 3, 5, 5650, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 07:01:08', '1', '2026-02-22 07:01:08', '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 (5660, '出货检验', '', 2, 8, 5500, 'oqc', 'ep:sell', 'mes/qc/oqc/index', 'MesQcOqc', 0, '1', '1', '1', '1', '2026-02-22 06:55:53', '1', '2026-02-22 14:58:31', '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 (5661, '出货检验查询', 'mes:qc-oqc:query', 3, 1, 5660, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 06:55:53', '1', '2026-02-22 06:55:53', '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 (5662, '出货检验创建', 'mes:qc-oqc:create', 3, 2, 5660, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 06:55:53', '1', '2026-02-22 06:55:53', '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 (5663, '出货检验更新', 'mes:qc-oqc:update', 3, 3, 5660, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 06:55:53', '1', '2026-02-22 06:55:53', '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 (5664, '出货检验删除', 'mes:qc-oqc:delete', 3, 4, 5660, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 06:55:53', '1', '2026-02-22 06:55:53', '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 (5665, '出货检验导出', 'mes:qc-oqc:export', 3, 5, 5660, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 06:55:53', '1', '2026-02-22 06:55:53', '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 (5670, '退料检验', '', 2, 7, 5500, 'rqc', 'ep:warning-filled', 'mes/qc/rqc/index', 'MesQcRqc', 0, '1', '1', '1', '1', '2026-02-22 06:43:25', '1', '2026-02-22 14:46:41', '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 (5671, '退货检验查询', 'mes:qc-rqc:query', 3, 1, 5670, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 06:43:25', '1', '2026-02-22 06:43:25', '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 (5672, '退货检验创建', 'mes:qc-rqc:create', 3, 2, 5670, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 06:43:25', '1', '2026-02-22 06:43:25', '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 (5673, '退货检验更新', 'mes:qc-rqc:update', 3, 3, 5670, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 06:43:25', '1', '2026-02-22 06:43:25', '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 (5674, '退货检验删除', 'mes:qc-rqc:delete', 3, 4, 5670, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 06:43:25', '1', '2026-02-22 06:43:25', '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 (5675, '退货检验导出', 'mes:qc-rqc:export', 3, 5, 5670, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 06:43:25', '1', '2026-02-22 06:43:25', '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 (5680, '待检任务', '', 2, 4, 5500, 'pending-inspect', 'ep:clock', 'mes/qc/pendinginspect/index', 'MesQcPendingInspect', 0, '1', '1', '1', '1', '2026-02-23 07:15:41', '1', '2026-02-24 05:24:04', '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 (5681, '待检任务查询', 'mes:qc-pending-inspect:query', 3, 1, 5680, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-23 07:15:41', '1', '2026-02-23 07:15:41', '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 (5700, '生产管理', '', 1, 50, 5100, 'pro', 'ep:management', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:39:58', '1', '2026-02-17 19:53:35', '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 (5710, '工序设置', '', 2, 2, 5700, 'process', 'ep:operation', 'mes/pro/process/index', 'MesProProcess', 0, '1', '1', '1', '1', '2026-02-17 11:39:58', '1', '2026-02-19 16:23:45', '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 (5711, '工序查询', 'mes:pro-process:query', 3, 1, 5710, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:39:58', '1', '2026-02-17 11:39:58', '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 (5712, '工序创建', 'mes:pro-process:create', 3, 2, 5710, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:39:58', '1', '2026-02-17 11:39:58', '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 (5713, '工序更新', 'mes:pro-process:update', 3, 3, 5710, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:39:58', '1', '2026-02-17 11:39:58', '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 (5714, '工序删除', 'mes:pro-process:delete', 3, 4, 5710, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:39:58', '1', '2026-02-17 11:39:58', '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 (5715, '工序导出', 'mes:pro-process:export', 3, 5, 5710, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 11:39:58', '1', '2026-02-17 11:39:58', '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 (5720, '工艺流程', '', 2, 3, 5700, 'route', 'ep:guide', 'mes/pro/route/index', 'MesProRoute', 0, '1', '1', '1', '1', '2026-02-19 04:24:53', '1', '2026-02-19 16:24:23', '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 (5721, '工艺路线查询', 'mes:pro-route:query', 3, 1, 5720, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '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 (5722, '工艺路线创建', 'mes:pro-route:create', 3, 2, 5720, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '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 (5723, '工艺路线更新', 'mes:pro-route:update', 3, 3, 5720, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '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 (5724, '工艺路线删除', 'mes:pro-route:delete', 3, 4, 5720, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '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 (5725, '工艺路线导出', 'mes:pro-route:export', 3, 5, 5720, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '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 (5750, '安灯呼叫', '', 2, 7, 5700, 'andon', 'ep:bell', 'mes/pro/andon/record/index', 'MesProAndonRecord', 0, '1', '1', '1', '1', '2026-02-21 00:24:18', '1', '2026-02-21 11:35:45', '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 (5751, '安灯记录查询', 'mes:pro-andon-record:query', 3, 1, 5750, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:24:18', '1', '2026-02-21 00:24:18', '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 (5752, '安灯记录创建', 'mes:pro-andon-record:create', 3, 2, 5750, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:24:18', '1', '2026-02-21 00:24:18', '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 (5753, '安灯记录更新', 'mes:pro-andon-record:update', 3, 3, 5750, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:24:18', '1', '2026-02-21 00:24:18', '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 (5754, '安灯记录删除', 'mes:pro-andon-record:delete', 3, 4, 5750, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:24:18', '1', '2026-02-21 00:24:18', '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 (5755, '安灯记录导出', 'mes:pro-andon-record:export', 3, 5, 5750, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:24:18', '1', '2026-02-21 00:24:18', '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 (5756, '安灯配置查询', 'mes:pro-andon-config:query', 3, 6, 5750, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:24:18', '1', '2026-02-21 00:24:18', '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 (5757, '安灯配置创建', 'mes:pro-andon-config:create', 3, 7, 5750, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:24:18', '1', '2026-02-21 00:24:18', '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 (5758, '安灯配置更新', 'mes:pro-andon-config:update', 3, 8, 5750, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:24:18', '1', '2026-02-21 00:24:18', '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 (5759, '安灯配置删除', 'mes:pro-andon-config:delete', 3, 9, 5750, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-21 00:24:18', '1', '2026-02-21 00:24:18', '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 (5780, '仓库管理', '', 1, 20, 5100, 'wm', 'ep:box', '', '', 0, '1', '1', '1', '1', '2026-02-17 15:37:58', '1', '2026-02-17 23:38:18', '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 (5781, '仓库设置', '', 2, 1, 5780, 'warehouse', 'ep:office-building', 'mes/wm/warehouse/index', 'MesWmWarehouse', 0, '1', '1', '1', '1', '2026-02-17 15:37:58', '1', '2026-02-17 15:37:58', '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 (5782, '仓库查询', 'mes:wm-warehouse:query', 3, 1, 5781, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 15:37:58', '1', '2026-02-17 15:37:58', '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 (5783, '仓库创建', 'mes:wm-warehouse:create', 3, 2, 5781, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 15:37:58', '1', '2026-02-17 15:37:58', '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 (5784, '仓库更新', 'mes:wm-warehouse:update', 3, 3, 5781, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 15:37:58', '1', '2026-02-17 15:37:58', '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 (5785, '仓库删除', 'mes:wm-warehouse:delete', 3, 4, 5781, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-17 15:37:58', '1', '2026-02-17 15:37:58', '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 (5786, '库存现有量', '', 2, 2, 5780, 'material-stock', 'ep:document', 'mes/wm/materialstock/index', 'MesWmMaterialStock', 0, '1', '1', '1', '1', '2026-02-20 01:02:36', '1', '2026-02-20 09:03:40', '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 (5787, '库存台账查询', 'mes:wm-material-stock:query', 3, 1, 5786, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 01:02:36', '1', '2026-02-20 01:02:36', '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 (5788, '库存台账创建', 'mes:wm-material-stock:create', 3, 2, 5786, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 01:02:37', '1', '2026-02-20 01:02:37', '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 (5789, '库存台账更新', 'mes:wm-material-stock:update', 3, 3, 5786, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 01:02:37', '1', '2026-02-20 01:02:37', '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 (5790, '库存台账删除', 'mes:wm-material-stock:delete', 3, 4, 5786, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 01:02:37', '1', '2026-02-20 01:02:37', '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 (5791, '库存台账导出', 'mes:wm-material-stock:export', 3, 5, 5786, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-20 01:02:37', '1', '2026-02-20 01:02:37', '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 (5792, '到货通知', '', 2, 4, 5780, 'arrival-notice', 'ep:document-checked', 'mes/wm/arrivalnotice/index', 'MesWmArrivalNotice', 0, '1', '1', '1', '1', '2026-02-22 09:11:05', '1', '2026-02-22 17:13:15', '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 (5793, '到货通知单查询', 'mes:wm-arrival-notice:query', 3, 1, 5792, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 09:11:05', '1', '2026-02-22 09:11:05', '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 (5794, '到货通知单创建', 'mes:wm-arrival-notice:create', 3, 2, 5792, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 09:11:05', '1', '2026-02-22 09:11:05', '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 (5795, '到货通知单更新', 'mes:wm-arrival-notice:update', 3, 3, 5792, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 09:11:05', '1', '2026-02-22 09:11:05', '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 (5796, '到货通知单删除', 'mes:wm-arrival-notice:delete', 3, 4, 5792, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 09:11:05', '1', '2026-02-22 09:11:05', '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 (5797, '到货通知单导出', 'mes:wm-arrival-notice:export', 3, 5, 5792, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 09:11:05', '1', '2026-02-22 09:11:05', '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 (5798, '采购入库', '', 2, 3, 5780, 'item-receipt', 'ep:goods', 'mes/wm/itemreceipt/index', 'MesWmItemReceipt', 0, '1', '1', '1', '1', '2026-02-22 10:23:22', '1', '2026-02-22 18:35:01', '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 (5799, '采购入库单查询', 'mes:wm-item-receipt:query', 3, 1, 5798, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 10:23:22', '1', '2026-02-22 10:23:22', '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 (5800, '采购入库单创建', 'mes:wm-item-receipt:create', 3, 2, 5798, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 10:23:22', '1', '2026-02-22 10:23:22', '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 (5801, '采购入库单更新', 'mes:wm-item-receipt:update', 3, 3, 5798, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 10:23:22', '1', '2026-02-22 10:23:22', '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 (5802, '采购入库单删除', 'mes:wm-item-receipt:delete', 3, 4, 5798, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 10:23:22', '1', '2026-02-22 10:23:22', '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 (5803, '采购入库单导出', 'mes:wm-item-receipt:export', 3, 5, 5798, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 10:23:22', '1', '2026-02-22 10:23:22', '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 (5804, '采购入库单完成', 'mes:wm-item-receipt:finish', 3, 6, 5798, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-22 10:23:22', '1', '2026-03-02 11:24:29', '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 (5805, '生产领料', '', 2, 6, 5780, 'product-issue', 'ep:sell', 'mes/wm/productissue/index', 'MesWmProductIssue', 0, '1', '1', '1', '1', '2026-02-26 15:53:24', '1', '2026-03-23 12:21:59', '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 (5806, '领料出库单查询', 'mes:wm-product-issue:query', 3, 1, 5805, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-26 15:53:24', '1', '2026-03-23 12:21:59', '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 (5807, '领料出库单新增', 'mes:wm-product-issue:create', 3, 2, 5805, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-26 15:53:24', '1', '2026-03-23 12:21:59', '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 (5808, '领料出库单修改', 'mes:wm-product-issue:update', 3, 3, 5805, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-26 15:53:24', '1', '2026-03-23 12:21:59', '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 (5809, '领料出库单删除', 'mes:wm-product-issue:delete', 3, 4, 5805, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-26 15:53:24', '1', '2026-03-23 12:21:59', '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 (5810, '领料出库单导出', 'mes:wm-product-issue:export', 3, 5, 5805, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-26 15:53:24', '1', '2026-03-23 12:21:59', '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 (5811, '领料出库单完成', 'mes:wm-product-issue:finish', 3, 6, 5805, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-26 15:53:24', '1', '2026-03-30 03:06:32', '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 (5819, '产品入库', '', 2, 8, 5780, 'product-receipt', 'ep:box', 'mes/wm/productreceipt/index', 'MesWmProductReceipt', 0, '1', '1', '1', '1', '2026-03-01 04:03:25', '1', '2026-03-04 04:29:46', '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 (5820, '产品入库单查询', 'mes:wm-product-receipt:query', 3, 1, 5819, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 04:03:25', '1', '2026-03-01 06:03:44', '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 (5821, '产品入库单新增', 'mes:wm-product-receipt:create', 3, 2, 5819, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 04:03:25', '1', '2026-03-01 06:03:44', '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 (5822, '产品入库单修改', 'mes:wm-product-receipt:update', 3, 3, 5819, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 04:03:25', '1', '2026-03-01 06:03:44', '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 (5823, '产品入库单删除', 'mes:wm-product-receipt:delete', 3, 4, 5819, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 04:03:25', '1', '2026-03-01 06:03:44', '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 (5824, '产品入库单导出', 'mes:wm-product-receipt:export', 3, 5, 5819, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 04:03:25', '1', '2026-03-01 06:03:44', '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 (5825, '产品入库单完成', 'mes:wm-product-receipt:finish', 3, 6, 5819, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 04:03:25', '1', '2026-03-02 11:24:29', '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 (5830, '采购退货', '', 2, 5, 5780, 'return-vendor', 'ep:box', 'mes/wm/returnvendor/index', 'MesWmReturnVendor', 0, '1', '1', '1', '1', '2026-02-28 09:47:48', '1', '2026-03-29 20:22:49', '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 (5831, '供应商退货单查询', 'mes:wm-return-vendor:query', 3, 1, 5830, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 09:47:48', '1', '2026-02-28 09:47:48', '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 (5832, '供应商退货单新增', 'mes:wm-return-vendor:create', 3, 2, 5830, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 09:47:48', '1', '2026-02-28 09:47:48', '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 (5833, '供应商退货单修改', 'mes:wm-return-vendor:update', 3, 3, 5830, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 09:47:48', '1', '2026-02-28 09:47:48', '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 (5834, '供应商退货单删除', 'mes:wm-return-vendor:delete', 3, 4, 5830, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 09:47:48', '1', '2026-02-28 09:47:48', '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 (5835, '供应商退货单导出', 'mes:wm-return-vendor:export', 3, 5, 5830, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 09:47:48', '1', '2026-02-28 09:47:48', '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 (5836, '供应商退货单状态变更', 'mes:wm-return-vendor:update-status', 3, 6, 5830, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 09:47:48', '1', '2026-02-28 09:47:48', '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 (5840, '生产退料', '', 2, 7, 5780, 'return-issue', 'ep:refresh-left', 'mes/wm/returnissue/index', 'MesWmReturnIssue', 0, '1', '1', '1', '1', '2026-02-28 14:11:04', '1', '2026-03-04 04:29:46', '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 (5841, '生产退料单查询', 'mes:wm-return-issue:query', 3, 1, 5840, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 14:11:04', '1', '2026-02-28 14:11:04', '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 (5842, '生产退料单新增', 'mes:wm-return-issue:create', 3, 2, 5840, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 14:11:04', '1', '2026-02-28 14:11:04', '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 (5843, '生产退料单修改', 'mes:wm-return-issue:update', 3, 3, 5840, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 14:11:04', '1', '2026-02-28 14:11:04', '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 (5844, '生产退料单删除', 'mes:wm-return-issue:delete', 3, 4, 5840, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 14:11:04', '1', '2026-02-28 14:11:04', '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 (5845, '生产退料单导出', 'mes:wm-return-issue:export', 3, 5, 5840, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-02-28 14:11:04', '1', '2026-02-28 14:11:04', '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 (5846, '生产退料单执行', 'mes:wm-return-issue:finish', 3, 6, 5840, '', '', '', '', 0, '1', '1', '1', '1', '2026-02-28 14:11:04', '1', '2026-03-02 11:24:30', '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 (5847, '销售退货', '', 2, 11, 5780, 'return-sales', 'ep:refresh-left', 'mes/wm/returnsales/index', 'MesWmReturnSales', 0, '1', '1', '1', '1', '2026-03-01 10:31:35', '1', '2026-03-04 04:29:46', '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 (5848, '销售退货单查询', 'mes:wm-return-sales:query', 3, 1, 5847, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 10:31:35', '1', '2026-03-01 10:31:35', '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 (5849, '销售退货单新增', 'mes:wm-return-sales:create', 3, 2, 5847, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 10:31:35', '1', '2026-03-01 10:31:35', '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 (5850, '销售退货单修改', 'mes:wm-return-sales:update', 3, 3, 5847, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 10:31:35', '1', '2026-03-01 10:31:35', '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 (5851, '销售退货单删除', 'mes:wm-return-sales:delete', 3, 4, 5847, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 10:31:35', '1', '2026-03-01 10:31:35', '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 (5852, '销售退货单导出', 'mes:wm-return-sales:export', 3, 5, 5847, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 10:31:35', '1', '2026-03-01 10:31:35', '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 (5853, '销售退货单提交', 'mes:wm-return-sales:submit', 3, 6, 5847, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 10:31:35', '1', '2026-03-01 10:31:35', '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 (5854, '销售退货单完成退货', 'mes:wm-return-sales:finish', 3, 7, 5847, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 10:31:35', '1', '2026-03-02 11:24:29', '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 (5855, '销售退货单执行上架', 'mes:wm-return-sales:stock', 3, 8, 5847, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 10:31:35', '1', '2026-03-01 10:31:35', '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 (5856, '销售退货单取消', 'mes:wm-return-sales:cancel', 3, 9, 5847, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-01 10:31:35', '1', '2026-03-01 10:31:35', '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 (5857, '杂项出库', '', 2, 20, 5780, 'misc-issue', 'ep:sell', 'mes/wm/miscissue/index', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:33:34', '1', '2026-03-03 09:12:16', '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 (5858, '杂项出库查询', 'mes:wm-misc-issue:query', 3, 1, 5857, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:33:34', '1', '2026-03-02 14:33:34', '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 (5859, '杂项出库新增', 'mes:wm-misc-issue:create', 3, 2, 5857, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:33:34', '1', '2026-03-02 14:33:34', '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 (5860, '杂项出库修改', 'mes:wm-misc-issue:update', 3, 3, 5857, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:33:34', '1', '2026-03-02 14:33:34', '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 (5861, '杂项出库删除', 'mes:wm-misc-issue:delete', 3, 4, 5857, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:33:34', '1', '2026-03-02 14:33:34', '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 (5862, '杂项出库导出', 'mes:wm-misc-issue:export', 3, 5, 5857, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:33:34', '1', '2026-03-02 14:33:34', '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 (5863, '杂项出库执行', 'mes:wm-misc-issue:finish', 3, 6, 5857, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:33:34', '1', '2026-03-02 14:33:34', '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 (5867, '发货通知', '', 2, 9, 5780, 'sales-notice', 'fa-solid:notes-medical', 'mes/wm/salesnotice/index', 'MesWmSalesNotice', 0, '1', '1', '1', '1', '2026-03-30 08:54:30', '1', '2026-03-30 17:09:27', '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 (5868, '查询发货通知', 'mes:wm-sales-notice:query', 3, 1, 5867, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-30 08:54:30', '1', '2026-03-30 16:57:31', '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 (5869, '创建发货通知', 'mes:wm-sales-notice:create', 3, 2, 5867, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-30 08:54:30', '1', '2026-03-30 16:57:37', '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 (5870, '更新发货通知', 'mes:wm-sales-notice:update', 3, 3, 5867, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-30 08:54:30', '1', '2026-03-30 16:57:49', '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 (5871, '删除发货通知', 'mes:wm-sales-notice:delete', 3, 4, 5867, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-30 08:54:30', '1', '2026-03-30 16:57:57', '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 (5872, '导出发货通知', 'mes:wm-sales-notice:export', 3, 5, 5867, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-30 08:54:30', '1', '2026-03-30 16:58:03', '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 (5873, '外协入库', '', 2, 18, 5780, 'outsource-receipt', 'fa-solid:truck', 'mes/wm/outsourcereceipt/index', 'MesWmOutsourceReceipt', 0, '1', '1', '1', '1', '2026-03-31 15:36:37', '1', '2026-04-01 00:04:11', '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 (5874, '外协入库单查询', 'mes:wm-outsource-receipt:query', 3, 1, 5873, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-31 15:36:37', '1', '2026-03-31 15:36:37', '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 (5875, '外协入库单新增', 'mes:wm-outsource-receipt:create', 3, 2, 5873, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-31 15:36:37', '1', '2026-03-31 15:36:37', '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 (5876, '外协入库单修改', 'mes:wm-outsource-receipt:update', 3, 3, 5873, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-31 15:36:37', '1', '2026-03-31 15:36:37', '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 (5877, '外协入库单删除', 'mes:wm-outsource-receipt:delete', 3, 4, 5873, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-31 15:36:37', '1', '2026-03-31 15:36:37', '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 (5878, '外协入库单导出', 'mes:wm-outsource-receipt:export', 3, 5, 5873, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-31 15:36:37', '1', '2026-03-31 15:36:37', '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 (5879, '外协入库单完成', 'mes:wm-outsource-receipt:finish', 3, 6, 5873, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-31 15:36:37', '1', '2026-03-31 15:36:37', '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 (5881, '外协发料', '', 2, 17, 5780, 'outsource-issue', 'ep:sell', 'mes/wm/outsourceissue/index', 'MesWmOutsourceIssue', 0, '1', '1', '1', '1', '2026-03-02 14:36:04', '1', '2026-03-03 19:59:38', '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 (5882, '外协发料单查询', 'mes:wm-outsource-issue:query', 3, 1, 5881, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:36:04', '1', '2026-03-02 14:36:04', '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 (5883, '外协发料单新增', 'mes:wm-outsource-issue:create', 3, 2, 5881, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:36:04', '1', '2026-03-02 14:36:04', '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 (5884, '外协发料单修改', 'mes:wm-outsource-issue:update', 3, 3, 5881, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:36:04', '1', '2026-03-02 14:36:04', '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 (5885, '外协发料单删除', 'mes:wm-outsource-issue:delete', 3, 4, 5881, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:36:04', '1', '2026-03-02 14:36:04', '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 (5886, '外协发料单导出', 'mes:wm-outsource-issue:export', 3, 5, 5881, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:36:04', '1', '2026-03-02 14:36:04', '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 (5887, '外协发料单执行出库', 'mes:wm-outsource-issue:execute', 3, 6, 5881, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-02 14:36:04', '1', '2026-03-02 14:36:04', '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 (5888, '杂项入库', '', 2, 19, 5780, 'misc-receipt', 'ep:bottom', 'mes/wm/miscreceipt/index', NULL, 0, '1', '1', '1', '1', '2026-03-03 07:17:50', '1', '2026-03-03 07:46:53', '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 (5889, '杂项入库查询', 'mes:wm:misc-receipt:query', 3, 1, 5888, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-03 07:17:50', '1', '2026-03-03 07:17:50', '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 (5890, '杂项入库创建', 'mes:wm:misc-receipt:create', 3, 2, 5888, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-03 07:17:50', '1', '2026-03-03 07:17:50', '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 (5891, '杂项入库更新', 'mes:wm:misc-receipt:update', 3, 3, 5888, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-03 07:17:50', '1', '2026-03-03 07:17:50', '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 (5892, '杂项入库删除', 'mes:wm:misc-receipt:delete', 3, 4, 5888, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-03 07:17:50', '1', '2026-03-03 07:17:50', '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 (5893, '杂项入库提交', 'mes:wm:misc-receipt:submit', 3, 5, 5888, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-03 07:17:50', '1', '2026-03-03 07:17:50', '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 (5894, '杂项入库执行', 'mes:wm:misc-receipt:finish', 3, 6, 5888, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-03 07:17:50', '1', '2026-03-03 07:17:50', '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 (5895, '杂项入库取消', 'mes:wm:misc-receipt:cancel', 3, 7, 5888, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-03 07:17:50', '1', '2026-03-03 07:17:50', '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 (5896, '杂项入库导出', 'mes:wm:misc-receipt:export', 3, 8, 5888, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-03 07:17:50', '1', '2026-03-03 07:17:50', '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 (5897, 'SN 码', '', 2, 16, 5780, 'sn', 'ep:document', 'mes/wm/sn/index', 'MesWmSn', 0, '1', '1', '1', '1', '2026-03-05 01:30:21', '1', '2026-03-05 13:37:30', '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 (5898, 'SN 码查询', 'mes:wm-sn:query', 3, 1, 5897, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-05 01:30:21', '1', '2026-03-05 01:30:21', '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 (5899, 'SN 码生成', 'mes:wm-sn:create', 3, 2, 5897, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-05 01:30:21', '1', '2026-03-05 01:30:21', '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 (5900, 'SN 码删除', 'mes:wm-sn:delete', 3, 4, 5897, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-05 01:30:21', '1', '2026-03-05 01:30:21', '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 (5901, 'SN 码导出', 'mes:wm-sn:export', 3, 5, 5897, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-05 01:30:21', '1', '2026-03-05 01:30:21', '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 (5906, '条码配置', '', 2, 15, 5780, 'barcode/config', 'fa:barcode', 'mes/wm/barcode/config/index', 'MesWmBarcodeConfig', 0, '0', '1', '1', '1', '2026-03-05 14:37:20', '1', '2026-03-06 21:05:20', '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 (5907, '条码配置查询', 'mes:wm-barcode-config:query', 3, 1, 5906, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-05 14:37:20', '1', '2026-03-06 21:09:11', '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 (5908, '条码配置创建', 'mes:wm-barcode-config:create', 3, 2, 5906, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-05 14:37:20', '1', '2026-03-06 21:09:16', '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 (5909, '条码配置更新', 'mes:wm-barcode-config:update', 3, 3, 5906, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-05 14:37:20', '1', '2026-03-06 21:09:21', '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 (5910, '条码配置删除', 'mes:wm-barcode-config:delete', 3, 4, 5906, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-05 14:37:20', '1', '2026-03-06 21:09:26', '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 (5911, '赋能管理', '', 2, 15, 5780, 'barcode', 'fa:qrcode', 'mes/wm/barcode/index', 'MesWmBarcode', 0, '1', '1', '1', '1', '2026-03-05 14:37:20', '1', '2026-03-06 10:01:03', '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 (5912, '条码清单查询', 'mes:wm-barcode:query', 3, 1, 5911, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-05 14:37:20', '1', '2026-03-06 09:58:59', '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 (5913, '条码清单创建', 'mes:wm-barcode:create', 3, 2, 5911, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-05 14:37:20', '1', '2026-03-06 09:59:03', '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 (5914, '条码清单更新', 'mes:wm-barcode:update', 3, 3, 5911, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-05 14:37:20', '1', '2026-03-06 09:59:08', '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 (5915, '条码清单删除', 'mes:wm-barcode:delete', 3, 4, 5911, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-05 14:37:20', '1', '2026-03-06 09:59:12', '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 (5916, '条码清单导出', 'mes:wm-barcode:export', 3, 5, 5911, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-07 02:08:17', '1', '2026-03-07 02:08:17', '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 (5917, '装箱管理', '', 2, 14, 5780, 'packages', 'ep:box', 'mes/wm/packages/index', 'MesWmPackages', 0, '1', '1', '1', '1', '2026-03-08 02:05:46', '1', '2026-03-08 19:16:27', '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 (5918, '装箱管理查询', 'mes:wm-package:query', 3, 1, 5917, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-08 02:05:46', '1', '2026-03-08 02:05:46', '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 (5919, '装箱管理新增', 'mes:wm-package:create', 3, 2, 5917, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-08 02:05:46', '1', '2026-03-08 02:05:46', '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 (5920, '装箱管理修改', 'mes:wm-package:update', 3, 3, 5917, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-08 02:05:46', '1', '2026-03-08 02:05:46', '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 (5921, '装箱管理删除', 'mes:wm-package:delete', 3, 4, 5917, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-08 02:05:46', '1', '2026-03-08 02:05:46', '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 (5922, '转移调拨', '', 2, 12, 5780, 'transfer', 'ep:sort', 'mes/wm/transfer/index', 'MesWmTransfer', 0, '1', '1', '1', '1', '2026-03-08 11:55:25', '1', '2026-03-22 15:22:57', '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 (5923, '调拨单查询', 'mes:wm-transfer:query', 3, 1, 5922, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '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 (5924, '调拨单新增', 'mes:wm-transfer:create', 3, 2, 5922, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '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 (5925, '调拨单修改', 'mes:wm-transfer:update', 3, 3, 5922, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '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 (5926, '调拨单删除', 'mes:wm-transfer:delete', 3, 4, 5922, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '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 (5927, '调拨单导出', 'mes:wm-transfer:export', 3, 5, 5922, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '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 (5928, '调拨单提交', 'mes:wm-transfer:submit', 3, 6, 5922, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '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 (5929, '调拨单确认', 'mes:wm-transfer:confirm', 3, 7, 5922, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '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 (5930, '调拨单完成', 'mes:wm-transfer:finish', 3, 8, 5922, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '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 (5931, '调拨单取消', 'mes:wm-transfer:cancel', 3, 9, 5922, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-08 11:55:25', '1', '2026-03-08 11:55:25', '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 (5950, '库存盘点', '', 1, 13, 5780, 'stock-taking', 'ep:circle-check-filled', '', '', 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 21:19:09', '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 (5951, '盘点方案', '', 2, 1, 5950, 'plan', 'ep:document', 'mes/wm/stocktaking/plan/index', 'MesWmStockTakingPlan', 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 13:19:52', '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 (5952, '盘点方案查询', 'mes:wm-stock-taking-plan:query', 3, 1, 5951, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 13:35:06', '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 (5953, '盘点方案创建', 'mes:wm-stock-taking-plan:create', 3, 2, 5951, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 13:35:07', '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 (5954, '盘点方案更新', 'mes:wm-stock-taking-plan:update', 3, 3, 5951, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 13:35:09', '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 (5955, '盘点方案删除', 'mes:wm-stock-taking-plan:delete', 3, 4, 5951, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 13:35:11', '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 (5956, '盘点方案导出', 'mes:wm-stock-taking-plan:export', 3, 5, 5951, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 13:35:13', '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 (5957, '盘点任务', '', 2, 2, 5950, 'task', 'ep:list', 'mes/wm/stocktaking/task/index', 'MesWmStockTakingTask', 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 23:00:02', '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 (5958, '盘点任务查询', 'mes:wm-stock-taking-task:query', 3, 1, 5957, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '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 (5959, '盘点任务创建', 'mes:wm-stock-taking-task:create', 3, 2, 5957, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '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 (5960, '盘点任务更新', 'mes:wm-stock-taking-task:update', 3, 3, 5957, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '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 (5961, '盘点任务删除', 'mes:wm-stock-taking-task:delete', 3, 4, 5957, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '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 (5962, '盘点任务导出', 'mes:wm-stock-taking-task:export', 3, 5, 5957, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-09 00:00:00', '1', '2026-03-09 00:00:00', '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 (5966, '批次追溯', '', 2, 9, 5500, 'batch-trace', 'ep:aim', 'mes/qc/batchtrace/index', 'BatchTrace', 0, '1', '1', '1', '1', '2026-03-14 06:59:25', '1', '2026-04-05 09:39:54', '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 (5967, '批次追溯查询', 'mes:wm-batch:query', 3, 1, 5966, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-03-14 06:59:25', '1', '2026-03-14 06:59:25', '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 (5969, '报告审批', 'mes:pro-feedback:approve', 3, 6, 5550, '', '', '', '', 0, '1', '1', '1', '1', '2026-03-19 08:51:16', '1', '2026-03-19 08:51:16', '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 (5970, '销售出库', '', 2, 10, 5780, 'product-sales', 'ep:sell', 'mes/wm/productsales/index', 'MesWmProductSales', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5971, '查询销售出库', 'mes:wm-product-sales:query', 3, 1, 5970, '', '', '', '', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5972, '创建销售出库', 'mes:wm-product-sales:create', 3, 2, 5970, '', '', '', '', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5973, '更新销售出库', 'mes:wm-product-sales:update', 3, 3, 5970, '', '', '', '', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5974, '删除销售出库', 'mes:wm-product-sales:delete', 3, 4, 5970, '', '', '', '', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5975, '导出销售出库', 'mes:wm-product-sales:export', 3, 5, 5970, '', '', '', '', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5976, '提交销售出库', 'mes:wm-product-sales:submit', 3, 6, 5970, '', '', '', '', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5977, '执行拣货', 'mes:wm-product-sales:stock', 3, 7, 5970, '', '', '', '', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5978, '填写运单', 'mes:wm-product-sales:shipping', 3, 8, 5970, '', '', '', '', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5979, '执行出库', 'mes:wm-product-sales:finish', 3, 9, 5970, '', '', '', '', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5980, '取消销售出库', 'mes:wm-product-sales:cancel', 3, 10, 5970, '', '', '', '', 0, '1', '1', '1', '', '2026-03-30 10:37:08', '', '2026-03-30 10:37:08', '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 (5981, '工作记录', '', 2, 99, 5700, 'workrecord', 'ep:timer', 'mes/pro/workrecord/index', 'MesProWorkRecordLog', 0, '1', '1', '1', '1', '2026-04-05 14:08:44', '1', '2026-04-05 14:08:44', '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 (5982, '工作记录查询', 'mes:pro-workrecord:query', 3, 1, 5981, '', '', '', '', 0, '1', '1', '1', '1', '2026-04-05 14:08:44', '1', '2026-04-05 14:08:44', '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 (5983, '工作记录导出', 'mes:pro-workrecord:export', 3, 2, 5981, '', '', '', '', 0, '1', '1', '1', '1', '2026-04-05 14:08:44', '1', '2026-04-05 14:08:44', '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 (5984, '上工下工', 'mes:pro-workrecord:clock', 3, 3, 5981, '', '', '', '', 0, '1', '1', '1', '1', '2026-04-05 14:08:44', '1', '2026-04-05 14:08:44', '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 (5985, 'MES 首页', 'mes:home:query', 2, 0, 5100, 'mes/home/index', 'ep:home-filled', 'mes/home/index', 'MesHome', 0, '1', '1', '1', '1', '2026-04-05 23:24:03', '1', '2026-04-06 01:20:52', '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 (6400, 'WMS 系统', '', 1, 310, 0, '/wms', 'ep:box', '', '', 0, '1', '1', '1', '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '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 (6401, '基础数据', '', 1, 6, 6400, 'md', 'ep:files', '', '', 0, '1', '1', '1', '1', '2026-05-09 16:11:01', '1', '2026-05-12 17:48:35', '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 (6402, '仓库管理', '', 2, 1, 6401, 'warehouse', 'ep:office-building', 'wms/md/warehouse/index', 'WmsWarehouse', 0, '1', '1', '1', '1', '2026-05-09 16:11:01', '1', '2026-05-10 00:54:16', '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 (6403, '仓库查询', 'wms:warehouse:query', 3, 1, 6402, '', '', '', '', 0, '1', '1', '1', '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '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 (6404, '仓库创建', 'wms:warehouse:create', 3, 2, 6402, '', '', '', '', 0, '1', '1', '1', '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '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 (6405, '仓库更新', 'wms:warehouse:update', 3, 3, 6402, '', '', '', '', 0, '1', '1', '1', '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '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 (6406, '仓库删除', 'wms:warehouse:delete', 3, 4, 6402, '', '', '', '', 0, '1', '1', '1', '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '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 (6411, '商品品牌', '', 2, 3, 6401, 'item/brand', 'ep:price-tag', 'wms/md/item/brand/index', 'WmsItemBrand', 0, '1', '1', '1', '1', '2026-05-10 01:43:12', '1', '2026-05-10 02:12:51', '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 (6412, '品牌查询', 'wms:item-brand:query', 3, 1, 6411, '', '', '', '', 0, '1', '1', '1', '1', '2026-05-10 01:43:12', '1', '2026-05-10 01:43:12', '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 (6413, '品牌创建', 'wms:item-brand:create', 3, 2, 6411, '', '', '', '', 0, '1', '1', '1', '1', '2026-05-10 01:43:12', '1', '2026-05-10 01:43:12', '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 (6414, '品牌更新', 'wms:item-brand:update', 3, 3, 6411, '', '', '', '', 0, '1', '1', '1', '1', '2026-05-10 01:43:12', '1', '2026-05-10 01:43:12', '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 (6415, '品牌删除', 'wms:item-brand:delete', 3, 4, 6411, '', '', '', '', 0, '1', '1', '1', '1', '2026-05-10 01:43:12', '1', '2026-05-10 01:43:12', '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 (6416, '品牌导出', 'wms:item-brand:export', 3, 5, 6411, '', '', '', '', 0, '1', '1', '1', '1', '2026-05-10 01:43:12', '1', '2026-05-10 01:43:12', '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 (6417, '仓库导出', 'wms:warehouse:export', 3, 5, 6402, '', '', '', '', 0, '1', '1', '1', '1', '2026-05-10 02:42:47', '1', '2026-05-10 02:42:47', '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 (6419, '商品分类', '', 2, 2, 6401, 'item/category', 'ep:folder', 'wms/md/item/category/index', 'WmsItemCategory', 0, '1', '1', '1', '1', '2026-05-10 07:14:18', '1', '2026-05-10 07:14:18', '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 (6420, '分类查询', 'wms:item-category:query', 3, 1, 6419, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 07:14:18', '1', '2026-05-10 07:14:18', '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 (6421, '分类创建', 'wms:item-category:create', 3, 2, 6419, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 07:14:18', '1', '2026-05-10 07:14:18', '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 (6422, '分类更新', 'wms:item-category:update', 3, 3, 6419, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 07:14:18', '1', '2026-05-10 07:14:18', '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 (6423, '分类删除', 'wms:item-category:delete', 3, 4, 6419, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 07:14:18', '1', '2026-05-10 07:14:18', '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 (6424, '商品管理', '', 2, 4, 6401, 'item', 'ep:goods', 'wms/md/item/index', 'WmsItem', 0, '1', '1', '1', '1', '2026-05-10 09:15:34', '1', '2026-05-10 09:15:34', '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 (6425, '商品查询', 'wms:item:query', 3, 1, 6424, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 09:15:34', '1', '2026-05-10 09:15:34', '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 (6426, '商品创建', 'wms:item:create', 3, 2, 6424, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 09:15:34', '1', '2026-05-10 09:15:34', '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 (6427, '商品更新', 'wms:item:update', 3, 3, 6424, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 09:15:34', '1', '2026-05-10 09:15:34', '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 (6428, '商品删除', 'wms:item:delete', 3, 4, 6424, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 09:15:34', '1', '2026-05-10 09:15:34', '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 (6429, '商品导出', 'wms:item:export', 3, 5, 6424, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 09:15:34', '1', '2026-05-10 09:15:34', '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 (6430, '往来企业', '', 2, 5, 6401, 'merchant', 'ep:office-building', 'wms/md/merchant/index', 'WmsMerchant', 0, '1', '1', '1', '1', '2026-05-10 15:26:09', '1', '2026-05-10 15:48:07', '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 (6431, '往来企业查询', 'wms:merchant:query', 3, 1, 6430, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 15:26:09', '1', '2026-05-10 15:26:09', '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 (6432, '往来企业创建', 'wms:merchant:create', 3, 2, 6430, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 15:26:09', '1', '2026-05-10 15:26:09', '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 (6433, '往来企业更新', 'wms:merchant:update', 3, 3, 6430, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 15:26:09', '1', '2026-05-10 15:26:09', '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 (6434, '往来企业删除', 'wms:merchant:delete', 3, 4, 6430, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 15:26:09', '1', '2026-05-10 15:26:09', '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 (6435, '往来企业导出', 'wms:merchant:export', 3, 5, 6430, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 15:26:09', '1', '2026-05-10 15:26:09', '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 (6440, '库存管理', '', 1, 5, 6400, 'inventory', 'ep:box', '', '', 0, '1', '1', '1', '1', '2026-05-10 17:51:46', '1', '2026-05-13 01:23:11', '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 (6441, '库存统计', '', 2, 1, 6440, 'index', 'ep:data-board', 'wms/inventory/index/index', 'WmsInventory', 0, '1', '1', '1', '1', '2026-05-10 17:51:46', '1', '2026-05-11 02:08:28', '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 (6442, '库存统计查询', 'wms:inventory:query', 3, 1, 6441, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 17:51:46', '1', '2026-05-11 00:30:41', '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 (6445, '库存流水', '', 2, 2, 6440, 'history', 'ep:document', 'wms/inventory/history/index', 'WmsInventoryHistory', 0, '1', '1', '1', '1', '2026-05-10 17:51:46', '1', '2026-05-14 07:59:15', '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 (6446, '库存流水查询', 'wms:inventory-history:query', 3, 1, 6445, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-10 17:51:46', '1', '2026-05-11 00:29:15', '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 (6451, '入库管理', '', 2, 1, 6400, 'receipt', 'ep:download', 'wms/order/receipt/index', 'WmsReceiptOrder', 0, '1', '1', '1', '1', '2026-05-11 11:58:58', '1', '2026-05-11 16:58:02', '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 (6452, '入库单查询', 'wms:receipt-order:query', 3, 1, 6451, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-11 11:58:58', '1', '2026-05-11 16:58:02', '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 (6453, '入库单创建', 'wms:receipt-order:create', 3, 2, 6451, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-11 11:58:58', '1', '2026-05-11 16:58:02', '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 (6454, '入库单更新', 'wms:receipt-order:update', 3, 3, 6451, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-11 11:58:58', '1', '2026-05-11 16:58:02', '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 (6455, '入库单删除', 'wms:receipt-order:delete', 3, 4, 6451, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-11 11:58:58', '1', '2026-05-11 16:58:02', '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 (6456, '入库单完成入库', 'wms:receipt-order:complete', 3, 5, 6451, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-11 11:58:58', '1', '2026-05-11 16:58:02', '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 (6457, '入库单作废', 'wms:receipt-order:cancel', 3, 6, 6451, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-11 11:58:58', '1', '2026-05-12 16:27:16', '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 (6458, '入库单导出', 'wms:receipt-order:export', 3, 7, 6451, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-11 16:58:02', '1', '2026-05-12 16:27:16', '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 (6461, '出库管理', '', 2, 2, 6400, 'shipment', 'ep:upload', 'wms/order/shipment/index', 'WmsShipmentOrder', 0, '1', '1', '1', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '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 (6462, '出库单查询', 'wms:shipment-order:query', 3, 1, 6461, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '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 (6463, '出库单创建', 'wms:shipment-order:create', 3, 2, 6461, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '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 (6464, '出库单更新', 'wms:shipment-order:update', 3, 3, 6461, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '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 (6465, '出库单删除', 'wms:shipment-order:delete', 3, 4, 6461, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '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 (6466, '出库单完成出库', 'wms:shipment-order:complete', 3, 5, 6461, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '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 (6467, '出库单作废', 'wms:shipment-order:cancel', 3, 6, 6461, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '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 (6468, '出库单导出', 'wms:shipment-order:export', 3, 7, 6461, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', '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 (6470, '移库管理', '', 2, 3, 6400, 'movement', 'ep:sort', 'wms/order/movement/index', 'WmsMovementOrder', 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6471, '移库单查询', 'wms:movement-order:query', 3, 1, 6470, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6472, '移库单创建', 'wms:movement-order:create', 3, 2, 6470, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6473, '移库单更新', 'wms:movement-order:update', 3, 3, 6470, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6474, '移库单删除', 'wms:movement-order:delete', 3, 4, 6470, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6475, '移库单完成移库', 'wms:movement-order:complete', 3, 5, 6470, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6476, '移库单作废', 'wms:movement-order:cancel', 3, 6, 6470, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6477, '移库单导出', 'wms:movement-order:export', 3, 7, 6470, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6480, '盘库管理', '', 2, 4, 6400, 'check', 'ep:circle-check-filled', 'wms/order/check/index', 'WmsCheckOrder', 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6481, '盘库单查询', 'wms:check-order:query', 3, 1, 6480, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6482, '盘库单创建', 'wms:check-order:create', 3, 2, 6480, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6483, '盘库单更新', 'wms:check-order:update', 3, 3, 6480, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6484, '盘库单删除', 'wms:check-order:delete', 3, 4, 6480, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6485, '盘库单完成盘库', 'wms:check-order:complete', 3, 5, 6480, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6486, '盘库单作废', 'wms:check-order:cancel', 3, 6, 6480, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6487, '盘库单导出', 'wms:check-order:export', 3, 7, 6480, '', '#', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-13 01:23:11', '1', '2026-05-14 02:21:20', '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 (6490, 'WMS 首页', 'wms:home:query', 2, 0, 6400, 'home', 'ep:home-filled', 'wms/home/index', 'WmsHome', 0, '1', '1', '1', '1', '2026-05-14 09:34:27', '1', '2026-05-14 10:05:06', '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 (6500, 'IM 即时通讯', '', 1, 501, 0, '/im', 'ep:chat-dot-round', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', '1', '2026-05-09 16:19:34', '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 (6510, '数据统计', 'im:manager:statistics:query', 2, 10, 6500, 'statistics', 'ep:trend-charts', 'im/manager/statistics/index', 'ImStatistics', 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', '1', '2026-04-30 19:35:54', '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 (6515, '好友申请', 'im:manager:friend-request:query', 2, 20, 6600, 'friend-request', 'ep:document', 'im/manager/friend/request/index', 'ImFriendRequest', 0, '1', '1', '1', '1', '2026-05-05 11:15:48', '1', '2026-05-07 00:40:35', '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 (6520, '好友列表', 'im:manager:friend:list', 2, 10, 6600, 'friend', 'ep:user', 'im/manager/friend/index', 'ImFriend', 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', '1', '2026-05-07 00:40:35', '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 (6521, '好友查询', 'im:manager:friend:query', 3, 10, 6520, '', '', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', 'admin', '2026-04-30 09:11:20', '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 (6530, '好友消息', 'im:manager:message:private:list', 2, 30, 6600, 'message', 'ep:chat-dot-square', 'im/manager/message/private/index', 'ImPrivateMessage', 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', '1', '2026-05-07 00:40:35', '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 (6531, '私聊消息查询', 'im:manager:message:query', 3, 10, 6530, '', '', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', 'admin', '2026-04-30 09:11:20', '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 (6540, '群聊列表', 'im:manager:group:list', 2, 10, 6610, 'list', 'ep:user-filled', 'im/manager/group/index', 'ImGroup', 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', '1', '2026-05-07 00:40:35', '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 (6541, '群组查询', 'im:manager:group:query', 3, 10, 6540, '', '', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', 'admin', '2026-04-30 09:11:20', '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 (6542, '群组封禁/解封', 'im:manager:group:ban', 3, 20, 6540, '', '', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', 'admin', '2026-04-30 09:11:20', '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 (6545, '群聊申请', 'im:manager:group-request:query', 2, 20, 6610, 'request', 'ep:document', 'im/manager/group/request/index', 'ImGroupRequest', 0, '1', '1', '1', '1', '2026-05-07 00:31:35', '1', '2026-05-07 00:40:35', '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 (6550, '群聊消息', 'im:manager:message:group:list', 2, 30, 6610, 'message', 'ep:chat-line-round', 'im/manager/message/group/index', 'ImGroupMessage', 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', '1', '2026-05-07 00:40:35', '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 (6551, '群聊消息查询', 'im:manager:message:query', 3, 10, 6550, '', '', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', 'admin', '2026-04-30 09:11:20', '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 (6560, '敏感词管理', 'im:manager:sensitive-word:list', 2, 60, 6500, 'sensitive-word', 'ep:warning', 'im/manager/sensitiveword/index', 'ImSensitiveWord', 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', '1', '2026-04-30 22:25:48', '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 (6561, '敏感词查询', 'im:manager:sensitive-word:query', 3, 10, 6560, '', '', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', 'admin', '2026-04-30 09:11:20', '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 (6562, '敏感词新增', 'im:manager:sensitive-word:create', 3, 20, 6560, '', '', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', 'admin', '2026-04-30 09:11:20', '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 (6563, '敏感词修改', 'im:manager:sensitive-word:update', 3, 30, 6560, '', '', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', 'admin', '2026-04-30 09:11:20', '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 (6564, '敏感词删除', 'im:manager:sensitive-word:delete', 3, 40, 6560, '', '', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-04-30 09:11:20', 'admin', '2026-04-30 09:11:20', '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 (6570, '表情管理', '', 1, 70, 6500, 'face', 'ep:magic-stick', NULL, NULL, 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6571, '系统表情', 'im:manager:face-pack:list', 2, 10, 6570, 'pack', 'ep:picture', 'im/manager/face/pack/index', 'ImManagerFacePack', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6572, '表情包查询', 'im:manager:face-pack:query', 3, 10, 6571, '', '', '', '', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6573, '表情包创建', 'im:manager:face-pack:create', 3, 20, 6571, '', '', '', '', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6574, '表情包修改', 'im:manager:face-pack:update', 3, 30, 6571, '', '', '', '', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6575, '表情包删除', 'im:manager:face-pack:delete', 3, 40, 6571, '', '', '', '', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6576, '表情图查询', 'im:manager:face-pack-item:query', 3, 50, 6571, '', '', '', '', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6577, '表情图创建', 'im:manager:face-pack-item:create', 3, 60, 6571, '', '', '', '', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6578, '表情图修改', 'im:manager:face-pack-item:update', 3, 70, 6571, '', '', '', '', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6579, '表情图删除', 'im:manager:face-pack-item:delete', 3, 80, 6571, '', '', '', '', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6580, '用户表情', 'im:manager:face-user-item:list', 2, 20, 6570, 'user-item', 'ep:user', 'im/manager/face/userItem/index', 'ImManagerFaceUserItem', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 14:24:20', '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 (6581, '用户表情查询', 'im:manager:face-user-item:query', 3, 10, 6580, '', '', '', '', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6582, '用户表情删除', 'im:manager:face-user-item:delete', 3, 20, 6580, '', '', '', '', 0, '1', '1', '1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', '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 (6600, '私聊管理', '', 1, 20, 6500, 'private', 'ep:chat-round', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-07 00:40:35', '1', '2026-05-07 00:40:35', '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 (6610, '群聊管理', '', 1, 30, 6500, 'group', 'ep:chat-line-round', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-07 00:40:35', '1', '2026-05-07 00:40:35', '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 (6611, '通话记录', '', 2, 40, 6500, 'rtc', 'ep:phone', 'im/manager/rtc/index', 'ImRtcCall', 0, '1', '1', '1', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '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 (6612, '通话记录查询', 'im:manager:rtc:query', 3, 1, 6611, '', '', NULL, NULL, 0, '1', '1', '1', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', '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 (6700, '频道管理', '', 1, 90, 6500, 'channel', 'ep:promotion', NULL, NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6710, '频道列表', '', 2, 1, 6700, 'list', 'ep:promotion', 'im/manager/channel/list/index', 'ImChannel', 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-19 09:44:34', '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 (6711, '频道查询', 'im:manager:channel:query', 3, 1, 6710, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6712, '频道创建', 'im:manager:channel:create', 3, 2, 6710, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6713, '频道修改', 'im:manager:channel:update', 3, 3, 6710, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6714, '频道删除', 'im:manager:channel:delete', 3, 4, 6710, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6720, '频道素材', '', 2, 2, 6700, 'material', 'ep:document', 'im/manager/channel/material/index', 'ImChannelMaterial', 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-19 09:44:34', '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 (6721, '素材查询', 'im:manager:channel-material:query', 3, 1, 6720, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6722, '素材创建', 'im:manager:channel-material:create', 3, 2, 6720, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6723, '素材修改', 'im:manager:channel-material:update', 3, 3, 6720, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6724, '素材删除', 'im:manager:channel-material:delete', 3, 4, 6720, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6730, '频道消息', '', 2, 3, 6700, 'message', 'ep:message', 'im/manager/channel/message/index', 'ImChannelMessage', 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-19 09:44:34', '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 (6731, '消息查询', 'im:manager:channel-message:query', 3, 1, 6730, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6732, '立即推送', 'im:manager:channel-message:send', 3, 2, 6730, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6733, '消息删除', 'im:manager:channel-message:delete', 3, 3, 6730, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', '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 (6734, '解散群', 'im:manager:group:dissolve', 3, 21, 6540, '', '', '', NULL, 0, '1', '1', '1', '1', '2026-05-24 12:02:38', '1', '2026-05-24 12:02:38', '0'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_menu_seq; +CREATE SEQUENCE system_menu_seq + START 6735; + +-- ---------------------------- +-- Table structure for system_notice +-- ---------------------------- +DROP TABLE IF EXISTS system_notice; +CREATE TABLE system_notice ( + id int8 NOT NULL, + title varchar(50) NOT NULL, + content text NOT NULL, + type int2 NOT NULL, + status int2 NOT NULL DEFAULT 0, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_notice ADD CONSTRAINT pk_system_notice PRIMARY KEY (id); + +COMMENT ON COLUMN system_notice.id IS '公告ID'; +COMMENT ON COLUMN system_notice.title IS '公告标题'; +COMMENT ON COLUMN system_notice.content IS '公告内容'; +COMMENT ON COLUMN system_notice.type IS '公告类型(1通知 2公告)'; +COMMENT ON COLUMN system_notice.status IS '公告状态(0正常 1关闭)'; +COMMENT ON COLUMN system_notice.creator IS '创建者'; +COMMENT ON COLUMN system_notice.create_time IS '创建时间'; +COMMENT ON COLUMN system_notice.updater IS '更新者'; +COMMENT ON COLUMN system_notice.update_time IS '更新时间'; +COMMENT ON COLUMN system_notice.deleted IS '是否删除'; +COMMENT ON COLUMN system_notice.tenant_id IS '租户编号'; +COMMENT ON TABLE system_notice IS '通知公告表'; + +-- ---------------------------- +-- Records of system_notice +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_notice (id, title, content, type, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, '芋道的公众', '

新版本内容133222

', 1, 0, 'admin', '2021-01-05 17:03:48', '"1"', '2025-08-31 09:38:22', '0', 1); +INSERT INTO system_notice (id, title, content, type, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2, '维护通知:2018-07-01 系统凌晨维护', '

11112222image3333

\n

', 2, 1, 'admin', '2021-01-05 17:03:48', '1', '2026-05-03 23:07:48', '0', 1); +INSERT INTO system_notice (id, title, content, type, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4, '我是测试标题', '

哈哈哈哈123

', 1, 0, '110', '2022-02-22 01:01:25', '110', '2022-02-22 01:01:46', '0', 121); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_notice_seq; +CREATE SEQUENCE system_notice_seq + START 5; + +-- ---------------------------- +-- Table structure for system_notify_message +-- ---------------------------- +DROP TABLE IF EXISTS system_notify_message; +CREATE TABLE system_notify_message ( + id int8 NOT NULL, + user_id int8 NOT NULL, + user_type int2 NOT NULL, + template_id int8 NOT NULL, + template_code varchar(64) NOT NULL, + template_nickname varchar(63) NOT NULL, + template_content varchar(1024) NOT NULL, + template_type int4 NOT NULL, + template_params varchar(255) NOT NULL, + read_status bool NOT NULL, + read_time timestamp NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_notify_message ADD CONSTRAINT pk_system_notify_message PRIMARY KEY (id); + +CREATE INDEX idx_system_notify_message_01 ON system_notify_message (user_id, user_type, read_status); + +COMMENT ON COLUMN system_notify_message.id IS '用户ID'; +COMMENT ON COLUMN system_notify_message.user_id IS '用户id'; +COMMENT ON COLUMN system_notify_message.user_type IS '用户类型'; +COMMENT ON COLUMN system_notify_message.template_id IS '模版编号'; +COMMENT ON COLUMN system_notify_message.template_code IS '模板编码'; +COMMENT ON COLUMN system_notify_message.template_nickname IS '模版发送人名称'; +COMMENT ON COLUMN system_notify_message.template_content IS '模版内容'; +COMMENT ON COLUMN system_notify_message.template_type IS '模版类型'; +COMMENT ON COLUMN system_notify_message.template_params IS '模版参数'; +COMMENT ON COLUMN system_notify_message.read_status IS '是否已读'; +COMMENT ON COLUMN system_notify_message.read_time IS '阅读时间'; +COMMENT ON COLUMN system_notify_message.creator IS '创建者'; +COMMENT ON COLUMN system_notify_message.create_time IS '创建时间'; +COMMENT ON COLUMN system_notify_message.updater IS '更新者'; +COMMENT ON COLUMN system_notify_message.update_time IS '更新时间'; +COMMENT ON COLUMN system_notify_message.deleted IS '是否删除'; +COMMENT ON COLUMN system_notify_message.tenant_id IS '租户编号'; +COMMENT ON TABLE system_notify_message IS '站内信消息表'; + +-- ---------------------------- +-- Records of system_notify_message +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_notify_message (id, user_id, user_type, template_id, template_code, template_nickname, template_content, template_type, template_params, read_status, read_time, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2, 1, 2, 1, 'test', '123', '我是 1,我开始 2 了', 1, '{"name":"1","what":"2"}', '1', '2025-12-15 21:24:36', '1', '2023-01-28 11:44:08', '1', '2025-12-15 21:24:36', '0', 1); +INSERT INTO system_notify_message (id, user_id, user_type, template_id, template_code, template_nickname, template_content, template_type, template_params, read_status, read_time, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3, 1, 2, 1, 'test', '123', '我是 1,我开始 2 了', 1, '{"name":"1","what":"2"}', '1', '2025-12-15 21:24:36', '1', '2023-01-28 11:45:04', '1', '2025-12-15 21:24:36', '0', 1); +INSERT INTO system_notify_message (id, user_id, user_type, template_id, template_code, template_nickname, template_content, template_type, template_params, read_status, read_time, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4, 103, 2, 2, 'register', '系统消息', '你好,欢迎 哈哈 加入大家庭!', 2, '{"name":"哈哈"}', '0', NULL, '1', '2023-01-28 21:02:20', '1', '2023-01-28 21:02:20', '0', 1); +INSERT INTO system_notify_message (id, user_id, user_type, template_id, template_code, template_nickname, template_content, template_type, template_params, read_status, read_time, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5, 1, 2, 1, 'test', '123', '我是 芋艿,我开始 写代码 了', 1, '{"name":"芋艿","what":"写代码"}', '1', '2025-12-08 17:25:28', '1', '2023-01-28 22:21:42', '1', '2025-12-08 17:25:28', '0', 1); +INSERT INTO system_notify_message (id, user_id, user_type, template_id, template_code, template_nickname, template_content, template_type, template_params, read_status, read_time, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6, 1, 2, 1, 'test', '123', '我是 芋艿,我开始 写代码 了', 1, '{"name":"芋艿","what":"写代码"}', '1', '2025-12-08 17:25:30', '1', '2023-01-28 22:22:07', '1', '2025-12-08 17:25:30', '0', 1); +INSERT INTO system_notify_message (id, user_id, user_type, template_id, template_code, template_nickname, template_content, template_type, template_params, read_status, read_time, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (7, 1, 2, 1, 'test', '123', '我是 2,我开始 3 了', 1, '{"name":"2","what":"3"}', '1', '2025-12-08 17:25:22', '1', '2023-01-28 23:45:21', '1', '2025-12-08 17:25:22', '0', 1); +INSERT INTO system_notify_message (id, user_id, user_type, template_id, template_code, template_nickname, template_content, template_type, template_params, read_status, read_time, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (8, 1, 2, 2, 'register', '系统消息', '你好,欢迎 123 加入大家庭!', 2, '{"name":"123"}', '1', '2025-12-08 16:46:01', '1', '2023-01-28 23:50:21', '1', '2025-12-08 16:46:01', '0', 1); +INSERT INTO system_notify_message (id, user_id, user_type, template_id, template_code, template_nickname, template_content, template_type, template_params, read_status, read_time, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (9, 247, 1, 4, 'brokerage_withdraw_audit_approve', 'system', '您在2023-09-28 08:35:46提现¥0.09元的申请已通过审核', 2, '{"reason":null,"createTime":"2023-09-28 08:35:46","price":"0.09"}', '0', NULL, '1', '2023-09-28 16:36:22', '1', '2023-09-28 16:36:22', '0', 1); +INSERT INTO system_notify_message (id, user_id, user_type, template_id, template_code, template_nickname, template_content, template_type, template_params, read_status, read_time, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (10, 247, 1, 4, 'brokerage_withdraw_audit_approve', 'system', '您在2023-09-30 20:59:40提现¥1.00元的申请已通过审核', 2, '{"reason":null,"createTime":"2023-09-30 20:59:40","price":"1.00"}', '0', NULL, '1', '2023-10-03 12:11:34', '1', '2023-10-03 12:11:34', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_notify_message_seq; +CREATE SEQUENCE system_notify_message_seq + START 11; + +-- ---------------------------- +-- Table structure for system_notify_template +-- ---------------------------- +DROP TABLE IF EXISTS system_notify_template; +CREATE TABLE system_notify_template ( + id int8 NOT NULL, + name varchar(63) NOT NULL, + code varchar(64) NOT NULL, + nickname varchar(255) NOT NULL, + content varchar(1024) NOT NULL, + type int2 NOT NULL, + params varchar(255) NULL DEFAULT NULL, + status int2 NOT NULL, + remark varchar(255) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_notify_template ADD CONSTRAINT pk_system_notify_template PRIMARY KEY (id); + +COMMENT ON COLUMN system_notify_template.id IS '主键'; +COMMENT ON COLUMN system_notify_template.name IS '模板名称'; +COMMENT ON COLUMN system_notify_template.code IS '模版编码'; +COMMENT ON COLUMN system_notify_template.nickname IS '发送人名称'; +COMMENT ON COLUMN system_notify_template.content IS '模版内容'; +COMMENT ON COLUMN system_notify_template.type IS '类型'; +COMMENT ON COLUMN system_notify_template.params IS '参数数组'; +COMMENT ON COLUMN system_notify_template.status IS '状态'; +COMMENT ON COLUMN system_notify_template.remark IS '备注'; +COMMENT ON COLUMN system_notify_template.creator IS '创建者'; +COMMENT ON COLUMN system_notify_template.create_time IS '创建时间'; +COMMENT ON COLUMN system_notify_template.updater IS '更新者'; +COMMENT ON COLUMN system_notify_template.update_time IS '更新时间'; +COMMENT ON COLUMN system_notify_template.deleted IS '是否删除'; +COMMENT ON TABLE system_notify_template IS '站内信模板表'; + +DROP SEQUENCE IF EXISTS system_notify_template_seq; +CREATE SEQUENCE system_notify_template_seq + START 1; + +-- ---------------------------- +-- Table structure for system_oauth2_access_token +-- ---------------------------- +DROP TABLE IF EXISTS system_oauth2_access_token; +CREATE TABLE system_oauth2_access_token ( + id int8 NOT NULL, + user_id int8 NOT NULL, + user_type int2 NOT NULL, + user_info varchar(512) NOT NULL, + access_token varchar(255) NOT NULL, + refresh_token varchar(32) NOT NULL, + client_id varchar(255) NOT NULL, + scopes varchar(255) NULL DEFAULT NULL, + expires_time timestamp NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_oauth2_access_token ADD CONSTRAINT pk_system_oauth2_access_token PRIMARY KEY (id); + +CREATE INDEX idx_system_oauth2_access_token_01 ON system_oauth2_access_token (access_token); +CREATE INDEX idx_system_oauth2_access_token_02 ON system_oauth2_access_token (refresh_token); + +COMMENT ON COLUMN system_oauth2_access_token.id IS '编号'; +COMMENT ON COLUMN system_oauth2_access_token.user_id IS '用户编号'; +COMMENT ON COLUMN system_oauth2_access_token.user_type IS '用户类型'; +COMMENT ON COLUMN system_oauth2_access_token.user_info IS '用户信息'; +COMMENT ON COLUMN system_oauth2_access_token.access_token IS '访问令牌'; +COMMENT ON COLUMN system_oauth2_access_token.refresh_token IS '刷新令牌'; +COMMENT ON COLUMN system_oauth2_access_token.client_id IS '客户端编号'; +COMMENT ON COLUMN system_oauth2_access_token.scopes IS '授权范围'; +COMMENT ON COLUMN system_oauth2_access_token.expires_time IS '过期时间'; +COMMENT ON COLUMN system_oauth2_access_token.creator IS '创建者'; +COMMENT ON COLUMN system_oauth2_access_token.create_time IS '创建时间'; +COMMENT ON COLUMN system_oauth2_access_token.updater IS '更新者'; +COMMENT ON COLUMN system_oauth2_access_token.update_time IS '更新时间'; +COMMENT ON COLUMN system_oauth2_access_token.deleted IS '是否删除'; +COMMENT ON COLUMN system_oauth2_access_token.tenant_id IS '租户编号'; +COMMENT ON TABLE system_oauth2_access_token IS 'OAuth2 访问令牌'; + +DROP SEQUENCE IF EXISTS system_oauth2_access_token_seq; +CREATE SEQUENCE system_oauth2_access_token_seq + START 1; + +-- ---------------------------- +-- Table structure for system_oauth2_approve +-- ---------------------------- +DROP TABLE IF EXISTS system_oauth2_approve; +CREATE TABLE system_oauth2_approve ( + id int8 NOT NULL, + user_id int8 NOT NULL, + user_type int2 NOT NULL, + client_id varchar(255) NOT NULL, + scope varchar(255) NOT NULL DEFAULT '', + approved bool NOT NULL DEFAULT '0', + expires_time timestamp NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_oauth2_approve ADD CONSTRAINT pk_system_oauth2_approve PRIMARY KEY (id); + +CREATE INDEX idx_system_oauth2_approve_01 ON system_oauth2_approve (user_id, user_type, client_id); + +COMMENT ON COLUMN system_oauth2_approve.id IS '编号'; +COMMENT ON COLUMN system_oauth2_approve.user_id IS '用户编号'; +COMMENT ON COLUMN system_oauth2_approve.user_type IS '用户类型'; +COMMENT ON COLUMN system_oauth2_approve.client_id IS '客户端编号'; +COMMENT ON COLUMN system_oauth2_approve.scope IS '授权范围'; +COMMENT ON COLUMN system_oauth2_approve.approved IS '是否接受'; +COMMENT ON COLUMN system_oauth2_approve.expires_time IS '过期时间'; +COMMENT ON COLUMN system_oauth2_approve.creator IS '创建者'; +COMMENT ON COLUMN system_oauth2_approve.create_time IS '创建时间'; +COMMENT ON COLUMN system_oauth2_approve.updater IS '更新者'; +COMMENT ON COLUMN system_oauth2_approve.update_time IS '更新时间'; +COMMENT ON COLUMN system_oauth2_approve.deleted IS '是否删除'; +COMMENT ON COLUMN system_oauth2_approve.tenant_id IS '租户编号'; +COMMENT ON TABLE system_oauth2_approve IS 'OAuth2 批准表'; + +DROP SEQUENCE IF EXISTS system_oauth2_approve_seq; +CREATE SEQUENCE system_oauth2_approve_seq + START 1; + +-- ---------------------------- +-- Table structure for system_oauth2_client +-- ---------------------------- +DROP TABLE IF EXISTS system_oauth2_client; +CREATE TABLE system_oauth2_client ( + id int8 NOT NULL, + client_id varchar(255) NOT NULL, + secret varchar(255) NOT NULL, + name varchar(255) NOT NULL, + logo varchar(255) NOT NULL, + description varchar(255) NULL DEFAULT NULL, + status int2 NOT NULL, + access_token_validity_seconds int4 NOT NULL, + refresh_token_validity_seconds int4 NOT NULL, + redirect_uris varchar(255) NOT NULL, + authorized_grant_types varchar(255) NOT NULL, + scopes varchar(255) NULL DEFAULT NULL, + auto_approve_scopes varchar(255) NULL DEFAULT NULL, + authorities varchar(255) NULL DEFAULT NULL, + resource_ids varchar(255) NULL DEFAULT NULL, + additional_information varchar(4096) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_oauth2_client ADD CONSTRAINT pk_system_oauth2_client PRIMARY KEY (id); + +CREATE INDEX idx_system_oauth2_client_01 ON system_oauth2_client (client_id); + +COMMENT ON COLUMN system_oauth2_client.id IS '编号'; +COMMENT ON COLUMN system_oauth2_client.client_id IS '客户端编号'; +COMMENT ON COLUMN system_oauth2_client.secret IS '客户端密钥'; +COMMENT ON COLUMN system_oauth2_client.name IS '应用名'; +COMMENT ON COLUMN system_oauth2_client.logo IS '应用图标'; +COMMENT ON COLUMN system_oauth2_client.description IS '应用描述'; +COMMENT ON COLUMN system_oauth2_client.status IS '状态'; +COMMENT ON COLUMN system_oauth2_client.access_token_validity_seconds IS '访问令牌的有效期'; +COMMENT ON COLUMN system_oauth2_client.refresh_token_validity_seconds IS '刷新令牌的有效期'; +COMMENT ON COLUMN system_oauth2_client.redirect_uris IS '可重定向的 URI 地址'; +COMMENT ON COLUMN system_oauth2_client.authorized_grant_types IS '授权类型'; +COMMENT ON COLUMN system_oauth2_client.scopes IS '授权范围'; +COMMENT ON COLUMN system_oauth2_client.auto_approve_scopes IS '自动通过的授权范围'; +COMMENT ON COLUMN system_oauth2_client.authorities IS '权限'; +COMMENT ON COLUMN system_oauth2_client.resource_ids IS '资源'; +COMMENT ON COLUMN system_oauth2_client.additional_information IS '附加信息'; +COMMENT ON COLUMN system_oauth2_client.creator IS '创建者'; +COMMENT ON COLUMN system_oauth2_client.create_time IS '创建时间'; +COMMENT ON COLUMN system_oauth2_client.updater IS '更新者'; +COMMENT ON COLUMN system_oauth2_client.update_time IS '更新时间'; +COMMENT ON COLUMN system_oauth2_client.deleted IS '是否删除'; +COMMENT ON TABLE system_oauth2_client IS 'OAuth2 客户端表'; + +-- ---------------------------- +-- Records of system_oauth2_client +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_oauth2_client (id, client_id, secret, name, logo, description, status, access_token_validity_seconds, refresh_token_validity_seconds, redirect_uris, authorized_grant_types, scopes, auto_approve_scopes, authorities, resource_ids, additional_information, creator, create_time, updater, update_time, deleted) VALUES (1, 'default', 'admin123', '芋道源码', 'http://test.yudao.iocoder.cn/20250502/sort2_1746189740718.png', '我是描述', 0, 1800, 2592000, '["https://www.iocoder.cn","https://doc.iocoder.cn"]', '["password","authorization_code","implicit","refresh_token","client_credentials"]', '["user.read","user.write"]', '[]', '["user.read","user.write"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2025-12-07 20:07:09', '0'); +INSERT INTO system_oauth2_client (id, client_id, secret, name, logo, description, status, access_token_validity_seconds, refresh_token_validity_seconds, redirect_uris, authorized_grant_types, scopes, auto_approve_scopes, authorities, resource_ids, additional_information, creator, create_time, updater, update_time, deleted) VALUES (40, 'test', 'test2', 'biubiu', 'http://test.yudao.iocoder.cn/20251227/javayuanma_1766829882970.jpg', '啦啦啦啦', 0, 1800, 43200, '["https://www.iocoder.cn"]', '["password","authorization_code","implicit"]', '["user_info","projects"]', '["user_info"]', '[]', '[]', '{}', '1', '2022-05-12 00:28:20', '1', '2025-12-27 18:04:44', '0'); +INSERT INTO system_oauth2_client (id, client_id, secret, name, logo, description, status, access_token_validity_seconds, refresh_token_validity_seconds, redirect_uris, authorized_grant_types, scopes, auto_approve_scopes, authorities, resource_ids, additional_information, creator, create_time, updater, update_time, deleted) VALUES (41, 'yudao-sso-demo-by-code', 'test', '基于授权码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/it/20250502/sign_1746181948685.png', NULL, 0, 1800, 43200, '["http://127.0.0.1:18080"]', '["authorization_code","refresh_token"]', '["user.read","user.write"]', '[]', '[]', '[]', NULL, '1', '2022-09-29 13:28:31', '1', '2025-05-02 18:32:30', '0'); +INSERT INTO system_oauth2_client (id, client_id, secret, name, logo, description, status, access_token_validity_seconds, refresh_token_validity_seconds, redirect_uris, authorized_grant_types, scopes, auto_approve_scopes, authorities, resource_ids, additional_information, creator, create_time, updater, update_time, deleted) VALUES (42, 'yudao-sso-demo-by-password', 'test', '基于密码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/20251025/images (3)_1761360515810.jpeg', NULL, 0, 1800, 43200, '["http://127.0.0.1:18080"]', '["password","refresh_token"]', '["user.read","user.write"]', '[]', '[]', '[]', NULL, '1', '2022-10-04 17:40:16', '1', '2025-10-25 10:49:40', '0'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_oauth2_client_seq; +CREATE SEQUENCE system_oauth2_client_seq + START 43; + +-- ---------------------------- +-- Table structure for system_oauth2_code +-- ---------------------------- +DROP TABLE IF EXISTS system_oauth2_code; +CREATE TABLE system_oauth2_code ( + id int8 NOT NULL, + user_id int8 NOT NULL, + user_type int2 NOT NULL, + code varchar(32) NOT NULL, + client_id varchar(255) NOT NULL, + scopes varchar(255) NULL DEFAULT '', + expires_time timestamp NOT NULL, + redirect_uri varchar(255) NULL DEFAULT NULL, + state varchar(255) NOT NULL DEFAULT '', + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_oauth2_code ADD CONSTRAINT pk_system_oauth2_code PRIMARY KEY (id); + +CREATE INDEX idx_system_oauth2_code_01 ON system_oauth2_code (code); + +COMMENT ON COLUMN system_oauth2_code.id IS '编号'; +COMMENT ON COLUMN system_oauth2_code.user_id IS '用户编号'; +COMMENT ON COLUMN system_oauth2_code.user_type IS '用户类型'; +COMMENT ON COLUMN system_oauth2_code.code IS '授权码'; +COMMENT ON COLUMN system_oauth2_code.client_id IS '客户端编号'; +COMMENT ON COLUMN system_oauth2_code.scopes IS '授权范围'; +COMMENT ON COLUMN system_oauth2_code.expires_time IS '过期时间'; +COMMENT ON COLUMN system_oauth2_code.redirect_uri IS '可重定向的 URI 地址'; +COMMENT ON COLUMN system_oauth2_code.state IS '状态'; +COMMENT ON COLUMN system_oauth2_code.creator IS '创建者'; +COMMENT ON COLUMN system_oauth2_code.create_time IS '创建时间'; +COMMENT ON COLUMN system_oauth2_code.updater IS '更新者'; +COMMENT ON COLUMN system_oauth2_code.update_time IS '更新时间'; +COMMENT ON COLUMN system_oauth2_code.deleted IS '是否删除'; +COMMENT ON COLUMN system_oauth2_code.tenant_id IS '租户编号'; +COMMENT ON TABLE system_oauth2_code IS 'OAuth2 授权码表'; + +DROP SEQUENCE IF EXISTS system_oauth2_code_seq; +CREATE SEQUENCE system_oauth2_code_seq + START 1; + +-- ---------------------------- +-- Table structure for system_oauth2_refresh_token +-- ---------------------------- +DROP TABLE IF EXISTS system_oauth2_refresh_token; +CREATE TABLE system_oauth2_refresh_token ( + id int8 NOT NULL, + user_id int8 NOT NULL, + refresh_token varchar(32) NOT NULL, + user_type int2 NOT NULL, + client_id varchar(255) NOT NULL, + scopes varchar(255) NULL DEFAULT NULL, + expires_time timestamp NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_oauth2_refresh_token ADD CONSTRAINT pk_system_oauth2_refresh_token PRIMARY KEY (id); + +CREATE INDEX idx_system_oauth2_refresh_token_01 ON system_oauth2_refresh_token (refresh_token); + +COMMENT ON COLUMN system_oauth2_refresh_token.id IS '编号'; +COMMENT ON COLUMN system_oauth2_refresh_token.user_id IS '用户编号'; +COMMENT ON COLUMN system_oauth2_refresh_token.refresh_token IS '刷新令牌'; +COMMENT ON COLUMN system_oauth2_refresh_token.user_type IS '用户类型'; +COMMENT ON COLUMN system_oauth2_refresh_token.client_id IS '客户端编号'; +COMMENT ON COLUMN system_oauth2_refresh_token.scopes IS '授权范围'; +COMMENT ON COLUMN system_oauth2_refresh_token.expires_time IS '过期时间'; +COMMENT ON COLUMN system_oauth2_refresh_token.creator IS '创建者'; +COMMENT ON COLUMN system_oauth2_refresh_token.create_time IS '创建时间'; +COMMENT ON COLUMN system_oauth2_refresh_token.updater IS '更新者'; +COMMENT ON COLUMN system_oauth2_refresh_token.update_time IS '更新时间'; +COMMENT ON COLUMN system_oauth2_refresh_token.deleted IS '是否删除'; +COMMENT ON COLUMN system_oauth2_refresh_token.tenant_id IS '租户编号'; +COMMENT ON TABLE system_oauth2_refresh_token IS 'OAuth2 刷新令牌'; + +DROP SEQUENCE IF EXISTS system_oauth2_refresh_token_seq; +CREATE SEQUENCE system_oauth2_refresh_token_seq + START 1; + +-- ---------------------------- +-- Table structure for system_operate_log +-- ---------------------------- +DROP TABLE IF EXISTS system_operate_log; +CREATE TABLE system_operate_log ( + id int8 NOT NULL, + trace_id varchar(64) NOT NULL DEFAULT '', + user_id int8 NOT NULL, + user_type int2 NOT NULL DEFAULT 0, + type varchar(50) NOT NULL, + sub_type varchar(50) NOT NULL, + biz_id int8 NOT NULL, + action varchar(2000) NOT NULL DEFAULT '', + success bool NOT NULL DEFAULT '1', + extra varchar(2000) NOT NULL DEFAULT '', + request_method varchar(16) NULL DEFAULT '', + request_url varchar(255) NULL DEFAULT '', + user_ip varchar(50) NULL DEFAULT NULL, + user_agent varchar(512) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_operate_log ADD CONSTRAINT pk_system_operate_log PRIMARY KEY (id); + +CREATE INDEX idx_system_operate_log_01 ON system_operate_log (user_id); +CREATE INDEX idx_system_operate_log_02 ON system_operate_log (create_time); + +COMMENT ON COLUMN system_operate_log.id IS '日志主键'; +COMMENT ON COLUMN system_operate_log.trace_id IS '链路追踪编号'; +COMMENT ON COLUMN system_operate_log.user_id IS '用户编号'; +COMMENT ON COLUMN system_operate_log.user_type IS '用户类型'; +COMMENT ON COLUMN system_operate_log.type IS '操作模块类型'; +COMMENT ON COLUMN system_operate_log.sub_type IS '操作名'; +COMMENT ON COLUMN system_operate_log.biz_id IS '操作数据模块编号'; +COMMENT ON COLUMN system_operate_log.action IS '操作内容'; +COMMENT ON COLUMN system_operate_log.success IS '操作结果'; +COMMENT ON COLUMN system_operate_log.extra IS '拓展字段'; +COMMENT ON COLUMN system_operate_log.request_method IS '请求方法名'; +COMMENT ON COLUMN system_operate_log.request_url IS '请求地址'; +COMMENT ON COLUMN system_operate_log.user_ip IS '用户 IP'; +COMMENT ON COLUMN system_operate_log.user_agent IS '浏览器 UA'; +COMMENT ON COLUMN system_operate_log.creator IS '创建者'; +COMMENT ON COLUMN system_operate_log.create_time IS '创建时间'; +COMMENT ON COLUMN system_operate_log.updater IS '更新者'; +COMMENT ON COLUMN system_operate_log.update_time IS '更新时间'; +COMMENT ON COLUMN system_operate_log.deleted IS '是否删除'; +COMMENT ON COLUMN system_operate_log.tenant_id IS '租户编号'; +COMMENT ON TABLE system_operate_log IS '操作日志记录 V2 版本'; + +DROP SEQUENCE IF EXISTS system_operate_log_seq; +CREATE SEQUENCE system_operate_log_seq + START 1; + +-- ---------------------------- +-- Table structure for system_post +-- ---------------------------- +DROP TABLE IF EXISTS system_post; +CREATE TABLE system_post ( + id int8 NOT NULL, + code varchar(64) NOT NULL, + name varchar(50) NOT NULL, + sort int4 NOT NULL, + status int2 NOT NULL, + remark varchar(500) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_post ADD CONSTRAINT pk_system_post PRIMARY KEY (id); + +COMMENT ON COLUMN system_post.id IS '岗位ID'; +COMMENT ON COLUMN system_post.code IS '岗位编码'; +COMMENT ON COLUMN system_post.name IS '岗位名称'; +COMMENT ON COLUMN system_post.sort IS '显示顺序'; +COMMENT ON COLUMN system_post.status IS '状态(0正常 1停用)'; +COMMENT ON COLUMN system_post.remark IS '备注'; +COMMENT ON COLUMN system_post.creator IS '创建者'; +COMMENT ON COLUMN system_post.create_time IS '创建时间'; +COMMENT ON COLUMN system_post.updater IS '更新者'; +COMMENT ON COLUMN system_post.update_time IS '更新时间'; +COMMENT ON COLUMN system_post.deleted IS '是否删除'; +COMMENT ON COLUMN system_post.tenant_id IS '租户编号'; +COMMENT ON TABLE system_post IS '岗位信息表'; + +-- ---------------------------- +-- Records of system_post +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_post (id, code, name, sort, status, remark, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2, 'se', '项目经理', 2, 0, '', 'admin', '2021-01-05 17:03:48', '1', '2025-12-15 22:38:43', '0', 1); +INSERT INTO system_post (id, code, name, sort, status, remark, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4, 'user', '普通员工', 4, 0, '111222', 'admin', '2021-01-05 17:03:48', '1', '2025-03-24 21:32:40', '0', 1); +INSERT INTO system_post (id, code, name, sort, status, remark, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5, 'HR', '人力资源', 5, 0, '`', '1', '2024-03-24 20:45:40', '1', '2025-03-29 19:08:10', '0', 1); +INSERT INTO system_post (id, code, name, sort, status, remark, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (7, 'test', '测试', 10, 0, NULL, '1', '2025-09-02 08:45:57', '1', '2025-09-02 08:45:57', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_post_seq; +CREATE SEQUENCE system_post_seq + START 8; + +-- ---------------------------- +-- Table structure for system_role +-- ---------------------------- +DROP TABLE IF EXISTS system_role; +CREATE TABLE system_role ( + id int8 NOT NULL, + name varchar(30) NOT NULL, + code varchar(100) NOT NULL, + sort int4 NOT NULL, + data_scope int2 NOT NULL DEFAULT 1, + data_scope_dept_ids varchar(500) NOT NULL DEFAULT '', + status int2 NOT NULL, + type int2 NOT NULL, + remark varchar(500) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_role ADD CONSTRAINT pk_system_role PRIMARY KEY (id); + +COMMENT ON COLUMN system_role.id IS '角色ID'; +COMMENT ON COLUMN system_role.name IS '角色名称'; +COMMENT ON COLUMN system_role.code IS '角色权限字符串'; +COMMENT ON COLUMN system_role.sort IS '显示顺序'; +COMMENT ON COLUMN system_role.data_scope IS '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)'; +COMMENT ON COLUMN system_role.data_scope_dept_ids IS '数据范围(指定部门数组)'; +COMMENT ON COLUMN system_role.status IS '角色状态(0正常 1停用)'; +COMMENT ON COLUMN system_role.type IS '角色类型'; +COMMENT ON COLUMN system_role.remark IS '备注'; +COMMENT ON COLUMN system_role.creator IS '创建者'; +COMMENT ON COLUMN system_role.create_time IS '创建时间'; +COMMENT ON COLUMN system_role.updater IS '更新者'; +COMMENT ON COLUMN system_role.update_time IS '更新时间'; +COMMENT ON COLUMN system_role.deleted IS '是否删除'; +COMMENT ON COLUMN system_role.tenant_id IS '租户编号'; +COMMENT ON TABLE system_role IS '角色信息表'; + +-- ---------------------------- +-- Records of system_role +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_role (id, name, code, sort, data_scope, data_scope_dept_ids, status, type, remark, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, '超级管理员', 'super_admin', 1, 1, '', 0, 1, '超级管理员', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:21', '0', 1); +INSERT INTO system_role (id, name, code, sort, data_scope, data_scope_dept_ids, status, type, remark, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2, '普通角色', 'common', 2, 2, '', 0, 1, '普通角色', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:20', '0', 1); +INSERT INTO system_role (id, name, code, sort, data_scope, data_scope_dept_ids, status, type, remark, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3, 'CRM 管理员', 'crm_admin', 2, 1, '', 0, 1, 'CRM 专属角色', '1', '2024-02-24 10:51:13', '1', '2024-02-24 02:51:32', '0', 1); +INSERT INTO system_role (id, name, code, sort, data_scope, data_scope_dept_ids, status, type, remark, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (109, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', '0', 121); +INSERT INTO system_role (id, name, code, sort, data_scope, data_scope_dept_ids, status, type, remark, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (111, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', '0', 122); +INSERT INTO system_role (id, name, code, sort, data_scope, data_scope_dept_ids, status, type, remark, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (155, '测试数据权限1', 'test-dp', 4, 2, '[112,100,102,103,104,105,107,108]', 0, 2, '1111', '1', '2025-03-31 14:58:06', '1', '2025-12-04 23:29:40', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_role_seq; +CREATE SEQUENCE system_role_seq + START 156; + +-- ---------------------------- +-- Table structure for system_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS system_role_menu; +CREATE TABLE system_role_menu ( + id int8 NOT NULL, + role_id int8 NOT NULL, + menu_id int8 NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_role_menu ADD CONSTRAINT pk_system_role_menu PRIMARY KEY (id); + +CREATE INDEX idx_system_role_menu_01 ON system_role_menu (role_id); + +COMMENT ON COLUMN system_role_menu.id IS '自增编号'; +COMMENT ON COLUMN system_role_menu.role_id IS '角色ID'; +COMMENT ON COLUMN system_role_menu.menu_id IS '菜单ID'; +COMMENT ON COLUMN system_role_menu.creator IS '创建者'; +COMMENT ON COLUMN system_role_menu.create_time IS '创建时间'; +COMMENT ON COLUMN system_role_menu.updater IS '更新者'; +COMMENT ON COLUMN system_role_menu.update_time IS '更新时间'; +COMMENT ON COLUMN system_role_menu.deleted IS '是否删除'; +COMMENT ON COLUMN system_role_menu.tenant_id IS '租户编号'; +COMMENT ON TABLE system_role_menu IS '角色和菜单关联表'; + +-- ---------------------------- +-- Records of system_role_menu +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (263, 109, 1, '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (434, 2, 1, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (454, 2, 1093, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (455, 2, 1094, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (460, 2, 1100, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (467, 2, 1107, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (476, 2, 1117, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (477, 2, 100, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (478, 2, 101, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (479, 2, 102, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (480, 2, 1126, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (481, 2, 103, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (483, 2, 104, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (485, 2, 105, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (488, 2, 107, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (490, 2, 108, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (492, 2, 109, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (498, 2, 1138, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (523, 2, 1224, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (524, 2, 1225, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (541, 2, 500, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (543, 2, 501, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (675, 2, 2, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (689, 2, 1077, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (690, 2, 1078, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (692, 2, 1083, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (693, 2, 1084, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (699, 2, 1090, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (703, 2, 106, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (704, 2, 110, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (705, 2, 111, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (706, 2, 112, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (707, 2, 113, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1296, 110, 1, '110', '2022-02-23 00:23:55', '110', '2022-02-23 00:23:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1578, 111, 1, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1729, 109, 100, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1730, 109, 101, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1731, 109, 1063, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1732, 109, 1064, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1733, 109, 1001, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1734, 109, 1065, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1735, 109, 1002, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1736, 109, 1003, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1737, 109, 1004, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1738, 109, 1005, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1739, 109, 1006, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1740, 109, 1007, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1741, 109, 1008, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1742, 109, 1009, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1743, 109, 1010, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1744, 109, 1011, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1745, 109, 1012, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1746, 111, 100, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1747, 111, 101, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1748, 111, 1063, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1749, 111, 1064, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1750, 111, 1001, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1751, 111, 1065, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1752, 111, 1002, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1753, 111, 1003, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1754, 111, 1004, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1755, 111, 1005, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1756, 111, 1006, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1757, 111, 1007, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1758, 111, 1008, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1759, 111, 1009, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1760, 111, 1010, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1761, 111, 1011, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1762, 111, 1012, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1763, 109, 100, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1764, 109, 101, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1765, 109, 1063, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1766, 109, 1064, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1767, 109, 1001, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1768, 109, 1065, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1769, 109, 1002, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1770, 109, 1003, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1771, 109, 1004, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1772, 109, 1005, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1773, 109, 1006, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1774, 109, 1007, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1775, 109, 1008, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1776, 109, 1009, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1777, 109, 1010, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1778, 109, 1011, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1779, 109, 1012, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1780, 111, 100, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1781, 111, 101, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1782, 111, 1063, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1783, 111, 1064, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1784, 111, 1001, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1785, 111, 1065, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1786, 111, 1002, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1787, 111, 1003, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1788, 111, 1004, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1789, 111, 1005, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1790, 111, 1006, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1791, 111, 1007, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1792, 111, 1008, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1793, 111, 1009, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1794, 111, 1010, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1795, 111, 1011, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1796, 111, 1012, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1797, 109, 100, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1798, 109, 101, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1799, 109, 1063, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1800, 109, 1064, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1801, 109, 1001, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1802, 109, 1065, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1803, 109, 1002, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1804, 109, 1003, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1805, 109, 1004, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1806, 109, 1005, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1807, 109, 1006, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1808, 109, 1007, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1809, 109, 1008, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1810, 109, 1009, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1811, 109, 1010, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1812, 109, 1011, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1813, 109, 1012, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1814, 111, 100, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1815, 111, 101, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1816, 111, 1063, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1817, 111, 1064, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1818, 111, 1001, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1819, 111, 1065, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1820, 111, 1002, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1821, 111, 1003, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1822, 111, 1004, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1823, 111, 1005, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1824, 111, 1006, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1825, 111, 1007, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1826, 111, 1008, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1827, 111, 1009, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1828, 111, 1010, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1829, 111, 1011, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1830, 111, 1012, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1831, 109, 103, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1832, 109, 1017, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1833, 109, 1018, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1834, 109, 1019, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1835, 109, 1020, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1836, 111, 103, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1837, 111, 1017, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1838, 111, 1018, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1839, 111, 1019, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1840, 111, 1020, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1841, 109, 1036, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1842, 109, 1037, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1843, 109, 1038, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1844, 109, 1039, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1845, 109, 107, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1846, 111, 1036, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1847, 111, 1037, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1848, 111, 1038, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1849, 111, 1039, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1850, 111, 107, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1991, 2, 1024, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1992, 2, 1025, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1993, 2, 1026, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1994, 2, 1027, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1995, 2, 1028, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1996, 2, 1029, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1997, 2, 1030, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1998, 2, 1031, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1999, 2, 1032, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2000, 2, 1033, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2001, 2, 1034, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2002, 2, 1035, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2003, 2, 1036, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2004, 2, 1037, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2005, 2, 1038, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2006, 2, 1039, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2007, 2, 1040, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2008, 2, 1042, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2009, 2, 1043, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2010, 2, 1045, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2011, 2, 1046, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2012, 2, 1048, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2013, 2, 1050, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2014, 2, 1051, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2015, 2, 1052, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2016, 2, 1053, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2017, 2, 1054, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2018, 2, 1056, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2019, 2, 1057, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2020, 2, 1058, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2021, 2, 2083, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2022, 2, 1059, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2023, 2, 1060, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2024, 2, 1063, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2025, 2, 1064, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2026, 2, 1065, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2027, 2, 1066, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2028, 2, 1067, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2029, 2, 1070, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2034, 2, 1075, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2036, 2, 1082, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2037, 2, 1085, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2038, 2, 1086, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2039, 2, 1087, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2040, 2, 1088, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2041, 2, 1089, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2042, 2, 1091, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2043, 2, 1092, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2044, 2, 1095, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2045, 2, 1096, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2046, 2, 1097, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2047, 2, 1098, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2048, 2, 1101, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2049, 2, 1102, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2050, 2, 1103, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2051, 2, 1104, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2052, 2, 1105, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2053, 2, 1106, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2054, 2, 1108, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2055, 2, 1109, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2061, 2, 1127, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2062, 2, 1128, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2063, 2, 1129, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2064, 2, 1130, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2066, 2, 1132, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2067, 2, 1133, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2068, 2, 1134, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2069, 2, 1135, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2070, 2, 1136, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2071, 2, 1137, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2072, 2, 114, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2073, 2, 1139, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2074, 2, 115, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2075, 2, 1140, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2076, 2, 116, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2077, 2, 1141, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2078, 2, 1142, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2079, 2, 1143, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2080, 2, 1150, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2081, 2, 1161, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2082, 2, 1162, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2086, 2, 1166, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2087, 2, 1173, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2088, 2, 1174, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2092, 2, 1178, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2099, 2, 1226, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2100, 2, 1227, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2101, 2, 1228, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2102, 2, 1229, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2103, 2, 1237, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2104, 2, 1238, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2105, 2, 1239, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2106, 2, 1240, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2107, 2, 1241, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2108, 2, 1242, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2109, 2, 1243, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2116, 2, 1254, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2117, 2, 1255, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2118, 2, 1256, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2119, 2, 1257, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2120, 2, 1258, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2121, 2, 1259, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2122, 2, 1260, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2123, 2, 1261, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2124, 2, 1263, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2125, 2, 1264, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2126, 2, 1265, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2127, 2, 1266, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2128, 2, 1267, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2129, 2, 1001, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2130, 2, 1002, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2131, 2, 1003, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2132, 2, 1004, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2133, 2, 1005, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2134, 2, 1006, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2135, 2, 1007, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2136, 2, 1008, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2137, 2, 1009, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2138, 2, 1010, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2139, 2, 1011, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2140, 2, 1012, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2141, 2, 1013, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2143, 2, 1015, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2145, 2, 1017, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2146, 2, 1018, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2147, 2, 1019, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2148, 2, 1020, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2149, 2, 1021, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2150, 2, 1022, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2151, 2, 1023, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2152, 2, 1281, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2153, 2, 1282, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2154, 2, 2000, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2155, 2, 2002, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2156, 2, 2003, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2157, 2, 2004, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2158, 2, 2005, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2159, 2, 2006, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2160, 2, 2008, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2161, 2, 2009, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2162, 2, 2010, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2163, 2, 2011, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2164, 2, 2012, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2170, 2, 2019, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2171, 2, 2020, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2172, 2, 2021, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2173, 2, 2022, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2174, 2, 2023, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2175, 2, 2025, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2177, 2, 2027, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2178, 2, 2028, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2179, 2, 2029, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2180, 2, 2014, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2181, 2, 2015, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2182, 2, 2016, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2183, 2, 2017, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2184, 2, 2018, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2929, 109, 1224, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2930, 109, 1225, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2931, 109, 1226, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2932, 109, 1227, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2933, 109, 1228, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2934, 109, 1229, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2935, 109, 1138, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2936, 109, 1139, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2937, 109, 1140, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2938, 109, 1141, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2939, 109, 1142, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2940, 109, 1143, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2941, 111, 1224, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2942, 111, 1225, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2943, 111, 1226, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2944, 111, 1227, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2945, 111, 1228, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2946, 111, 1229, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2947, 111, 1138, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2948, 111, 1139, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2949, 111, 1140, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2950, 111, 1141, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2951, 111, 1142, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2952, 111, 1143, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2993, 109, 2, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2994, 109, 1031, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2995, 109, 1032, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2996, 109, 1033, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2997, 109, 1034, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2998, 109, 1035, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2999, 109, 1050, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3000, 109, 1051, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3001, 109, 1052, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3002, 109, 1053, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3003, 109, 1054, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3004, 109, 1056, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3005, 109, 1057, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3006, 109, 1058, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3007, 109, 1059, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3008, 109, 1060, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3009, 109, 1066, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3010, 109, 1067, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3011, 109, 1070, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3012, 109, 1075, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3014, 109, 1077, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3015, 109, 1078, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3016, 109, 1082, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3017, 109, 1083, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3018, 109, 1084, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3019, 109, 1085, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3020, 109, 1086, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3021, 109, 1087, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3022, 109, 1088, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3023, 109, 1089, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3024, 109, 1090, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3025, 109, 1091, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3026, 109, 1092, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3027, 109, 106, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3028, 109, 110, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3029, 109, 111, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3030, 109, 112, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3031, 109, 113, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3032, 109, 114, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3033, 109, 115, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3034, 109, 116, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3035, 109, 2472, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3036, 109, 2478, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3037, 109, 2479, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3038, 109, 2480, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3039, 109, 2481, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3040, 109, 2482, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3041, 109, 2483, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3042, 109, 2484, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3043, 109, 2485, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3044, 109, 2486, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3045, 109, 2487, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3046, 109, 2488, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3047, 109, 2489, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3048, 109, 2490, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3049, 109, 2491, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3050, 109, 2492, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3051, 109, 2493, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3052, 109, 2494, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3053, 109, 2495, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3054, 109, 2497, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3055, 109, 1237, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3056, 109, 1238, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3057, 109, 1239, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3058, 109, 1240, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3059, 109, 1241, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3060, 109, 1242, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3061, 109, 1243, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3062, 109, 2525, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3063, 109, 1255, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3064, 109, 1256, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3065, 109, 1257, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3066, 109, 1258, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3067, 109, 1259, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3068, 109, 1260, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3069, 111, 2, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3070, 111, 1031, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3071, 111, 1032, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3072, 111, 1033, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3073, 111, 1034, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3074, 111, 1035, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3075, 111, 1050, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3076, 111, 1051, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3077, 111, 1052, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3078, 111, 1053, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3079, 111, 1054, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3080, 111, 1056, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3081, 111, 1057, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3082, 111, 1058, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3083, 111, 1059, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3084, 111, 1060, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3085, 111, 1066, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3086, 111, 1067, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3087, 111, 1070, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3088, 111, 1075, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3090, 111, 1077, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3091, 111, 1078, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3092, 111, 1082, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3093, 111, 1083, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3094, 111, 1084, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3095, 111, 1085, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3096, 111, 1086, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3097, 111, 1087, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3098, 111, 1088, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3099, 111, 1089, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3100, 111, 1090, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3101, 111, 1091, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3102, 111, 1092, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3103, 111, 106, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3104, 111, 110, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3105, 111, 111, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3106, 111, 112, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3107, 111, 113, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3108, 111, 114, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3109, 111, 115, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3110, 111, 116, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3111, 111, 2472, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3112, 111, 2478, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3113, 111, 2479, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3114, 111, 2480, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3115, 111, 2481, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3116, 111, 2482, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3117, 111, 2483, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3118, 111, 2484, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3119, 111, 2485, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3120, 111, 2486, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3121, 111, 2487, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3122, 111, 2488, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3123, 111, 2489, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3124, 111, 2490, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3125, 111, 2491, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3126, 111, 2492, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3127, 111, 2493, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3128, 111, 2494, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3129, 111, 2495, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3130, 111, 2497, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3131, 111, 1237, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3132, 111, 1238, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3133, 111, 1239, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3134, 111, 1240, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3135, 111, 1241, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3136, 111, 1242, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3137, 111, 1243, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3138, 111, 2525, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3139, 111, 1255, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3140, 111, 1256, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3141, 111, 1257, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3142, 111, 1258, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3143, 111, 1259, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3144, 111, 1260, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3221, 109, 102, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3222, 109, 1013, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3223, 109, 1014, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3224, 109, 1015, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3225, 109, 1016, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3226, 111, 102, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3227, 111, 1013, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3228, 111, 1014, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3229, 111, 1015, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3230, 111, 1016, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4163, 109, 5, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4164, 109, 1118, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4165, 109, 1119, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4166, 109, 1120, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4167, 109, 2713, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4168, 109, 2714, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4169, 109, 2715, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4170, 109, 2716, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4171, 109, 2717, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4172, 109, 2718, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4173, 109, 2720, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4174, 109, 1185, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4175, 109, 2721, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4176, 109, 1186, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4177, 109, 2722, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4178, 109, 1187, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4179, 109, 2723, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4180, 109, 1188, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4181, 109, 2724, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4182, 109, 1189, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4183, 109, 2725, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4184, 109, 1190, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4185, 109, 2726, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4186, 109, 1191, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4187, 109, 2727, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4188, 109, 1192, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4189, 109, 2728, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4190, 109, 1193, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4191, 109, 2729, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4192, 109, 1194, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4193, 109, 2730, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4194, 109, 1195, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4195, 109, 2731, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4197, 109, 2732, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4198, 109, 1197, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4199, 109, 2733, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4200, 109, 1198, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4201, 109, 2734, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4202, 109, 1199, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4203, 109, 2735, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4204, 109, 1200, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4205, 109, 1201, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4206, 109, 1202, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4207, 109, 1207, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4208, 109, 1208, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4209, 109, 1209, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4210, 109, 1210, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4211, 109, 1211, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4212, 109, 1212, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4213, 109, 1213, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4214, 109, 1215, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4215, 109, 1216, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4216, 109, 1217, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4217, 109, 1218, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4218, 109, 1219, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4219, 109, 1220, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4220, 109, 1221, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4221, 109, 1222, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4222, 111, 5, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4223, 111, 1118, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4224, 111, 1119, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4225, 111, 1120, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4226, 111, 2713, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4227, 111, 2714, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4228, 111, 2715, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4229, 111, 2716, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4230, 111, 2717, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4231, 111, 2718, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4232, 111, 2720, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4233, 111, 1185, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4234, 111, 2721, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4235, 111, 1186, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4236, 111, 2722, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4237, 111, 1187, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4238, 111, 2723, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4239, 111, 1188, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4240, 111, 2724, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4241, 111, 1189, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4242, 111, 2725, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4243, 111, 1190, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4244, 111, 2726, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4245, 111, 1191, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4246, 111, 2727, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4247, 111, 1192, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4248, 111, 2728, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4249, 111, 1193, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4250, 111, 2729, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4251, 111, 1194, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4252, 111, 2730, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4253, 111, 1195, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4254, 111, 2731, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4256, 111, 2732, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4257, 111, 1197, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4258, 111, 2733, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4259, 111, 1198, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4260, 111, 2734, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4261, 111, 1199, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4262, 111, 2735, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4263, 111, 1200, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4264, 111, 1201, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4265, 111, 1202, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4266, 111, 1207, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4267, 111, 1208, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4268, 111, 1209, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4269, 111, 1210, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4270, 111, 1211, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4271, 111, 1212, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4272, 111, 1213, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4273, 111, 1215, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4274, 111, 1216, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4275, 111, 1217, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4276, 111, 1218, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4277, 111, 1219, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4278, 111, 1220, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4279, 111, 1221, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4280, 111, 1222, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5779, 2, 2739, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5780, 2, 2740, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5781, 2, 2758, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5782, 2, 2759, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5783, 2, 2362, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5784, 2, 2387, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5785, 2, 2030, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5789, 109, 2739, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5790, 109, 2740, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5791, 111, 2739, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5792, 111, 2740, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6053, 155, 4000, '1', '2025-04-01 13:48:26', '1', '2025-04-01 13:48:26', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6097, 155, 4050, '1', '2025-04-01 13:48:26', '1', '2025-04-01 13:48:26', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6104, 155, 4032, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6105, 155, 4033, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6106, 155, 4034, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6107, 155, 4035, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6108, 155, 4036, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6109, 155, 4037, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6110, 155, 4038, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6111, 155, 4039, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6112, 155, 4040, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6113, 155, 4041, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6114, 155, 4042, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6115, 155, 4043, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6116, 155, 4044, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6117, 155, 4045, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6119, 155, 4001, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6120, 155, 4002, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6121, 155, 4003, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6122, 155, 4004, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6123, 155, 4005, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6124, 155, 4006, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6125, 155, 4007, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6126, 155, 4008, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6127, 155, 4009, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6128, 155, 4010, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6129, 155, 4011, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6130, 155, 4012, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6131, 155, 4013, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6132, 155, 4014, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6133, 155, 4015, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6134, 155, 4016, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6135, 155, 4017, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6136, 155, 4018, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6137, 155, 4031, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6139, 109, 1117, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6140, 109, 1126, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6141, 109, 1127, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6142, 109, 1128, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6143, 109, 1129, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6144, 109, 1130, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6145, 109, 1132, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6146, 109, 1133, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6147, 109, 1134, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6148, 109, 1135, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6149, 109, 1136, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6150, 109, 1137, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6151, 109, 2161, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6152, 109, 1150, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6153, 109, 1161, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6154, 109, 1162, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6155, 109, 1166, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6156, 109, 1173, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6157, 109, 1174, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6158, 109, 1178, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6159, 109, 2745, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6160, 109, 2746, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6161, 109, 2747, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6162, 109, 2748, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6163, 109, 2301, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6164, 109, 2302, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6165, 109, 5011, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6166, 109, 5012, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6167, 109, 2549, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6168, 109, 2550, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6169, 109, 2551, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6170, 109, 2552, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6171, 109, 2553, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6172, 109, 2554, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6173, 109, 2555, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6174, 109, 2556, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6175, 109, 2557, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6176, 109, 2558, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6177, 109, 2559, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6178, 111, 1117, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6179, 111, 1126, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6180, 111, 1127, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6181, 111, 1128, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6182, 111, 1129, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6183, 111, 1130, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6184, 111, 1132, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6185, 111, 1133, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6186, 111, 1134, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6187, 111, 1135, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6188, 111, 1136, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6189, 111, 1137, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6190, 111, 2161, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6191, 111, 1150, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6192, 111, 1161, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6193, 111, 1162, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6194, 111, 1166, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6195, 111, 1173, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6196, 111, 1174, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6197, 111, 1178, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6198, 111, 2745, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6199, 111, 2746, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6200, 111, 2747, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6201, 111, 2748, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6202, 111, 2301, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6203, 111, 2302, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6204, 111, 5011, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6205, 111, 5012, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6206, 111, 2549, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6207, 111, 2550, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6208, 111, 2551, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6209, 111, 2552, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6210, 111, 2553, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6211, 111, 2554, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6212, 111, 2555, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6213, 111, 2556, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6214, 111, 2557, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6215, 111, 2558, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6216, 111, 2559, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6217, 109, 2756, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6218, 109, 2757, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6219, 109, 2262, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6220, 109, 2275, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6221, 109, 2276, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6222, 109, 2277, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6223, 109, 2281, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6224, 109, 2282, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6225, 109, 2283, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6226, 109, 2284, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6227, 109, 2285, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6228, 109, 2287, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6229, 109, 2288, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6230, 109, 2293, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6231, 109, 2294, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6232, 109, 2297, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6233, 109, 2300, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6234, 109, 2317, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6235, 109, 2318, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6236, 109, 2319, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6237, 109, 2320, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6238, 109, 2321, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6239, 109, 2322, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6240, 109, 2323, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6241, 109, 2324, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6242, 109, 2325, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6243, 109, 2326, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6244, 109, 2327, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6245, 109, 2328, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6246, 109, 2329, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6247, 109, 2330, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6248, 109, 2331, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6249, 109, 2332, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6250, 109, 2333, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6251, 109, 2334, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6252, 109, 2335, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6253, 109, 2363, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6254, 109, 2364, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6255, 111, 2756, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6256, 111, 2757, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6257, 111, 2262, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6258, 111, 2275, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6259, 111, 2276, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6260, 111, 2277, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6261, 111, 2281, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6262, 111, 2282, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6263, 111, 2283, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6264, 111, 2284, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6265, 111, 2285, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6266, 111, 2287, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6267, 111, 2288, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6268, 111, 2293, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6269, 111, 2294, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6270, 111, 2297, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6271, 111, 2300, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6272, 111, 2317, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6273, 111, 2318, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6274, 111, 2319, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6275, 111, 2320, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6276, 111, 2321, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6277, 111, 2322, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6278, 111, 2323, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6279, 111, 2324, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6280, 111, 2325, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6281, 111, 2326, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6282, 111, 2327, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6283, 111, 2328, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6284, 111, 2329, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6285, 111, 2330, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6286, 111, 2331, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6287, 111, 2332, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6288, 111, 2333, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6289, 111, 2334, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6290, 111, 2335, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6291, 111, 2363, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6292, 111, 2364, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6293, 2, 5, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6294, 2, 1118, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6295, 2, 1119, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6296, 2, 1120, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6297, 2, 2713, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6298, 2, 2714, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6299, 2, 2715, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6300, 2, 2716, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6301, 2, 2717, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6302, 2, 2718, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6303, 2, 2720, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6304, 2, 1185, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6305, 2, 2721, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6306, 2, 1186, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6307, 2, 2722, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6308, 2, 1187, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6309, 2, 2723, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6310, 2, 1188, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6311, 2, 2724, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6312, 2, 1189, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6313, 2, 2725, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6314, 2, 1190, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6315, 2, 2726, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6316, 2, 1191, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6317, 2, 2727, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6318, 2, 1192, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6319, 2, 2728, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6320, 2, 1193, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6321, 2, 2729, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6322, 2, 1194, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6323, 2, 2730, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6324, 2, 1195, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6325, 2, 2731, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6326, 2, 2732, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6327, 2, 1197, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6328, 2, 2733, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6329, 2, 1198, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6330, 2, 2734, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6331, 2, 1199, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6332, 2, 2735, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6333, 2, 1200, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6334, 2, 1201, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6335, 2, 1202, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6336, 2, 1207, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6337, 2, 1208, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6338, 2, 1209, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6339, 2, 1210, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6340, 2, 1211, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6341, 2, 1212, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6342, 2, 1213, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6343, 2, 1215, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6344, 2, 1216, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6345, 2, 1217, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6346, 2, 1218, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6347, 2, 1219, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6348, 2, 1220, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6349, 2, 1221, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6350, 2, 1222, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6351, 2, 2913, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6352, 1, 5720, '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6353, 1, 5721, '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6354, 1, 5722, '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6355, 1, 5723, '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6356, 1, 5724, '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6357, 1, 5725, '1', '2026-02-19 04:24:53', '1', '2026-02-19 04:24:53', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6358, 1, 5811, '', '2026-03-30 03:11:59', '', '2026-03-30 03:11:59', '0', 0); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6359, 1, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6360, 2, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6361, 3, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6362, 109, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', '0', 121); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6363, 111, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', '0', 122); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6364, 155, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6365, 2, 6400, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6366, 2, 6401, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6367, 2, 6402, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6368, 2, 6403, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6369, 2, 6404, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6370, 2, 6405, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6371, 2, 6406, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', '0', 1); +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6380, 2, 6490, '1', '2026-05-14 09:36:12', '1', '2026-05-14 09:36:12', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_role_menu_seq; +CREATE SEQUENCE system_role_menu_seq + START 6381; + +-- ---------------------------- +-- Table structure for system_sms_channel +-- ---------------------------- +DROP TABLE IF EXISTS system_sms_channel; +CREATE TABLE system_sms_channel ( + id int8 NOT NULL, + signature varchar(12) NOT NULL, + code varchar(63) NOT NULL, + status int2 NOT NULL, + remark varchar(255) NULL DEFAULT NULL, + api_key varchar(128) NOT NULL, + api_secret varchar(128) NULL DEFAULT NULL, + callback_url varchar(255) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_sms_channel ADD CONSTRAINT pk_system_sms_channel PRIMARY KEY (id); + +COMMENT ON COLUMN system_sms_channel.id IS '编号'; +COMMENT ON COLUMN system_sms_channel.signature IS '短信签名'; +COMMENT ON COLUMN system_sms_channel.code IS '渠道编码'; +COMMENT ON COLUMN system_sms_channel.status IS '开启状态'; +COMMENT ON COLUMN system_sms_channel.remark IS '备注'; +COMMENT ON COLUMN system_sms_channel.api_key IS '短信 API 的账号'; +COMMENT ON COLUMN system_sms_channel.api_secret IS '短信 API 的秘钥'; +COMMENT ON COLUMN system_sms_channel.callback_url IS '短信发送回调 URL'; +COMMENT ON COLUMN system_sms_channel.creator IS '创建者'; +COMMENT ON COLUMN system_sms_channel.create_time IS '创建时间'; +COMMENT ON COLUMN system_sms_channel.updater IS '更新者'; +COMMENT ON COLUMN system_sms_channel.update_time IS '更新时间'; +COMMENT ON COLUMN system_sms_channel.deleted IS '是否删除'; +COMMENT ON TABLE system_sms_channel IS '短信渠道'; + +-- ---------------------------- +-- Records of system_sms_channel +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_sms_channel (id, signature, code, status, remark, api_key, api_secret, callback_url, creator, create_time, updater, update_time, deleted) VALUES (2, 'Ballcat', 'ALIYUN', 0, '你要改哦,只有我可以用!!!!', 'LTAI5tCnKso2uG3kJ5gRav88', 'fGJ5SNXL7P1NHNRmJ7DJaMJGPyE55C', NULL, '', '2021-03-31 11:53:10', '1', '2024-08-04 08:53:26', '0'); +INSERT INTO system_sms_channel (id, signature, code, status, remark, api_key, api_secret, callback_url, creator, create_time, updater, update_time, deleted) VALUES (4, '测试渠道', 'DEBUG_DING_TALK', 0, '123', '696b5d8ead48071237e4aa5861ff08dbadb2b4ded1c688a7b7c9afc615579859', 'SEC5c4e5ff888bc8a9923ae47f59e7ccd30af1f14d93c55b4e2c9cb094e35aeed67', NULL, '1', '2021-04-13 00:23:14', '1', '2022-03-27 20:29:49', '0'); +INSERT INTO system_sms_channel (id, signature, code, status, remark, api_key, api_secret, callback_url, creator, create_time, updater, update_time, deleted) VALUES (7, 'mock腾讯云', 'TENCENT', 0, '123', '1 2', '2 3', '', '1', '2024-09-30 08:53:45', '1', '2025-12-20 11:30:18', '0'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_sms_channel_seq; +CREATE SEQUENCE system_sms_channel_seq + START 8; + +-- ---------------------------- +-- Table structure for system_sms_code +-- ---------------------------- +DROP TABLE IF EXISTS system_sms_code; +CREATE TABLE system_sms_code ( + id int8 NOT NULL, + mobile varchar(11) NOT NULL, + code varchar(6) NOT NULL, + create_ip varchar(15) NOT NULL, + scene int2 NOT NULL, + today_index int2 NOT NULL, + used int2 NOT NULL, + used_time timestamp NULL DEFAULT NULL, + used_ip varchar(255) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_sms_code ADD CONSTRAINT pk_system_sms_code PRIMARY KEY (id); + +CREATE INDEX idx_system_sms_code_01 ON system_sms_code (mobile); + +COMMENT ON COLUMN system_sms_code.id IS '编号'; +COMMENT ON COLUMN system_sms_code.mobile IS '手机号'; +COMMENT ON COLUMN system_sms_code.code IS '验证码'; +COMMENT ON COLUMN system_sms_code.create_ip IS '创建 IP'; +COMMENT ON COLUMN system_sms_code.scene IS '发送场景'; +COMMENT ON COLUMN system_sms_code.today_index IS '今日发送的第几条'; +COMMENT ON COLUMN system_sms_code.used IS '是否使用'; +COMMENT ON COLUMN system_sms_code.used_time IS '使用时间'; +COMMENT ON COLUMN system_sms_code.used_ip IS '使用 IP'; +COMMENT ON COLUMN system_sms_code.creator IS '创建者'; +COMMENT ON COLUMN system_sms_code.create_time IS '创建时间'; +COMMENT ON COLUMN system_sms_code.updater IS '更新者'; +COMMENT ON COLUMN system_sms_code.update_time IS '更新时间'; +COMMENT ON COLUMN system_sms_code.deleted IS '是否删除'; +COMMENT ON COLUMN system_sms_code.tenant_id IS '租户编号'; +COMMENT ON TABLE system_sms_code IS '手机验证码'; + +DROP SEQUENCE IF EXISTS system_sms_code_seq; +CREATE SEQUENCE system_sms_code_seq + START 1; + +-- ---------------------------- +-- Table structure for system_sms_log +-- ---------------------------- +DROP TABLE IF EXISTS system_sms_log; +CREATE TABLE system_sms_log ( + id int8 NOT NULL, + channel_id int8 NOT NULL, + channel_code varchar(63) NOT NULL, + template_id int8 NOT NULL, + template_code varchar(63) NOT NULL, + template_type int2 NOT NULL, + template_content varchar(255) NOT NULL, + template_params varchar(255) NOT NULL, + api_template_id varchar(63) NOT NULL, + mobile varchar(11) NOT NULL, + user_id int8 NULL DEFAULT NULL, + user_type int2 NULL DEFAULT NULL, + send_status int2 NOT NULL DEFAULT 0, + send_time timestamp NULL DEFAULT NULL, + api_send_code varchar(63) NULL DEFAULT NULL, + api_send_msg varchar(255) NULL DEFAULT NULL, + api_request_id varchar(255) NULL DEFAULT NULL, + api_serial_no varchar(255) NULL DEFAULT NULL, + receive_status int2 NOT NULL DEFAULT 0, + receive_time timestamp NULL DEFAULT NULL, + api_receive_code varchar(63) NULL DEFAULT NULL, + api_receive_msg varchar(255) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_sms_log ADD CONSTRAINT pk_system_sms_log PRIMARY KEY (id); + +COMMENT ON COLUMN system_sms_log.id IS '编号'; +COMMENT ON COLUMN system_sms_log.channel_id IS '短信渠道编号'; +COMMENT ON COLUMN system_sms_log.channel_code IS '短信渠道编码'; +COMMENT ON COLUMN system_sms_log.template_id IS '模板编号'; +COMMENT ON COLUMN system_sms_log.template_code IS '模板编码'; +COMMENT ON COLUMN system_sms_log.template_type IS '短信类型'; +COMMENT ON COLUMN system_sms_log.template_content IS '短信内容'; +COMMENT ON COLUMN system_sms_log.template_params IS '短信参数'; +COMMENT ON COLUMN system_sms_log.api_template_id IS '短信 API 的模板编号'; +COMMENT ON COLUMN system_sms_log.mobile IS '手机号'; +COMMENT ON COLUMN system_sms_log.user_id IS '用户编号'; +COMMENT ON COLUMN system_sms_log.user_type IS '用户类型'; +COMMENT ON COLUMN system_sms_log.send_status IS '发送状态'; +COMMENT ON COLUMN system_sms_log.send_time IS '发送时间'; +COMMENT ON COLUMN system_sms_log.api_send_code IS '短信 API 发送结果的编码'; +COMMENT ON COLUMN system_sms_log.api_send_msg IS '短信 API 发送失败的提示'; +COMMENT ON COLUMN system_sms_log.api_request_id IS '短信 API 发送返回的唯一请求 ID'; +COMMENT ON COLUMN system_sms_log.api_serial_no IS '短信 API 发送返回的序号'; +COMMENT ON COLUMN system_sms_log.receive_status IS '接收状态'; +COMMENT ON COLUMN system_sms_log.receive_time IS '接收时间'; +COMMENT ON COLUMN system_sms_log.api_receive_code IS 'API 接收结果的编码'; +COMMENT ON COLUMN system_sms_log.api_receive_msg IS 'API 接收结果的说明'; +COMMENT ON COLUMN system_sms_log.creator IS '创建者'; +COMMENT ON COLUMN system_sms_log.create_time IS '创建时间'; +COMMENT ON COLUMN system_sms_log.updater IS '更新者'; +COMMENT ON COLUMN system_sms_log.update_time IS '更新时间'; +COMMENT ON COLUMN system_sms_log.deleted IS '是否删除'; +COMMENT ON TABLE system_sms_log IS '短信日志'; + +DROP SEQUENCE IF EXISTS system_sms_log_seq; +CREATE SEQUENCE system_sms_log_seq + START 1; + +-- ---------------------------- +-- Table structure for system_sms_template +-- ---------------------------- +DROP TABLE IF EXISTS system_sms_template; +CREATE TABLE system_sms_template ( + id int8 NOT NULL, + type int2 NOT NULL, + status int2 NOT NULL, + code varchar(63) NOT NULL, + name varchar(63) NOT NULL, + content varchar(255) NOT NULL, + params varchar(255) NOT NULL, + remark varchar(255) NULL DEFAULT NULL, + api_template_id varchar(63) NOT NULL, + channel_id int8 NOT NULL, + channel_code varchar(63) NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_sms_template ADD CONSTRAINT pk_system_sms_template PRIMARY KEY (id); + +COMMENT ON COLUMN system_sms_template.id IS '编号'; +COMMENT ON COLUMN system_sms_template.type IS '模板类型'; +COMMENT ON COLUMN system_sms_template.status IS '开启状态'; +COMMENT ON COLUMN system_sms_template.code IS '模板编码'; +COMMENT ON COLUMN system_sms_template.name IS '模板名称'; +COMMENT ON COLUMN system_sms_template.content IS '模板内容'; +COMMENT ON COLUMN system_sms_template.params IS '参数数组'; +COMMENT ON COLUMN system_sms_template.remark IS '备注'; +COMMENT ON COLUMN system_sms_template.api_template_id IS '短信 API 的模板编号'; +COMMENT ON COLUMN system_sms_template.channel_id IS '短信渠道编号'; +COMMENT ON COLUMN system_sms_template.channel_code IS '短信渠道编码'; +COMMENT ON COLUMN system_sms_template.creator IS '创建者'; +COMMENT ON COLUMN system_sms_template.create_time IS '创建时间'; +COMMENT ON COLUMN system_sms_template.updater IS '更新者'; +COMMENT ON COLUMN system_sms_template.update_time IS '更新时间'; +COMMENT ON COLUMN system_sms_template.deleted IS '是否删除'; +COMMENT ON TABLE system_sms_template IS '短信模板'; + +-- ---------------------------- +-- Records of system_sms_template +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (2, 1, 0, 'test_01', '测试验证码短信', '正在进行登录操作{operation},您的验证码是{code}', '["operation","code"]', '测试备注', '4383920', 4, 'DEBUG_DING_TALK', '', '2021-03-31 10:49:38', '1', '2024-08-18 11:57:18', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (3, 1, 0, 'test_02', '公告通知', '您的验证码{code},该验证码5分钟内有效,请勿泄漏于他人!', '["code"]', NULL, 'SMS_207945135', 2, 'ALIYUN', '', '2021-03-31 11:56:30', '1', '2021-04-10 01:22:02', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (6, 3, 0, 'test-01', '测试模板', '哈哈哈 {name}', '["name"]', 'f哈哈哈', '4383920', 4, 'DEBUG_DING_TALK', '1', '2021-04-10 01:07:21', '1', '2024-08-18 11:57:07', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (7, 3, 0, 'test-04', '测试下', '老鸡{name},牛逼{code}', '["name","code"]', '哈哈哈哈', 'suibian', 7, 'DEBUG_DING_TALK', '1', '2021-04-13 00:29:53', '1', '2024-09-30 00:56:24', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (8, 1, 0, 'user-sms-login', '前台用户短信登录', '您的验证码是{code}', '["code"]', NULL, '4372216', 4, 'DEBUG_DING_TALK', '1', '2021-10-11 08:10:00', '1', '2024-08-18 11:57:06', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (9, 2, 0, 'bpm_task_assigned', '【工作流】任务被分配', '您收到了一条新的待办任务:{processInstanceName}-{taskName},申请人:{startUserNickname},处理链接:{detailUrl}', '["processInstanceName","taskName","startUserNickname","detailUrl"]', NULL, 'suibian', 4, 'DEBUG_DING_TALK', '1', '2022-01-21 22:31:19', '1', '2022-01-22 00:03:36', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (10, 2, 0, 'bpm_process_instance_reject', '【工作流】流程被不通过', '您的流程被审批不通过:{processInstanceName},原因:{reason},查看链接:{detailUrl}', '["processInstanceName","reason","detailUrl"]', NULL, 'suibian', 4, 'DEBUG_DING_TALK', '1', '2022-01-22 00:03:31', '1', '2022-05-01 12:33:14', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (11, 2, 0, 'bpm_process_instance_approve', '【工作流】流程被通过', '您的流程被审批通过:{processInstanceName},查看链接:{detailUrl}', '["processInstanceName","detailUrl"]', NULL, 'suibian', 4, 'DEBUG_DING_TALK', '1', '2022-01-22 00:04:31', '1', '2022-03-27 20:32:21', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (12, 2, 0, 'demo', '演示模板', '我就是测试一下下', '[]', NULL, 'biubiubiu', 4, 'DEBUG_DING_TALK', '1', '2022-04-10 23:22:49', '1', '2024-08-18 11:57:04', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (14, 1, 0, 'user-update-mobile', '会员用户 - 修改手机', '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', '["code"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', '2023-08-19 18:58:01', '1', '2023-08-19 11:34:04', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (15, 1, 0, 'user-update-password', '会员用户 - 修改密码', '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', '["code"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', '2023-08-19 18:58:01', '1', '2023-08-19 11:34:18', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (16, 1, 0, 'user-reset-password', '会员用户 - 重置密码', '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', '["code"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', '2023-08-19 18:58:01', '1', '2023-12-02 22:35:27', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (17, 2, 0, 'bpm_task_timeout', '【工作流】任务审批超时', '您收到了一条超时的待办任务:{processInstanceName}-{taskName},处理链接:{detailUrl}', '["processInstanceName","taskName","detailUrl"]', '', 'X', 4, 'DEBUG_DING_TALK', '1', '2024-08-16 21:59:15', '1', '2024-08-16 21:59:34', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (18, 1, 0, 'admin-reset-password', '后台用户 - 忘记密码', '您的验证码{code},该验证码 5 分钟内有效,请勿泄漏于他人!', '["code"]', '', 'null', 4, 'DEBUG_DING_TALK', '1', '2025-03-16 14:19:34', '1', '2025-03-16 14:19:45', '0'); +INSERT INTO system_sms_template (id, type, status, code, name, content, params, remark, api_template_id, channel_id, channel_code, creator, create_time, updater, update_time, deleted) VALUES (19, 1, 0, 'admin-sms-login', '后台用户短信登录', '您的验证码是{code}', '["code"]', '', '4372216', 4, 'DEBUG_DING_TALK', '1', '2025-04-08 09:36:03', '1', '2025-04-08 09:36:17', '0'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_sms_template_seq; +CREATE SEQUENCE system_sms_template_seq + START 20; + +-- ---------------------------- +-- Table structure for system_social_client +-- ---------------------------- +DROP TABLE IF EXISTS system_social_client; +CREATE TABLE system_social_client ( + id int8 NOT NULL, + name varchar(255) NOT NULL, + social_type int2 NOT NULL, + user_type int2 NOT NULL, + client_id varchar(255) NOT NULL, + client_secret varchar(255) NOT NULL, + agent_id varchar(255) NULL DEFAULT NULL, + public_key varchar(2048) NULL DEFAULT NULL, + status int2 NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_social_client ADD CONSTRAINT pk_system_social_client PRIMARY KEY (id); + +COMMENT ON COLUMN system_social_client.id IS '编号'; +COMMENT ON COLUMN system_social_client.name IS '应用名'; +COMMENT ON COLUMN system_social_client.social_type IS '社交平台的类型'; +COMMENT ON COLUMN system_social_client.user_type IS '用户类型'; +COMMENT ON COLUMN system_social_client.client_id IS '客户端编号'; +COMMENT ON COLUMN system_social_client.client_secret IS '客户端密钥'; +COMMENT ON COLUMN system_social_client.agent_id IS '代理编号'; +COMMENT ON COLUMN system_social_client.public_key IS 'publicKey 公钥'; +COMMENT ON COLUMN system_social_client.status IS '状态'; +COMMENT ON COLUMN system_social_client.creator IS '创建者'; +COMMENT ON COLUMN system_social_client.create_time IS '创建时间'; +COMMENT ON COLUMN system_social_client.updater IS '更新者'; +COMMENT ON COLUMN system_social_client.update_time IS '更新时间'; +COMMENT ON COLUMN system_social_client.deleted IS '是否删除'; +COMMENT ON COLUMN system_social_client.tenant_id IS '租户编号'; +COMMENT ON TABLE system_social_client IS '社交客户端表'; + +-- ---------------------------- +-- Records of system_social_client +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_social_client (id, name, social_type, user_type, client_id, client_secret, agent_id, public_key, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, '钉钉', 20, 2, 'dingvrnreaje3yqvzhxg', 'i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI', NULL, NULL, 0, '', '2023-10-18 11:21:18', '1', '2023-12-20 21:28:26', '1', 1); +INSERT INTO system_social_client (id, name, social_type, user_type, client_id, client_secret, agent_id, public_key, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2, '钉钉(王土豆)', 20, 2, 'dingtsu9hpepjkbmthhw', 'FP_bnSq_HAHKCSncmJjw5hxhnzs6vaVDSZZn3egj6rdqTQ_hu5tQVJyLMpgCakdP', NULL, NULL, 0, '', '2023-10-18 11:21:18', '', '2023-12-20 21:28:26', '1', 121); +INSERT INTO system_social_client (id, name, social_type, user_type, client_id, client_secret, agent_id, public_key, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3, '微信公众号', 31, 1, 'wx5b23ba7a5589ecbb', '2a7b3b20c537e52e74afd395eb85f61f', NULL, NULL, 0, '', '2023-10-18 16:07:46', '1', '2023-12-20 21:28:23', '1', 1); +INSERT INTO system_social_client (id, name, social_type, user_type, client_id, client_secret, agent_id, public_key, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (43, '微信小程序', 34, 1, 'wx63c280fe3248a3e7', '6f270509224a7ae1296bbf1c8cb97aed', NULL, NULL, 0, '', '2023-10-19 13:37:41', '1', '2023-12-20 21:28:25', '1', 1); +INSERT INTO system_social_client (id, name, social_type, user_type, client_id, client_secret, agent_id, public_key, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (44, '1', 10, 1, '2', '3', NULL, NULL, 0, '1', '2025-04-06 20:36:28', '1', '2025-04-06 20:43:12', '1', 1); +INSERT INTO system_social_client (id, name, social_type, user_type, client_id, client_secret, agent_id, public_key, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (45, '1', 10, 1, '2', '3', NULL, NULL, 1, '1', '2025-09-06 20:26:15', '1', '2025-09-06 20:27:55', '1', 1); +INSERT INTO system_social_client (id, name, social_type, user_type, client_id, client_secret, agent_id, public_key, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (46, '1', 10, 1, '2', '3', NULL, NULL, 0, '1', '2025-11-29 16:04:23', '1', '2025-11-29 16:04:26', '1', 1); +INSERT INTO system_social_client (id, name, social_type, user_type, client_id, client_secret, agent_id, public_key, status, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (47, '123', 10, 1, '1', '2', '3', NULL, 0, '1', '2025-12-21 10:27:02', '1', '2025-12-21 10:27:20', '1', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_social_client_seq; +CREATE SEQUENCE system_social_client_seq + START 48; + +-- ---------------------------- +-- Table structure for system_social_user +-- ---------------------------- +DROP TABLE IF EXISTS system_social_user; +CREATE TABLE system_social_user ( + id int8 NOT NULL, + type int2 NOT NULL, + openid varchar(32) NOT NULL, + token varchar(256) NULL DEFAULT NULL, + raw_token_info varchar(1024) NOT NULL, + nickname varchar(32) NOT NULL, + avatar varchar(255) NULL DEFAULT NULL, + raw_user_info varchar(1024) NOT NULL, + code varchar(256) NOT NULL, + state varchar(256) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_social_user ADD CONSTRAINT pk_system_social_user PRIMARY KEY (id); + +CREATE INDEX idx_system_social_user_01 ON system_social_user (type, openid); +CREATE INDEX idx_system_social_user_02 ON system_social_user (type, code, state); + +COMMENT ON COLUMN system_social_user.id IS '主键(自增策略)'; +COMMENT ON COLUMN system_social_user.type IS '社交平台的类型'; +COMMENT ON COLUMN system_social_user.openid IS '社交 openid'; +COMMENT ON COLUMN system_social_user.token IS '社交 token'; +COMMENT ON COLUMN system_social_user.raw_token_info IS '原始 Token 数据,一般是 JSON 格式'; +COMMENT ON COLUMN system_social_user.nickname IS '用户昵称'; +COMMENT ON COLUMN system_social_user.avatar IS '用户头像'; +COMMENT ON COLUMN system_social_user.raw_user_info IS '原始用户数据,一般是 JSON 格式'; +COMMENT ON COLUMN system_social_user.code IS '最后一次的认证 code'; +COMMENT ON COLUMN system_social_user.state IS '最后一次的认证 state'; +COMMENT ON COLUMN system_social_user.creator IS '创建者'; +COMMENT ON COLUMN system_social_user.create_time IS '创建时间'; +COMMENT ON COLUMN system_social_user.updater IS '更新者'; +COMMENT ON COLUMN system_social_user.update_time IS '更新时间'; +COMMENT ON COLUMN system_social_user.deleted IS '是否删除'; +COMMENT ON COLUMN system_social_user.tenant_id IS '租户编号'; +COMMENT ON TABLE system_social_user IS '社交用户表'; + +DROP SEQUENCE IF EXISTS system_social_user_seq; +CREATE SEQUENCE system_social_user_seq + START 1; + +-- ---------------------------- +-- Table structure for system_social_user_bind +-- ---------------------------- +DROP TABLE IF EXISTS system_social_user_bind; +CREATE TABLE system_social_user_bind ( + id int8 NOT NULL, + user_id int8 NOT NULL, + user_type int2 NOT NULL, + social_type int2 NOT NULL, + social_user_id int8 NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_social_user_bind ADD CONSTRAINT pk_system_social_user_bind PRIMARY KEY (id); + +CREATE INDEX idx_system_social_user_bind_01 ON system_social_user_bind (user_type, social_user_id); + +COMMENT ON COLUMN system_social_user_bind.id IS '主键(自增策略)'; +COMMENT ON COLUMN system_social_user_bind.user_id IS '用户编号'; +COMMENT ON COLUMN system_social_user_bind.user_type IS '用户类型'; +COMMENT ON COLUMN system_social_user_bind.social_type IS '社交平台的类型'; +COMMENT ON COLUMN system_social_user_bind.social_user_id IS '社交用户的编号'; +COMMENT ON COLUMN system_social_user_bind.creator IS '创建者'; +COMMENT ON COLUMN system_social_user_bind.create_time IS '创建时间'; +COMMENT ON COLUMN system_social_user_bind.updater IS '更新者'; +COMMENT ON COLUMN system_social_user_bind.update_time IS '更新时间'; +COMMENT ON COLUMN system_social_user_bind.deleted IS '是否删除'; +COMMENT ON COLUMN system_social_user_bind.tenant_id IS '租户编号'; +COMMENT ON TABLE system_social_user_bind IS '社交绑定表'; + +DROP SEQUENCE IF EXISTS system_social_user_bind_seq; +CREATE SEQUENCE system_social_user_bind_seq + START 1; + +-- ---------------------------- +-- Table structure for system_tenant +-- ---------------------------- +DROP TABLE IF EXISTS system_tenant; +CREATE TABLE system_tenant ( + id int8 NOT NULL, + name varchar(30) NOT NULL, + contact_user_id int8 NULL DEFAULT NULL, + contact_name varchar(30) NOT NULL, + contact_mobile varchar(500) NULL DEFAULT NULL, + status int2 NOT NULL DEFAULT 0, + websites varchar(1024) NULL DEFAULT '', + package_id int8 NOT NULL, + expire_time timestamp NOT NULL, + account_count int4 NOT NULL, + creator varchar(64) NOT NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_tenant ADD CONSTRAINT pk_system_tenant PRIMARY KEY (id); + +COMMENT ON COLUMN system_tenant.id IS '租户编号'; +COMMENT ON COLUMN system_tenant.name IS '租户名'; +COMMENT ON COLUMN system_tenant.contact_user_id IS '联系人的用户编号'; +COMMENT ON COLUMN system_tenant.contact_name IS '联系人'; +COMMENT ON COLUMN system_tenant.contact_mobile IS '联系手机'; +COMMENT ON COLUMN system_tenant.status IS '租户状态'; +COMMENT ON COLUMN system_tenant.websites IS '绑定域名数组'; +COMMENT ON COLUMN system_tenant.package_id IS '租户套餐编号'; +COMMENT ON COLUMN system_tenant.expire_time IS '过期时间'; +COMMENT ON COLUMN system_tenant.account_count IS '账号数量'; +COMMENT ON COLUMN system_tenant.creator IS '创建者'; +COMMENT ON COLUMN system_tenant.create_time IS '创建时间'; +COMMENT ON COLUMN system_tenant.updater IS '更新者'; +COMMENT ON COLUMN system_tenant.update_time IS '更新时间'; +COMMENT ON COLUMN system_tenant.deleted IS '是否删除'; +COMMENT ON TABLE system_tenant IS '租户表'; + +-- ---------------------------- +-- Records of system_tenant +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_tenant (id, name, contact_user_id, contact_name, contact_mobile, status, websites, package_id, expire_time, account_count, creator, create_time, updater, update_time, deleted) VALUES (1, '芋道源码', NULL, '芋艿', '17321315478', 0, 'www.iocoder.cn,127.0.0.1:3000,wxc4598c446f8a9cb3', 0, '2099-02-19 17:14:16', 9999, '1', '2021-01-05 17:03:47', '1', '2025-08-19 05:18:41', '0'); +INSERT INTO system_tenant (id, name, contact_user_id, contact_name, contact_mobile, status, websites, package_id, expire_time, account_count, creator, create_time, updater, update_time, deleted) VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'zsxq.iocoder.cn,123321', 111, '2026-07-10 00:00:00', 30, '1', '2022-02-22 00:56:14', '1', '2025-08-19 21:19:29', '0'); +INSERT INTO system_tenant (id, name, contact_user_id, contact_name, contact_mobile, status, websites, package_id, expire_time, account_count, creator, create_time, updater, update_time, deleted) VALUES (122, '测试租户', 113, '芋道', '15601691300', 0, 'test.iocoder.cn,222,333', 111, '2023-04-29 00:00:00', 50, '1', '2022-03-07 21:37:58', '1', '2025-12-21 09:50:00', '0'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_tenant_seq; +CREATE SEQUENCE system_tenant_seq + START 123; + +-- ---------------------------- +-- Table structure for system_tenant_package +-- ---------------------------- +DROP TABLE IF EXISTS system_tenant_package; +CREATE TABLE system_tenant_package ( + id int8 NOT NULL, + name varchar(30) NOT NULL, + status int2 NOT NULL DEFAULT 0, + remark varchar(256) NULL DEFAULT '', + menu_ids varchar(4096) NOT NULL, + creator varchar(64) NOT NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_tenant_package ADD CONSTRAINT pk_system_tenant_package PRIMARY KEY (id); + +COMMENT ON COLUMN system_tenant_package.id IS '套餐编号'; +COMMENT ON COLUMN system_tenant_package.name IS '套餐名'; +COMMENT ON COLUMN system_tenant_package.status IS '租户状态(0正常 1停用)'; +COMMENT ON COLUMN system_tenant_package.remark IS '备注'; +COMMENT ON COLUMN system_tenant_package.menu_ids IS '关联的菜单编号'; +COMMENT ON COLUMN system_tenant_package.creator IS '创建者'; +COMMENT ON COLUMN system_tenant_package.create_time IS '创建时间'; +COMMENT ON COLUMN system_tenant_package.updater IS '更新者'; +COMMENT ON COLUMN system_tenant_package.update_time IS '更新时间'; +COMMENT ON COLUMN system_tenant_package.deleted IS '是否删除'; +COMMENT ON TABLE system_tenant_package IS '租户套餐表'; + +-- ---------------------------- +-- Records of system_tenant_package +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_tenant_package (id, name, status, remark, menu_ids, creator, create_time, updater, update_time, deleted) VALUES (111, '普通套餐', 0, '小功能', '[1,2,5,1031,1032,1033,1034,1035,1036,1037,1038,1039,1050,1051,1052,1053,1054,1056,1057,1058,1059,1060,1063,1064,1065,1066,1067,1070,1075,1077,1078,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1117,1118,1119,1120,100,101,102,1126,103,1127,1128,1129,106,1130,107,1132,1133,110,1134,111,1135,112,1136,113,1137,2161,114,1138,1139,115,1140,116,1141,1142,1143,1150,1161,1162,1166,1173,1174,2713,2714,1178,2715,2716,2717,2718,2720,2721,1185,2722,1186,1187,2723,1188,2724,1189,2725,1190,2726,1191,2727,1192,2728,2729,1193,1194,2730,1195,2731,2732,1197,2733,1198,2734,1199,2735,1200,1201,1202,2739,2740,1207,1208,1209,2745,1210,2746,1211,2747,1212,2748,1213,1215,1216,1217,1218,1219,1220,2756,1221,2757,1222,1224,1225,1226,1227,1228,1229,1237,1238,2262,1239,1240,1241,1242,1243,2275,2276,2277,1255,1256,1257,2281,1258,2282,1259,2283,1260,2284,2285,2287,2288,2293,2294,2297,2300,2301,2302,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2363,2364,5011,5012,2472,2478,2479,2480,2481,2482,2483,2484,2485,2486,2487,2488,2489,2490,2491,2492,2493,2494,2495,2497,2525,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,2549,1014,2550,1015,2551,1016,2552,1017,2553,1018,2554,1019,2555,1020,2556,2557,2558,2559]', '1', '2022-02-22 00:54:00', '1', '2025-09-06 20:52:25', '0'); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_tenant_package_seq; +CREATE SEQUENCE system_tenant_package_seq + START 112; + +-- ---------------------------- +-- Table structure for system_user_post +-- ---------------------------- +DROP TABLE IF EXISTS system_user_post; +CREATE TABLE system_user_post ( + id int8 NOT NULL, + user_id int8 NOT NULL DEFAULT 0, + post_id int8 NOT NULL DEFAULT 0, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_user_post ADD CONSTRAINT pk_system_user_post PRIMARY KEY (id); + +COMMENT ON COLUMN system_user_post.id IS 'id'; +COMMENT ON COLUMN system_user_post.user_id IS '用户ID'; +COMMENT ON COLUMN system_user_post.post_id IS '岗位ID'; +COMMENT ON COLUMN system_user_post.creator IS '创建者'; +COMMENT ON COLUMN system_user_post.create_time IS '创建时间'; +COMMENT ON COLUMN system_user_post.updater IS '更新者'; +COMMENT ON COLUMN system_user_post.update_time IS '更新时间'; +COMMENT ON COLUMN system_user_post.deleted IS '是否删除'; +COMMENT ON COLUMN system_user_post.tenant_id IS '租户编号'; +COMMENT ON TABLE system_user_post IS '用户岗位表'; + +-- ---------------------------- +-- Records of system_user_post +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (112, 1, 1, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', '0', 1); +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (113, 100, 1, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', '0', 1); +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (115, 104, 1, '1', '2022-05-16 19:36:28', '1', '2022-05-16 19:36:28', '0', 1); +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (116, 117, 2, '1', '2022-07-09 17:40:26', '1', '2022-07-09 17:40:26', '0', 1); +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (117, 118, 1, '1', '2022-07-09 17:44:44', '1', '2022-07-09 17:44:44', '0', 1); +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (119, 114, 5, '1', '2024-03-24 20:45:51', '1', '2024-03-24 20:45:51', '0', 1); +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (123, 115, 1, '1', '2024-04-04 09:37:14', '1', '2024-04-04 09:37:14', '0', 1); +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (124, 115, 2, '1', '2024-04-04 09:37:14', '1', '2024-04-04 09:37:14', '0', 1); +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (125, 1, 2, '1', '2024-07-13 22:31:39', '1', '2024-07-13 22:31:39', '0', 1); +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (128, 139, 2, '1', '2025-12-05 21:43:27', '1', '2025-12-05 21:43:27', '0', 1); +INSERT INTO system_user_post (id, user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (129, 139, 4, '1', '2025-12-05 21:43:27', '1', '2025-12-05 21:43:27', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_user_post_seq; +CREATE SEQUENCE system_user_post_seq + START 130; + +-- ---------------------------- +-- Table structure for system_user_role +-- ---------------------------- +DROP TABLE IF EXISTS system_user_role; +CREATE TABLE system_user_role ( + id int8 NOT NULL, + user_id int8 NOT NULL, + role_id int8 NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_user_role ADD CONSTRAINT pk_system_user_role PRIMARY KEY (id); + +CREATE INDEX idx_system_user_role_01 ON system_user_role (user_id); + +COMMENT ON COLUMN system_user_role.id IS '自增编号'; +COMMENT ON COLUMN system_user_role.user_id IS '用户ID'; +COMMENT ON COLUMN system_user_role.role_id IS '角色ID'; +COMMENT ON COLUMN system_user_role.creator IS '创建者'; +COMMENT ON COLUMN system_user_role.create_time IS '创建时间'; +COMMENT ON COLUMN system_user_role.updater IS '更新者'; +COMMENT ON COLUMN system_user_role.update_time IS '更新时间'; +COMMENT ON COLUMN system_user_role.deleted IS '是否删除'; +COMMENT ON COLUMN system_user_role.tenant_id IS '租户编号'; +COMMENT ON TABLE system_user_role IS '用户和角色关联表'; + +-- ---------------------------- +-- Records of system_user_role +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, 1, 1, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:17', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2, 2, 2, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:13', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5, 100, 1, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:12', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6, 100, 2, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:11', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (10, 103, 1, '1', '2022-01-11 13:19:45', '1', '2022-01-11 13:19:45', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (14, 110, 109, '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', '0', 121); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (15, 111, 110, '110', '2022-02-23 13:14:38', '110', '2022-02-23 13:14:38', '0', 121); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (16, 113, 111, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', '0', 122); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (18, 1, 2, '1', '2022-05-12 20:39:29', '1', '2022-05-12 20:39:29', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (22, 115, 2, '1', '2022-07-21 22:08:30', '1', '2022-07-21 22:08:30', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (35, 112, 1, '1', '2024-03-15 20:00:24', '1', '2024-03-15 20:00:24', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (36, 118, 1, '1', '2024-03-17 09:12:08', '1', '2024-03-17 09:12:08', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (46, 117, 1, '1', '2024-10-02 10:16:11', '1', '2024-10-02 10:16:11', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (47, 104, 2, '1', '2025-01-04 10:40:33', '1', '2025-01-04 10:40:33', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (48, 100, 155, '1', '2025-04-04 10:41:14', '1', '2025-04-04 10:41:14', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (49, 142, 1, '1', '2025-07-23 09:11:42', '1', '2025-07-23 09:11:42', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (50, 142, 2, '1', '2025-10-07 20:50:37', '1', '2025-10-07 20:50:37', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (51, 139, 1, '1', '2025-12-05 22:36:57', '1', '2025-12-05 22:36:57', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (52, 139, 2, '1', '2025-12-05 22:37:00', '1', '2025-12-05 22:37:00', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (53, 114, 2, '1', '2026-01-04 18:15:40', '1', '2026-01-04 18:15:40', '0', 1); +INSERT INTO system_user_role (id, user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (54, 114, 3, '1', '2026-01-04 18:16:19', '1', '2026-01-04 18:16:19', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_user_role_seq; +CREATE SEQUENCE system_user_role_seq + START 55; + +-- ---------------------------- +-- Table structure for system_users +-- ---------------------------- +DROP TABLE IF EXISTS system_users; +CREATE TABLE system_users ( + id int8 NOT NULL, + username varchar(30) NOT NULL, + password varchar(100) NOT NULL DEFAULT '', + nickname varchar(30) NOT NULL, + remark varchar(500) NULL DEFAULT NULL, + dept_id int8 NULL DEFAULT NULL, + post_ids varchar(255) NULL DEFAULT NULL, + email varchar(50) NULL DEFAULT '', + mobile varchar(11) NULL DEFAULT '', + sex int2 NULL DEFAULT 0, + avatar varchar(512) NULL DEFAULT '', + status int2 NOT NULL DEFAULT 0, + login_ip varchar(50) NULL DEFAULT '', + login_date timestamp NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE system_users ADD CONSTRAINT pk_system_users PRIMARY KEY (id); + +CREATE INDEX idx_system_users_01 ON system_users (username); +CREATE INDEX idx_system_users_02 ON system_users (mobile); +CREATE INDEX idx_system_users_03 ON system_users (email); +CREATE INDEX idx_system_users_04 ON system_users (dept_id); + +COMMENT ON COLUMN system_users.id IS '用户ID'; +COMMENT ON COLUMN system_users.username IS '用户账号'; +COMMENT ON COLUMN system_users.password IS '密码'; +COMMENT ON COLUMN system_users.nickname IS '用户昵称'; +COMMENT ON COLUMN system_users.remark IS '备注'; +COMMENT ON COLUMN system_users.dept_id IS '部门ID'; +COMMENT ON COLUMN system_users.post_ids IS '岗位编号数组'; +COMMENT ON COLUMN system_users.email IS '用户邮箱'; +COMMENT ON COLUMN system_users.mobile IS '手机号码'; +COMMENT ON COLUMN system_users.sex IS '用户性别'; +COMMENT ON COLUMN system_users.avatar IS '头像地址'; +COMMENT ON COLUMN system_users.status IS '帐号状态(0正常 1停用)'; +COMMENT ON COLUMN system_users.login_ip IS '最后登录IP'; +COMMENT ON COLUMN system_users.login_date IS '最后登录时间'; +COMMENT ON COLUMN system_users.creator IS '创建者'; +COMMENT ON COLUMN system_users.create_time IS '创建时间'; +COMMENT ON COLUMN system_users.updater IS '更新者'; +COMMENT ON COLUMN system_users.update_time IS '更新时间'; +COMMENT ON COLUMN system_users.deleted IS '是否删除'; +COMMENT ON COLUMN system_users.tenant_id IS '租户编号'; +COMMENT ON TABLE system_users IS '用户信息表'; + +-- ---------------------------- +-- Records of system_users +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, 'admin', '$2a$04$.vd8nPeLwxt6hnSzmAoAyul8BOLX7Cib6QhcxRe30rfvrIPQHH1OG', '芋道源码', '管理员', 103, '[1,2]', '13aoteman@126.com', '18818260272', 1, 'http://test.yudao.iocoder.cn/20260517/blob_1778998103688.png', 0, '0:0:0:0:0:0:0:1', '2026-05-31 21:54:49', 'admin', '2021-01-05 17:03:47', NULL, '2026-05-31 21:54:49', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (100, 'yudao', '$2a$04$.vd8nPeLwxt6hnSzmAoAyul8BOLX7Cib6QhcxRe30rfvrIPQHH1OG', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-04-19 17:40:55', '', '2021-01-07 09:07:17', NULL, '2026-04-19 17:40:55', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (103, 'yuanma', '$2a$04$k/d6mc0nySN0i2udwcI8Ee8V5aM5OHixBRbQfXmPuFTUl3Zf/DBs.', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2026-04-27 13:19:27', '', '2021-01-13 23:50:35', NULL, '2026-04-27 13:19:27', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-05-20 23:37:11', '', '2021-01-21 02:13:53', NULL, '2026-05-20 23:37:11', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2025-04-21 14:23:08', '0', 118); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2025-04-21 14:23:08', '0', 119); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2025-04-21 14:23:08', '0', 120); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (110, 'admin110', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '小王', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-07-20 22:23:17', '1', '2022-02-22 00:56:14', NULL, '2025-04-21 14:23:08', '0', 121); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (111, 'test', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '测试用户', NULL, NULL, '[]', '', '', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2023-12-30 11:42:17', '110', '2022-02-23 13:14:33', NULL, '2025-04-21 14:23:08', '0', 121); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (112, 'newobject', '$2a$04$dB0z8Q819fJWz0hbaLe6B.VfHCjYgWx6LFfET5lyz3JwcqlyCkQ4C', '新对象', NULL, 100, '[]', '', '15601691235', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-03-16 23:11:38', '1', '2022-02-23 19:08:03', NULL, '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道1', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', '1', '2025-05-05 15:30:53', '0', 122); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (114, 'hrmgr', '$2a$10$TR4eybBioGRhBmDBWkqWLO6NIh3mzYa8KBKDDB5woiGYFVlRAi.fu', 'hr 小姐姐', NULL, NULL, '[5]', '', '15601691236', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-01-04 18:16:01', '1', '2022-03-19 21:50:58', NULL, '2026-01-04 18:16:01', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (115, 'aotemane', '$2a$04$GcyP0Vyzb2F2Yni5PuIK9ueGxM0tkZGMtDwVRwrNbtMvorzbpNsV2', '阿呆', '11222', 102, '[1,2]', '7648@qq.com', '15601691229', 2, NULL, 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2025-04-21 14:23:08', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (117, 'admin123', '$2a$04$sEtimsHu9YCkYY4/oqElHem2Ijc9ld20eYO6lN.g/21NfLUTDLB9W', '测试号02', '1111', 100, '[2]', '', '15601691234', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-10-02 10:16:20', '1', '2022-07-09 17:40:26', '1', '2025-05-14 09:56:04', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (118, 'goudan', '$2a$04$3suGZjnA6rM5bErf38u1felbgqbsPHGdRG3l9NkxPCEt2ah9Y6aJi', '狗蛋', NULL, 103, '[1]', '', '15601691239', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-11-23 15:28:25', '1', '2022-07-09 17:44:43', NULL, '2025-11-23 15:28:25', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (139, 'wwbwwb', '$2a$04$FJLIyg8lbPytP29pbZaiU.LesJvCsYfEaHqQfB0pGQhK3e9BeZmLy', '小秃头', '123', 108, '[2,4]', '', '', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-09-10 21:03:58', NULL, '2024-09-10 21:03:58', '1', '2025-12-15 22:38:15', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (141, 'admin1', '$2a$04$oj6F6d7HrZ70kYVD3TNzEu.m3TPUzajOVuC66zdKna8KRerK1FmVa', '新用户', NULL, NULL, NULL, '', '', 0, '', 0, '0:0:0:0:0:0:0:1', '2025-04-08 13:09:07', '1', '2025-04-08 13:09:07', '1', '2025-05-14 19:11:48', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (142, 'test01', '$2a$04$4bCYWZkjxxOC4QE0LY2M9uEEKWeJbLfs489NFtQoyidL5I0FndRaO', 'test01', '', NULL, '[]', '', '19021719925', 1, '', 0, '0:0:0:0:0:0:0:1', '2025-07-29 19:47:17', '1', '2025-07-09 21:07:10', NULL, '2025-12-02 13:23:11', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (143, 'a00001', '$2a$04$GhVHFviOw/SsTmiQtifHJesDYFlHMeGK7OWh7aGCCjGGVCmbHVAwa', 'a00001', NULL, 104, NULL, '', '', 0, '', 0, '0:0:0:0:0:0:0:1', '2025-12-01 16:10:13', NULL, '2025-12-01 16:10:13', '1', '2025-12-05 21:34:05', '0', 1); +INSERT INTO system_users (id, username, password, nickname, remark, dept_id, post_ids, email, mobile, sex, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (144, 'aoteman001', '$2a$04$omQOmhz8OyUFBKw77nr8KOtMp6xdvoQ1gWStjk9r8.OYT3Bv6oEYe', 'aoteman001', NULL, 104, NULL, '', '', 0, '', 1, '0:0:0:0:0:0:0:1', '2025-12-01 17:05:27', '1', '2025-12-01 17:05:27', '1', '2026-05-31 21:52:48', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS system_users_seq; +CREATE SEQUENCE system_users_seq + START 145; + +-- ---------------------------- +-- Table structure for yudao_demo01_contact +-- ---------------------------- +DROP TABLE IF EXISTS yudao_demo01_contact; +CREATE TABLE yudao_demo01_contact ( + id int8 NOT NULL, + name varchar(100) NOT NULL DEFAULT '', + sex int2 NOT NULL, + birthday timestamp NOT NULL, + description varchar(255) NOT NULL, + avatar varchar(512) NULL DEFAULT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE yudao_demo01_contact ADD CONSTRAINT pk_yudao_demo01_contact PRIMARY KEY (id); + +COMMENT ON COLUMN yudao_demo01_contact.id IS '编号'; +COMMENT ON COLUMN yudao_demo01_contact.name IS '名字'; +COMMENT ON COLUMN yudao_demo01_contact.sex IS '性别'; +COMMENT ON COLUMN yudao_demo01_contact.birthday IS '出生年'; +COMMENT ON COLUMN yudao_demo01_contact.description IS '简介'; +COMMENT ON COLUMN yudao_demo01_contact.avatar IS '头像'; +COMMENT ON COLUMN yudao_demo01_contact.creator IS '创建者'; +COMMENT ON COLUMN yudao_demo01_contact.create_time IS '创建时间'; +COMMENT ON COLUMN yudao_demo01_contact.updater IS '更新者'; +COMMENT ON COLUMN yudao_demo01_contact.update_time IS '更新时间'; +COMMENT ON COLUMN yudao_demo01_contact.deleted IS '是否删除'; +COMMENT ON COLUMN yudao_demo01_contact.tenant_id IS '租户编号'; +COMMENT ON TABLE yudao_demo01_contact IS '示例联系人表'; + +-- ---------------------------- +-- Records of yudao_demo01_contact +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO yudao_demo01_contact (id, name, sex, birthday, description, avatar, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, '土豆', 2, '2023-11-07 00:00:00', '

天蚕土豆!呀

', 'http://127.0.0.1:48080/admin-api/infra/file/4/get/46f8fa1a37db3f3960d8910ff2fe3962ab3b2db87cf2f8ccb4dc8145b8bdf237.jpeg', '1', '2023-11-15 23:34:30', '1', '2023-11-15 23:47:39', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS yudao_demo01_contact_seq; +CREATE SEQUENCE yudao_demo01_contact_seq + START 2; + +-- ---------------------------- +-- Table structure for yudao_demo02_category +-- ---------------------------- +DROP TABLE IF EXISTS yudao_demo02_category; +CREATE TABLE yudao_demo02_category ( + id int8 NOT NULL, + name varchar(100) NOT NULL DEFAULT '', + parent_id int8 NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE yudao_demo02_category ADD CONSTRAINT pk_yudao_demo02_category PRIMARY KEY (id); + +COMMENT ON COLUMN yudao_demo02_category.id IS '编号'; +COMMENT ON COLUMN yudao_demo02_category.name IS '名字'; +COMMENT ON COLUMN yudao_demo02_category.parent_id IS '父级编号'; +COMMENT ON COLUMN yudao_demo02_category.creator IS '创建者'; +COMMENT ON COLUMN yudao_demo02_category.create_time IS '创建时间'; +COMMENT ON COLUMN yudao_demo02_category.updater IS '更新者'; +COMMENT ON COLUMN yudao_demo02_category.update_time IS '更新时间'; +COMMENT ON COLUMN yudao_demo02_category.deleted IS '是否删除'; +COMMENT ON COLUMN yudao_demo02_category.tenant_id IS '租户编号'; +COMMENT ON TABLE yudao_demo02_category IS '示例分类表'; + +-- ---------------------------- +-- Records of yudao_demo02_category +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO yudao_demo02_category (id, name, parent_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, '土豆', 0, '1', '2023-11-15 23:34:30', '1', '2023-11-16 20:24:23', '0', 1); +INSERT INTO yudao_demo02_category (id, name, parent_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2, '番茄', 0, '1', '2023-11-16 20:24:00', '1', '2023-11-16 20:24:15', '0', 1); +INSERT INTO yudao_demo02_category (id, name, parent_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3, '怪怪', 0, '1', '2023-11-16 20:24:32', '1', '2023-11-16 20:24:32', '0', 1); +INSERT INTO yudao_demo02_category (id, name, parent_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (4, '小番茄', 2, '1', '2023-11-16 20:24:39', '1', '2023-11-16 20:24:39', '0', 1); +INSERT INTO yudao_demo02_category (id, name, parent_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5, '大番茄', 2, '1', '2023-11-16 20:24:46', '1', '2023-11-16 20:24:46', '0', 1); +INSERT INTO yudao_demo02_category (id, name, parent_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6, '11', 3, '1', '2023-11-24 19:29:34', '1', '2023-11-24 19:29:34', '0', 1); +INSERT INTO yudao_demo02_category (id, name, parent_id, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (7, '1', 0, '1', '2025-10-01 09:19:20', '1', '2025-10-01 09:19:20', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS yudao_demo02_category_seq; +CREATE SEQUENCE yudao_demo02_category_seq + START 8; + +-- ---------------------------- +-- Table structure for yudao_demo03_course +-- ---------------------------- +DROP TABLE IF EXISTS yudao_demo03_course; +CREATE TABLE yudao_demo03_course ( + id int8 NOT NULL, + student_id int8 NOT NULL, + name varchar(100) NOT NULL DEFAULT '', + score int2 NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE yudao_demo03_course ADD CONSTRAINT pk_yudao_demo03_course PRIMARY KEY (id); + +COMMENT ON COLUMN yudao_demo03_course.id IS '编号'; +COMMENT ON COLUMN yudao_demo03_course.student_id IS '学生编号'; +COMMENT ON COLUMN yudao_demo03_course.name IS '名字'; +COMMENT ON COLUMN yudao_demo03_course.score IS '分数'; +COMMENT ON COLUMN yudao_demo03_course.creator IS '创建者'; +COMMENT ON COLUMN yudao_demo03_course.create_time IS '创建时间'; +COMMENT ON COLUMN yudao_demo03_course.updater IS '更新者'; +COMMENT ON COLUMN yudao_demo03_course.update_time IS '更新时间'; +COMMENT ON COLUMN yudao_demo03_course.deleted IS '是否删除'; +COMMENT ON COLUMN yudao_demo03_course.tenant_id IS '租户编号'; +COMMENT ON TABLE yudao_demo03_course IS '学生课程表'; + +-- ---------------------------- +-- Records of yudao_demo03_course +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2, 2, '语文', 66, '1', '2023-11-16 23:21:49', '1', '2024-09-17 10:55:30', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (3, 2, '数学', 22, '1', '2023-11-16 23:21:49', '1', '2024-09-17 10:55:30', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (6, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:44:40', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (7, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:44:40', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (8, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:47:09', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (9, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2023-11-16 15:47:09', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (10, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2024-09-17 10:55:28', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (11, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2024-09-17 10:55:28', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (12, 2, '电脑', 33, '1', '2023-11-17 00:20:42', '1', '2023-11-16 16:20:45', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (13, 9, '滑雪', 12, '1', '2023-11-17 13:13:20', '1', '2024-09-17 10:55:26', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (14, 9, '滑雪', 12, '1', '2023-11-17 13:13:20', '1', '2024-09-17 10:55:49', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (15, 5, '体育', 23, '1', '2023-11-16 23:22:46', '1', '2024-09-17 18:55:29', '0', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (16, 5, '计算机', 11, '1', '2023-11-16 23:22:46', '1', '2024-09-17 18:55:29', '0', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (17, 2, '语文', 66, '1', '2023-11-16 23:21:49', '1', '2024-09-17 18:55:31', '0', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (18, 2, '数学', 22, '1', '2023-11-16 23:21:49', '1', '2024-09-17 18:55:31', '0', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (19, 9, '滑雪', 12, '1', '2023-11-17 13:13:20', '1', '2025-04-19 02:49:03', '1', 1); +INSERT INTO yudao_demo03_course (id, student_id, name, score, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (20, 9, '滑雪', 12, '1', '2023-11-17 13:13:20', '1', '2025-04-19 10:49:04', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS yudao_demo03_course_seq; +CREATE SEQUENCE yudao_demo03_course_seq + START 21; + +-- ---------------------------- +-- Table structure for yudao_demo03_grade +-- ---------------------------- +DROP TABLE IF EXISTS yudao_demo03_grade; +CREATE TABLE yudao_demo03_grade ( + id int8 NOT NULL, + student_id int8 NOT NULL, + name varchar(100) NOT NULL DEFAULT '', + teacher varchar(255) NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE yudao_demo03_grade ADD CONSTRAINT pk_yudao_demo03_grade PRIMARY KEY (id); + +COMMENT ON COLUMN yudao_demo03_grade.id IS '编号'; +COMMENT ON COLUMN yudao_demo03_grade.student_id IS '学生编号'; +COMMENT ON COLUMN yudao_demo03_grade.name IS '名字'; +COMMENT ON COLUMN yudao_demo03_grade.teacher IS '班主任'; +COMMENT ON COLUMN yudao_demo03_grade.creator IS '创建者'; +COMMENT ON COLUMN yudao_demo03_grade.create_time IS '创建时间'; +COMMENT ON COLUMN yudao_demo03_grade.updater IS '更新者'; +COMMENT ON COLUMN yudao_demo03_grade.update_time IS '更新时间'; +COMMENT ON COLUMN yudao_demo03_grade.deleted IS '是否删除'; +COMMENT ON COLUMN yudao_demo03_grade.tenant_id IS '租户编号'; +COMMENT ON TABLE yudao_demo03_grade IS '学生班级表'; + +-- ---------------------------- +-- Records of yudao_demo03_grade +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO yudao_demo03_grade (id, student_id, name, teacher, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (7, 2, '三年 2 班', '周杰伦', '1', '2023-11-16 23:21:49', '1', '2024-09-17 18:55:31', '0', 1); +INSERT INTO yudao_demo03_grade (id, student_id, name, teacher, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (8, 5, '华为', '遥遥领先', '1', '2023-11-16 23:22:46', '1', '2024-09-17 18:55:29', '0', 1); +INSERT INTO yudao_demo03_grade (id, student_id, name, teacher, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (9, 9, '小图', '小娃111', '1', '2023-11-17 13:10:23', '1', '2025-04-19 10:49:04', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS yudao_demo03_grade_seq; +CREATE SEQUENCE yudao_demo03_grade_seq + START 10; + +-- ---------------------------- +-- Table structure for yudao_demo03_student +-- ---------------------------- +DROP TABLE IF EXISTS yudao_demo03_student; +CREATE TABLE yudao_demo03_student ( + id int8 NOT NULL, + name varchar(100) NOT NULL DEFAULT '', + sex int2 NOT NULL, + birthday timestamp NOT NULL, + description varchar(255) NOT NULL, + creator varchar(64) NULL DEFAULT '', + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater varchar(64) NULL DEFAULT '', + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted int2 NOT NULL DEFAULT 0, + tenant_id int8 NOT NULL DEFAULT 0 +); + +ALTER TABLE yudao_demo03_student ADD CONSTRAINT pk_yudao_demo03_student PRIMARY KEY (id); + +COMMENT ON COLUMN yudao_demo03_student.id IS '编号'; +COMMENT ON COLUMN yudao_demo03_student.name IS '名字'; +COMMENT ON COLUMN yudao_demo03_student.sex IS '性别'; +COMMENT ON COLUMN yudao_demo03_student.birthday IS '出生日期'; +COMMENT ON COLUMN yudao_demo03_student.description IS '简介'; +COMMENT ON COLUMN yudao_demo03_student.creator IS '创建者'; +COMMENT ON COLUMN yudao_demo03_student.create_time IS '创建时间'; +COMMENT ON COLUMN yudao_demo03_student.updater IS '更新者'; +COMMENT ON COLUMN yudao_demo03_student.update_time IS '更新时间'; +COMMENT ON COLUMN yudao_demo03_student.deleted IS '是否删除'; +COMMENT ON COLUMN yudao_demo03_student.tenant_id IS '租户编号'; +COMMENT ON TABLE yudao_demo03_student IS '学生表'; + +-- ---------------------------- +-- Records of yudao_demo03_student +-- ---------------------------- +-- @formatter:off +BEGIN; +INSERT INTO yudao_demo03_student (id, name, sex, birthday, description, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (2, '小白', 1, '2023-11-16 00:00:00', '

厉害

', '1', '2023-11-16 23:21:49', '1', '2024-09-17 18:55:31', '0', 1); +INSERT INTO yudao_demo03_student (id, name, sex, birthday, description, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (5, '大黑', 2, '2023-11-13 00:00:00', '

你在教我做事?

', '1', '2023-11-16 23:22:46', '1', '2024-09-17 18:55:29', '0', 1); +INSERT INTO yudao_demo03_student (id, name, sex, birthday, description, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (9, '小花', 1, '2023-11-07 00:00:00', '

哈哈哈

', '1', '2023-11-17 00:04:47', '1', '2025-04-19 10:49:04', '0', 1); +COMMIT; +-- @formatter:on + +DROP SEQUENCE IF EXISTS yudao_demo03_student_seq; +CREATE SEQUENCE yudao_demo03_student_seq + START 10; + diff --git a/sql/kingbase/ruoyi-vue-pro.sql b/sql/kingbase/ruoyi-vue-pro.sql index ec0251a480..b0e0ed01b9 100644 --- a/sql/kingbase/ruoyi-vue-pro.sql +++ b/sql/kingbase/ruoyi-vue-pro.sql @@ -1371,6 +1371,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); @@ -1653,9 +1655,9 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3443, 1, '草稿', '0', 'mes_wm_product_produce_status', 0, 'info', '', '草稿状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3444, 2, '已完成', '4', 'mes_wm_product_produce_status', 0, 'success', '', '已完成状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3445, 3, '已取消', '5', 'mes_wm_product_produce_status', 0, 'danger', '', '已取消状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); COMMIT; -- @formatter:on @@ -5943,4 +5945,3 @@ COMMIT; DROP SEQUENCE IF EXISTS yudao_demo03_student_seq; CREATE SEQUENCE yudao_demo03_student_seq START 10; - diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index 3f498b3f66..4f70c5c0e8 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -11,7 +11,7 @@ Target Server Version : 80200 (8.2.0) File Encoding : 65001 - Date: 16/05/2026 06:24:32 + Date: 31/05/2026 22:29:18 */ SET NAMES utf8mb4; @@ -92,7 +92,7 @@ CREATE TABLE `infra_api_error_log` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 23883 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; +) ENGINE = InnoDB AUTO_INCREMENT = 23958 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; -- ---------------------------- -- Records of infra_api_error_log @@ -253,7 +253,7 @@ CREATE TABLE `infra_file` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2216 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; +) ENGINE = InnoDB AUTO_INCREMENT = 2233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; -- ---------------------------- -- Records of infra_file @@ -284,17 +284,17 @@ CREATE TABLE `infra_file_config` ( -- Records of infra_file_config -- ---------------------------- BEGIN; -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '数据库(示例)', 1, '我是数据库', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.db.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:56:24', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (22, '七牛存储器(示例)', 20, '请换成你自己的密钥!!!', b'1', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-01-13 22:11:12', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (24, '腾讯云存储(示例)', 20, '请换成你的密钥!!!', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"https://cos.ap-shanghai.myqcloud.com\",\"domain\":\"http://tengxun-oss.iocoder.cn\",\"bucket\":\"aoteman-1255880240\",\"accessKey\":\"AKIDAF6WSh1uiIjwqtrOsGSN3WryqTM6cTMt\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:03:22', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (25, '阿里云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"oss-cn-beijing.aliyuncs.com\",\"domain\":\"http://ali-oss.iocoder.cn\",\"bucket\":\"yunai-aoteman\",\"accessKey\":\"LTAI5tEQLgnDyjh3WpNcdMKA\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:47:08', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (26, '火山云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"tos-s3-cn-beijing.volces.com\",\"domain\":null,\"bucket\":\"yunai\",\"accessKey\":\"AKLTZjc3Zjc4MzZmMjU3NDk0ZTgxYmIyMmFkNTIwMDI1ZGE\",\"accessSecret\":\"X==\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:56:42', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (27, '华为云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"obs.cn-east-3.myhuaweicloud.com\",\"domain\":\"\",\"bucket\":\"yudao\",\"accessKey\":\"PVDONDEIOTW88LF8DC4U\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 17:18:41', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (28, 'MinIO 存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"http://127.0.0.1:9000\",\"domain\":\"http://127.0.0.1:9000/yudao\",\"bucket\":\"yudao\",\"accessKey\":\"admin\",\"accessSecret\":\"password\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 17:43:10', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (29, '本地存储(示例)', 10, 'mac/linux 使用 /,windows 使用 \\', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig\",\"basePath\":\"/Users/yunai/tmp/file\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2025-05-02 11:25:45', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (30, 'SFTP 存储(示例)', 12, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.sftp.SftpFileClientConfig\",\"basePath\":\"/upload\",\"domain\":\"http://127.0.0.1:48080\",\"host\":\"127.0.0.1\",\"port\":2222,\"username\":\"foo\",\"password\":\"pass\"}', '1', '2025-05-02 16:34:10', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (34, '七牛云存储【私有】(示例)', 20, '请换成你自己的密钥!!!', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://t151glocd.hn-bkt.clouddn.com\",\"bucket\":\"ruoyi-vue-pro-private\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":false}', '1', '2025-08-17 21:22:00', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (35, '1', 20, '1', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"http://www.baidu.com\",\"domain\":\"http://www.xxx.com\",\"bucket\":\"1\",\"accessKey\":\"2\",\"accessSecret\":\"3\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":false,\"region\":\"1\"}', '1', '2025-10-02 14:32:12', '1', '2025-11-29 15:59:39', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '数据库(示例)', 1, '我是数据库', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.db.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:56:24', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (22, '七牛存储器(示例)', 20, '请换成你自己的密钥!!!', b'1', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-01-13 22:11:12', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (24, '腾讯云存储(示例)', 20, '请换成你的密钥!!!', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"https://cos.ap-shanghai.myqcloud.com\",\"domain\":\"http://tengxun-oss.iocoder.cn\",\"bucket\":\"aoteman-1255880240\",\"accessKey\":\"AKIDAF6WSh1uiIjwqtrOsGSN3WryqTM6cTMt\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:03:22', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (25, '阿里云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"oss-cn-beijing.aliyuncs.com\",\"domain\":\"http://ali-oss.iocoder.cn\",\"bucket\":\"yunai-aoteman\",\"accessKey\":\"LTAI5tEQLgnDyjh3WpNcdMKA\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:47:08', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (26, '火山云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"tos-s3-cn-beijing.volces.com\",\"domain\":null,\"bucket\":\"yunai\",\"accessKey\":\"AKLTZjc3Zjc4MzZmMjU3NDk0ZTgxYmIyMmFkNTIwMDI1ZGE\",\"accessSecret\":\"X==\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:56:42', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (27, '华为云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"obs.cn-east-3.myhuaweicloud.com\",\"domain\":\"\",\"bucket\":\"yudao\",\"accessKey\":\"PVDONDEIOTW88LF8DC4U\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 17:18:41', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (28, 'MinIO 存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"http://127.0.0.1:9000\",\"domain\":\"http://127.0.0.1:9000/yudao\",\"bucket\":\"yudao\",\"accessKey\":\"admin\",\"accessSecret\":\"password\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 17:43:10', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (29, '本地存储(示例)', 10, 'mac/linux 使用 /,windows 使用 \\', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig\",\"basePath\":\"/Users/yunai/tmp/file\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2025-05-02 11:25:45', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (30, 'SFTP 存储(示例)', 12, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.sftp.SftpFileClientConfig\",\"basePath\":\"/upload\",\"domain\":\"http://127.0.0.1:48080\",\"host\":\"127.0.0.1\",\"port\":2222,\"username\":\"foo\",\"password\":\"pass\"}', '1', '2025-05-02 16:34:10', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (34, '七牛云存储【私有】(示例)', 20, '请换成你自己的密钥!!!', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://t151glocd.hn-bkt.clouddn.com\",\"bucket\":\"ruoyi-vue-pro-private\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":false}', '1', '2025-08-17 21:22:00', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (35, '1', 20, '1', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"http://www.baidu.com\",\"domain\":\"http://www.xxx.com\",\"bucket\":\"1\",\"accessKey\":\"2\",\"accessSecret\":\"3\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":false,\"region\":\"1\"}', '1', '2025-10-02 14:32:12', '1', '2026-05-17 14:05:15', b'0'); COMMIT; -- ---------------------------- @@ -313,7 +313,7 @@ CREATE TABLE `infra_file_content` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_config_id_path`(`config_id` ASC, `path` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 286 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; +) ENGINE = InnoDB AUTO_INCREMENT = 288 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; -- ---------------------------- -- Records of infra_file_content @@ -462,7 +462,7 @@ CREATE TABLE `system_dict_data` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1061096 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表'; +) ENGINE = InnoDB AUTO_INCREMENT = 1061122 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表'; -- ---------------------------- -- Records of system_dict_data @@ -1095,6 +1095,8 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', '2026-05-23 13:52:25', '1', '2026-05-23 13:52:25', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', '2026-05-23 13:52:25', '1', '2026-05-23 13:52:25', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', b'0'); @@ -1372,17 +1374,14 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3443, 1, '草稿', '0', 'mes_wm_product_produce_status', 0, 'info', '', '草稿状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3444, 2, '已完成', '4', 'mes_wm_product_produce_status', 0, 'success', '', '已完成状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3445, 3, '已取消', '5', 'mes_wm_product_produce_status', 0, 'danger', '', '已取消状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3520, 1, '未读', '0', 'im_private_message_status', 0, 'warning', '', '私聊=未读,群聊=正常', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 15:14:36', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3521, 2, '已撤回', '2', 'im_private_message_status', 0, 'danger', '', 'RECALL', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 15:14:36', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3522, 3, '已读', '3', 'im_private_message_status', 0, 'success', '', 'READ(仅私聊)', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 15:14:36', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3525, 1, '正常', '0', 'im_group_message_status', 0, 'success', '', '群聊正常(初始状态)', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3526, 2, '已撤回', '2', 'im_group_message_status', 0, 'danger', '', '群聊已撤回', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3530, 1, '不需要回执', '0', 'im_group_message_receipt_status', 0, 'info', '', 'NO_RECEIPT', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3531, 2, '待完成', '1', 'im_group_message_receipt_status', 0, 'warning', '', 'PENDING', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3532, 3, '已完成', '2', 'im_group_message_receipt_status', 0, 'success', '', 'DONE', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3525, 1, '正常', '0', 'im_message_status', 0, 'success', '', '正常(初始状态)', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3526, 2, '已撤回', '2', 'im_message_status', 0, 'danger', '', '已撤回', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3530, 1, '不需要回执', '0', 'im_message_receipt_status', 0, 'info', '', 'NO_RECEIPT', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3531, 2, '待完成', '1', 'im_message_receipt_status', 0, 'warning', '', 'PENDING', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3532, 3, '已完成', '2', 'im_message_receipt_status', 0, 'success', '', 'DONE', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3540, 1, '正常', '0', 'im_friend_status', 0, 'success', '', '正常好友关系', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3541, 2, '已删除', '1', 'im_friend_status', 0, 'danger', '', '已删除好友关系', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3550, 1, '正常', '0', 'im_group_status', 0, 'success', '', '群正常', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); @@ -1397,39 +1396,39 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3559, 1, '等待验证', '0', 'im_friend_request_handle_result', 0, 'warning', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3560, 2, '已添加', '1', 'im_friend_request_handle_result', 0, 'success', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3561, 3, '已拒绝', '2', 'im_friend_request_handle_result', 0, 'info', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3562, 101, '文本', '101', 'im_message_type', 0, '', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3563, 102, '图片', '102', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3564, 103, '语音', '103', 'im_message_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3565, 104, '视频', '104', 'im_message_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3566, 105, '文件', '105', 'im_message_type', 0, 'info', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3568, 2101, '撤回', '2101', 'im_message_type', 0, 'danger', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3569, 2200, '回执', '2200', 'im_message_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3570, 2201, '已读', '2201', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3571, 1501, '群创建', '1501', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3572, 1502, '群信息变更', '1502', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3573, 1503, '入群申请', '1503', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3574, 1504, '成员退群', '1504', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3575, 1505, '入群申请通过', '1505', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3576, 1506, '入群申请拒绝', '1506', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3577, 1507, '群主转让', '1507', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3578, 1508, '成员被移出', '1508', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3579, 1509, '成员加入', '1509', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3580, 1510, '自由进群', '1510', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3581, 1511, '群解散', '1511', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3582, 1512, '成员禁言', '1512', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3583, 1513, '成员取消禁言', '1513', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3584, 1514, '全群禁言', '1514', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3585, 1515, '全群取消禁言', '1515', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3586, 1516, '成员昵称变更', '1516', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3587, 1517, '添加管理员', '1517', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3588, 1518, '撤销管理员', '1518', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3589, 1519, '群公告变更', '1519', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3590, 1520, '群名变更', '1520', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3591, 1531, '群消息置顶', '1531', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3592, 1532, '群消息取消置顶', '1532', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3593, 1533, '群封禁变更', '1533', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3594, 1204, '新增好友', '1204', 'im_message_type', 0, 'success', '', NULL, 'admin', '2026-05-05 13:26:53', 'admin', '2026-05-05 13:26:53', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3595, 1205, '好友被删除', '1205', 'im_message_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 13:26:53', 'admin', '2026-05-05 13:26:53', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3562, 101, '文本', '101', 'im_content_type', 0, '', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3563, 102, '图片', '102', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3564, 103, '语音', '103', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3565, 104, '视频', '104', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3566, 105, '文件', '105', 'im_content_type', 0, 'info', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3568, 2101, '撤回', '2101', 'im_content_type', 0, 'danger', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3569, 2200, '回执', '2200', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3570, 2201, '已读', '2201', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3571, 1501, '群创建', '1501', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3572, 1502, '群信息变更', '1502', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3573, 1503, '入群申请', '1503', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3574, 1504, '成员退群', '1504', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3575, 1505, '入群申请通过', '1505', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3576, 1506, '入群申请拒绝', '1506', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3577, 1507, '群主转让', '1507', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3578, 1508, '成员被移出', '1508', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3579, 1509, '成员加入', '1509', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3580, 1510, '自由进群', '1510', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3581, 1511, '群解散', '1511', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3582, 1512, '成员禁言', '1512', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3583, 1513, '成员取消禁言', '1513', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3584, 1514, '全群禁言', '1514', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3585, 1515, '全群取消禁言', '1515', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3586, 1516, '成员昵称变更', '1516', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3587, 1517, '添加管理员', '1517', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3588, 1518, '撤销管理员', '1518', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3589, 1519, '群公告变更', '1519', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3590, 1520, '群名变更', '1520', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3591, 1531, '群消息置顶', '1531', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3592, 1532, '群消息取消置顶', '1532', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3593, 1533, '群封禁变更', '1533', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3594, 1204, '新增好友', '1204', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 13:26:53', 'admin', '2026-05-05 13:26:53', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3595, 1205, '好友被删除', '1205', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 13:26:53', 'admin', '2026-05-05 13:26:53', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3596, 1, '搜索', '1', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3597, 2, '邀请', '2', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3598, 3, '扫码', '3', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', b'0'); @@ -1454,8 +1453,32 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061091, 1, '退货出库', '200', 'wms_shipment_order_type', 0, 'warning', '', '退货出库', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061092, 2, '销售出库', '201', 'wms_shipment_order_type', 0, 'primary', '', '销售出库', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', b'0'); INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061093, 3, '生产出库', '202', 'wms_shipment_order_type', 0, 'success', '', '生产出库', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061094, 1, '语音', '1', 'im_rtc_call_media_type', 0, '', '', '语音通话', 'admin', '2026-05-14 13:48:31', 'admin', '2026-05-14 13:48:31', b'0'); -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061095, 2, '视频', '2', 'im_rtc_call_media_type', 0, '', '', '视频通话', 'admin', '2026-05-14 13:48:31', 'admin', '2026-05-14 13:48:31', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061096, 1, '语音', '1', 'im_rtc_call_media_type', 0, '', '', '语音通话', 'admin', '2026-05-16 11:34:50', 'admin', '2026-05-16 11:34:50', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061097, 2, '视频', '2', 'im_rtc_call_media_type', 0, '', '', '视频通话', 'admin', '2026-05-16 11:34:50', 'admin', '2026-05-16 11:34:50', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061098, 1, '私聊', '1', 'im_rtc_call_conversation_type', 0, 'primary', '', '一对一私聊通话', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061099, 2, '群聊', '2', 'im_rtc_call_conversation_type', 0, 'success', '', '群内多人通话', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061100, 1, '创建', '10', 'im_rtc_call_status', 0, 'info', '', '通话已创建,等待接通', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061101, 2, '进行中', '20', 'im_rtc_call_status', 0, 'primary', '', '已有人接通,通话中', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061102, 3, '已结束', '30', 'im_rtc_call_status', 0, 'success', '', '通话结束', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061103, 1, '通话结束', '1', 'im_rtc_call_end_reason', 0, 'success', '', '接通后任一方主动挂断', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061104, 2, '已拒绝', '2', 'im_rtc_call_end_reason', 0, 'warning', '', '被叫接通前点拒接', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061105, 3, '已取消', '3', 'im_rtc_call_end_reason', 0, 'info', '', '主叫接通前主动取消', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061106, 4, '无人接听', '4', 'im_rtc_call_end_reason', 0, 'info', '', '振铃超时未接通', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061107, 5, '对方正忙', '5', 'im_rtc_call_end_reason', 0, 'warning', '', '对方在另一通话中', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061108, 6, '通话异常', '9', 'im_rtc_call_end_reason', 0, 'danger', '', '网络中断、设备失败等', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061109, 1, '发起人', '1', 'im_rtc_participant_role', 0, 'primary', '', '通话发起者', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061110, 2, '被邀请者', '2', 'im_rtc_participant_role', 0, 'info', '', '被邀请加入', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061111, 3, '主动加入者', '3', 'im_rtc_participant_role', 0, 'success', '', '群通话场景,旁观者主动加入', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061112, 1, '邀请中', '10', 'im_rtc_participant_status', 0, 'info', '', '已发出 invite,等待响应', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061113, 2, '已加入', '20', 'im_rtc_participant_status', 0, 'primary', '', '已接通并进入房间', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061114, 3, '已拒绝', '30', 'im_rtc_participant_status', 0, 'warning', '', '接通前点拒接', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061115, 4, '未应答', '40', 'im_rtc_participant_status', 0, 'info', '', '通话已结束仍未应答', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061116, 5, '已离开', '50', 'im_rtc_participant_status', 0, 'success', '', '接通后挂断 / 离开', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061117, 1610, '通话开始', '1610', 'im_content_type', 0, 'info', '', '入消息流;私聊定向通知,群聊全员广播', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061118, 1611, '通话结束', '1611', 'im_content_type', 0, 'info', '', '入消息流;私聊准气泡,群聊系统 tip', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061119, 125, '素材', '125', 'im_content_type', 0, 'success', '', '频道运营推送的图文卡片消息', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061120, 1, '富文本', '1', 'im_channel_material_type', 0, 'primary', '', '', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', b'0'); +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061121, 2, '外链', '2', 'im_channel_material_type', 0, 'info', '', '', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', b'0'); COMMIT; -- ---------------------------- @@ -1475,7 +1498,7 @@ CREATE TABLE `system_dict_type` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1061092 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表'; +) ENGINE = InnoDB AUTO_INCREMENT = 1061099 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表'; -- ---------------------------- -- Records of system_dict_type @@ -1666,12 +1689,11 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2133, 'MES 销售退货单状态', 'mes_wm_return_sales_status', 0, 'MES 销售退货单状态枚举', '1', '2026-04-03 17:20:25', '1', '2026-04-05 15:05:39', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2135, 'MES 上下工状态类型', 'mes_pro_work_record_type', 0, 'MES 上下工状态类型', '1', '2026-04-05 14:07:27', '1', '2026-04-05 14:07:27', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2138, 'MES 生产入库单状态', 'mes_wm_product_produce_status', 0, 'MES 生产入库单状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0', '1970-01-01 00:00:00'); -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2200, 'IM 消息类型', 'im_message_type', 0, '对应 ImMessageTypeEnum', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL); -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2201, 'IM 私聊消息状态', 'im_private_message_status', 0, '对应 ImMessageStatusEnum;私聊 0=未读 / 2=已撤回 / 3=已读', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 15:14:36', b'0', NULL); -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2202, 'IM 群消息回执状态', 'im_group_message_receipt_status', 0, '对应 ImGroupMessageReceiptStatusEnum', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2200, 'IM 内容类型', 'im_content_type', 0, '对应 ImContentTypeEnum', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2202, 'IM 消息回执状态', 'im_message_receipt_status', 0, '对应 ImMessageReceiptStatusEnum', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2203, 'IM 好友状态', 'im_friend_status', 0, '0=正常 / 1=已删除', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2204, 'IM 群状态', 'im_group_status', 0, '0=正常 / 1=已解散', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL); -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2205, 'IM 群聊消息状态', 'im_group_message_status', 0, '对应 ImMessageStatusEnum;群聊 0=正常 / 2=已撤回(无未读概念)', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2205, 'IM 消息状态', 'im_message_status', 0, '对应 ImMessageStatusEnum;0=正常 / 2=已撤回(私聊 / 群聊共用)', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2206, 'IM 群成员角色', 'im_group_member_role', 0, NULL, '1', '2026-05-02 02:14:12', '1', '2026-05-02 02:14:12', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2207, 'IM 好友添加来源', 'im_friend_add_source', 0, NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2208, 'IM 好友申请处理结果', 'im_friend_request_handle_result', 0, NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0', NULL); @@ -1682,7 +1704,13 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061070, 'WMS 单据状态', 'wms_order_status', 0, 'WMS 单据状态', '1', '2026-05-12 13:40:29', '1', '2026-05-12 13:40:29', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061080, '入库单类型', 'wms_receipt_order_type', 0, 'WMS 入库单类型', '1', '2026-05-11 11:21:49', '1', '2026-05-12 13:40:29', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061090, '出库单类型', 'wms_shipment_order_type', 0, 'WMS 出库单类型', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', b'0', NULL); -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061091, 'IM 通话媒体类型', 'im_rtc_call_media_type', 0, NULL, 'admin', '2026-05-14 13:48:31', 'admin', '2026-05-14 13:48:31', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061092, 'IM 通话媒体类型', 'im_rtc_call_media_type', 0, NULL, 'admin', '2026-05-16 11:34:50', 'admin', '2026-05-16 11:34:50', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061093, 'IM 通话会话类型', 'im_rtc_call_conversation_type', 0, '1=私聊;2=群聊', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061094, 'IM 通话状态', 'im_rtc_call_status', 0, '10=创建;20=进行中;30=已结束', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061095, 'IM 通话结束原因', 'im_rtc_call_end_reason', 0, '1=通话结束;2=已拒绝;3=已取消;4=无人接听;5=对方正忙;9=通话异常', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061096, 'IM 通话参与角色', 'im_rtc_participant_role', 0, '1=发起人;2=被邀请者;3=主动加入者', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061097, 'IM 通话参与状态', 'im_rtc_participant_status', 0, '10=邀请中;20=已加入;30=已拒绝;40=未应答;50=已离开', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061098, 'IM 频道素材内容类型', 'im_channel_material_type', 0, '1=站内富文本 / 2=外链', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', b'0', NULL); COMMIT; -- ---------------------------- @@ -1708,7 +1736,7 @@ CREATE TABLE `system_login_log` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_username`(`username` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 4557 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; +) ENGINE = InnoDB AUTO_INCREMENT = 4697 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; -- ---------------------------- -- Records of system_login_log @@ -1841,7 +1869,7 @@ CREATE TABLE `system_menu` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 6611 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表'; +) ENGINE = InnoDB AUTO_INCREMENT = 6735 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表'; -- ---------------------------- -- Records of system_menu @@ -2773,7 +2801,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i 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 (5034, 'OTA 固件创建', 'iot:ota-firmware:create', 3, 2, 5032, '', '', '', '', 0, b'1', b'1', b'1', '', '2025-06-30 07:50:29', '\"1\"', '2025-06-30 17:38:21', 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 (5035, 'OTA 固件更新', 'iot:ota-firmware:update', 3, 3, 5032, '', '', '', '', 0, b'1', b'1', b'1', '', '2025-06-30 07:50:29', '\"1\"', '2025-06-30 17:38:29', 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 (5036, 'OTA 固件删除', 'iot:ota-firmware:delete', 3, 4, 5032, '', '', '', '', 0, b'1', b'1', b'1', '', '2025-06-30 07:50:29', '\"1\"', '2025-06-30 17:38:37', 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 (5037, 'OTA 升级任务查询', 'iot:ota-task:create', 3, 11, 5032, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-07-02 23:56:56', '1', '2025-07-02 23:56:56', 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 (5037, 'OTA 升级任务查询', 'iot:ota-task:query', 3, 11, 5032, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-07-02 23:56:56', '1', '2026-05-19 08:48:53', 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 (5038, 'OTA 升级任务取消', 'iot:ota-task:cancel', 3, 13, 5032, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-07-02 23:57:26', '1', '2025-07-02 23:57:26', 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 (5039, 'OTA 升级任务创建', 'iot:ota-task:create', 3, 12, 5032, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-07-02 23:57:52', '1', '2025-07-02 23:57:52', 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 (5040, 'OTA 升级记录查询', 'iot:ota-task-record:query', 3, 21, 5032, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-07-02 23:58:30', '1', '2025-07-02 23:58:30', b'0'); @@ -3270,6 +3298,24 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i 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 (6582, '用户表情删除', 'im:manager:face-user-item:delete', 3, 20, 6580, '', '', '', '', 0, b'1', b'1', b'1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', 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 (6600, '私聊管理', '', 1, 20, 6500, 'private', 'ep:chat-round', NULL, NULL, 0, b'1', b'1', b'1', '1', '2026-05-07 00:40:35', '1', '2026-05-07 00:40:35', 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 (6610, '群聊管理', '', 1, 30, 6500, 'group', 'ep:chat-line-round', NULL, NULL, 0, b'1', b'1', b'1', '1', '2026-05-07 00:40:35', '1', '2026-05-07 00:40:35', 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 (6611, '通话记录', '', 2, 40, 6500, 'rtc', 'ep:phone', 'im/manager/rtc/index', 'ImRtcCall', 0, b'1', b'1', b'1', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', 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 (6612, '通话记录查询', 'im:manager:rtc:query', 3, 1, 6611, '', '', NULL, NULL, 0, b'1', b'1', b'1', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', 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 (6700, '频道管理', '', 1, 90, 6500, 'channel', 'ep:promotion', NULL, NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6710, '频道列表', '', 2, 1, 6700, 'list', 'ep:promotion', 'im/manager/channel/list/index', 'ImChannel', 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-19 09:44:34', 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 (6711, '频道查询', 'im:manager:channel:query', 3, 1, 6710, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6712, '频道创建', 'im:manager:channel:create', 3, 2, 6710, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6713, '频道修改', 'im:manager:channel:update', 3, 3, 6710, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6714, '频道删除', 'im:manager:channel:delete', 3, 4, 6710, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6720, '频道素材', '', 2, 2, 6700, 'material', 'ep:document', 'im/manager/channel/material/index', 'ImChannelMaterial', 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-19 09:44:34', 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 (6721, '素材查询', 'im:manager:channel-material:query', 3, 1, 6720, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6722, '素材创建', 'im:manager:channel-material:create', 3, 2, 6720, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6723, '素材修改', 'im:manager:channel-material:update', 3, 3, 6720, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6724, '素材删除', 'im:manager:channel-material:delete', 3, 4, 6720, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6730, '频道消息', '', 2, 3, 6700, 'message', 'ep:message', 'im/manager/channel/message/index', 'ImChannelMessage', 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-19 09:44:34', 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 (6731, '消息查询', 'im:manager:channel-message:query', 3, 1, 6730, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6732, '立即推送', 'im:manager:channel-message:send', 3, 2, 6730, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6733, '消息删除', 'im:manager:channel-message:delete', 3, 3, 6730, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6734, '解散群', 'im:manager:group:dissolve', 3, 21, 6540, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-24 12:02:38', '1', '2026-05-24 12:02:38', b'0'); COMMIT; -- ---------------------------- @@ -3392,7 +3438,7 @@ CREATE TABLE `system_oauth2_access_token` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_access_token`(`access_token` ASC) USING BTREE, INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 54457 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; +) ENGINE = InnoDB AUTO_INCREMENT = 57395 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; -- ---------------------------- -- Records of system_oauth2_access_token @@ -3518,7 +3564,7 @@ CREATE TABLE `system_oauth2_refresh_token` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2590 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; +) ENGINE = InnoDB AUTO_INCREMENT = 2728 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; -- ---------------------------- -- Records of system_oauth2_refresh_token @@ -3554,7 +3600,7 @@ CREATE TABLE `system_operate_log` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_user_id`(`user_id` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 9194 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本'; +) ENGINE = InnoDB AUTO_INCREMENT = 9195 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本'; -- ---------------------------- -- Records of system_operate_log @@ -4952,16 +4998,16 @@ CREATE TABLE `system_users` ( INDEX `idx_mobile`(`mobile` ASC) USING BTREE, INDEX `idx_email`(`email` ASC) USING BTREE, INDEX `idx_dept_id`(`dept_id` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 145 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表'; +) ENGINE = InnoDB AUTO_INCREMENT = 225 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表'; -- ---------------------------- -- Records of system_users -- ---------------------------- BEGIN; -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$04$.vd8nPeLwxt6hnSzmAoAyul8BOLX7Cib6QhcxRe30rfvrIPQHH1OG', '芋道源码', '管理员', 103, '[1,2]', '13aoteman@126.com', '18818260272', 1, 'http://test.yudao.iocoder.cn/user/avatar/20251220/blob_1766215463801.jpg', 0, '0:0:0:0:0:0:0:1', '2026-05-15 22:24:39', 'admin', '2021-01-05 17:03:47', NULL, '2026-05-15 22:24:39', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$04$.vd8nPeLwxt6hnSzmAoAyul8BOLX7Cib6QhcxRe30rfvrIPQHH1OG', '芋道源码', '管理员', 103, '[1,2]', '13aoteman@126.com', '18818260272', 1, 'http://test.yudao.iocoder.cn/20260517/blob_1778998103688.png', 0, '0:0:0:0:0:0:0:1', '2026-05-31 21:54:49', 'admin', '2021-01-05 17:03:47', NULL, '2026-05-31 21:54:49', b'0', 1); INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$04$.vd8nPeLwxt6hnSzmAoAyul8BOLX7Cib6QhcxRe30rfvrIPQHH1OG', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-04-19 17:40:55', '', '2021-01-07 09:07:17', NULL, '2026-04-19 17:40:55', b'0', 1); INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$04$k/d6mc0nySN0i2udwcI8Ee8V5aM5OHixBRbQfXmPuFTUl3Zf/DBs.', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2026-04-27 13:19:27', '', '2021-01-13 23:50:35', NULL, '2026-04-27 13:19:27', b'0', 1); -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-05-09 09:57:13', '', '2021-01-21 02:13:53', NULL, '2026-05-09 09:57:13', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-05-20 23:37:11', '', '2021-01-21 02:13:53', NULL, '2026-05-20 23:37:11', b'0', 1); INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2025-04-21 14:23:08', b'0', 118); INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2025-04-21 14:23:08', b'0', 119); INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2025-04-21 14:23:08', b'0', 120); @@ -4977,7 +5023,7 @@ INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (141, 'admin1', '$2a$04$oj6F6d7HrZ70kYVD3TNzEu.m3TPUzajOVuC66zdKna8KRerK1FmVa', '新用户', NULL, NULL, NULL, '', '', 0, '', 0, '0:0:0:0:0:0:0:1', '2025-04-08 13:09:07', '1', '2025-04-08 13:09:07', '1', '2025-05-14 19:11:48', b'0', 1); INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (142, 'test01', '$2a$04$4bCYWZkjxxOC4QE0LY2M9uEEKWeJbLfs489NFtQoyidL5I0FndRaO', 'test01', '', NULL, '[]', '', '19021719925', 1, '', 0, '0:0:0:0:0:0:0:1', '2025-07-29 19:47:17', '1', '2025-07-09 21:07:10', NULL, '2025-12-02 13:23:11', b'0', 1); INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (143, 'a00001', '$2a$04$GhVHFviOw/SsTmiQtifHJesDYFlHMeGK7OWh7aGCCjGGVCmbHVAwa', 'a00001', NULL, 104, NULL, '', '', 0, '', 0, '0:0:0:0:0:0:0:1', '2025-12-01 16:10:13', NULL, '2025-12-01 16:10:13', '1', '2025-12-05 21:34:05', b'0', 1); -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (144, 'aoteman001', '$2a$04$omQOmhz8OyUFBKw77nr8KOtMp6xdvoQ1gWStjk9r8.OYT3Bv6oEYe', 'aoteman001', NULL, 116, NULL, '', '', 0, '', 1, '0:0:0:0:0:0:0:1', '2025-12-01 17:05:27', '1', '2025-12-01 17:05:27', '1', '2025-12-15 15:55:54', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (144, 'aoteman001', '$2a$04$omQOmhz8OyUFBKw77nr8KOtMp6xdvoQ1gWStjk9r8.OYT3Bv6oEYe', 'aoteman001', NULL, 104, NULL, '', '', 0, '', 1, '0:0:0:0:0:0:0:1', '2025-12-01 17:05:27', '1', '2025-12-01 17:05:27', '1', '2026-05-31 21:52:48', b'0', 1); COMMIT; -- ---------------------------- diff --git a/sql/opengauss/ruoyi-vue-pro.sql b/sql/opengauss/ruoyi-vue-pro.sql index 926ffe6016..443705e97c 100644 --- a/sql/opengauss/ruoyi-vue-pro.sql +++ b/sql/opengauss/ruoyi-vue-pro.sql @@ -1371,6 +1371,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); @@ -1653,9 +1655,9 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3443, 1, '草稿', '0', 'mes_wm_product_produce_status', 0, 'info', '', '草稿状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3444, 2, '已完成', '4', 'mes_wm_product_produce_status', 0, 'success', '', '已完成状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3445, 3, '已取消', '5', 'mes_wm_product_produce_status', 0, 'danger', '', '已取消状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); COMMIT; -- @formatter:on @@ -5943,4 +5945,3 @@ COMMIT; DROP SEQUENCE IF EXISTS yudao_demo03_student_seq; CREATE SEQUENCE yudao_demo03_student_seq START 10; - diff --git a/sql/oracle/ruoyi-vue-pro.sql b/sql/oracle/ruoyi-vue-pro.sql index 12f8e40a1d..b731aab682 100644 --- a/sql/oracle/ruoyi-vue-pro.sql +++ b/sql/oracle/ruoyi-vue-pro.sql @@ -1323,6 +1323,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', to_date('2025-09-06 00:02:21', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2025-09-06 00:02:31', 'SYYYY-MM-DD HH24:MI:SS'), '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', to_date('2023-11-04 13:05:38', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-11-04 13:07:16', 'SYYYY-MM-DD HH24:MI:SS'), '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', to_date('2025-12-16 19:25:51', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2025-12-17 09:46:15', 'SYYYY-MM-DD HH24:MI:SS'), '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', SYSDATE, '1', SYSDATE, '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', SYSDATE, '1', SYSDATE, '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', to_date('2026-02-04 00:32:47', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-02-04 00:32:47', 'SYYYY-MM-DD HH24:MI:SS'), '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '0'); @@ -1605,9 +1607,9 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3443, 1, '草稿', '0', 'mes_wm_product_produce_status', 0, 'info', '', '草稿状态', '1', to_date('2026-04-05 15:53:46', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-04-05 15:53:46', 'SYYYY-MM-DD HH24:MI:SS'), '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3444, 2, '已完成', '4', 'mes_wm_product_produce_status', 0, 'success', '', '已完成状态', '1', to_date('2026-04-05 15:53:46', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-04-05 15:53:46', 'SYYYY-MM-DD HH24:MI:SS'), '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3445, 3, '已取消', '5', 'mes_wm_product_produce_status', 0, 'danger', '', '已取消状态', '1', to_date('2026-04-05 15:53:46', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-04-05 15:53:46', 'SYYYY-MM-DD HH24:MI:SS'), '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-04-16 09:47:00', 'SYYYY-MM-DD HH24:MI:SS'), '0'); COMMIT; -- @formatter:on @@ -5801,4 +5803,3 @@ COMMIT; CREATE SEQUENCE yudao_demo03_student_seq START WITH 10; - diff --git a/sql/postgresql/ruoyi-vue-pro.sql b/sql/postgresql/ruoyi-vue-pro.sql index d52f015cf0..7bceebd25a 100644 --- a/sql/postgresql/ruoyi-vue-pro.sql +++ b/sql/postgresql/ruoyi-vue-pro.sql @@ -1371,6 +1371,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0'); @@ -1653,9 +1655,9 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3443, 1, '草稿', '0', 'mes_wm_product_produce_status', 0, 'info', '', '草稿状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3444, 2, '已完成', '4', 'mes_wm_product_produce_status', 0, 'success', '', '已完成状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3445, 3, '已取消', '5', 'mes_wm_product_produce_status', 0, 'danger', '', '已取消状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', '0'); COMMIT; -- @formatter:on @@ -5943,4 +5945,3 @@ COMMIT; DROP SEQUENCE IF EXISTS yudao_demo03_student_seq; CREATE SEQUENCE yudao_demo03_student_seq START 10; - diff --git a/sql/sqlserver/ruoyi-vue-pro.sql b/sql/sqlserver/ruoyi-vue-pro.sql index 82c4896ed7..9a237d5792 100644 --- a/sql/sqlserver/ruoyi-vue-pro.sql +++ b/sql/sqlserver/ruoyi-vue-pro.sql @@ -3367,6 +3367,10 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t GO INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, N'Admin Uniapp 移动端', N'60', N'infra_codegen_front_type', 0, N'', N'', NULL, N'1', N'2025-12-16 19:25:51', N'1', N'2025-12-17 09:46:15', N'0') GO +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, N'Vben5.0 Antdv Next Schema 模版', N'42', N'infra_codegen_front_type', 0, N'', N'', N'', N'1', N'2026-05-16 00:00:00', N'1', N'2026-05-16 00:00:00', N'0') +GO +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, N'Vben5.0 Antdv Next 标准模版', N'43', N'infra_codegen_front_type', 0, N'', N'', N'', N'1', N'2026-05-16 00:00:00', N'1', N'2026-05-16 00:00:00', N'0') +GO INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, N'UDP', N'udp', N'iot_protocol_type', 0, N'', N'', N'UDP 协议', N'1', N'2026-02-04 00:32:47', N'1', N'2026-02-04 00:32:47', N'0') GO INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, N'WebSocket', N'websocket', N'iot_protocol_type', 0, N'', N'', N'WebSocket 协议', N'1', N'2026-02-04 00:32:55', N'1', N'2026-02-04 00:32:55', N'0') @@ -3931,11 +3935,11 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t GO INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3445, 3, N'已取消', N'5', N'mes_wm_product_produce_status', 0, N'danger', N'', N'已取消状态', N'1', N'2026-04-05 15:53:46', N'1', N'2026-04-05 15:53:46', N'0') GO -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, N'草稿', N'0', N'mes_pro_task_status', 0, N'', N'', NULL, N'1', N'2026-04-16 09:47:00', N'1', N'2026-04-16 09:47:00', N'0') +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3446, 0, N'草稿', N'0', N'mes_pro_task_status', 0, N'', N'', NULL, N'1', N'2026-04-16 09:47:00', N'1', N'2026-04-16 09:47:00', N'0') GO -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, N'已完成', N'4', N'mes_pro_task_status', 0, N'', N'', NULL, N'1', N'2026-04-16 09:47:00', N'1', N'2026-04-16 09:47:00', N'0') +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3447, 1, N'已完成', N'4', N'mes_pro_task_status', 0, N'', N'', NULL, N'1', N'2026-04-16 09:47:00', N'1', N'2026-04-16 09:47:00', N'0') GO -INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, N'已取消', N'5', N'mes_pro_task_status', 0, N'', N'', NULL, N'1', N'2026-04-16 09:47:00', N'1', N'2026-04-16 09:47:00', N'0') +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3448, 2, N'已取消', N'5', N'mes_pro_task_status', 0, N'', N'', NULL, N'1', N'2026-04-16 09:47:00', N'1', N'2026-04-16 09:47:00', N'0') GO SET IDENTITY_INSERT system_dict_data OFF GO @@ -13915,4 +13919,3 @@ GO COMMIT GO -- @formatter:on - diff --git a/sql/tools/README.md b/sql/tools/README.md index 94c5300a54..6ed4641c90 100644 --- a/sql/tools/README.md +++ b/sql/tools/README.md @@ -90,6 +90,25 @@ docker compose up -d opengauss docker compose exec opengauss bash -c '/usr/local/opengauss/bin/gsql -U $GS_USERNAME -W $GS_PASSWORD -d postgres -f /tmp/schema.sql' ``` +### 1.8 HighGo 瀚高数据库 + +① 下载瀚高官方 Docker 镜像,并加载镜像文件。加载后,将镜像打成本地标签: + +```Bash +docker load -i .tar +docker tag : highgo:local +``` + +② 在项目 `sql/tools` 目录下运行: + +```Bash +docker compose up -d highgo +``` + +> 注意:不同瀚高镜像的数据目录可能不同,如果容器无法启动,请按镜像实际 `PGDATA` 修改 `docker-compose.yaml` 中的 `highgo` 数据卷挂载目录。 + +③ 启动完成后,需要手动导入 Quartz 和项目 SQL。瀚高兼容 PostgreSQL,具体客户端命令以当前镜像为准,可使用 `psql` 或瀚高镜像内置的兼容客户端执行 `/tmp/quartz.sql`、`/tmp/schema.sql`。 + ## 1.X 容器的销毁重建 开发测试过程中,有时候需要创建全新干净的数据库。由于测试数据 Docker 容器采用数据卷 Volume 挂载数据库实例的数据目录,因此销毁数据需要停止容器后,删除数据卷,然后再重新创建容器。 @@ -103,7 +122,7 @@ docker volume rm ruoyi-vue-pro_postgres ## 2. MySQL 转换其它数据库 -项目提供了 `sql/tools/convertor.py` 脚本,支持将 MySQL 转换为 Oracle、PostgreSQL、SQL Server、达梦、人大金仓、OpenGauss 等数据库的脚本。 +项目提供了 `sql/tools/convertor.py` 脚本,支持将 MySQL 转换为 Oracle、PostgreSQL、SQL Server、达梦、人大金仓、OpenGauss、瀚高等数据库的脚本。 ### 2.1 实现原理 @@ -118,11 +137,12 @@ pip install simple-ddl-parser # pip3 install simple-ddl-parser ``` -② 在 `sql/tools/` 目录下,执行如下命令打印生成 postgres 的脚本内容,其他可选参数有:`oracle`、`sqlserver`、`dm8`、`kingbase`、`opengauss`: +② 在 `sql/tools/` 目录下,执行如下命令打印生成 postgres 的脚本内容,其他可选参数有:`oracle`、`sqlserver`、`dm8`、`kingbase`、`opengauss`、`highgo`: ```Bash python3 convertor.py postgres # python3 convertor.py postgres > tmp.sql +# python3 convertor.py highgo ../mysql/ruoyi-vue-pro.sql > ../highgo/ruoyi-vue-pro.sql ``` 程序将 SQL 脚本打印到终端,可以重定向到临时文件 `tmp.sql`。 diff --git a/sql/tools/convertor.py b/sql/tools/convertor.py index 3a8b9f37fc..2b4c790a08 100644 --- a/sql/tools/convertor.py +++ b/sql/tools/convertor.py @@ -10,6 +10,7 @@ uv run --with simple-ddl-parser convertor.py postgres ../mysql/ruoyi-vue-pro.sql uv run --with simple-ddl-parser convertor.py sqlserver ../mysql/ruoyi-vue-pro.sql > ../sqlserver/ruoyi-vue-pro.sql uv run --with simple-ddl-parser convertor.py kingbase ../mysql/ruoyi-vue-pro.sql > ../kingbase/ruoyi-vue-pro.sql uv run --with simple-ddl-parser convertor.py opengauss ../mysql/ruoyi-vue-pro.sql > ../opengauss/ruoyi-vue-pro.sql +uv run --with simple-ddl-parser convertor.py highgo ../mysql/ruoyi-vue-pro.sql > ../highgo/ruoyi-vue-pro.sql uv run --with simple-ddl-parser convertor.py oracle ../mysql/ruoyi-vue-pro.sql > ../oracle/ruoyi-vue-pro.sql uv run --with simple-ddl-parser convertor.py dm8 ../mysql/ruoyi-vue-pro.sql > ../dm/ruoyi-vue-pro-dm8.sql """ @@ -77,6 +78,9 @@ def load_and_clean(sql_file: str) -> str: class Convertor(ABC): + # 不同数据库的关键字不完全一致;子类按需声明需要转义的列名。 + reserved_column_names = set() + def __init__(self, src: str, db_type) -> None: self.src = src self.db_type = db_type @@ -179,6 +183,31 @@ class Convertor(ABC): """ return "" + def escape_column_name(self, name: str) -> str: + """转义目标库保留字列名,例如 Oracle / Kingbase 的 level。""" + + column_name = name.lower() + if column_name in self.reserved_column_names: + return f'"{column_name}"' + return column_name + + def escape_insert_columns(self, insert_script: str) -> str: + """INSERT 显式列清单需要和 CREATE / COMMENT 使用同一套列名转义。""" + + match = re.match( + r"(INSERT INTO\s+\S+\s*\()([^)]+)(\)\s+VALUES\s+[\s\S]*)", + insert_script, + flags=re.IGNORECASE, + ) + if not match: + return insert_script + + columns = [ + self.escape_column_name(column.strip()) + for column in match.group(2).split(",") + ] + return f"{match.group(1)}{', '.join(columns)}{match.group(3)}" + @staticmethod def inserts(table_name: str, script_content: str) -> Generator: PREFIX = f"INSERT INTO `{table_name}`" @@ -204,18 +233,55 @@ class Convertor(ABC): Generator[str]: create index 语句 """ - def generate_columns(columns): - keys = [ - f"{col['name'].lower()}{' ' + col['order'].lower() if col['order'] != 'ASC' else ''}" - for col in columns[0] - ] - return ", ".join(keys) - - for no, index in enumerate(ddl["index"], 1): - columns = generate_columns(index["columns"]) + for no, index in enumerate(ddl.get("index", []), 1): + columns = ", ".join(Convertor.index_columns(index.get("columns", []))) + if not columns: + continue table_name = ddl["table_name"].lower() yield f"CREATE INDEX idx_{table_name}_{no:02d} ON {table_name} ({columns})" + @staticmethod + def index_columns(columns) -> list: + """兼容 simple-ddl-parser 不同版本的索引列结构。""" + + keys = [] + + def append(name, order="ASC"): + if not name: + return + column_name = str(name).strip("`").lower() + column_order = str(order or "ASC").upper() + if column_order == "DESC": + keys.append(f"{column_name} desc") + else: + keys.append(column_name) + + def visit(value): + # 普通索引常见结构:[[{'name': 'user_id', 'order': 'ASC'}]] + if isinstance(value, (list, tuple)): + for item in value: + visit(item) + return + if isinstance(value, dict): + name = value.get("name") + if isinstance(name, (dict, list, tuple)): + visit(name) + return + append(name, value.get("order", "ASC")) + return + # 唯一索引在部分版本中会被解析成 ['mobile', 'ASC', 'tenant_id', 'ASC']。 + if isinstance(value, str): + token = value.strip("`") + order = token.upper() + if order in ("ASC", "DESC"): + if order == "DESC" and keys and not keys[-1].endswith(" desc"): + keys[-1] = f"{keys[-1]} desc" + return + append(token) + + visit(columns) + return keys + @staticmethod def unique_index(ddl: Dict) -> Generator: if "constraints" in ddl and "uniques" in ddl["constraints"]: @@ -223,7 +289,9 @@ class Convertor(ABC): for uk in uk_list: table_name = ddl["table_name"] uk_name = uk["constraint_name"] - uk_columns = uk["columns"] + uk_columns = Convertor.index_columns(uk["columns"]) + if not uk_columns: + continue yield table_name, uk_name, uk_columns @staticmethod @@ -381,7 +449,7 @@ class PostgreSQLConvertor(Convertor): ) nullable = "NULL" if col["nullable"] else "NOT NULL" default = f"DEFAULT {col['default']}" if col["default"] is not None else "" - return f"{name} {full_type} {nullable} {default}" + return f"{self.escape_column_name(name)} {full_type} {nullable} {default}" table_name = ddl["table_name"].lower() columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]] @@ -406,7 +474,7 @@ CREATE TABLE {table_name} ( for column in table_ddl["columns"]: table_comment = column["comment"] script += ( - f"COMMENT ON COLUMN {table_ddl['table_name']}.{column['name']} IS '{table_comment}';" + f"COMMENT ON COLUMN {table_ddl['table_name']}.{self.escape_column_name(column['name'])} IS '{table_comment}';" + "\n" ) @@ -435,6 +503,7 @@ CREATE TABLE {table_name} ( """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence""" inserts = list(Convertor.inserts(table_name, self.content)) + inserts = [self.escape_insert_columns(s) for s in inserts] # 转换 MySQL 字符串转义为 PostgreSQL 格式:\\ -> \,\' -> '' inserts = [re.sub(r"\\\\|\\'", lambda m: "\\" if m.group() == "\\\\" else "''", s) for s in inserts] ## 生成 insert 脚本 @@ -482,6 +551,8 @@ INSERT INTO dual VALUES (1); class OracleConvertor(Convertor): + reserved_column_names = {"level", "size"} + def __init__(self, src): super().__init__(src, "Oracle") @@ -526,10 +597,8 @@ class OracleConvertor(Convertor): # Oracle的 INSERT '' 不能通过NOT NULL校验,因此对文字类型字段覆写为 NULL nullable = "NULL" if type in ("varchar", "text", "longtext") else nullable default = f"DEFAULT {col['default']}" if col["default"] is not None else "" - # Oracle 中 size 不能作为字段名 - field_name = '"size"' if name == "size" else name # Oracle DEFAULT 定义在 NULLABLE 之前 - return f"{field_name} {full_type} {default} {nullable}" + return f"{self.escape_column_name(name)} {full_type} {default} {nullable}" table_name = ddl["table_name"].lower() columns = [f"{generate_column(col).strip()}" for col in ddl["columns"]] @@ -554,7 +623,7 @@ CREATE TABLE {table_name} ( for column in table_ddl["columns"]: table_comment = column["comment"] script += ( - f"COMMENT ON COLUMN {table_ddl['table_name']}.{column['name']} IS '{table_comment}';" + f"COMMENT ON COLUMN {table_ddl['table_name']}.{self.escape_column_name(column['name'])} IS '{table_comment}';" + "\n" ) @@ -586,6 +655,7 @@ CREATE TABLE {table_name} ( """拷贝 INSERT 语句""" inserts = [] for insert_script in Convertor.inserts(table_name, self.content): + insert_script = self.escape_insert_columns(insert_script) # 对日期数据添加 TO_DATE 转换 insert_script = re.sub( r"('\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}')", @@ -907,6 +977,8 @@ SET IDENTITY_INSERT {table_name.lower()} OFF; class KingbaseConvertor(PostgreSQLConvertor): + reserved_column_names = {"level"} + def __init__(self, src): super().__init__(src) self.db_type = "Kingbase" @@ -925,7 +997,7 @@ class KingbaseConvertor(PostgreSQLConvertor): if full_type == "text": nullable = "NULL" default = f"DEFAULT {col['default']}" if col["default"] is not None else "" - return f"{name} {full_type} {nullable} {default}" + return f"{self.escape_column_name(name)} {full_type} {nullable} {default}" table_name = ddl["table_name"].lower() columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]] @@ -945,23 +1017,32 @@ CREATE TABLE {table_name} ( class OpengaussConvertor(KingbaseConvertor): + reserved_column_names = set() + def __init__(self, src): super().__init__(src) self.db_type = "OpenGauss" +class HighGoConvertor(PostgreSQLConvertor): + def __init__(self, src): + super().__init__(src) + self.db_type = "HighGo" + + def main(): parser = argparse.ArgumentParser(description="芋道系统数据库转换工具") parser.add_argument( "type", type=str, help="目标数据库类型", - choices=["postgres", "oracle", "sqlserver", "dm8", "kingbase", "opengauss"], + choices=["postgres", "oracle", "sqlserver", "dm8", "kingbase", "opengauss", "highgo"], ) parser.add_argument( "path", type=str, help="源数据库脚本路径", + nargs="?", default="../mysql/ruoyi-vue-pro.sql" ) args = parser.parse_args() @@ -980,6 +1061,8 @@ def main(): convertor = KingbaseConvertor(sql_file) elif args.type == "opengauss": convertor = OpengaussConvertor(sql_file) + elif args.type == "highgo": + convertor = HighGoConvertor(sql_file) else: raise NotImplementedError(f"不支持目标数据库类型: {args.type}") diff --git a/sql/tools/docker-compose.yaml b/sql/tools/docker-compose.yaml index 0fa95130b2..de25dfafaf 100644 --- a/sql/tools/docker-compose.yaml +++ b/sql/tools/docker-compose.yaml @@ -7,6 +7,7 @@ volumes: dm8: { } kingbase: { } opengauss: { } + highgo: { } services: mysql: @@ -131,4 +132,16 @@ services: volumes: - opengauss:/var/lib/opengauss - ../opengauss/ruoyi-vue-pro.sql:/tmp/schema.sql:ro - # docker compose exec opengauss bash -c '/usr/local/opengauss/bin/gsql -U $GS_USERNAME -W $GS_PASSWORD -d postgres -f /tmp/schema.sql' \ No newline at end of file + # docker compose exec opengauss bash -c '/usr/local/opengauss/bin/gsql -U $GS_USERNAME -W $GS_PASSWORD -d postgres -f /tmp/schema.sql' + + highgo: + # 使用瀚高官方提供的 Docker 镜像,加载后打成本地标签: + # docker tag : highgo:local + image: highgo:local + restart: unless-stopped + ports: + - "5866:5866" + volumes: + - highgo:/home/highgo/hgdb/data + - ../highgo/quartz.sql:/tmp/quartz.sql:ro + - ../highgo/ruoyi-vue-pro.sql:/tmp/schema.sql:ro diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index a5360cf01a..10a00e3232 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -14,7 +14,7 @@ https://github.com/YunaiV/ruoyi-vue-pro - 2026.04-SNAPSHOT + 2026.05-SNAPSHOT 1.7.2 4.1.0 @@ -50,6 +50,8 @@ 1.4.0 1.22.2 + 0.29.5 + 2.5.1 1.18.46 1.6.3 5.8.44 @@ -57,6 +59,7 @@ 1.3.0 2.4.1 1.2.83 + 2.0.61 33.6.0-jre 2.14.5 3.13.0 @@ -65,7 +68,7 @@ 3.3.0 2.7.0 3.0.6 - 4.2.12.Final + 4.2.14.Final 1.2.5 4.5.26 4.12.0 @@ -75,10 +78,11 @@ 2.44.0 1.16.7 1.4.0 - 2.3.2 + 2.3.4 2.3.2 4.8.2-20260501.180637 - 4.40.771.ALL + 1.80 + 4.40.806.ALL @@ -521,6 +525,11 @@ fastjson ${fastjson.version} + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + com.google.guava @@ -569,6 +578,18 @@ ${jsoup.version} + + com.github.houbb + sensitive-word + ${sensitive-word.version} + + + + com.belerweb + pinyin4j + ${pinyin4j.version} + + io.vertx @@ -657,6 +678,24 @@ + + + org.bouncycastle + bcprov-jdk18on + ${bouncycastle.version} + + + org.bouncycastle + bcutil-jdk18on + ${bouncycastle.version} + + + org.bouncycastle + bcpkix-jdk18on + ${bouncycastle.version} + + com.github.binarywang weixin-java-pay diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java index 28265a5d59..b2db78b289 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java @@ -124,6 +124,22 @@ public class CollectionUtils { return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet()); } + public static Set convertLinkedSet(Collection from, Function func) { + if (CollUtil.isEmpty(from)) { + return new LinkedHashSet<>(); + } + return from.stream().map(func).filter(Objects::nonNull) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + public static Set convertLinkedSet(Collection from, Function func, Predicate filter) { + if (CollUtil.isEmpty(from)) { + return new LinkedHashSet<>(); + } + return from.stream().filter(filter).map(func).filter(Objects::nonNull) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + public static Map convertMapByFilter(Collection from, Predicate filter, Function keyFunc) { if (CollUtil.isEmpty(from)) { return new HashMap<>(); @@ -372,4 +388,14 @@ public class CollectionUtils { return false; } + /** + * 把单元素 head 与集合 tail 合并成新 List(head 在前,tail 顺序保留) + */ + public static List of(T head, Collection tail) { + List list = new ArrayList<>(); + list.add(head); + CollUtil.addAll(list, tail); + return list; + } + } \ No newline at end of file diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java index 34e4d85f2f..f942d6ec83 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.common.util.date; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.LocalDateTimeUtil; -import cn.hutool.core.date.TemporalAccessorUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum; @@ -16,6 +15,7 @@ import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAdjusters; import java.util.ArrayList; import java.util.List; +import java.util.TimeZone; import static cn.hutool.core.date.DatePattern.*; @@ -33,6 +33,11 @@ public class LocalDateTimeUtils { public static DateTimeFormatter UTC_MS_WITH_XXX_OFFSET_FORMATTER = createFormatter(UTC_MS_WITH_XXX_OFFSET_PATTERN); + /** + * 默认时区 + */ + private static final ZoneId DEFAULT_ZONE_ID = TimeZone.getTimeZone(DateUtils.TIME_ZONE_DEFAULT).toZoneId(); + /** * 解析时间 * @@ -65,6 +70,27 @@ public class LocalDateTimeUtils { return date.isAfter(LocalDateTime.now()); } + /** + * 将 Unix 秒时间戳转换为默认时区的本地时间 + * + * @param epochSecond Unix 秒时间戳 + * @return 本地时间 + */ + public static LocalDateTime ofEpochSecond(long epochSecond) { + return ofEpochSecond(epochSecond, DEFAULT_ZONE_ID); + } + + /** + * 将 Unix 秒时间戳转换为指定时区的本地时间 + * + * @param epochSecond Unix 秒时间戳 + * @param zoneId 时区编号 + * @return 本地时间 + */ + public static LocalDateTime ofEpochSecond(long epochSecond, ZoneId zoneId) { + return LocalDateTime.ofInstant(Instant.ofEpochSecond(epochSecond), zoneId); + } + /** * 创建指定时间 * @@ -236,6 +262,23 @@ public class LocalDateTimeUtils { return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN); } + /** + * 获取最近 N 天的 0 点时刻序列(升序,含今天) + *

+ * 例:getLatestDays(3) 返回 [前天 00:00, 昨天 00:00, 今天 00:00] + * + * @param days 天数(含今天) + * @return 升序的 LocalDateTime 列表 + */ + public static List getLatestDays(int days) { + LocalDateTime today = getToday(); + List dates = new ArrayList<>(days); + for (int i = days - 1; i >= 0; i--) { + dates.add(today.minusDays(i)); + } + return dates; + } + public static List getDateRangeList(LocalDateTime startTime, LocalDateTime endTime, Integer interval) { @@ -376,7 +419,18 @@ public class LocalDateTimeUtils { * @throws DateTimeException 如果转换过程中发生时间超出范围或其他时间处理异常 */ public static Long toEpochSecond(LocalDateTime sourceDateTime) { - return TemporalAccessorUtil.toInstant(sourceDateTime).getEpochSecond(); + return toEpochSecond(sourceDateTime, DEFAULT_ZONE_ID); + } + + /** + * 将给定的 {@link LocalDateTime} 按指定时区转换为自 Unix 纪元时间(1970-01-01T00:00:00Z)以来的秒数。 + * + * @param sourceDateTime 需要转换的本地日期时间,不能为空 + * @param zoneId 时区编号 + * @return 自 1970-01-01T00:00:00Z 起的秒数(epoch second) + */ + public static Long toEpochSecond(LocalDateTime sourceDateTime, ZoneId zoneId) { + return sourceDateTime.atZone(zoneId).toEpochSecond(); } } diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java index d27498185b..95220a9fb6 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java @@ -9,6 +9,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriUtils; import java.net.URI; import java.net.URLDecoder; @@ -55,11 +56,61 @@ public class HttpUtils { * @return 解码后的路径 */ public static String decodeUrlPath(String path) { + if (StrUtil.isEmpty(path)) { + return path; + } // 先将 + 替换为 %2B,避免被 URLDecoder 解码为空格 String encoded = path.replace("+", "%2B"); return URLDecoder.decode(encoded, StandardCharsets.UTF_8); } + /** + * 编码 URL 路径,按路径段编码,保留 / 分隔符 + * + * @param path URL 路径,例如 20250602/xxx.pdf + * @return 编码后的路径 + */ + public static String encodeUrlPath(String path) { + if (StrUtil.isEmpty(path)) { + return path; + } + String[] segments = path.split(StrUtil.SLASH, -1); + StringBuilder result = new StringBuilder(path.length()); + for (int i = 0; i < segments.length; i++) { + if (i > 0) { + result.append(StrUtil.SLASH); + } + result.append(encodeUrlPathSegment(segments[i])); + } + return result.toString(); + } + + /** + * 编码 URL 路径段 + * + * @param segment URL 路径段 + * @return 编码后的路径段 + */ + public static String encodeUrlPathSegment(String segment) { + return UriUtils.encodePathSegment(segment, StandardCharsets.UTF_8); + } + + public static String removeUrlPathQueryAndFragment(String path) { + if (StrUtil.isEmpty(path)) { + return path; + } + int endIndex = path.length(); + int queryIndex = path.indexOf('?'); + if (queryIndex >= 0) { + endIndex = queryIndex; + } + int fragmentIndex = path.indexOf('#'); + if (fragmentIndex >= 0 && fragmentIndex < endIndex) { + endIndex = fragmentIndex; + } + return path.substring(0, endIndex); + } + public static String replaceUrlQuery(String url, String key, String value) { UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset()); // 先移除;再添加 @@ -200,4 +251,14 @@ public class HttpUtils { } } + /** + * WebSocket URL 切换成 HTTP URL:ws:// → http://;wss:// → https://;其它格式原样保留 + * + * @param url 原始 URL + * @return 切换协议后的 URL + */ + public static String wsUrlToHttp(String url) { + return StrUtil.startWithIgnoreCase(url, "ws") ? "http" + url.substring(2) : url; + } + } diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java index 60b45b8621..0b34f88f00 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java @@ -22,6 +22,7 @@ import java.lang.reflect.Type; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * JSON 工具类 @@ -174,6 +175,23 @@ public class JsonUtils { } } + /** + * 解析 JSON 字符串成 Map,空字符串或解析失败返回 null + * + * @param text JSON 字符串 + * @return Map 对象 + */ + public static Map parseMap(String text) { + if (StrUtil.isEmpty(text)) { + return null; + } + try { + return objectMapper.readValue(text, new TypeReference>() {}); + } catch (IOException e) { + return null; + } + } + /** * 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null * @@ -236,6 +254,14 @@ public class JsonUtils { } } + public static String getText(JsonNode node, String fieldName) { + if (node == null) { + return null; + } + JsonNode value = node.get(fieldName); + return value != null && !value.isNull() ? value.asText() : null; + } + public static boolean isJson(String text) { return JSONUtil.isTypeJSON(text); } diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java index 47a53a5957..f0e423e8c3 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.string; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.pinyin.PinyinUtil; import org.aspectj.lang.JoinPoint; import java.util.Arrays; @@ -78,6 +79,25 @@ public class StrUtils { .collect(Collectors.joining("\n")); } + /** + * 转小写拼音,字之间以空格分隔,便于调用方按需拼接 / 取首字母 / 拼音搜索 + * + * 例:「老张」→ "lao zhang"、「ZhangSan」→ "zhangsan" + * 英文 / 数字 / 符号原样返回,空值返回 null + * + * 注意:底层依赖 hutool-extra 的 {@link PinyinUtil},需要业务模块自行引入拼音引擎依赖 + * (pinyin4j / TinyPinyin / Bopomofo4j 任选其一),否则运行时会抛 NoClassDefFoundError + * + * @param str 字符串 + * @return 拼音串(保留空格分隔) + */ + public static String toPinyin(String str) { + if (StrUtil.isBlank(str)) { + return null; + } + return PinyinUtil.getPinyin(str); + } + /** * 拼接方法的参数 * diff --git a/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/http/HttpUtilsTest.java b/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/http/HttpUtilsTest.java index a42874f04a..e2b6120073 100644 --- a/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/http/HttpUtilsTest.java +++ b/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/http/HttpUtilsTest.java @@ -9,6 +9,36 @@ import static org.junit.jupiter.api.Assertions.assertEquals; */ public class HttpUtilsTest { + @Test + public void testEncodeUrlPath() { + // 准备参数 + String path = "avatar/中文 100%+文件.jpg"; + + // 调用 + String result = HttpUtils.encodeUrlPath(path); + + // 断言 + assertEquals("avatar/%E4%B8%AD%E6%96%87%20100%25+%E6%96%87%E4%BB%B6.jpg", result); + } + + @Test + public void testDecodeUrlPath() { + // 准备参数:+ 是路径字符,不应该按 query parameter 语义解码为空格 + String path = "avatar/%E4%B8%AD%E6%96%87%20100%25+%E6%96%87%E4%BB%B6.jpg"; + + // 调用 + String result = HttpUtils.decodeUrlPath(path); + + // 断言 + assertEquals("avatar/中文 100%+文件.jpg", result); + } + + @Test + public void testRemoveUrlPathQueryAndFragment() { + assertEquals("avatar/test.jpg", HttpUtils.removeUrlPathQueryAndFragment("avatar/test.jpg?token=1#preview")); + assertEquals("avatar/test.jpg", HttpUtils.removeUrlPathQueryAndFragment("avatar/test.jpg#preview?token=1")); + } + @Test public void testReplaceUrlQuery_replace() { // 准备参数 diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java index cd3354b59c..17f302b7b1 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.cache.BatchStrategies; import org.springframework.data.redis.cache.RedisCacheConfiguration; @@ -159,28 +160,37 @@ public class YudaoTenantAutoConfiguration { // ========== MQ ========== - @Bean - public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() { - return new TenantRedisMessageInterceptor(); + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor") + public static class TenantRedisMQConfiguration { + + @Bean + public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() { + return new TenantRedisMessageInterceptor(); + } + } - @Bean + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate") - public TenantRabbitMQInitializer tenantRabbitMQInitializer() { - return new TenantRabbitMQInitializer(); + public static class TenantRabbitMQConfiguration { + + @Bean + public TenantRabbitMQInitializer tenantRabbitMQInitializer() { + return new TenantRabbitMQInitializer(); + } + } - @Bean + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "org.apache.rocketmq.spring.core.RocketMQTemplate") - public TenantRocketMQInitializer tenantRocketMQInitializer() { - return new TenantRocketMQInitializer(); - } + public static class TenantRocketMQConfiguration { - // ========== Job ========== + @Bean + public TenantRocketMQInitializer tenantRocketMQInitializer() { + return new TenantRocketMQInitializer(); + } - @Bean - public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) { - return new TenantJobAspect(tenantFrameworkService); } // ========== Redis ========== @@ -196,7 +206,25 @@ public class YudaoTenantAutoConfiguration { RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize())); // 创建 TenantRedisCacheManager 对象 - return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, tenantProperties.getIgnoreCaches()); + TenantRedisCacheManager cacheManager = new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, + tenantProperties.getIgnoreCaches()); + // 开启事务感知:@Transactional 方法内的 @CacheEvict / @CachePut 自动延迟到 afterCommit, + // 避免事务未提交就清缓存被并发读穿写脏值;无事务时立即生效,行为不变 + cacheManager.setTransactionAware(true); + return cacheManager; + } + + // ========== Job ========== + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "cn.iocoder.yudao.framework.quartz.core.handler.JobHandler") + public static class TenantJobConfiguration { + + @Bean + public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) { + return new TenantJobAspect(tenantFrameworkService); + } + } } diff --git a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java index b80244456d..5b0d8866cf 100644 --- a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java @@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessag import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RedissonClient; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.context.annotation.Bean; @@ -70,7 +69,8 @@ public class YudaoRedisMQConsumerAutoConfiguration { public RedisPendingMessageResendJob redisPendingMessageResendJob(List> listeners, RedisMQTemplate redisTemplate, RedissonClient redissonClient) { - return new RedisPendingMessageResendJob(listeners, redisTemplate, redissonClient); + return new RedisPendingMessageResendJob(listeners, redisTemplate, redissonClient, + RedisPendingMessageResendJob.DEFAULT_RESEND_LOCK_KEY); } /** @@ -81,7 +81,8 @@ public class YudaoRedisMQConsumerAutoConfiguration { public RedisStreamMessageCleanupJob redisStreamMessageCleanupJob(List> listeners, RedisMQTemplate redisTemplate, RedissonClient redissonClient) { - return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient); + return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient, + RedisStreamMessageCleanupJob.DEFAULT_CLEANUP_LOCK_KEY); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java index bb16be0eeb..d0b7ae3099 100644 --- a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java +++ b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java @@ -23,7 +23,9 @@ import java.util.Objects; @AllArgsConstructor public class RedisPendingMessageResendJob { - private static final String LOCK_KEY = "redis:stream:pending-message-resend:lock"; + public static final String DEFAULT_RESEND_LOCK_KEY = "redis:stream:pending-message-resend:lock"; + + public static final String IOT_RESEND_LOCK_KEY = "redis:stream:pending-message-resend:lock:iot"; /** * 消息超时时间,默认 5 分钟 @@ -36,22 +38,26 @@ public class RedisPendingMessageResendJob { private final List> listeners; private final RedisMQTemplate redisTemplate; private final RedissonClient redissonClient; + private final String resendLockKey; /** * 一分钟执行一次,这里选择每分钟的 35 秒执行,是为了避免整点任务过多的问题 */ @Scheduled(cron = "35 * * * * ?") public void messageResend() { - RLock lock = redissonClient.getLock(LOCK_KEY); - // 尝试加锁 + RLock lock = redissonClient.getLock(resendLockKey); if (lock.tryLock()) { try { execute(); } catch (Exception ex) { - log.error("[messageResend][执行异常]", ex); + log.error("[messageResend][执行异常][lockKey={}]", resendLockKey, ex); } finally { - lock.unlock(); + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } } + } else { + log.debug("[messageResend][未获取到锁,跳过本轮][lockKey={}]", resendLockKey); } } diff --git a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java index 19da84594e..eff64fd4d1 100644 --- a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java +++ b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java @@ -23,7 +23,16 @@ import java.util.List; @AllArgsConstructor public class RedisStreamMessageCleanupJob { - private static final String LOCK_KEY = "redis:stream:message-cleanup:lock"; + /** + * 业务 MQ(Spring 容器内 AbstractRedisStreamMessageListener)清理任务使用的分布式锁 + */ + public static final String DEFAULT_CLEANUP_LOCK_KEY = "redis:stream:message-cleanup:lock"; + + /** + * IoT Redis 总线清理任务使用的分布式锁(须与 {@link #DEFAULT_CLEANUP_LOCK_KEY} 区分,否则会共抢一把锁, + * 同一时刻只有一侧能执行 XTRIM,另一侧 Stream 可能无限积压) + */ + public static final String IOT_CLEANUP_LOCK_KEY = "redis:stream:message-cleanup:lock:iot"; /** * 保留的消息数量,默认保留最近 10000 条消息 @@ -33,22 +42,29 @@ public class RedisStreamMessageCleanupJob { private final List> listeners; private final RedisMQTemplate redisTemplate; private final RedissonClient redissonClient; + /** + * Redisson 锁键(多 Bean 注册清理任务时必须各不相同) + */ + private final String cleanupLockKey; /** * 每小时执行一次清理任务 */ @Scheduled(cron = "0 0 * * * ?") public void cleanup() { - RLock lock = redissonClient.getLock(LOCK_KEY); - // 尝试加锁 + RLock lock = redissonClient.getLock(cleanupLockKey); if (lock.tryLock()) { try { execute(); } catch (Exception ex) { - log.error("[cleanup][执行异常]", ex); + log.error("[cleanup][执行异常][lockKey={}]", cleanupLockKey, ex); } finally { - lock.unlock(); + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } } + } else { + log.debug("[cleanup][未获取到锁,跳过本轮][lockKey={}]", cleanupLockKey); } } @@ -59,8 +75,8 @@ public class RedisStreamMessageCleanupJob { StreamOperations ops = redisTemplate.getRedisTemplate().opsForStream(); listeners.forEach(listener -> { try { - // 使用 XTRIM 命令清理消息,只保留最近的 MAX_LEN 条消息 - Long trimCount = ops.trim(listener.getStreamKey(), MAX_COUNT, true); + // 使用 XTRIM MAXLEN 精确裁剪(approximate=false),避免 ~ 模式下长期明显高于上限 + Long trimCount = ops.trim(listener.getStreamKey(), MAX_COUNT, false); if (trimCount != null && trimCount > 0) { log.info("[execute][Stream({}) 清理消息数量({})]", listener.getStreamKey(), trimCount); } diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml b/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml index 2e6875b06c..b356fe8f2e 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml @@ -105,6 +105,13 @@ org.dromara easy-trans-mybatis-plus-extend + + + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java index 460ea78116..62a5b08300 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java @@ -20,51 +20,49 @@ public enum DbTypeEnum { /** * H2 - * - * 注意:H2 不支持 find_in_set 函数 */ - H2(DbType.H2, "H2", ""), + H2(DbType.H2, "H2", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), /** * MySQL */ - MY_SQL(DbType.MYSQL, "MySQL", "FIND_IN_SET('#{value}', #{column}) <> 0"), + MY_SQL(DbType.MYSQL, "MySQL", "FIND_IN_SET(#{value}, #{column}) <> 0"), /** * Oracle */ - ORACLE(DbType.ORACLE, "Oracle", "FIND_IN_SET('#{value}', #{column}) <> 0"), + ORACLE(DbType.ORACLE, "Oracle", "INSTR(',' || #{column} || ',', ',' || #{value} || ',') > 0"), /** * PostgreSQL * * 华为 openGauss 使用 ProductName 与 PostgreSQL 相同 */ - POSTGRE_SQL(DbType.POSTGRE_SQL,"PostgreSQL", "POSITION('#{value}' IN #{column}) <> 0"), + POSTGRE_SQL(DbType.POSTGRE_SQL, "PostgreSQL", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), /** * SQL Server */ - SQL_SERVER(DbType.SQL_SERVER, "Microsoft SQL Server", "CHARINDEX(',' + #{value} + ',', ',' + #{column} + ',') <> 0"), + SQL_SERVER(DbType.SQL_SERVER, "Microsoft SQL Server", "CHARINDEX(',' + CAST(#{value} AS varchar(255)) + ',', ',' + #{column} + ',') > 0"), /** * SQL Server 2005 */ - SQL_SERVER2005(DbType.SQL_SERVER2005, "Microsoft SQL Server 2005", "CHARINDEX(',' + #{value} + ',', ',' + #{column} + ',') <> 0"), + SQL_SERVER2005(DbType.SQL_SERVER2005, "Microsoft SQL Server 2005", "CHARINDEX(',' + CAST(#{value} AS varchar(255)) + ',', ',' + #{column} + ',') > 0"), /** * 达梦 */ - DM(DbType.DM, "DM DBMS", "FIND_IN_SET('#{value}', #{column}) <> 0"), + DM(DbType.DM, "DM DBMS", "FIND_IN_SET(#{value}, #{column}) <> 0"), /** * 人大金仓 */ - KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION('#{value}' IN #{column}) <> 0"), + KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), /** * OceanBase */ - OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET('#{value}', #{column}) <> 0") + OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET(#{value}, #{column}) <> 0") ; @@ -95,7 +93,9 @@ public enum DbTypeEnum { } public static String getFindInSetTemplate(DbType dbType) { - return Optional.of(MAP_BY_MP.get(dbType).getFindInSetTemplate()) + return Optional.ofNullable(MAP_BY_MP.get(dbType)) + .map(DbTypeEnum::getFindInSetTemplate) + .filter(StrUtil::isNotBlank) .orElseThrow(() -> new IllegalArgumentException("FIND_IN_SET not supported")); } } diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java index 5ccee2008f..08bdf19c41 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java @@ -170,6 +170,17 @@ public interface BaseMapperX extends MPJBaseMapper { return CollUtil.getFirst(list); } + /** + * 获取满足条件的最新一条记录 + *

+ * 目的:解决并发场景下,插入多条记录后,使用 selectOne 会报错的问题 + * + * @param queryWrapper 查询条件 + * @return 最新一条;不存在返回 null + */ + default T selectLastOne(LambdaQueryWrapper queryWrapper) { + return CollUtil.getLast(selectList(queryWrapper)); + } default Long selectCount() { return selectCount(new QueryWrapper<>()); diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperX.java index a728365e6e..b766199547 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperX.java @@ -25,6 +25,13 @@ public class LambdaQueryWrapperX extends LambdaQueryWrapper { return this; } + public LambdaQueryWrapperX likeRightIfPresent(SFunction column, String val) { + if (StringUtils.hasText(val)) { + return (LambdaQueryWrapperX) super.likeRight(column, val); + } + return this; + } + public LambdaQueryWrapperX inIfPresent(SFunction column, Collection values) { if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) { return (LambdaQueryWrapperX) super.in(column, values); diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java index aed2f02df3..6b0de23c5d 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java @@ -27,6 +27,13 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper { return this; } + public MPJLambdaWrapperX likeRightIfPresent(SFunction column, String val) { + if (StringUtils.hasText(val)) { + return (MPJLambdaWrapperX) super.likeRight(column, val); + } + return this; + } + public MPJLambdaWrapperX inIfPresent(SFunction column, Collection values) { if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) { return (MPJLambdaWrapperX) super.in(column, values); @@ -102,7 +109,6 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper { return this; } - // ========== 重写父类方法,方便链式调用 ========== @Override diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java index 087b1b846e..bff0122498 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java @@ -25,6 +25,13 @@ public class QueryWrapperX extends QueryWrapper { return this; } + public QueryWrapperX likeRightIfPresent(String column, String val) { + if (StringUtils.hasText(val)) { + return (QueryWrapperX) super.likeRight(column, val); + } + return this; + } + public QueryWrapperX inIfPresent(String column, Collection values) { if (!CollectionUtils.isEmpty(values)) { return (QueryWrapperX) super.in(column, values); @@ -95,13 +102,13 @@ public class QueryWrapperX extends QueryWrapper { } public QueryWrapperX betweenIfPresent(String column, Object[] values) { - if (values!= null && values.length != 0 && values[0] != null && values[1] != null) { + if (values != null && values.length != 0 && values[0] != null && values[1] != null) { return (QueryWrapperX) super.between(column, values[0], values[1]); } - if (values!= null && values.length != 0 && values[0] != null) { + if (values != null && values.length != 0 && values[0] != null) { return (QueryWrapperX) ge(column, values[0]); } - if (values!= null && values.length != 0 && values[1] != null) { + if (values != null && values.length != 0 && values[1] != null) { return (QueryWrapperX) le(column, values[1]); } return this; diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java index 2b4d138ef9..ab4612cb0f 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java @@ -23,6 +23,7 @@ import net.sf.jsqlparser.schema.Table; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.regex.Pattern; /** * MyBatis 工具类 @@ -31,6 +32,12 @@ public class MyBatisUtils { private static final String MYSQL_ESCAPE_CHARACTER = "`"; + private static final Pattern SAFE_COLUMN_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)*$"); + + private static final String FIND_IN_SET_VALUE_PLACEHOLDER = "#{value}"; + + private static final String FIND_IN_SET_COLUMN_PLACEHOLDER = "#{column}"; + public static Page buildPage(PageParam pageParam) { return buildPage(pageParam, null); } @@ -42,8 +49,11 @@ public class MyBatisUtils { // 排序字段 if (CollUtil.isNotEmpty(sortingFields)) { for (SortingField sortingField : sortingFields) { - page.addOrder(new OrderItem().setAsc(SortingField.ORDER_ASC.equals(sortingField.getOrder())) - .setColumn(StrUtil.toUnderlineCase(sortingField.getField()))); + String columnName = buildSafeOrderColumn(sortingField.getField()); + if (columnName == null) { + continue; + } + page.addOrder(new OrderItem().setAsc(isAscOrder(sortingField.getOrder())).setColumn(columnName)); } } return page; @@ -57,23 +67,29 @@ public class MyBatisUtils { if (wrapper instanceof QueryWrapper) { QueryWrapper query = (QueryWrapper) wrapper; for (SortingField sortingField : sortingFields) { - query.orderBy(true, - SortingField.ORDER_ASC.equals(sortingField.getOrder()), - StrUtil.toUnderlineCase(sortingField.getField())); + String columnName = buildSafeOrderColumn(sortingField.getField()); + if (columnName == null) { + continue; + } + query.orderBy(true, isAscOrder(sortingField.getOrder()), columnName); } } else if (wrapper instanceof LambdaQueryWrapper) { // LambdaQueryWrapper 不直接支持字符串字段排序,使用 last 方法拼接 ORDER BY LambdaQueryWrapper lambdaQuery = (LambdaQueryWrapper) wrapper; StringBuilder orderBy = new StringBuilder(); for (SortingField sortingField : sortingFields) { + String columnName = buildSafeOrderColumn(sortingField.getField()); + if (columnName == null) { + continue; + } if (StrUtil.isNotEmpty(orderBy)) { orderBy.append(", "); } - orderBy.append(StrUtil.toUnderlineCase(sortingField.getField())) - .append(" ") - .append(SortingField.ORDER_ASC.equals(sortingField.getOrder()) ? "ASC" : "DESC"); + orderBy.append(columnName).append(" ").append(getOrderDirection(sortingField.getOrder())); + } + if (StrUtil.isNotEmpty(orderBy)) { + lambdaQuery.last("ORDER BY " + orderBy); } - lambdaQuery.last("ORDER BY " + orderBy); // 另外个思路:https://blog.csdn.net/m0_59084856/article/details/138450913 } else { throw new IllegalArgumentException("Unsupported wrapper type: " + wrapper.getClass().getName()); @@ -81,6 +97,22 @@ public class MyBatisUtils { } + public static boolean isAscOrder(String order) { + return SortingField.ORDER_ASC.equals(order); + } + + public static String getOrderDirection(String order) { + return isAscOrder(order) ? "ASC" : "DESC"; + } + + private static String buildSafeOrderColumn(String field) { + String columnName = StrUtil.toUnderlineCase(field); + if (StrUtil.isEmpty(columnName) || !SAFE_COLUMN_NAME_PATTERN.matcher(columnName).matches()) { + return null; + } + return columnName; + } + /** * 将拦截器添加到链中 * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置 @@ -129,15 +161,43 @@ public class MyBatisUtils { /** * 跨数据库的 find_in_set 实现 * - * @param column 字段名称 - * @param value 查询值(不带单引号) + * @param columnName 字段名称 * @return sql */ - public static String findInSet(String column, Object value) { + public static String findInSet(String columnName) { + return findInSet(columnName, 0); + } + + /** + * 跨数据库的 find_in_set 实现,适用于同一个 apply 语句中有多个参数的场景 + * + * @param columnName 字段名称 + * @param paramIndex apply 参数序号 + * @return sql + */ + public static String findInSetWithParamIndex(String columnName, int paramIndex) { + return findInSet(columnName, paramIndex); + } + + private static String findInSet(String columnName, int paramIndex) { DbType dbType = JdbcUtils.getDbType(); + return findInSet(dbType, columnName, paramIndex); + } + + static String findInSet(DbType dbType, String columnName, int paramIndex) { + if (!isSafeColumnName(columnName)) { + throw new IllegalArgumentException("Invalid column name: " + columnName); + } + if (paramIndex < 0) { + throw new IllegalArgumentException("Invalid param index: " + paramIndex); + } return DbTypeEnum.getFindInSetTemplate(dbType) - .replace("#{column}", column) - .replace("#{value}", StrUtil.toString(value)); + .replace(FIND_IN_SET_COLUMN_PLACEHOLDER, columnName) + .replace(FIND_IN_SET_VALUE_PLACEHOLDER, "{" + paramIndex + "}"); + } + + private static boolean isSafeColumnName(String columnName) { + return StrUtil.isNotEmpty(columnName) && SAFE_COLUMN_NAME_PATTERN.matcher(columnName).matches(); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java new file mode 100644 index 0000000000..abe8891540 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java @@ -0,0 +1,173 @@ +package cn.iocoder.yudao.framework.mybatis.core.util; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.SortingField; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link MyBatisUtils} 的单元测试 + */ +public class MyBatisUtilsTest { + + @Test + public void testBuildPage_sortingFields() { + // 准备参数 + PageParam pageParam = new PageParam(); + pageParam.setPageNo(2); + pageParam.setPageSize(20); + List sortingFields = Arrays.asList( + new SortingField("userName", SortingField.ORDER_ASC), + new SortingField("u.id", SortingField.ORDER_DESC), + new SortingField("name desc", SortingField.ORDER_DESC)); + + // 调用 + Page page = MyBatisUtils.buildPage(pageParam, sortingFields); + + // 断言 + assertEquals(2, page.getCurrent()); + assertEquals(20, page.getSize()); + assertEquals(2, page.orders().size()); + assertOrderItem(page.orders().get(0), "user_name", true); + assertOrderItem(page.orders().get(1), "u.id", false); + } + + @Test + public void testAddOrder_queryWrapper() { + // 准备参数 + QueryWrapper query = new QueryWrapper<>(); + List sortingFields = Arrays.asList( + new SortingField("userName", SortingField.ORDER_ASC), + new SortingField("u.id", SortingField.ORDER_DESC), + new SortingField("name;drop", SortingField.ORDER_ASC)); + + // 调用 + MyBatisUtils.addOrder(query, sortingFields); + + // 断言 + assertEquals(" ORDER BY user_name ASC,u.id DESC", query.getSqlSegment()); + } + + @Test + public void testAddOrder_lambdaQueryWrapper() { + // 准备参数 + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + List sortingFields = Arrays.asList( + new SortingField("userName", SortingField.ORDER_ASC), + new SortingField("u.id", SortingField.ORDER_DESC), + new SortingField("name`", SortingField.ORDER_ASC)); + + // 调用 + MyBatisUtils.addOrder(query, sortingFields); + + // 断言 + assertEquals(" ORDER BY user_name ASC, u.id DESC", query.getSqlSegment()); + } + + @Test + public void testAddOrder_lambdaQueryWrapper_invalidSortingFields() { + // 准备参数 + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + List sortingFields = Arrays.asList( + new SortingField("name desc", SortingField.ORDER_ASC), + new SortingField("name;drop", SortingField.ORDER_DESC)); + + // 调用 + MyBatisUtils.addOrder(query, sortingFields); + + // 断言 + assertEquals("", query.getSqlSegment()); + } + + @Test + public void testOrderDirection() { + assertTrue(MyBatisUtils.isAscOrder(SortingField.ORDER_ASC)); + assertFalse(MyBatisUtils.isAscOrder(SortingField.ORDER_DESC)); + assertEquals("ASC", MyBatisUtils.getOrderDirection(SortingField.ORDER_ASC)); + assertEquals("DESC", MyBatisUtils.getOrderDirection(SortingField.ORDER_DESC)); + assertEquals("DESC", MyBatisUtils.getOrderDirection(null)); + } + + @Test + public void testFindInSet() { + assertEquals("FIND_IN_SET({0}, websites) <> 0", + MyBatisUtils.findInSet(DbType.MYSQL, "websites", 0)); + assertEquals("POSITION(',' || CAST({0} AS VARCHAR) || ',' IN ',' || websites || ',') > 0", + MyBatisUtils.findInSet(DbType.H2, "websites", 0)); + assertEquals("INSTR(',' || t.websites || ',', ',' || {0} || ',') > 0", + MyBatisUtils.findInSet(DbType.ORACLE, "t.websites", 0)); + assertEquals("POSITION(',' || CAST({1} AS VARCHAR) || ',' IN ',' || websites || ',') > 0", + MyBatisUtils.findInSet(DbType.POSTGRE_SQL, "websites", 1)); + assertEquals("CHARINDEX(',' + CAST({2} AS varchar(255)) + ',', ',' + websites + ',') > 0", + MyBatisUtils.findInSet(DbType.SQL_SERVER, "websites", 2)); + } + + @Test + public void testFindInSet_invalidColumnName() { + assertThrows(IllegalArgumentException.class, + () -> MyBatisUtils.findInSet(DbType.MYSQL, "websites;drop table system_tenant", 0)); + assertThrows(IllegalArgumentException.class, + () -> MyBatisUtils.findInSet(DbType.MYSQL, "FIND_IN_SET(value, websites)", 0)); + } + + @Test + public void testFindInSet_invalidParamIndex() { + assertThrows(IllegalArgumentException.class, + () -> MyBatisUtils.findInSet(DbType.MYSQL, "websites", -1)); + } + + @Test + public void testFindInSet_applyBindsValue() { + // 准备参数 + QueryWrapper query = new QueryWrapper<>(); + String value = "test' OR 1 = 1"; + + // 调用 + query.apply(MyBatisUtils.findInSet(DbType.MYSQL, "to_mails", 0), value); + + // 断言:SQL 片段里只有 MyBatis Plus 参数占位,用户输入不会被直接拼接进去 + assertEquals("(FIND_IN_SET(#{ew.paramNameValuePairs.MPGENVAL1}, to_mails) <> 0)", + query.getSqlSegment()); + assertFalse(query.getSqlSegment().contains(value)); + assertEquals(value, query.getParamNameValuePairs().get("MPGENVAL1")); + } + + @Test + public void testFindInSet_applyBindsMultipleValues() { + // 准备参数 + QueryWrapper query = new QueryWrapper<>(); + String value1 = "1' OR 1 = 1"; + String value2 = "2' OR 1 = 1"; + + // 调用 + query.apply(MyBatisUtils.findInSet(DbType.MYSQL, "tag_ids", 0) + + " OR " + MyBatisUtils.findInSet(DbType.MYSQL, "tag_ids", 1), value1, value2); + + // 断言:多个参数都由 MyBatis Plus 生成占位符,不拼接用户输入 + assertEquals("(FIND_IN_SET(#{ew.paramNameValuePairs.MPGENVAL1}, tag_ids) <> 0" + + " OR FIND_IN_SET(#{ew.paramNameValuePairs.MPGENVAL2}, tag_ids) <> 0)", + query.getSqlSegment()); + assertFalse(query.getSqlSegment().contains(value1)); + assertFalse(query.getSqlSegment().contains(value2)); + assertEquals(value1, query.getParamNameValuePairs().get("MPGENVAL1")); + assertEquals(value2, query.getParamNameValuePairs().get("MPGENVAL2")); + } + + private void assertOrderItem(OrderItem orderItem, String column, boolean asc) { + assertEquals(column, orderItem.getColumn()); + assertEquals(asc, orderItem.isAsc()); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java index 636f821c61..cc94094066 100644 --- a/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java @@ -75,8 +75,12 @@ public class YudaoCacheAutoConfiguration { RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory()); RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize())); - // 创建 TenantRedisCacheManager 对象 - return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration); + // 创建 TimeoutRedisCacheManager 对象 + TimeoutRedisCacheManager cacheManager = new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration); + // 开启事务感知:@Transactional 方法内的 @CacheEvict / @CachePut 自动延迟到 afterCommit, + // 避免事务未提交就清缓存被并发读穿写脏值;无事务时立即生效,行为不变 + cacheManager.setTransactionAware(true); + return cacheManager; } } diff --git a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/handler/JsonWebSocketMessageHandler.java b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/handler/JsonWebSocketMessageHandler.java index 120f529c23..88ceee627a 100644 --- a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/handler/JsonWebSocketMessageHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/handler/JsonWebSocketMessageHandler.java @@ -76,7 +76,7 @@ public class JsonWebSocketMessageHandler extends TextWebSocketHandler { Long tenantId = WebSocketFrameworkUtils.getTenantId(session); TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj)); } catch (Throwable ex) { - log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload()); + log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload(), ex); } } diff --git a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/security/LoginUserHandshakeInterceptor.java b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/security/LoginUserHandshakeInterceptor.java index 3a31825f50..f3392f6b5c 100644 --- a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/security/LoginUserHandshakeInterceptor.java +++ b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/security/LoginUserHandshakeInterceptor.java @@ -27,9 +27,10 @@ public class LoginUserHandshakeInterceptor implements HandshakeInterceptor { public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) { LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); - if (loginUser != null) { - WebSocketFrameworkUtils.setLoginUser(loginUser, attributes); + if (loginUser == null) { + return false; } + WebSocketFrameworkUtils.setLoginUser(loginUser, attributes); return true; } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java index 0a785b10f4..c8a994d6fe 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java @@ -107,6 +107,9 @@ public class BpmApprovalDetailRespVO { @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") private String signPicUrl; + @Schema(description = "附件", example = "[https://test.yudao.iocoder.cn/20260609/test.txt]") + private List attachments; + } } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java index 0969fda135..9c651144fb 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java @@ -21,6 +21,9 @@ public class BpmTaskApproveReqVO { @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") private String signPicUrl; + @Schema(description = "附件", example = "[https://test.yudao.iocoder.cn/20260609/test.txt]") + private List attachments; + @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) private Map variables; diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java index d4dca29aa3..21408fd8fe 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java @@ -5,6 +5,8 @@ import lombok.Data; import jakarta.validation.constraints.NotEmpty; +import java.util.List; + @Schema(description = "管理后台 - 不通过流程任务的 Request VO") @Data public class BpmTaskRejectReqVO { @@ -16,4 +18,7 @@ public class BpmTaskRejectReqVO { @Schema(description = "审批意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "不错不错!") private String reason; + @Schema(description = "附件", example = "[https://test.yudao.iocoder.cn/20260609/test.txt]") + private List attachments; + } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java index 90e7753c75..fbd5ab7a1a 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.collection.SetUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent; import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; @@ -21,7 +22,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; -import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; @@ -34,6 +34,7 @@ import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.task.Attachment; import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; import org.mapstruct.Mapper; @@ -82,7 +83,7 @@ public interface BpmProcessInstanceConvert { if (CollUtil.isNotEmpty(respVO.getTasks())) { respVO.getTasks().forEach(task -> { AdminUserRespDTO assigneeUser = userMap.get(task.getAssignee()); - if (assigneeUser!= null) { + if (assigneeUser != null) { task.setAssigneeUser(BeanUtils.toBean(assigneeUser, UserSimpleBaseVO.class)); MapUtils.findAndThen(deptMap, assigneeUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName())); } @@ -137,10 +138,10 @@ public interface BpmProcessInstanceConvert { default BpmMessageSendWhenProcessInstanceRejectReqDTO buildProcessInstanceRejectMessage(ProcessInstance instance, String reason) { return new BpmMessageSendWhenProcessInstanceRejectReqDTO() - .setProcessInstanceName(instance.getName()) - .setProcessInstanceId(instance.getId()) - .setReason(reason) - .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); + .setProcessInstanceName(instance.getName()) + .setProcessInstanceId(instance.getId()) + .setReason(reason) + .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); } default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance, @@ -183,8 +184,8 @@ public interface BpmProcessInstanceConvert { } default UserSimpleBaseVO buildUser(Long userId, - Map userMap, - Map deptMap) { + Map userMap, + Map deptMap) { if (userId == null) { return null; } @@ -200,13 +201,14 @@ public interface BpmProcessInstanceConvert { return userVO; } - default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task) { + default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task, List attachments) { if (task == null) { return null; } return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class) .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)) - .setSignPicUrl(FlowableUtils.getTaskSignPicUrl(task)); + .setSignPicUrl(FlowableUtils.getTaskSignPicUrl(task)) + .setAttachments(convertList(attachments, Attachment::getUrl)); } default Set parseUserIds(HistoricProcessInstance processInstance, diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmAttachmentTypeEnum.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmAttachmentTypeEnum.java new file mode 100644 index 0000000000..fa1bf26191 --- /dev/null +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmAttachmentTypeEnum.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.bpm.enums.task; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 附件类型枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmAttachmentTypeEnum { + + TASK_ATTACHMENT("1", "用户任务附件"); + + /** + * 操作类型 + *

+ * 由于 BPM attachment 类型为 String,所以这里就不使用 Integer + */ + private final String type; + /** + * 操作名字 + */ + private final String name; +} diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java index 2a45e3a10c..15d35051ed 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java @@ -24,7 +24,7 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable { MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), POST(22, "岗位"), USER(30, "用户"), - APPROVE_USER_SELECT(34, "审批人自身"), // 当前审批人,可在审批时,选择下一个节点的审批人 + APPROVE_USER_SELECT(34, "审批人自选"), // 当前审批人,可在审批时,选择下一个节点的审批人 START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时,选择此节点的审批人 START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 START_USER_DEPT_LEADER(37, "发起人部门负责人"), diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index b624c532e4..726e0dcd92 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -456,6 +456,54 @@ public class BpmnModelUtils { return new ArrayList<>(); } + /** + * 根据节点,递归获取上游 source 为 UserTask 的入口连线 + * + * 1. 如果当前节点的直接入口连线 source 就是 UserTask,则直接返回该连线 + * 2. 如果当前节点的直接入口连线 source 不是 UserTask,则继续向上递归查找 + * 3. 如果递归过程中遇到 StartEvent 或 SubProcess,则停止该分支继续向上查找 + * + * @param source 起始节点 + * @return 上游连接 UserTask 的入口连线列表 + */ + public static List getElementIncomingUserTaskFlows(FlowElement source) { + List result = new ArrayList<>(); + collectElementIncomingUserTaskFlows(source, new HashSet<>(), new HashSet<>(), result); + return result; + } + + private static void collectElementIncomingUserTaskFlows(FlowElement source, Set visitedSequenceFlowIds, + Set resultSequenceFlowIds, List result) { + // 如果是开始节点或子流程,则停止该分支向上查找 + if (source == null || source instanceof StartEvent || source instanceof SubProcess) { + return; + } + // 获取入口连线 + List incomingFlows = getElementIncomingFlows(source); + if (CollUtil.isEmpty(incomingFlows)) { + return; + } + + // 循环找到目标元素 + for (SequenceFlow incomingFlow : incomingFlows) { + // 如果发现连线重复,说明连线已经走过。跳过 + if (incomingFlow == null || !visitedSequenceFlowIds.add(incomingFlow.getId())) { + continue; + } + // 如果 source 是 UserTask,则添加到结果中 + FlowElement sourceFlowElement = incomingFlow.getSourceFlowElement(); + if (sourceFlowElement instanceof UserTask) { + if (resultSequenceFlowIds.add(incomingFlow.getId())) { + result.add(incomingFlow); + } + continue; + } + // 递归向上查找 UserTask + collectElementIncomingUserTaskFlows(sourceFlowElement, visitedSequenceFlowIds, + resultSequenceFlowIds, result); + } + } + /** * 根据节点,获取出口连线 * @@ -910,13 +958,88 @@ public class BpmnModelUtils { } /** - * 查找起始节点下一个用户任务列表列表 + * 查找起始节点下一个用户任务列表, 该方法会递归向下查找:跳过非 UserTask 节点, 支持网关条件判断 + * + *

    + *
  • 排他网关:走满足条件的唯一一条路径(含默认路径兜底)
  • + *
  • 包容网关:走所有满足条件的路径
  • + *
  • 并行网关:走所有出口路径
  • + *
+ * + * @param currentElement 当前节点 + * @param bpmnModel BPMN 模型 + * @param variables 流程变量(用于网关条件判断) + * @return 下一个用户任务节点列表 + */ + public static List getNextUserTasks(FlowElement currentElement, BpmnModel bpmnModel, + Map variables) { + return getNextUserTasks(currentElement, bpmnModel, variables, new HashSet<>(), new ArrayList<>()); + } + + private static List getNextUserTasks(FlowElement currentElement, BpmnModel bpmnModel, + Map variables, + Set hasSequenceFlow, List userTaskList) { + // 1. 根据节点类型决定要遍历的出口连线 + // 网关需要根据条件表达式筛选;其它节点直接取所有 outgoing flows + List outgoingFlows; + if (currentElement instanceof Gateway) { + outgoingFlows = getGatewayOutgoingFlows((Gateway) currentElement, variables); + } else { + outgoingFlows = getElementOutgoingFlows(currentElement); + } + if (CollUtil.isEmpty(outgoingFlows)) { + return userTaskList; + } + // 2. 遍历出口连线,递归查找用户任务 + for (SequenceFlow outgoingFlow : outgoingFlows) { + // 防止连线成环导致死循环 + if (hasSequenceFlow.contains(outgoingFlow.getId())) { + continue; + } + hasSequenceFlow.add(outgoingFlow.getId()); + // 获取目标节点 + FlowElement targetElement = bpmnModel.getFlowElement(outgoingFlow.getTargetRef()); + if (targetElement == null || targetElement instanceof EndEvent) { + continue; + } + if (targetElement instanceof UserTask) { + // 找到用户任务:加入结果 + userTaskList.add((UserTask) targetElement); + } else { + // 非用户任务(网关、服务任务、中间事件等):继续递归向下查找 + getNextUserTasks(targetElement, bpmnModel, variables, hasSequenceFlow, userTaskList); + } + } + return userTaskList; + } + + /** + * 根据网关类型与流程变量,筛选网关的出口连线 + * + * @param gateway 网关节点 + * @param variables 流程变量 + * @return 命中的出口连线列表 + */ + private static List getGatewayOutgoingFlows(Gateway gateway, Map variables) { + if (gateway instanceof ExclusiveGateway) { + SequenceFlow matchFlow = findMatchSequenceFlowByExclusiveGateway(gateway, variables); + return matchFlow == null ? Collections.emptyList() : Collections.singletonList(matchFlow); + } + if (gateway instanceof InclusiveGateway) { + return new ArrayList<>(findMatchSequenceFlowsByInclusiveGateway(gateway, variables)); + } + // 默认(并行网关等):走所有出口 + return gateway.getOutgoingFlows(); + } + + /** + * 查找起始节点下一个用户任务列表列表, 不判断网关条件 * * @param source 起始节点 * @return 结果 */ public static List getNextUserTasks(FlowElement source) { - return getNextUserTasks(source, null, null); + return getNextUserTasks(source, new HashSet<>(), new ArrayList<>()); } /** diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java index 4c91611c37..fc21f70547 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormFi import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; +import com.fasterxml.jackson.databind.JsonNode; import lombok.SneakyThrows; import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.variable.VariableContainer; @@ -27,6 +28,7 @@ import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.TaskInfo; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -245,10 +247,10 @@ public class FlowableUtils { } // 解析表单配置 - Map formFieldsMap = new HashMap<>(); + Map formFieldsMap = new LinkedHashMap<>(); processDefinitionInfo.getFormFields().forEach(formFieldStr -> { - BpmFormFieldVO formField = JsonUtils.parseObject(formFieldStr, BpmFormFieldVO.class); - parseFormField(formField, formFieldsMap); + JsonNode formFieldNode = JsonUtils.parseObject(formFieldStr, JsonNode.class); + parseFormField(formFieldNode, formFieldsMap); }); // 情况一:当自定义了摘要 @@ -275,18 +277,32 @@ public class FlowableUtils { /** * 递归解析表单字段 */ - private static void parseFormField(BpmFormFieldVO formField, Map formFieldsMap) { - if (formField == null) { + private static void parseFormField(JsonNode formFieldNode, Map formFieldsMap) { + if (formFieldNode == null || !formFieldNode.isObject()) { return; } - // 如果存在 children -> 说明是布局组件 - if (formField.getChildren() != null && !formField.getChildren().isEmpty()) { - for (BpmFormFieldVO child : formField.getChildren()) { + + // 如果 children 里存在对象节点,说明是布局组件;字符串节点是分割线、标签、文字等展示组件内容,直接跳过。 + JsonNode children = formFieldNode.get("children"); + if (children != null && children.isArray() && children.size() > 0) { + boolean hasObjectChild = false; + for (JsonNode child : children) { + if (!child.isObject()) { + continue; + } + hasObjectChild = true; parseFormField(child, formFieldsMap); } - return; + if (hasObjectChild) { + return; + } } + // 真实字段才加入 map + BpmFormFieldVO formField = new BpmFormFieldVO() + .setType(JsonUtils.getText(formFieldNode, "type")) + .setField(JsonUtils.getText(formFieldNode, "field")) + .setTitle(JsonUtils.getText(formFieldNode, "title")); if (StrUtil.isNotBlank(formField.getField())) { formFieldsMap.put(formField.getField(), formField); } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 5344dd7c56..a1050abb2f 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -7,7 +7,6 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; @@ -25,6 +24,7 @@ import cn.iocoder.yudao.module.bpm.dal.redis.BpmProcessIdRedisDAO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeTypeEnum; +import cn.iocoder.yudao.module.bpm.enums.task.BpmAttachmentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; @@ -57,6 +57,7 @@ import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstanceBuilder; +import org.flowable.engine.task.Attachment; import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; @@ -275,11 +276,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService processVariables.putAll(reqVO.getProcessVariables()); } - // 3. 获取下一个将要执行的节点集合 + // 3.1 获取下一个将要执行的节点集合 FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); - List nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables); - // 仅仅获取 UserTask 节点 TODO add from jason:如果网关节点和网关节点相连,获取下个 UserTask. 貌似有点不准。 - List nextUserTaskList = CollectionUtils.filterList(nextFlowNodes, node -> node instanceof UserTask); + // 3.2 获取 UserTask 节点 + List nextUserTaskList = BpmnModelUtils.getNextUserTasks(flowElement, bpmnModel, processVariables); List nextActivityNodes = convertList(nextUserTaskList, node -> new ActivityNode().setId(node.getId()) .setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) .setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) @@ -404,8 +404,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService // 遍历 tasks 列表,只处理已结束的 UserTask // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 List endTasks = filterList(tasks, task -> task.getEndTime() != null); + // 获取已完成节点的附件 + Set endTaskIds = convertSet(endTasks, HistoricTaskInstance::getId); + List attachments = taskService.getAttachments(historicProcessInstance.getId(), endTaskIds, BpmAttachmentTypeEnum.TASK_ATTACHMENT); + Map> taskAttachmentMap = convertMultiMap(attachments, Attachment::getTaskId); List approvalNodes = convertList(endTasks, task -> { FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + List taskAttachments = taskAttachmentMap.get(task.getId()); ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName()) .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType() @@ -414,7 +419,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService .setStatus(getEndActivityNodeStatus(task)) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) - .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task))); + .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task, taskAttachments))); // 如果是取消状态,则跳过 if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) { return null; @@ -528,14 +533,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService if (task == null) { continue; } - activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); + activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task, null)); // 加签子任务,需要过滤掉已经完成的加签子任务 List childrenTasks = filterList( taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks), childTask -> childTask.getEndTime() == null); if (CollUtil.isNotEmpty(childrenTasks)) { activityNode.getTasks().addAll( - convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo)); + convertList(childrenTasks, item->BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(item, null))); } } // 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index f7d260ed13..36a35b3492 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -4,9 +4,11 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; +import cn.iocoder.yudao.module.bpm.enums.task.BpmAttachmentTypeEnum; import jakarta.validation.Valid; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.task.Attachment; import org.flowable.task.api.Task; import org.flowable.task.api.TaskInfo; import org.flowable.task.api.history.HistoricTaskInstance; @@ -14,6 +16,7 @@ import org.flowable.task.api.history.HistoricTaskInstance; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; /** * 流程任务实例 Service 接口 @@ -184,6 +187,17 @@ public interface BpmTaskService { */ List getFinishedTaskListByProcessInstanceIdWithoutCancel(String processInstanceId); + + /** + * 根据条件获取附件 + * + * @param processInstanceId 流程 id + * @param taskIds 任务 id 集合 + * @param attachmentType 附件类型 + * @return 附件集合 + */ + List getAttachments(String processInstanceId, Set taskIds, BpmAttachmentTypeEnum attachmentType); + // ========== Update 写入相关方法 ========== /** diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 89b7bb293d..6b46df6338 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.*; import cn.hutool.extra.spring.SpringUtil; @@ -19,10 +20,7 @@ import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.*; -import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; -import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; -import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; -import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; +import cn.iocoder.yudao.module.bpm.enums.task.*; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; @@ -49,6 +47,7 @@ import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.runtime.ActivityInstance; import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.task.Attachment; import org.flowable.task.api.DelegationState; import org.flowable.task.api.Task; import org.flowable.task.api.TaskInfo; @@ -510,6 +509,18 @@ public class BpmTaskServiceImpl implements BpmTaskService { .orderByHistoricTaskInstanceStartTime().asc().list(); } + @Override + public List getAttachments(String processInstanceId, Set taskIds, BpmAttachmentTypeEnum attachmentType) { + List result = taskService.getProcessInstanceAttachments(processInstanceId); + if (CollUtil.isNotEmpty(taskIds)) { + result = filterList(result, attachment -> taskIds.contains(attachment.getTaskId())); + } + if (attachmentType != null) { + result = filterList(result, attachment -> attachmentType.getType().equals(attachment.getType())); + } + return result; + } + /** * 判断指定用户,是否是当前任务的审批人 * @@ -592,6 +603,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2.2 添加评论 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(), BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); + // 2.3 添加附件 + if (CollUtil.isNotEmpty(reqVO.getAttachments())) { + reqVO.getAttachments().forEach(attachment -> taskService.createAttachment(BpmAttachmentTypeEnum.TASK_ATTACHMENT.getType(), + task.getId(), task.getProcessInstanceId(), FileUtil.getName(URLUtil.getPath(attachment)), null, attachment)); + } // 3. 设置流程变量。如果流程变量前端传空,需要从历史实例中获取,原因:前端表单如果在当前节点无可编辑的字段时 variables 一定会为空 // 场景一:A 节点发起,B 节点表单无可编辑字段,审批通过时,C 节点需要流程变量获取下一个执行节点,但因为 B 节点无可编辑的字段,variables 为空,流程可能出现问题。 @@ -618,7 +634,14 @@ public class BpmTaskServiceImpl implements BpmTaskService { runtimeService.setVariable(task.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS, needSimulateTaskIdsByReturn); } - // 6. 调用 BPM complete 去完成任务 + // 6. 清理退回设置的不自动通过的变量。仅在该标记存在时才删除,避免每次完成任务都产生无谓的 DB delete + String returnFlagKey = String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()); + if (runtimeService.hasVariable(task.getProcessInstanceId(), returnFlagKey)) { + log.info("[approveTask][taskId({}) 清理退回标记变量({})]", task.getId(), returnFlagKey); + runtimeService.removeVariable(task.getProcessInstanceId(), returnFlagKey); + } + + // 7. 调用 BPM complete 去完成任务 taskService.complete(task.getId(), variables, true); // 【加签专属】处理加签任务 @@ -646,10 +669,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 1. 获取下一个将要执行的节点集合 FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey); - List nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables); + List nextFlowNodes = getNextUserTasks(flowElement, bpmnModel, variables); // 2. 校验选择的下一个节点的审批人,是否合法 - for (FlowNode nextFlowNode : nextFlowNodes) { + for (UserTask nextFlowNode : nextFlowNodes) { Integer candidateStrategy = parseCandidateStrategy(nextFlowNode); // 2.1 情况一:如果节点中的审批人策略为 发起人自选 if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) { @@ -675,8 +698,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空 if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) { - // 如果节点存在,但未配置审批人 + // 特殊:如果当前节点已经存在审批人,则不允许覆盖。 例如并行节点后,设置的审批人自选节点。 https://t.zsxq.com/daxv1 Map> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables()); + if (approveUserSelectAssignees != null && CollUtil.isNotEmpty(approveUserSelectAssignees.get(nextFlowNode.getId()))) { + continue; + } + // 如果节点存在,但未配置审批人 List assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); @@ -810,7 +837,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2.2 添加流程评论 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); - // 2.3 如果当前任务时被加签的,则加它的根任务也标记成未通过 + // 2.3 添加附件 + if (CollUtil.isNotEmpty(reqVO.getAttachments())) { + reqVO.getAttachments().forEach(attachment -> taskService.createAttachment(BpmAttachmentTypeEnum.TASK_ATTACHMENT.getType(), + task.getId(), task.getProcessInstanceId(), FileUtil.getName(URLUtil.getPath(attachment)), null, attachment)); + } + + // 2.4 如果当前任务时被加签的,则加它的根任务也标记成未通过 // 疑问:为什么要标记未通过呢? // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。 if (task.getParentTaskId() != null) { @@ -922,14 +955,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 为什么不直接使用 runTaskKeyList 呢?因为可能存在多个审批分支,例如说:A -> B -> C 和 D -> F,而只要 C 撤回到 A,需要排除掉 F List returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); List returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); - // 2. 给当前要被退回的 task 数组,设置退回意见 taskList.forEach(task -> { // 需要排除掉,不需要设置退回意见的任务 if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { return; } - // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务 if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记 // 2.1.1 添加评论 @@ -950,14 +981,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 相关 issue: https://github.com/flowable/flowable-engine/issues/3944 // ② flowable 7.2.0 版本后,继续使用 moveActivityIdsToSingleActivityId 方法。原因:flowable 7.2.0 版本修复了该问题。 // 相关 issue:https://github.com/YunaiV/ruoyi-vue-pro/issues/1018 + // ③ moveActivityIdsToSingleActivityId 使用遇到问题, 相关 issue https://gitee.com/zhijiantianya/yudao-cloud/issues/IJM8MS + // 改成 moveExecutionsToSingleActivityId 好像并没有遇到 ② 提到的超时提醒失效的问题。暂时先改回 moveExecutionsToSingleActivityId + // ④ moveExecutionsToSingleActivityId 回退多实例的时候不会去删除多实例根, 应改成 moveActivityIdsToSingleActivityId + // flowable 8.0.0 修复上面相关问题, 还修复了并行分支回退的问题 https://t.zsxq.com/z4d9i。 runtimeService.createChangeActivityStateBuilder() .processInstanceId(currentTask.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(returnTaskKeyList, reqVO.getTargetTaskDefinitionKey()) // 设置需要预测的任务 ids 的流程变量,用于辅助预测 .processVariable(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS, needSimulateTaskDefinitionKeys) - // 设置流程变量(local)节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 - .localVariable(reqVO.getTargetTaskDefinitionKey(), - String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE) + // 设置流程变量节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 + .processVariable(String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE) .changeState(); } @@ -1464,101 +1498,105 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } - // 自动去重,通过自动审批的方式 - BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo(task.getProcessDefinitionId()); - if (processDefinitionInfo == null) { - log.error("[processTaskAssigned][taskId({}) 没有找到流程定义({})]", task.getId(), task.getProcessDefinitionId()); - return; - } - if (processDefinitionInfo.getAutoApprovalType() != null) { - HistoricTaskInstanceQuery sameAssigneeQuery = historyService.createHistoricTaskInstanceQuery() - .processInstanceId(task.getProcessInstanceId()) - .taskAssignee(task.getAssignee()) // 相同审批人 - .taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus()) - .finished(); - if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType()) - && sameAssigneeQuery.count() > 0) { - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName())); + // 需要基于 instance 设置租户编号,避免 Flowable 内部异步执行时【例如:超时自动通过】 丢失租户编号 + FlowableUtils.execute(processInstance.getTenantId(), () -> { + // 自动去重,通过自动审批的方式 + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo(task.getProcessDefinitionId()); + if (processDefinitionInfo == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程定义({})]", task.getId(), task.getProcessDefinitionId()); return; } - if (BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getType().equals(processDefinitionInfo.getAutoApprovalType())) { - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); - if (bpmnModel == null) { - log.error("[processTaskAssigned][taskId({}) 没有找到流程模型({})]", task.getId(), task.getProcessDefinitionId()); - return; - } - List sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点 - BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())), - SequenceFlow::getSourceRef); - if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) { + if (processDefinitionInfo.getAutoApprovalType() != null) { + HistoricTaskInstanceQuery approvedTaskQuery = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(task.getProcessInstanceId()) + .taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus()) + .finished(); + if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType()) + && approvedTaskQuery.taskAssignee(task.getAssignee()).count() > 0) { getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getName())); + .setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName())); return; } - } - } - - // 获取发起人节点 - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); - if (bpmnModel == null) { - log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); - return; - } - FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); - // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略(使用 local variable) - Boolean returnTaskFlag = runtimeService.getVariableLocal(task.getExecutionId(), - String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); - Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(), - BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); - if (userTaskElement.getId().equals(START_USER_NODE_ID) - && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核 - || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核 - && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason())); - return; - } - // 当不为发起人节点时,审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 - if (ObjectUtil.notEqual(userTaskElement.getId(), START_USER_NODE_ID) - && StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { - if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { - Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); - - // 情况一:自动跳过 - if (ObjectUtils.equalsAny(assignStartUserHandlerType, - BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason())); - return; - } - // 情况二:转交给部门负责人审批 - if (ObjectUtils.equalsAny(assignStartUserHandlerType, - BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) { - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); - Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); - DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; - Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); - // 找不到部门负责人的情况下,自动审批通过 - // noinspection DataFlowIssue - if (dept.getLeaderUserId() == null) { + // 连续审批的节点自动通过 + if (BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getType().equals(processDefinitionInfo.getAutoApprovalType())) { + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + if (bpmnModel == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型({})]", task.getId(), task.getProcessDefinitionId()); + return; + } + List sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingUserTaskFlows( // 获取所有的上一个 UserTask 节点连线 + BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())), + SequenceFlow::getSourceRef); + approvedTaskQuery.taskDefinitionKeys(sourceTaskIds).orderByTaskCreateTime().desc(); // 设置 taskIds, 并按创建时间倒序排序 + HistoricTaskInstance firstHisTask = CollUtil.getFirst(approvedTaskQuery.list()); + if (firstHisTask != null && StrUtil.equals(firstHisTask.getAssignee(), task.getAssignee())) { getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason())); + .setReason(BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getName())); return; } - // 找得到部门负责人的情况下,修改负责人 - if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { - getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() - .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason())); - return; - } - // 如果部门负责人是自己,还是自己审批吧~ } } - } - // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 - FlowableUtils.execute(processInstance.getTenantId(), () -> { + + // 获取发起人节点 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + if (bpmnModel == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); + return; + } + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略 + Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), + String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); + Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(), + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); + if (userTaskElement.getId().equals(START_USER_NODE_ID) + && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核 + || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核 + && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason())); + return; + } + // 当不为发起人节点时,审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 + if (ObjectUtil.notEqual(userTaskElement.getId(), START_USER_NODE_ID) + && StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { + if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); + + // 情况一:自动跳过 + if (ObjectUtils.equalsAny(assignStartUserHandlerType, + BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason())); + return; + } + // 情况二:转交给部门负责人审批 + if (ObjectUtils.equalsAny(assignStartUserHandlerType, + BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) { + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); + Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); + DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; + Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); + // 找不到部门负责人的情况下,自动审批通过 + // noinspection DataFlowIssue + if (dept.getLeaderUserId() == null) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason())); + return; + } + // 找得到部门负责人的情况下,修改负责人 + if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { + getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() + .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason())); + return; + } + // 如果部门负责人是自己,还是自己审批吧~ + } + } + } + + // 发送消息 AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); }); diff --git a/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java index 308d866516..a726377dd6 100644 --- a/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java +++ b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; @@ -64,7 +65,7 @@ public class BpmTaskCandidateInvokerTest extends BaseMockitoUnitTest { public void setUp() { userStrategy = new BpmTaskCandidateUserStrategy(); // 创建 strategy 实例 when(emptyStrategy.getStrategy()).thenReturn(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY); - strategyList = List.of(userStrategy, emptyStrategy); // 创建 strategyList + strategyList = ListUtil.of(userStrategy, emptyStrategy); // 创建 strategyList taskCandidateInvoker = new BpmTaskCandidateInvoker(strategyList, adminUserApi); } diff --git a/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategyTest.java b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategyTest.java index f63ccc332c..07ae141598 100644 --- a/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategyTest.java +++ b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategyTest.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.map.MapUtil; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; @@ -12,7 +13,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; @@ -41,7 +41,7 @@ public class BpmTaskCandidateStartUserSelectStrategyTest extends BaseMockitoUnit // mock 方法(FlowableUtils) Map processVariables = new HashMap<>(); processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, - MapUtil.of("activity_001", List.of(1L, 2L))); + MapUtil.of("activity_001", ListUtil.of(1L, 2L))); when(processInstance.getProcessVariables()).thenReturn(processVariables); // 调用 @@ -56,7 +56,7 @@ public class BpmTaskCandidateStartUserSelectStrategyTest extends BaseMockitoUnit String activityId = "activity_001"; Map processVariables = new HashMap<>(); processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, - MapUtil.of("activity_001", List.of(1L, 2L))); + MapUtil.of("activity_001", ListUtil.of(1L, 2L))); // 调用 Set userIds = strategy.calculateUsersByActivity(null, activityId, null, diff --git a/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java new file mode 100644 index 0000000000..e1bebe1b46 --- /dev/null +++ b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java @@ -0,0 +1,167 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; + +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * {@link FlowableUtils} 的单元测试。 + * + * @author 芋道源码 + */ +class FlowableUtilsTest { + + @Test + public void testGetSummary_customSummary_parseDbFormFields() { + // 准备参数:模拟 DB 中 form_fields 字段,列表里每个元素都是一个 form-create 字段 JSON。 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(dbFormFields(), + summarySetting(true, "reason", "days", "notExists", "startTime")); + Map processVariables = processVariables(); + + // 调用 + List> summary = FlowableUtils.getSummary(processDefinitionInfo, processVariables); + + // 断言 + assertEquals(Arrays.asList( + new KeyValue<>("请假原因", "事假"), + new KeyValue<>("请假天数", "3"), + new KeyValue<>("开始时间", "2026-05-31 09:00:00")), + summary); + } + + @Test + public void testGetSummary_defaultSummary_parseFirstThreeFieldsByFormOrder() { + // 准备参数:未开启自定义摘要时,默认取表单配置顺序里的前三个真实字段。 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(dbFormFields(), null); + Map processVariables = processVariables(); + + // 调用 + List> summary = FlowableUtils.getSummary(processDefinitionInfo, processVariables); + + // 断言 + assertEquals(Arrays.asList( + new KeyValue<>("请假原因", "事假"), + new KeyValue<>("开始时间", "2026-05-31 09:00:00"), + new KeyValue<>("请假天数", "3")), + summary); + } + + @Test + public void testGetSummary_summaryDisabled_useDefaultSummary() { + // 准备参数:摘要设置存在但未启用时,仍走默认摘要逻辑。 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(dbFormFields(), + summarySetting(false, "remark")); + Map processVariables = processVariables(); + + // 调用 + List> summary = FlowableUtils.getSummary(processDefinitionInfo, processVariables); + + // 断言 + assertEquals(Arrays.asList( + new KeyValue<>("请假原因", "事假"), + new KeyValue<>("开始时间", "2026-05-31 09:00:00"), + new KeyValue<>("请假天数", "3")), + summary); + } + + @Test + public void testGetSummary_displayComponentsOnly_returnEmpty() { + // 准备参数:分割线、标签、文字等展示组件的 children 是字符串数组,不是表单字段对象。 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(Arrays.asList( + DIVIDER_FIELD, + TEXT_FIELD, + TAG_FIELD), null); + + // 调用 + List> summary = FlowableUtils.getSummary(processDefinitionInfo, + Collections.emptyMap()); + + // 断言 + assertEquals(Collections.emptyList(), summary); + } + + @Test + public void testGetSummary_notNormalForm_returnNull() { + // 准备参数 + BpmProcessDefinitionInfoDO processDefinitionInfo = BpmProcessDefinitionInfoDO.builder() + .formType(BpmModelFormTypeEnum.CUSTOM.getType()) + .build(); + + // 调用 & 断言 + assertNull(FlowableUtils.getSummary(null, Collections.emptyMap())); + assertNull(FlowableUtils.getSummary(processDefinitionInfo, Collections.emptyMap())); + } + + private static BpmProcessDefinitionInfoDO processDefinitionInfo(List formFields, + BpmModelMetaInfoVO.SummarySetting summarySetting) { + return BpmProcessDefinitionInfoDO.builder() + .formType(BpmModelFormTypeEnum.NORMAL.getType()) + .formFields(formFields) + .summarySetting(summarySetting) + .build(); + } + + private static BpmModelMetaInfoVO.SummarySetting summarySetting(Boolean enable, String... fields) { + BpmModelMetaInfoVO.SummarySetting summarySetting = new BpmModelMetaInfoVO.SummarySetting(); + summarySetting.setEnable(enable); + summarySetting.setSummary(Arrays.asList(fields)); + return summarySetting; + } + + private static List dbFormFields() { + return Arrays.asList( + DIVIDER_FIELD, + "{\"type\":\"input\",\"field\":\"reason\",\"title\":\"请假原因\",\"value\":\"\"," + + "\"props\":{\"type\":\"textarea\",\"placeholder\":\"请输入请假原因\"}," + + "\"$required\":\"请输入请假原因\",\"_fc_id\":\"id_F1\",\"_fc_drag_tag\":\"input\"," + + "\"hidden\":false,\"display\":true}", + TEXT_FIELD, + "{\"type\":\"elRow\",\"title\":\"栅格布局\",\"children\":[" + + "{\"type\":\"elCol\",\"props\":{\"span\":12},\"children\":[" + + "{\"type\":\"DatePicker\",\"field\":\"startTime\",\"title\":\"开始时间\"," + + "\"props\":{\"type\":\"datetime\",\"placeholder\":\"请选择开始时间\"}," + + "\"_fc_id\":\"id_F2\",\"_fc_drag_tag\":\"datePicker\"}]}," + + "\"字段说明\"," + + "{\"type\":\"elCol\",\"props\":{\"span\":12},\"children\":[" + + "{\"type\":\"inputNumber\",\"field\":\"days\",\"title\":\"请假天数\"," + + "\"props\":{\"min\":0,\"precision\":1},\"_fc_id\":\"id_F3\"," + + "\"_fc_drag_tag\":\"inputNumber\"}]}],\"_fc_id\":\"id_LAYOUT\"," + + "\"_fc_drag_tag\":\"row\"}", + TAG_FIELD, + "{\"type\":\"input\",\"field\":\"remark\",\"title\":\"备注\",\"value\":\"\"," + + "\"props\":{\"placeholder\":\"请输入备注\"},\"_fc_id\":\"id_F4\"," + + "\"_fc_drag_tag\":\"input\",\"hidden\":false,\"display\":true}"); + } + + private static Map processVariables() { + Map processVariables = new HashMap<>(); + processVariables.put("reason", "事假"); + processVariables.put("startTime", "2026-05-31 09:00:00"); + processVariables.put("days", 3); + processVariables.put("remark", "下午到家"); + return processVariables; + } + + private static final String DIVIDER_FIELD = "{\"type\":\"elDivider\",\"children\":[\"基础信息\"]," + + "\"props\":{\"contentPosition\":\"left\"},\"_fc_id\":\"id_DIVIDER\"," + + "\"_fc_drag_tag\":\"elDivider\"}"; + + private static final String TEXT_FIELD = "{\"type\":\"div\",\"children\":[\"请按实际情况填写\"]," + + "\"props\":{\"style\":{\"color\":\"#909399\"}},\"_fc_id\":\"id_TEXT\"," + + "\"_fc_drag_tag\":\"text\"}"; + + private static final String TAG_FIELD = "{\"type\":\"elTag\",\"children\":[\"重要\"]," + + "\"props\":{\"type\":\"warning\"},\"_fc_id\":\"id_TAG\",\"_fc_drag_tag\":\"elTag\"}"; + +} diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java index 33e8d84abd..84d31ab779 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java @@ -138,6 +138,26 @@ public class CrmBusinessController { .setCustomerId(business.getCustomerId()))); } + @GetMapping("/list-by-customer") + @Operation(summary = "获得商机列表,基于指定客户") + @Parameter(name = "customerId", description = "客户编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:business:query')") + public CommonResult> getBusinessListByCustomer(@RequestParam("customerId") Long customerId) { + List list = businessService.getBusinessListByCustomerId(customerId); + return success(convertList(list, business -> // 只返回 id、name 字段 + new CrmBusinessRespVO().setId(business.getId()).setName(business.getName()) + .setCustomerId(business.getCustomerId()))); + } + + @GetMapping("/list-by-contact") + @Operation(summary = "获得商机列表,基于指定联系人") + @Parameter(name = "contactId", description = "联系人编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:business:query')") + public CommonResult> getBusinessListByContact(@RequestParam("contactId") Long contactId) { + List list = businessService.getBusinessListByContact(contactId); + return success(buildBusinessDetailList(list)); + } + @GetMapping("/page") @Operation(summary = "获得商机分页") @PreAuthorize("@ss.hasPermission('crm:business:query')") diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java index 58ea20d493..1ff695c9c2 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java @@ -114,6 +114,17 @@ public class CrmContactController { .setCustomerId(contact.getCustomerId()))); } + @GetMapping("/list-by-customer") + @Operation(summary = "获得联系人列表,基于指定客户") + @Parameter(name = "customerId", description = "客户编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:contact:query')") + public CommonResult> getContactListByCustomer(@RequestParam("customerId") Long customerId) { + List list = contactService.getContactListByCustomerId(customerId); + return success(convertList(list, contact -> // 只返回 id、name 字段 + new CrmContactRespVO().setId(contact.getId()).setName(contact.getName()) + .setCustomerId(contact.getCustomerId()))); + } + @GetMapping("/page") @Operation(summary = "获得联系人分页") @PreAuthorize("@ss.hasPermission('crm:contact:query')") diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java index 7946aea0e8..b59ef8081f 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java @@ -11,9 +11,12 @@ import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecor import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; @@ -27,10 +30,13 @@ import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.Map; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_NOT_EXISTS; @Tag(name = "管理后台 - 跟进记录") @@ -45,6 +51,8 @@ public class CrmFollowUpRecordController { private CrmContactService contactService; @Resource private CrmBusinessService businessService; + @Resource + private CrmPermissionService permissionService; @Resource private AdminUserApi adminUserApi; @@ -68,6 +76,13 @@ public class CrmFollowUpRecordController { @Parameter(name = "id", description = "编号", required = true, example = "1024") public CommonResult getFollowUpRecord(@RequestParam("id") Long id) { CrmFollowUpRecordDO followUpRecord = followUpRecordService.getFollowUpRecord(id); + if (followUpRecord == null) { + throw exception(FOLLOW_UP_RECORD_NOT_EXISTS); + } + if (!permissionService.hasPermission(followUpRecord.getBizType(), followUpRecord.getBizId(), + getLoginUserId(), CrmPermissionLevelEnum.READ)) { + throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(followUpRecord.getBizType())); + } return success(BeanUtils.toBean(followUpRecord, CrmFollowUpRecordRespVO.class)); } diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java index 894ab325dd..cc6d598b88 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java @@ -65,6 +65,10 @@ public interface CrmBusinessMapper extends BaseMapperX { .eq(CrmBusinessDO::getOwnerUserId, ownerUserId)); } + default List selectListByCustomerId(Long customerId) { + return selectList(CrmBusinessDO::getCustomerId, customerId); + } + default PageResult selectPage(CrmStatisticsFunnelReqVO pageVO) { return selectPage(pageVO, new LambdaQueryWrapperX() .in(CrmBusinessDO::getOwnerUserId, pageVO.getUserIds()) diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java index a62aabb7ad..230ec1f173 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer; 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.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import org.apache.ibatis.annotations.Mapper; @@ -28,9 +29,9 @@ public interface CrmCustomerLimitConfigMapper extends BaseMapperX query = new LambdaQueryWrapperX() .eq(CrmCustomerLimitConfigDO::getType, type); query.and(w -> { - w.apply("FIND_IN_SET({0}, user_ids) > 0", userId); + w.apply(MyBatisUtils.findInSet("user_ids"), userId); if (deptId != null) { - w.or().apply("FIND_IN_SET({0}, dept_ids) > 0", deptId); + w.or().apply(MyBatisUtils.findInSet("dept_ids"), deptId); } }); return selectList(query); diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java index 07a6dad60d..5c1eb8dd71 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java @@ -26,6 +26,7 @@ public interface ErrorCodeConstants { ErrorCode BUSINESS_DELETE_FAIL_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除"); ErrorCode BUSINESS_UPDATE_STATUS_FAIL_END_STATUS = new ErrorCode(1_020_002_002, "更新商机状态失败,原因:已经是结束状态"); ErrorCode BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS = new ErrorCode(1_020_002_003, "更新商机状态失败,原因:已经是该状态"); + ErrorCode BUSINESS_CONTACT_CUSTOMER_NOT_MATCH = new ErrorCode(1_020_002_004, "商机关联的联系人不属于该客户"); // ========== 联系人管理 1-020-003-000 ========== ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在"); diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java index abe5a70dfa..67a6cf3fb9 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java @@ -195,6 +195,22 @@ public interface CrmBusinessService { */ List getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId); + /** + * 获得商机列表,基于指定客户 + * + * @param customerId 客户编号 + * @return 商机列表 + */ + List getBusinessListByCustomerId(Long customerId); + + /** + * 获得商机列表,基于指定联系人 + * + * @param contactId 联系人编号 + * @return 商机列表 + */ + List getBusinessListByContact(Long contactId); + /** * 获得商机分页,目前用于【数据统计】 * diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java index 71b1884cc2..1828558a4b 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.service.business; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -15,6 +16,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper; import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; @@ -194,9 +196,15 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { if (saveReqVO.getCustomerId() != null) { customerService.validateCustomer(saveReqVO.getCustomerId()); } - // 校验联系人 + // 校验联系人(且必须与商机属于同一客户,避免跨客户关联) if (saveReqVO.getContactId() != null) { contactService.validateContact(saveReqVO.getContactId()); + if (saveReqVO.getCustomerId() != null) { + CrmContactDO contact = contactService.getContact(saveReqVO.getContactId()); + if (ObjUtil.notEqual(saveReqVO.getCustomerId(), contact.getCustomerId())) { + throw exception(BUSINESS_CONTACT_CUSTOMER_NOT_MATCH); + } + } } // 校验负责人 if (saveReqVO.getOwnerUserId() != null) { @@ -377,6 +385,24 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { return businessMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId); } + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#customerId", level = CrmPermissionLevelEnum.READ) + public List getBusinessListByCustomerId(Long customerId) { + return businessMapper.selectListByCustomerId(customerId); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.READ) + public List getBusinessListByContact(Long contactId) { + // 1. 查询关联的商机编号 + List contactBusinessList = contactBusinessService.getContactBusinessListByContactId(contactId); + if (CollUtil.isEmpty(contactBusinessList)) { + return ListUtil.empty(); + } + // 2. 查询商机列表 + return businessMapper.selectByIds(convertSet(contactBusinessList, CrmContactBusinessDO::getBusinessId)); + } + @Override public PageResult getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO) { return businessMapper.selectPage(pageVO); diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java index 971d413afd..3095c732cb 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java @@ -169,4 +169,12 @@ public interface CrmContactService { */ List getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId); + /** + * 获得联系人列表,基于指定客户 + * + * @param customerId 客户编号 + * @return 联系人列表 + */ + List getContactListByCustomerId(Long customerId); + } diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java index c3855f70aa..def0fe7994 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java @@ -304,4 +304,10 @@ public class CrmContactServiceImpl implements CrmContactService { return contactMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId); } + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#customerId", level = CrmPermissionLevelEnum.READ) + public List getContactListByCustomerId(Long customerId) { + return contactMapper.selectListByCustomerId(customerId); + } + } diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java index b0ec991718..eee8cd950d 100644 --- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java +++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java @@ -61,14 +61,14 @@ public class ErpSaleOrderController { @PostMapping("/create") @Operation(summary = "创建销售订单") - @PreAuthorize("@ss.hasPermission('erp:sale-out:create')") + @PreAuthorize("@ss.hasPermission('erp:sale-order:create')") public CommonResult createSaleOrder(@Valid @RequestBody ErpSaleOrderSaveReqVO createReqVO) { return success(saleOrderService.createSaleOrder(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新销售订单") - @PreAuthorize("@ss.hasPermission('erp:sale-out:update')") + @PreAuthorize("@ss.hasPermission('erp:sale-order:update')") public CommonResult updateSaleOrder(@Valid @RequestBody ErpSaleOrderSaveReqVO updateReqVO) { saleOrderService.updateSaleOrder(updateReqVO); return success(true); @@ -76,7 +76,7 @@ public class ErpSaleOrderController { @PutMapping("/update-status") @Operation(summary = "更新销售订单的状态") - @PreAuthorize("@ss.hasPermission('erp:sale-out:update-status')") + @PreAuthorize("@ss.hasPermission('erp:sale-order:update-status')") public CommonResult updateSaleOrderStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) { saleOrderService.updateSaleOrderStatus(id, status); @@ -86,7 +86,7 @@ public class ErpSaleOrderController { @DeleteMapping("/delete") @Operation(summary = "删除销售订单") @Parameter(name = "ids", description = "编号数组", required = true) - @PreAuthorize("@ss.hasPermission('erp:sale-out:delete')") + @PreAuthorize("@ss.hasPermission('erp:sale-order:delete')") public CommonResult deleteSaleOrder(@RequestParam("ids") List ids) { saleOrderService.deleteSaleOrder(ids); return success(true); @@ -95,7 +95,7 @@ public class ErpSaleOrderController { @GetMapping("/get") @Operation(summary = "获得销售订单") @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('erp:sale-out:query')") + @PreAuthorize("@ss.hasPermission('erp:sale-order:query')") public CommonResult getSaleOrder(@RequestParam("id") Long id) { ErpSaleOrderDO saleOrder = saleOrderService.getSaleOrder(id); if (saleOrder == null) { @@ -115,7 +115,7 @@ public class ErpSaleOrderController { @GetMapping("/page") @Operation(summary = "获得销售订单分页") - @PreAuthorize("@ss.hasPermission('erp:sale-out:query')") + @PreAuthorize("@ss.hasPermission('erp:sale-order:query')") public CommonResult> getSaleOrderPage(@Valid ErpSaleOrderPageReqVO pageReqVO) { PageResult pageResult = saleOrderService.getSaleOrderPage(pageReqVO); return success(buildSaleOrderVOPageResult(pageResult)); @@ -123,7 +123,7 @@ public class ErpSaleOrderController { @GetMapping("/export-excel") @Operation(summary = "导出销售订单 Excel") - @PreAuthorize("@ss.hasPermission('erp:sale-out:export')") + @PreAuthorize("@ss.hasPermission('erp:sale-order:export')") @ApiAccessLog(operateType = EXPORT) public void exportSaleOrderExcel(@Valid ErpSaleOrderPageReqVO pageReqVO, HttpServletResponse response) throws IOException { @@ -161,4 +161,4 @@ public class ErpSaleOrderController { }); } -} \ No newline at end of file +} diff --git a/yudao-module-im/pom.xml b/yudao-module-im/pom.xml new file mode 100644 index 0000000000..6fdb223c56 --- /dev/null +++ b/yudao-module-im/pom.xml @@ -0,0 +1,85 @@ + + + + cn.iocoder.boot + yudao + ${revision} + + 4.0.0 + yudao-module-im + jar + + ${project.artifactId} + + im 模块,我们放即时通讯业务。 + 例如说:单聊、群聊、消息收发、消息撤回、消息已读等等 + + + + + cn.iocoder.boot + yudao-module-system + ${revision} + + + cn.iocoder.boot + yudao-module-infra + ${revision} + + + + cn.iocoder.boot + yudao-common + + + + + cn.iocoder.boot + yudao-spring-boot-starter-web + + + + cn.iocoder.boot + yudao-spring-boot-starter-security + + + + + cn.iocoder.boot + yudao-spring-boot-starter-mybatis + + + + cn.iocoder.boot + yudao-spring-boot-starter-redis + + + + + cn.iocoder.boot + yudao-spring-boot-starter-excel + + + + + com.github.houbb + sensitive-word + + + + + com.belerweb + pinyin4j + + + + + cn.iocoder.boot + yudao-spring-boot-starter-test + + + + + diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/api/package-info.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/api/package-info.java new file mode 100644 index 0000000000..49380970f2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/api/package-info.java @@ -0,0 +1,5 @@ +/** + * @author anhaohao + * @since 2024/3/9 下午8:59 + */ +package cn.iocoder.yudao.module.im.api; \ No newline at end of file diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/channel/ImChannelMaterialController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/channel/ImChannelMaterialController.java new file mode 100644 index 0000000000..e2427a1307 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/channel/ImChannelMaterialController.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.controller.admin.channel; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; +import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService; +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.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "用户 APP - IM 频道素材") +@RestController +@RequestMapping("/im/channel/material") +@Validated +public class ImChannelMaterialController { + + @Resource + private ImChannelMaterialService channelMaterialService; + + @GetMapping("/get") + @Operation(summary = "获取素材详情;用于客户端点击图文卡片渲染详情页") + @Parameter(name = "id", description = "素材编号", required = true, example = "1024") + public CommonResult getMaterial(@RequestParam("id") Long id) { + ImChannelMaterialDO material = channelMaterialService.validateMaterialExists(id); + return success(BeanUtils.toBean(material, ImChannelMaterialRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/ImConversationReadController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/ImConversationReadController.java new file mode 100644 index 0000000000..aea2b9ce12 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/ImConversationReadController.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.im.controller.admin.conversation; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationReadRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; +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.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - IM 会话读位置") +@RestController +@RequestMapping("/im/conversation-read") +@Validated +public class ImConversationReadController { + + @Resource + private ImConversationReadService conversationReadService; + + @GetMapping("/pull") + @Operation(summary = "增量拉取当前用户的会话读位置(重连 / 离线补偿)") + @Parameters({ + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"), + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"), + @Parameter(name = "limit", description = "单次拉取条数", required = true) + }) + public CommonResult> pullMyConversationRead( + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime, + @RequestParam(value = "lastId", required = false) Long lastId, + @RequestParam("limit") @Min(1) @Max(200) Integer limit) { + List list = conversationReadService.pullConversationReadList( + getLoginUserId(), lastUpdateTime, lastId, limit); + return success(BeanUtils.toBean(list, ImConversationReadRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationReadRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationReadRespVO.java new file mode 100644 index 0000000000..2cafb46e85 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationReadRespVO.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.im.controller.admin.conversation.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * IM 会话读位置 Response VO + * + * @author 芋道源码 + */ +@Schema(description = "管理后台 - IM 会话读位置 Response VO") +@Data +public class ImConversationReadRespVO { + + @Schema(description = "读位置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "会话类型", example = "1") + private Integer conversationType; // 参见 ImConversationTypeEnum 枚举 + + @Schema(description = "目标编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long targetId; + + @Schema(description = "最大已读消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096") + private Long messageId; + + @Schema(description = "最近更新时间(增量拉取游标用)") + private LocalDateTime updateTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFacePackController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFacePackController.java new file mode 100644 index 0000000000..6438c4d702 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFacePackController.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.im.controller.admin.face; + +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.face.vo.pack.ImFacePackUserRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import cn.iocoder.yudao.module.im.service.face.ImFacePackItemService; +import cn.iocoder.yudao.module.im.service.face.ImFacePackService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; + +@Tag(name = "管理后台 - IM 表情包") +@RestController +@RequestMapping("/im/face-pack") +@Validated +public class ImFacePackController { + + @Resource + private ImFacePackService facePackService; + @Resource + private ImFacePackItemService facePackItemService; + + @GetMapping("/list") + @Operation(summary = "获得启用的表情包列表(含表情)") + public CommonResult> getFacePackList() { + // 1.1 拉所有启用表情包 + List packs = facePackService.getEnabledFacePackList(); + if (packs.isEmpty()) { + return success(ListUtil.of()); + } + // 1.2 拉这些包下所有启用表情,按 packId 分组 + List items = facePackItemService.getEnabledItemListByPackIds( + convertList(packs, ImFacePackDO::getId)); + Map> itemsByPackId = convertMultiMap(items, ImFacePackItemDO::getPackId); + + // 2. 拼装:BeanUtils 把 pack 字段映射 + 自己塞 items + List result = convertList(packs, pack -> { + ImFacePackUserRespVO vo = BeanUtils.toBean(pack, ImFacePackUserRespVO.class); + vo.setItems(BeanUtils.toBean(itemsByPackId.getOrDefault(pack.getId(), ListUtil.of()), ImFacePackUserRespVO.Item.class)); + return vo; + }); + return success(result); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFaceUserItemController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFaceUserItemController.java new file mode 100644 index 0000000000..7f27c173c0 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/ImFaceUserItemController.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.im.controller.admin.face; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem.ImFaceUserItemRespVO; +import cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem.ImFaceUserItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; +import cn.iocoder.yudao.module.im.service.face.ImFaceUserItemService; +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.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - IM 个人表情") +@RestController +@RequestMapping("/im/face-user-item") +@Validated +public class ImFaceUserItemController { + + @Resource + private ImFaceUserItemService faceUserItemService; + + @GetMapping("/list") + @Operation(summary = "获得我的个人表情列表") + public CommonResult> getFaceUserItemList() { + List items = faceUserItemService.getFaceUserItemList(getLoginUserId()); + return success(BeanUtils.toBean(items, ImFaceUserItemRespVO.class)); + } + + @PostMapping("/create") + @Operation(summary = "添加个人表情") + public CommonResult createFaceUserItem(@Valid @RequestBody ImFaceUserItemSaveReqVO reqVO) { + return success(faceUserItemService.createFaceUserItem(getLoginUserId(), reqVO)); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除个人表情") + @Parameter(name = "id", description = "编号", required = true, example = "4096") + public CommonResult deleteFaceUserItem(@RequestParam("id") Long id) { + faceUserItemService.deleteFaceUserItem(getLoginUserId(), id); + return success(true); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/pack/ImFacePackUserRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/pack/ImFacePackUserRespVO.java new file mode 100644 index 0000000000..5a73d821ce --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/pack/ImFacePackUserRespVO.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.im.controller.admin.face.vo.pack; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "IM 表情包(用户端) Response VO") +@Data +public class ImFacePackUserRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子") + private String name; + + @Schema(description = "表情包图标", example = "https://cdn.example.com/face/pack/cat.png") + private String icon; + + @Schema(description = "表情列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "IM 表情包项(用户端)") + @Data + public static class Item { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long id; + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/pack/cat-001.png") + private String url; + + @Schema(description = "表情名", example = "狗头") + private String name; + + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer width; + + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer height; + + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/userItem/ImFaceUserItemRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/userItem/ImFaceUserItemRespVO.java new file mode 100644 index 0000000000..61be778579 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/userItem/ImFaceUserItemRespVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "IM 个人表情 Response VO") +@Data +public class ImFaceUserItemRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096") + private Long id; + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/user/abc.gif") + private String url; + + @Schema(description = "表情名", example = "狗头") + private String name; + + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer width; + + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer height; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/userItem/ImFaceUserItemSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/userItem/ImFaceUserItemSaveReqVO.java new file mode 100644 index 0000000000..b9a508d005 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/face/vo/userItem/ImFaceUserItemSaveReqVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "IM 个人表情新增 Request VO") +@Data +public class ImFaceUserItemSaveReqVO { + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/user/abc.gif") + @NotBlank(message = "表情图 URL 不能为空") + @Size(max = 512, message = "表情图 URL 长度不能超过 512") + private String url; + + @Schema(description = "表情名", example = "狗头") + @Size(max = 64, message = "表情名长度不能超过 64") + private String name; + + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "渲染宽度不能为空") + @Min(value = 1, message = "渲染宽度不能小于 1 像素") + @Max(value = 2048, message = "渲染宽度不能大于 2048 像素") + private Integer width; + + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "渲染高度不能为空") + @Min(value = 1, message = "渲染高度不能小于 1 像素") + @Max(value = 2048, message = "渲染高度不能大于 2048 像素") + private Integer height; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java new file mode 100644 index 0000000000..1cb8913ec4 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java @@ -0,0 +1,143 @@ +package cn.iocoder.yudao.module.im.controller.admin.friend; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.common.util.string.StrUtils; +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendRespVO; +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.singleton; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - IM 好友") +@RestController +@RequestMapping("/im/friend") +@Validated +public class ImFriendController { + + @Resource + private ImFriendService friendService; + + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/list") + @Operation(summary = "获得当前登录用户的好友列表") + public CommonResult> getMyFriendList() { + List friends = friendService.getFriendList(getLoginUserId()); + return success(buildFriendRespVOList(friends)); + } + + @GetMapping("/pull") + @Operation(summary = "增量拉取当前用户的好友关系(重连 / 离线补偿)") + @Parameters({ + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"), + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"), + @Parameter(name = "limit", description = "单次拉取条数", required = true) + }) + public CommonResult> pullMyFriendList( + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime, + @RequestParam(value = "lastId", required = false) Long lastId, + @RequestParam("limit") @Min(1) @Max(200) Integer limit) { + List list = friendService.pullFriendList(getLoginUserId(), lastUpdateTime, lastId, limit); + return success(buildFriendRespVOList(list)); + } + + @GetMapping("/get") + @Operation(summary = "获得好友详情") + @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048") + public CommonResult getFriend(@RequestParam("friendUserId") Long friendUserId) { + ImFriendDO friend = friendService.getFriend(getLoginUserId(), friendUserId); + return success(buildFriendRespVO(friend)); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除好友(单向软删除)") + @Parameters({ + @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048"), + @Parameter(name = "clear", description = "是否级联清理本端相关数据(如私聊会话)") + }) + public CommonResult deleteFriend( + @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId, + @RequestParam(value = "clear", required = false) Boolean clear) { + friendService.deleteFriend(getLoginUserId(), friendUserId, clear); + return success(true); + } + + @PutMapping("/update") + @Operation(summary = "更新好友单边属性(备注 / 免打扰 / 联系人置顶)") + public CommonResult updateFriend(@Valid @RequestBody ImFriendUpdateReqVO reqVO) { + friendService.updateFriend(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/block") + @Operation(summary = "拉黑好友(必须先是好友;单边屏蔽对方私聊消息)") + @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048") + public CommonResult blockFriend( + @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId) { + friendService.blockFriend(getLoginUserId(), friendUserId); + return success(true); + } + + @PutMapping("/unblock") + @Operation(summary = "移出黑名单") + @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048") + public CommonResult unblockFriend( + @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId) { + friendService.unblockFriend(getLoginUserId(), friendUserId); + return success(true); + } + + // ========== 私有方法:VO 组装 ========== + + private List buildFriendRespVOList(Collection friends) { + if (CollUtil.isEmpty(friends)) { + return Collections.emptyList(); + } + // 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1 + Map userMap = adminUserApi.getUserMap( + convertList(friends, ImFriendDO::getFriendUserId)); + return convertList(friends, friend -> { + ImFriendRespVO vo = BeanUtils.toBean(friend, ImFriendRespVO.class); + MapUtils.findAndThen(userMap, friend.getFriendUserId(), user -> + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar())); + // 备注 / 昵称的拼音,给前端做字母分桶 + 拼音搜索 + vo.setDisplayNamePinyin(StrUtils.toPinyin(vo.getDisplayName())) + .setNicknamePinyin(StrUtils.toPinyin(vo.getNickname())); + return vo; + }); + } + + private ImFriendRespVO buildFriendRespVO(ImFriendDO friend) { + if (friend == null) { + return null; + } + return CollUtil.getFirst(buildFriendRespVOList(singleton(friend))); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendRequestController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendRequestController.java new file mode 100644 index 0000000000..c0745689f8 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendRequestController.java @@ -0,0 +1,143 @@ +package cn.iocoder.yudao.module.im.controller.admin.friend; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.request.ImFriendRequestApplyReqVO; +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.request.ImFriendRequestRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; +import cn.iocoder.yudao.module.im.service.friend.ImFriendRequestService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * IM 好友申请记录 Controller + * + * @author 芋道源码 + */ +@Tag(name = "管理后台 - IM 好友申请") +@RestController +@RequestMapping("/im/friend-request") +@Validated +public class ImFriendRequestController { + + @Resource + private ImFriendRequestService friendRequestService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/apply") + @Operation(summary = "发起好友申请") + public CommonResult applyFriend(@Valid @RequestBody ImFriendRequestApplyReqVO reqVO) { + ImFriendRequestDO request = friendRequestService.applyFriend(getLoginUserId(), reqVO); + return success(request != null ? request.getId() : null); + } + + @PutMapping("/agree") + @Operation(summary = "同意好友申请") + @Parameter(name = "id", description = "申请编号", required = true, example = "1024") + public CommonResult agreeFriendRequest( + @RequestParam("id") @NotNull(message = "申请编号不能为空") Long id) { + friendRequestService.agreeFriendRequest(getLoginUserId(), id); + return success(true); + } + + @PutMapping("/refuse") + @Operation(summary = "拒绝好友申请") + public CommonResult refuseFriendRequest( + @RequestParam("id") @NotNull(message = "申请编号不能为空") Long id, + @RequestParam(value = "handleContent", required = false) + @Size(max = 255, message = "处理理由最多 255 个字符") String handleContent) { + friendRequestService.refuseFriendRequest(getLoginUserId(), id, handleContent); + return success(true); + } + + @GetMapping("/list") + @Operation(summary = "查询「我相关」的好友申请列表(游标分页:传 maxId 加载更多)") + @Parameters({ + @Parameter(name = "maxId", description = "当前列表最旧记录的 id;首页不传"), + @Parameter(name = "limit", description = "单次拉取条数", required = true) + }) + public CommonResult> getMyFriendRequestList( + @RequestParam(value = "maxId", required = false) Long maxId, + @RequestParam("limit") @Min(1) @Max(200) Integer limit) { + List list = friendRequestService.getMyFriendRequestList(getLoginUserId(), maxId, limit); + return success(buildList(list)); + } + + @GetMapping("/pull") + @Operation(summary = "增量拉取「我相关」的好友申请(重连 / 离线补偿)") + @Parameters({ + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"), + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"), + @Parameter(name = "limit", description = "单次拉取条数", required = true) + }) + public CommonResult> pullMyFriendRequestList( + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime, + @RequestParam(value = "lastId", required = false) Long lastId, + @RequestParam("limit") @Min(1) @Max(200) Integer limit) { + List list = friendRequestService.pullFriendRequestList(getLoginUserId(), lastUpdateTime, lastId, limit); + return success(buildList(list)); + } + + @GetMapping("/get") + @Operation(summary = "按 id 单查「我相关」的申请记录(带越权过滤;WebSocket 通知到达后用)") + @Parameter(name = "id", description = "申请记录编号", required = true) + public CommonResult getMyFriendRequest(@RequestParam("id") Long id) { + ImFriendRequestDO request = friendRequestService.getFriendRequest(id); + // 越权过滤:fromUser / toUser 必有一方是当前用户,否则当不存在返回 null + Long currentUserId = getLoginUserId(); + if (request == null || (ObjUtil.notEqual(request.getFromUserId(), currentUserId) + && ObjUtil.notEqual(request.getToUserId(), currentUserId))) { + return success(null); + } + return success(CollUtil.getFirst(buildList(Collections.singletonList(request)))); + } + + // ========== 私有方法:VO 组装 ========== + + private List buildList(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + // 双向 OR 列表,userIds 取 from + to 两组并集 + Set userIds = convertSetByFlatMap(list, + request -> Stream.of(request.getFromUserId(), request.getToUserId())); + Map userMap = adminUserApi.getUserMap(userIds); + return convertList(list, request -> { + ImFriendRequestRespVO vo = BeanUtils.toBean(request, ImFriendRequestRespVO.class); + MapUtils.findAndThen(userMap, request.getFromUserId(), user -> + vo.setFromNickname(user.getNickname()).setFromAvatar(user.getAvatar())); + MapUtils.findAndThen(userMap, request.getToUserId(), user -> + vo.setToNickname(user.getNickname()).setToAvatar(user.getAvatar())); + return vo; + }); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendRespVO.java new file mode 100644 index 0000000000..6f4b14e90d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendRespVO.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.im.controller.admin.friend.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * IM 好友 Response VO + * + * @author 芋道源码 + */ +@Schema(description = "管理后台 - IM 好友 Response VO") +@Data +public class ImFriendRespVO { + + @Schema(description = "关系记录编号", example = "1024") + private Long id; + + @Schema(description = "好友的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long friendUserId; + + @Schema(description = "是否免打扰", example = "false") + private Boolean silent; + + @Schema(description = "好友展示备注(仅自己可见)", example = "老张") + private String displayName; + + @Schema(description = "好友展示备注的拼音(小写无空格)", example = "laozhang") + private String displayNamePinyin; + + @Schema(description = "添加来源", example = "1") + private Integer addSource; // 参见 ImFriendAddSourceEnum 枚举 + + @Schema(description = "是否置顶联系人", example = "false") + private Boolean pinned; + + @Schema(description = "是否拉黑(仅自己可见)", example = "false") + private Boolean blocked; + + @Schema(description = "好友状态", example = "0") + private Integer status; + + @Schema(description = "添加好友时间") + private LocalDateTime addTime; + + @Schema(description = "删除好友时间") + private LocalDateTime deleteTime; + + @Schema(description = "最近更新时间(增量拉取游标用)") + private LocalDateTime updateTime; + + // ========== 下面是聚合字段,方便前端显示 ========== + + @Schema(description = "好友昵称(实时聚合自 AdminUser)", example = "芋道") + private String nickname; + + @Schema(description = "好友昵称的拼音(小写无空格)", example = "yudao") + private String nicknamePinyin; + + @Schema(description = "好友头像(实时聚合自 AdminUser)") + private String avatar; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendUpdateReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendUpdateReqVO.java new file mode 100644 index 0000000000..8eee96f3a9 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendUpdateReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.im.controller.admin.friend.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 好友更新 Request VO") +@Data +public class ImFriendUpdateReqVO { + + @Schema(description = "好友的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "好友用户编号不能为空") + private Long friendUserId; + + @Schema(description = "是否免打扰;不传表示不修改", example = "true") + private Boolean silent; + + @Schema(description = "好友展示备注(仅自己可见);不传表示不修改,传空串表示清空", example = "老张") + @Size(max = 16, message = "好友备注最多 16 个字符") + private String displayName; + + @Schema(description = "是否置顶联系人;不传表示不修改", example = "true") + private Boolean pinned; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestApplyReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestApplyReqVO.java new file mode 100644 index 0000000000..fea58c1c4a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestApplyReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.im.controller.admin.friend.vo.request; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendAddSourceEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * IM 好友申请 - 发起 Request VO + * + * @author 芋道源码 + */ +@Schema(description = "管理后台 - IM 好友申请发起 Request VO") +@Data +public class ImFriendRequestApplyReqVO { + + @Schema(description = "接收方用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "接收方用户编号不能为空") + private Long toUserId; + + @Schema(description = "申请理由", example = "我是芋艿(一种食材)") + @Size(max = 255, message = "申请理由最多 255 个字符") + private String applyContent; + + @Schema(description = "对接收方的备注(仅自己可见)", example = "老张") + @Size(max = 16, message = "好友备注最多 16 个字符") + private String displayName; + + @Schema(description = "添加来源", example = "1") + @InEnum(ImFriendAddSourceEnum.class) + private Integer addSource; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestRespVO.java new file mode 100644 index 0000000000..d32a098322 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestRespVO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.im.controller.admin.friend.vo.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * IM 好友申请 Response VO + * + * @author 芋道源码 + */ +@Schema(description = "管理后台 - IM 好友申请 Response VO") +@Data +public class ImFriendRequestRespVO { + + @Schema(description = "申请编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "发起方用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long fromUserId; + + @Schema(description = "接收方用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Long toUserId; + + @Schema(description = "处理结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer handleResult; // 参见 ImFriendRequestHandleResultEnum 枚举 + + @Schema(description = "申请理由", example = "我是芋艿(一种食材)") + private String applyContent; + + @Schema(description = "处理理由(接收方拒绝时可选填)", example = "暂不通过") + private String handleContent; + + @Schema(description = "添加来源", example = "1") + private Integer addSource; // 参见 ImFriendAddSourceEnum 枚举 + + @Schema(description = "处理时间") + private LocalDateTime handleTime; + + @Schema(description = "申请创建时间") + private LocalDateTime createTime; + + @Schema(description = "最近更新时间(增量拉取游标用)") + private LocalDateTime updateTime; + + // ========== 下面是聚合字段,方便前端显示 ========== + + @Schema(description = "发起方昵称(实时聚合自 AdminUser)", example = "芋道") + private String fromNickname; + + @Schema(description = "发起方头像(实时聚合自 AdminUser)") + private String fromAvatar; + + @Schema(description = "接收方昵称(实时聚合自 AdminUser)", example = "老张") + private String toNickname; + + @Schema(description = "接收方头像(实时聚合自 AdminUser)") + private String toAvatar; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java new file mode 100644 index 0000000000..587b216c8b --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java @@ -0,0 +1,215 @@ +package cn.iocoder.yudao.module.im.controller.admin.group; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.*; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; +import cn.iocoder.yudao.module.im.service.group.ImGroupService; +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; +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.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.*; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 群") +@RestController +@RequestMapping("/im/group") +@Validated +public class ImGroupController { + + @Resource + private ImGroupService groupService; + @Resource + private ImGroupMemberService groupMemberService; + @Resource + private ImGroupMessageService groupMessageService; + + // ==================== 群的写操作 ==================== + + @PostMapping("/create") + @Operation(summary = "创建群") + public CommonResult createGroup(@Valid @RequestBody ImGroupCreateReqVO createReqVO) { + ImGroupDO group = groupService.createGroup(createReqVO, getLoginUserId()); + // 新建群必无 pinnedMessages,跳过关联回填 + return success(BeanUtils.toBean(group, ImGroupRespVO.class)); + } + + @PutMapping("/update") + @Operation(summary = "更新群") + public CommonResult updateGroup(@Valid @RequestBody ImGroupUpdateReqVO updateReqVO) { + ImGroupDO group = groupService.updateGroup(updateReqVO, getLoginUserId()); + return success(buildGroupRespVO(group, getLoginUserId())); + } + + @DeleteMapping("/dissolve") + @Operation(summary = "解散群") + @Parameter(name = "id", description = "群编号", required = true) + public CommonResult dissolveGroup(@RequestParam("id") Long id) { + groupService.dissolveGroup(id, getLoginUserId()); + return success(true); + } + + // ==================== 群的读操作 ==================== + + @GetMapping("/get") + @Operation(summary = "获得群") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + public CommonResult getGroup(@RequestParam("id") Long id) { + ImGroupDO group = groupService.getGroup(id); + return success(buildGroupRespVO(group, getLoginUserId())); + } + + @GetMapping("/list") + @Operation(summary = "获得当前登录用户的群列表") + public CommonResult> getMyGroupList() { + Long loginUserId = getLoginUserId(); + List groups = groupService.getMyGroupList(loginUserId); + return success(buildGroupRespVOList(groups, loginUserId)); + } + + // ==================== 群成员的写操作 ==================== + + @PostMapping("/invite") + @Operation(summary = "邀请用户加入群") + public CommonResult inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) { + groupService.inviteGroupMember(getLoginUserId(), inviteReqVO); + return success(true); + } + + @DeleteMapping("/quit") + @Operation(summary = "退出群") + @Parameter(name = "groupId", description = "群编号", required = true) + public CommonResult quitGroup(@RequestParam("groupId") Long groupId) { + groupService.quitGroup(groupId, getLoginUserId()); + return success(true); + } + + @DeleteMapping("/kicking") + @Operation(summary = "移除群成员") + public CommonResult removeGroupMember(@Valid @RequestBody ImGroupMemberRemoveReqVO removeReqVO) { + groupService.removeGroupMember(getLoginUserId(), removeReqVO); + return success(true); + } + + @PutMapping("/add-admin") + @Operation(summary = "添加群管理员") + public CommonResult addGroupAdmin(@Valid @RequestBody ImGroupAdminAddReqVO reqVO) { + groupService.addGroupAdmin(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/remove-admin") + @Operation(summary = "撤销群管理员") + public CommonResult removeGroupAdmin(@Valid @RequestBody ImGroupAdminRemoveReqVO reqVO) { + groupService.removeGroupAdmin(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/transfer-owner") + @Operation(summary = "转让群主") + public CommonResult transferGroupOwner(@Valid @RequestBody ImGroupTransferOwnerReqVO transferReqVO) { + groupService.transferGroupOwner(getLoginUserId(), transferReqVO); + return success(true); + } + + // ==================== 群消息置顶 ==================== + + @PutMapping("/pin-message") + @Operation(summary = "置顶群消息(群主 / 管理员)") + public CommonResult pinGroupMessage(@Valid @RequestBody ImGroupMessagePinReqVO reqVO) { + groupService.pinGroupMessage(getLoginUserId(), reqVO.getId(), reqVO.getMessageId()); + return success(true); + } + + @PutMapping("/unpin-message") + @Operation(summary = "取消置顶群消息(群主 / 管理员)") + public CommonResult unpinGroupMessage(@Valid @RequestBody ImGroupMessagePinReqVO reqVO) { + groupService.unpinGroupMessage(getLoginUserId(), reqVO.getId(), reqVO.getMessageId()); + return success(true); + } + + // ==================== 群禁言 ==================== + + @PutMapping("/mute-all") + @Operation(summary = "全群禁言 / 取消(群主 / 管理员)") + public CommonResult muteAll(@Valid @RequestBody ImGroupMuteAllReqVO reqVO) { + groupService.muteAll(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/mute-member") + @Operation(summary = "禁言成员") + public CommonResult muteMember(@Valid @RequestBody ImGroupMuteMemberReqVO reqVO) { + groupService.muteMember(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/cancel-mute-member") + @Operation(summary = "取消成员禁言") + public CommonResult cancelMuteMember(@Valid @RequestBody ImGroupCancelMuteMemberReqVO reqVO) { + groupService.cancelMuteMember(getLoginUserId(), reqVO); + return success(true); + } + + /** 单群转 VO + 关联回填 pinnedMessages(仅当登录用户是该群有效成员) */ + private ImGroupRespVO buildGroupRespVO(ImGroupDO group, Long loginUserId) { + if (group == null) { + return null; + } + return buildGroupRespVOList(Collections.singletonList(group), loginUserId).get(0); + } + + /** + * 群列表批量转 VO + 关联回填 pinnedMessages + *

+ * 仅当登录用户是某群的有效成员时才回填该群的 pinnedMessages,避免非成员 / 已退群用户越权拿到置顶消息内容 + */ + private List buildGroupRespVOList(List groups, Long loginUserId) { + if (CollUtil.isEmpty(groups)) { + return Collections.emptyList(); + } + // 仅当前用户是有效成员的群才允许回填置顶消息 + Map memberMap = convertMap( + groupMemberService.getGroupMemberListByUserId(loginUserId), ImGroupMemberDO::getGroupId); + Set activeGroupIds = convertSet(memberMap.values(), ImGroupMemberDO::getGroupId, + member -> CommonStatusEnum.ENABLE.getStatus().equals(member.getStatus())); + Set allMessageIds = convertSetByFlatMap(groups, group -> activeGroupIds.contains(group.getId()) + ? CollUtil.emptyIfNull(group.getPinnedMessageIds()).stream() : Stream.empty()); + Map messageMap = groupMessageService.getGroupMessageMap(allMessageIds); + // 转换输出 + return convertList(groups, group -> { + ImGroupRespVO groupVO = BeanUtils.toBean(group, ImGroupRespVO.class); + // 标记登录用户在该群的成员状态,供前端区分当前群与历史退群 + boolean joined = activeGroupIds.contains(group.getId()); + groupVO.setJoinStatus(joined ? CommonStatusEnum.ENABLE.getStatus() : CommonStatusEnum.DISABLE.getStatus()); + MapUtils.findAndThen(memberMap, group.getId(), member -> + groupVO.setGroupRemark(member.getGroupRemark()).setSilent(Boolean.TRUE.equals(member.getSilent()))); + if (!joined || CollUtil.isEmpty(group.getPinnedMessageIds())) { + return groupVO; + } + // 按 pin 顺序输出,已被删除的消息(messageMap 没命中)跳过 + List pinnedMesages = convertList(group.getPinnedMessageIds(), messageMap::get); + return groupVO.setPinnedMessages(BeanUtils.toBean(pinnedMesages, ImGroupMessageRespVO.class)); + }); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java new file mode 100644 index 0000000000..c752eebbbf --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java @@ -0,0 +1,140 @@ +package cn.iocoder.yudao.module.im.controller.admin.group; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; + +@Tag(name = "管理后台 - 群成员") +@RestController +@RequestMapping("/im/group-member") +@Validated +public class ImGroupMemberController { + + @Resource + private ImGroupMemberService groupMemberService; + + @Resource + private AdminUserApi adminUserApi; + + @PutMapping("/update") + @Operation(summary = "更新群成员") + public CommonResult updateGroupMember(@Valid @RequestBody ImGroupMemberUpdateReqVO updateReqVO) { + groupMemberService.updateGroupMember(getLoginUserId(), updateReqVO); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得群成员") + @Parameters({ + @Parameter(name = "id", description = "编号(与 groupId + userId 二选一)", example = "1024"), + @Parameter(name = "groupId", description = "群编号(与 userId 配合查)", example = "1"), + @Parameter(name = "userId", description = "用户编号(与 groupId 配合查)", example = "100") + }) + public CommonResult getGroupMember(@RequestParam(value = "id", required = false) Long id, + @RequestParam(value = "groupId", required = false) Long groupId, + @RequestParam(value = "userId", required = false) Long userId) { + // 1. 查询群成员 + ImGroupMemberDO member; + if (id != null) { + member = groupMemberService.getGroupMember(id); + } else if (groupId != null && userId != null) { + member = groupMemberService.getGroupMember(groupId, userId); + } else { + // 避免 selectByGroupIdAndUserId 收到 null 参数走全表扫 / 抛 SQL 异常 + throw new IllegalArgumentException("参数缺失:需传 id 或 (groupId, userId)"); + } + if (member == null) { + return success(null); + } + + // 2. 校验当前登录用户是该成员所在群的有效成员 + Long loginUserId = getLoginUserId(); + groupMemberService.validateMemberInGroup(member.getGroupId(), loginUserId); + + // 3. 转化 VO + ImGroupMemberRespVO memberVO = BeanUtils.toBean(member, ImGroupMemberRespVO.class); + AdminUserRespDTO user = adminUserApi.getUser(member.getUserId()); + if (user != null) { + memberVO.setNickname(user.getNickname()).setAvatar(user.getAvatar()); + } + hidePrivateFieldsIfNotSelf(memberVO, member.getUserId(), loginUserId); + return success(memberVO); + } + + @GetMapping("/list") + @Operation(summary = "获得指定群的成员列表(按群全量拉取,作为前端本地成员 cache 的完整基线)") + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") + public CommonResult> getGroupMemberList(@RequestParam("groupId") Long groupId) { + // 1.1 查询群成员列表(包含 DISABLE 已退群的成员,不按时间过滤) + // 说明:保留已退群成员,是为了前端展示历史消息时,仍能通过该接口拿到已退群成员的昵称 / 头像信息,避免显示为空 + List members = groupMemberService.getGroupMemberListByGroupId(groupId); + // 1.2 校验当前登录用户是否为群的有效成员,非成员不可查看 + Long loginUserId = getLoginUserId(); + if (CollUtil.findOne(members, member -> loginUserId.equals(member.getUserId()) + && CommonStatusEnum.ENABLE.getStatus().equals(member.getStatus())) == null) { + throw exception(GROUP_MEMBER_NOT_IN_GROUP); + } + + // 2. 批量聚合昵称 / 头像,并对非本人置空私人字段 + return success(buildGroupMemberRespVOList(members)); + } + + // ========== 私有方法:VO 组装 ========== + + /** + * 群成员列表批量转 VO + 关联回填昵称 / 头像,并对非本人置空私人字段 + */ + private List buildGroupMemberRespVOList(List members) { + if (CollUtil.isEmpty(members)) { + return Collections.emptyList(); + } + Long loginUserId = getLoginUserId(); + // 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1 + Map userMap = adminUserApi.getUserMap( + convertList(members, ImGroupMemberDO::getUserId)); + return convertList(members, member -> { + ImGroupMemberRespVO vo = BeanUtils.toBean(member, ImGroupMemberRespVO.class); + MapUtils.findAndThen(userMap, member.getUserId(), user -> + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar())); + hidePrivateFieldsIfNotSelf(vo, member.getUserId(), loginUserId); + return vo; + }); + } + + /** + * 非本人查看时,置空成员的私人设置字段(groupRemark / silent) + */ + private void hidePrivateFieldsIfNotSelf(ImGroupMemberRespVO vo, Long memberUserId, Long loginUserId) { + if (ObjUtil.notEqual(loginUserId, memberUserId)) { + vo.setGroupRemark(null).setSilent(null); + } + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java new file mode 100644 index 0000000000..6ffebe5257 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java @@ -0,0 +1,174 @@ +package cn.iocoder.yudao.module.im.controller.admin.group; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.request.ImGroupRequestApplyReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.request.ImGroupRequestRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; +import cn.iocoder.yudao.module.im.service.group.ImGroupRequestService; +import cn.iocoder.yudao.module.im.service.group.ImGroupService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.*; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * IM 加群申请 Controller + * + * @author 芋道源码 + */ +@Tag(name = "管理后台 - IM 加群申请") +@RestController +@RequestMapping("/im/group-request") +@Validated +public class ImGroupRequestController { + + @Resource + private ImGroupRequestService groupRequestService; + @Resource + private ImGroupService groupService; + @Resource + private ImGroupMemberService groupMemberService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/apply") + @Operation(summary = "申请加群") + public CommonResult applyJoinGroup(@Valid @RequestBody ImGroupRequestApplyReqVO reqVO) { + ImGroupRequestDO request = groupRequestService.applyJoinGroup(getLoginUserId(), reqVO); + return success(request != null ? request.getId() : null); + } + + @PutMapping("/agree") + @Operation(summary = "同意加群申请(群主或管理员)") + @Parameter(name = "id", description = "申请编号", required = true, example = "1024") + public CommonResult agreeGroupRequest( + @RequestParam("id") @NotNull(message = "申请编号不能为空") Long id) { + groupRequestService.agreeGroupRequest(getLoginUserId(), id); + return success(true); + } + + @PutMapping("/refuse") + @Operation(summary = "拒绝加群申请(群主或管理员)") + public CommonResult refuseGroupRequest( + @RequestParam("id") @NotNull(message = "申请编号不能为空") Long id, + @RequestParam(value = "handleContent", required = false) + @Size(max = 255, message = "处理理由最多 255 个字符") String handleContent) { + groupRequestService.refuseGroupRequest(getLoginUserId(), id, handleContent); + return success(true); + } + + @GetMapping("/unhandled-list") + @Operation(summary = "查询「我管理的所有群」下的未处理加群申请列表(不分页);前端 store 据此派生横幅红点 + Drawer 列表") + public CommonResult> getUnhandledRequestList() { + List list = groupRequestService.getUnhandledRequestListByOwnerOrAdmin(getLoginUserId()); + return success(buildVOList(list)); + } + + @GetMapping("/pull") + @Operation(summary = "增量拉取「我管理的群」下的加群申请(重连 / 离线补偿)") + @Parameters({ + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"), + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"), + @Parameter(name = "limit", description = "单次拉取条数", required = true) + }) + public CommonResult> pullMyGroupRequestList( + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime, + @RequestParam(value = "lastId", required = false) Long lastId, + @RequestParam("limit") @Min(1) @Max(200) Integer limit) { + List list = groupRequestService.pullGroupRequestList( + getLoginUserId(), lastUpdateTime, lastId, limit); + return success(buildVOList(list)); + } + + @GetMapping("/list-by-group") + @Operation(summary = "查询指定群下的全部加群申请(含已处理);仅群主 / 管理员可查") + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") + public CommonResult> getGroupRequestListByGroupId( + @RequestParam("groupId") @NotNull(message = "群编号不能为空") Long groupId) { + List list = groupRequestService.getGroupRequestListByGroupId(getLoginUserId(), groupId); + return success(buildVOList(list)); + } + + @GetMapping("/get") + @Operation(summary = "按 id 单查申请记录(带越权过滤;WebSocket 通知到达后用)") + @Parameter(name = "id", description = "申请记录编号", required = true) + public CommonResult getGroupRequest(@RequestParam("id") Long id) { + ImGroupRequestDO request = groupRequestService.getGroupRequest(id); + if (request == null) { + return success(null); + } + // 越权过滤:申请人 / 邀请人 / 群主 / 管理员之外,当不存在返回 null + Long currentUserId = getLoginUserId(); + boolean canSee = ObjUtil.equal(request.getUserId(), currentUserId) + || ObjUtil.equal(request.getInviterUserId(), currentUserId) + || isGroupOwnerOrAdmin(request.getGroupId(), currentUserId); + if (!canSee) { + return success(null); + } + + // 转换并返回 + return success(CollUtil.getFirst(buildVOList(Collections.singletonList(request)))); + } + + /** + * 当前用户是否该群的有效群主 / 管理员 + */ + private boolean isGroupOwnerOrAdmin(Long groupId, Long userId) { + ImGroupMemberDO member = groupMemberService.getGroupMember(groupId, userId); + return member != null + && !CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus()) + && ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole()); + } + + /** 申请记录列表批量转 VO + 关联回填用户 / 群信息 */ + private List buildVOList(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + // 1. 聚合 user / inviter 用户信息;convertSetByFlatMap 内部已过滤 null + Set userIds = convertSetByFlatMap(list, + request -> Stream.of(request.getUserId(), request.getInviterUserId())); + Map userMap = adminUserApi.getUserMap(userIds); + // 2. 聚合群信息(封禁 / 解散群也要回填,便于前端展示历史) + Set groupIds = convertSet(list, ImGroupRequestDO::getGroupId); + Map groupMap = groupService.getGroupMap(groupIds); + return convertList(list, request -> { + ImGroupRequestRespVO vo = BeanUtils.toBean(request, ImGroupRequestRespVO.class); + MapUtils.findAndThen(userMap, request.getUserId(), user -> + vo.setUserNickname(user.getNickname()).setUserAvatar(user.getAvatar())); + MapUtils.findAndThen(userMap, request.getInviterUserId(), user -> + vo.setInviterNickname(user.getNickname()).setInviterAvatar(user.getAvatar())); + MapUtils.findAndThen(groupMap, request.getGroupId(), group -> + vo.setGroupName(group.getName()).setGroupAvatar(group.getAvatar())); + return vo; + }); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupAdminAddReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupAdminAddReqVO.java new file mode 100644 index 0000000000..e4183f0c73 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupAdminAddReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 添加群管理员 Request VO") +@Data +public class ImGroupAdminAddReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279") + @NotNull(message = "群编号不能为空") + private Long id; + + @Schema(description = "目标用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[101, 102]") + @NotEmpty(message = "目标用户编号列表不能为空") + private List userIds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupAdminRemoveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupAdminRemoveReqVO.java new file mode 100644 index 0000000000..7c35dfb95c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupAdminRemoveReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 撤销群管理员 Request VO") +@Data +public class ImGroupAdminRemoveReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279") + @NotNull(message = "群编号不能为空") + private Long id; + + @Schema(description = "目标用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[101, 102]") + @NotEmpty(message = "目标用户编号列表不能为空") + private List userIds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCancelMuteMemberReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCancelMuteMemberReqVO.java new file mode 100644 index 0000000000..2e2243abba --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCancelMuteMemberReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 取消成员禁言 Request VO") +@Data +public class ImGroupCancelMuteMemberReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "群编号不能为空") + private Long id; + + @Schema(description = "被取消禁言的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "用户编号不能为空") + private Long userId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java new file mode 100644 index 0000000000..ec88708a82 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 群创建 Request VO") +@Data +public class ImGroupCreateReqVO { + + @Schema(description = "群名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道技术交流群") + @NotBlank(message = "群名称不能为空") + @Size(max = 64, message = "群名称长度不能超过 64") + private String name; + + @Schema(description = "初始成员用户编号列表(建群同时邀请的好友,不含创建者自己)", example = "[1024, 2048]") + private List memberUserIds; + + @Schema(description = "进群是否需群主 / 管理员审批;不传默认 false 自由进群", example = "false") + private Boolean joinApproval; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMessagePinReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMessagePinReqVO.java new file mode 100644 index 0000000000..49c14ffe9f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMessagePinReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 管理后台 - 群消息置顶 / 取消置顶 Request VO + */ +@Schema(description = "管理后台 - 群消息置顶 / 取消置顶 Request VO") +@Data +public class ImGroupMessagePinReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279") + @NotNull(message = "群编号不能为空") + private Long id; + + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9527") + @NotNull(message = "消息编号不能为空") + private Long messageId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMuteAllReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMuteAllReqVO.java new file mode 100644 index 0000000000..0a23cb05cc --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMuteAllReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 全群禁言 / 取消 Request VO") +@Data +public class ImGroupMuteAllReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "群编号不能为空") + private Long id; + + @Schema(description = "是否全群禁言", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "是否全群禁言不能为空") + private Boolean mutedAll; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMuteMemberReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMuteMemberReqVO.java new file mode 100644 index 0000000000..184764860f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupMuteMemberReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 成员禁言 Request VO") +@Data +public class ImGroupMuteMemberReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "群编号不能为空") + private Long id; + + @Schema(description = "被禁言的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "用户编号不能为空") + private Long userId; + + @Schema(description = "禁言时长(秒),0 表示永久禁言", requiredMode = Schema.RequiredMode.REQUIRED, example = "600") + @NotNull(message = "禁言时长不能为空") + @Min(value = 0, message = "禁言时长不能小于 0 秒") + private Integer mutedSeconds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupPageReqVO.java new file mode 100644 index 0000000000..fdb6016394 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupPageReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import lombok.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +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 ImGroupPageReqVO extends PageParam { + + @Schema(description = "群名称", example = "芋艿") + private String name; + + @Schema(description = "群主用户编号", example = "31460") + private Long ownerUserId; + + @Schema(description = "群公告") + private String notice; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java new file mode 100644 index 0000000000..f15d7663b7 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageRespVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 群 Response VO") +@Data +public class ImGroupRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1003") + private Long id; + + @Schema(description = "群名称", example = "芋艿") + private String name; + + @Schema(description = "群主用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31460") + private Long ownerUserId; + + @Schema(description = "群头像") + private String avatar; + + @Schema(description = "群公告") + private String notice; + + @Schema(description = "是否封禁") + private Boolean banned; + + @Schema(description = "是否全群禁言") + private Boolean mutedAll; + + @Schema(description = "进群是否需群主 / 管理员审批", example = "false") + private Boolean joinApproval; + + @Schema(description = "封禁时间") + private LocalDateTime bannedTime; + + @Schema(description = "群状态", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer status; + + @Schema(description = "解散时间") + private LocalDateTime dissolvedTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "当前登录用户在该群的成员状态:在群 / 已退群(历史退群群仍返回,供前端展示离线消息的群名 / 头像)") + private Integer joinStatus; // 参见 CommonStatusEnum 枚举 + + @Schema(description = "当前登录用户对该群的备注") + private String groupRemark; + + @Schema(description = "当前登录用户是否免打扰") + private Boolean silent; + + @Schema(description = "群置顶消息列表,按 pin 顺序(最先置顶的在前);非该群有效成员时为空") + private List pinnedMessages; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupSaveReqVO.java new file mode 100644 index 0000000000..0275fd8ef1 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupSaveReqVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import jakarta.validation.constraints.*; + +@Schema(description = "管理后台 - 群新增/修改 Request VO") +@Data +public class ImGroupSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1003") + private Long id; + + @Schema(description = "群名称", example = "芋艿") + private String name; + + @Schema(description = "群主用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31460") + @NotNull(message = "群主用户编号不能为空") + private Long ownerUserId; + + @Schema(description = "群头像") + private String avatar; + + @Schema(description = "群公告") + private String notice; + +} \ No newline at end of file diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupTransferOwnerReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupTransferOwnerReqVO.java new file mode 100644 index 0000000000..226d07e480 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupTransferOwnerReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 群主转让 Request VO") +@Data +public class ImGroupTransferOwnerReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279") + @NotNull(message = "群编号不能为空") + private Long id; + + @Schema(description = "新群主用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "202") + @NotNull(message = "新群主用户编号不能为空") + private Long newOwnerUserId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java new file mode 100644 index 0000000000..99bdfb358e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - 群更新 Request VO") +@Data +public class ImGroupUpdateReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1003") + @NotNull(message = "群编号不能为空") + private Long id; + + @Schema(description = "群名称", example = "芋道技术交流群") + @Size(max = 64, message = "群名称长度不能超过 64") + private String name; + + @Schema(description = "群头像") + @Size(max = 512, message = "群头像长度不能超过 512") + private String avatar; + + @Schema(description = "群公告") + @Size(max = 2048, message = "群公告长度不能超过 2048") + private String notice; + + @Schema(description = "进群是否需群主 / 管理员审批", example = "true") + private Boolean joinApproval; + + @AssertTrue(message = "群名称不能为空") + @JsonIgnore + public boolean isNameValid() { + return name == null || StrUtil.isNotBlank(name); + } + + @AssertTrue(message = "群头像不能为空") + @JsonIgnore + public boolean isAvatarValid() { + return avatar == null || StrUtil.isNotBlank(avatar); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberCreateReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberCreateReqVO.java new file mode 100644 index 0000000000..a486190fbb --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberCreateReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 群成员邀请 Request VO") +@Data +public class ImGroupMemberCreateReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279") + @NotNull(message = "群编号不能为空") + private Long groupId; + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730") + @NotNull(message = "用户编号不能为空") + private Long userId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java new file mode 100644 index 0000000000..12cd09d377 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 群成员邀请 Request VO") +@Data +public class ImGroupMemberInviteReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279") + @NotNull(message = "群编号不能为空") + private Long groupId; + + @Schema(description = "被邀请的用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]") + @NotEmpty(message = "被邀请的用户编号列表不能为空") + private List memberUserIds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRemoveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRemoveReqVO.java new file mode 100644 index 0000000000..9862b12d92 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRemoveReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 群成员移除 Request VO") +@Data +public class ImGroupMemberRemoveReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "群编号不能为空") + private Long groupId; + + @Schema(description = "被移除的用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]") + @NotEmpty(message = "被移除的用户编号列表不能为空") + private List memberUserIds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRespVO.java new file mode 100644 index 0000000000..ce095e1342 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRespVO.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 群成员 Response VO") +@Data +public class ImGroupMemberRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17071") + private Long id; + + @Schema(description = "群编号", example = "13279") + private Long groupId; + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730") + private Long userId; + + @Schema(description = "组内显示名", example = "芋艿") + private String displayUserName; + + @Schema(description = "群备注", example = "核心群") + private String groupRemark; + + @Schema(description = "是否免打扰") + private Boolean silent; + + @Schema(description = "成员状态", example = "0") + private Integer status; + + @Schema(description = "成员角色", example = "3") + private Integer role; // 参见 ImGroupMemberRoleEnum 枚举类 + + @Schema(description = "入群时间") + private LocalDateTime joinTime; + + @Schema(description = "退群时间") + private LocalDateTime quitTime; + + @Schema(description = "禁言到期时间") + private LocalDateTime muteEndTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + // ========== 关联 AdminUser 的字段 ========== + + @Schema(description = "用户昵称", example = "芋道") + private String nickname; + + @Schema(description = "用户头像") + private String avatar; + +} \ No newline at end of file diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberUpdateReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberUpdateReqVO.java new file mode 100644 index 0000000000..8c2c6fd1fa --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberUpdateReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.experimental.Accessors; + +@Schema(description = "管理后台 - 群成员更新 Request VO") +@Data +@Accessors(chain = true) +public class ImGroupMemberUpdateReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "群编号不能为空") + private Long groupId; + + @Schema(description = "群内昵称", example = "芋头") + private String displayUserName; + + @Schema(description = "群备注", example = "公司群") + private String groupRemark; + + @Schema(description = "是否免打扰") + private Boolean silent; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestApplyReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestApplyReqVO.java new file mode 100644 index 0000000000..6f34a05c9c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestApplyReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo.request; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 加群申请发起 Request VO") +@Data +public class ImGroupRequestApplyReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "群编号不能为空") + private Long groupId; + + @Schema(description = "申请理由", example = "我是芋艿(一种食材)") + @Size(max = 255, message = "申请理由最多 255 个字符") + private String applyContent; + + @Schema(description = "加入来源", example = "1") + @InEnum(ImGroupAddSourceEnum.class) + private Integer addSource; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestRespVO.java new file mode 100644 index 0000000000..9e21cae3ee --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestRespVO.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 加群申请 Response VO") +@Data +public class ImGroupRequestRespVO { + + @Schema(description = "申请编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long groupId; + + @Schema(description = "申请人 / 被邀请人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long userId; + + @Schema(description = "邀请人用户编号;NULL 表示用户主动申请", example = "200") + private Long inviterUserId; + + @Schema(description = "处理结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer handleResult; // 参见 ImGroupRequestHandleResultEnum 枚举 + + @Schema(description = "申请理由", example = "我想加入这个群") + private String applyContent; + + @Schema(description = "处理理由(拒绝时可选填)", example = "暂不通过") + private String handleContent; + + @Schema(description = "处理人用户编号", example = "31460") + private Long handleUserId; + + @Schema(description = "加入来源", example = "1") + private Integer addSource; // 参见 ImGroupAddSourceEnum 枚举 + + @Schema(description = "处理时间") + private LocalDateTime handleTime; + + @Schema(description = "申请创建时间") + private LocalDateTime createTime; + + @Schema(description = "申请更新时间;增量拉取游标") + private LocalDateTime updateTime; + + // ========== 下面是聚合字段,方便前端显示 ========== + + @Schema(description = "申请人 / 被邀请人昵称(实时聚合自 AdminUser)", example = "芋道") + private String userNickname; + + @Schema(description = "申请人 / 被邀请人头像(实时聚合自 AdminUser)") + private String userAvatar; + + @Schema(description = "邀请人昵称(实时聚合自 AdminUser)", example = "老张") + private String inviterNickname; + + @Schema(description = "邀请人头像(实时聚合自 AdminUser)") + private String inviterAvatar; + + @Schema(description = "群名称(实时聚合自 ImGroup)", example = "芋道技术交流群") + private String groupName; + + @Schema(description = "群头像(实时聚合自 ImGroup)") + private String groupAvatar; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelManagerController.java new file mode 100644 index 0000000000..65e06f775a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelManagerController.java @@ -0,0 +1,82 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.channel; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +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.im.controller.admin.manager.channel.vo.channel.ImChannelPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel.ImChannelRespVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel.ImChannelSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; +import cn.iocoder.yudao.module.im.service.channel.ImChannelService; +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 java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IM 频道") +@RestController +@RequestMapping("/im/manager/channel") +@Validated +public class ImChannelManagerController { + + @Resource + private ImChannelService channelService; + + @PostMapping("/create") + @Operation(summary = "新增频道") + @PreAuthorize("@ss.hasPermission('im:manager:channel:create')") + public CommonResult createChannel(@Valid @RequestBody ImChannelSaveReqVO reqVO) { + return success(channelService.createChannel(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改频道") + @PreAuthorize("@ss.hasPermission('im:manager:channel:update')") + public CommonResult updateChannel(@Valid @RequestBody ImChannelSaveReqVO reqVO) { + channelService.updateChannel(reqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除频道") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:channel:delete')") + public CommonResult deleteChannel(@RequestParam("id") Long id) { + channelService.deleteChannel(id); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得频道分页") + @PreAuthorize("@ss.hasPermission('im:manager:channel:query')") + public CommonResult> getChannelPage(@Valid ImChannelPageReqVO pageReqVO) { + PageResult pageResult = channelService.getChannelPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ImChannelRespVO.class)); + } + + @GetMapping("/get") + @Operation(summary = "获得频道详情") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:channel:query')") + public CommonResult getChannel(@RequestParam("id") Long id) { + ImChannelDO channel = channelService.getChannel(id); + return success(BeanUtils.toBean(channel, ImChannelRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得启用的频道精简列表;前端表单选择频道时调用") + public CommonResult> getSimpleChannelList() { + List list = channelService.getChannelListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(BeanUtils.toBean(list, ImChannelRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelMaterialManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelMaterialManagerController.java new file mode 100644 index 0000000000..e6e0271437 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelMaterialManagerController.java @@ -0,0 +1,99 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.channel; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialRespVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; +import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService; +import cn.iocoder.yudao.module.im.service.channel.ImChannelService; +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 java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +@Tag(name = "管理后台 - IM 频道素材") +@RestController +@RequestMapping("/im/manager/channel-material") +@Validated +public class ImChannelMaterialManagerController { + + @Resource + private ImChannelMaterialService channelMaterialService; + @Resource + private ImChannelService channelService; + + @PostMapping("/create") + @Operation(summary = "新增素材") + @PreAuthorize("@ss.hasPermission('im:manager:channel-material:create')") + public CommonResult createMaterial(@Valid @RequestBody ImChannelMaterialSaveReqVO reqVO) { + return success(channelMaterialService.createMaterial(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改素材") + @PreAuthorize("@ss.hasPermission('im:manager:channel-material:update')") + public CommonResult updateMaterial(@Valid @RequestBody ImChannelMaterialSaveReqVO reqVO) { + channelMaterialService.updateMaterial(reqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除素材") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:channel-material:delete')") + public CommonResult deleteMaterial(@RequestParam("id") Long id) { + channelMaterialService.deleteMaterial(id); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得素材分页;含频道名回填") + @PreAuthorize("@ss.hasPermission('im:manager:channel-material:query')") + public CommonResult> getMaterialPage(@Valid ImChannelMaterialPageReqVO pageReqVO) { + PageResult pageResult = channelMaterialService.getMaterialPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 回填频道名 + List channels = channelService.getChannelList( + convertSet(pageResult.getList(), ImChannelMaterialDO::getChannelId)); + Map channelMap = convertMap(channels, ImChannelDO::getId); + return success(BeanUtils.toBean(pageResult, ImChannelMaterialRespVO.class, vo -> + MapUtils.findAndThen(channelMap, vo.getChannelId(), c -> vo.setChannelName(c.getName())))); + } + + @GetMapping("/get") + @Operation(summary = "获得素材详情(含富文本正文)") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:channel-material:query')") + public CommonResult getMaterial(@RequestParam("id") Long id) { + ImChannelMaterialDO material = channelMaterialService.getMaterial(id); + return success(BeanUtils.toBean(material, ImChannelMaterialRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得指定频道下的素材精简列表;用于推送弹窗的素材下拉") + @Parameter(name = "channelId", description = "频道编号", required = true, example = "1") + public CommonResult> getSimpleMaterialList(@RequestParam("channelId") Long channelId) { + List list = channelMaterialService.getMaterialListByChannelId(channelId); + return success(BeanUtils.toBean(list, ImChannelMaterialRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelPageReqVO.java new file mode 100644 index 0000000000..51ac09af96 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelPageReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - IM 频道分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImChannelPageReqVO extends PageParam { + + @Schema(description = "频道业务码", example = "system_notice") + private String code; + + @Schema(description = "频道名称", example = "系统") + private String name; + + @Schema(description = "状态", example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelRespVO.java new file mode 100644 index 0000000000..59fed78af4 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelRespVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 频道 Response VO") +@Data +public class ImChannelRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "频道业务码", requiredMode = Schema.RequiredMode.REQUIRED, example = "system_notice") + private String code; + + @Schema(description = "频道名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "系统公告") + private String name; + + @Schema(description = "频道头像") + private String avatar; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelSaveReqVO.java new file mode 100644 index 0000000000..6e1b04283f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/channel/ImChannelSaveReqVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 频道新增 / 修改 Request VO") +@Data +public class ImChannelSaveReqVO { + + @Schema(description = "编号(修改时必填)", example = "1024") + private Long id; + + @Schema(description = "频道业务码;唯一", requiredMode = Schema.RequiredMode.REQUIRED, example = "system_notice") + @NotBlank(message = "频道编码不能为空") + @Size(max = 64, message = "频道编码长度不能超过 64") + @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "频道编码只能由小写字母 / 数字 / 下划线组成,且必须以字母开头") + private String code; + + @Schema(description = "频道名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "系统公告") + @NotBlank(message = "频道名称不能为空") + @Size(max = 64, message = "频道名称长度不能超过 64") + private String name; + + @Schema(description = "频道头像", example = "https://cdn.example.com/channel/system_notice.png") + @Size(max = 512, message = "头像长度不能超过 512") + private String avatar; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialPageReqVO.java new file mode 100644 index 0000000000..1bb834b3bc --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialPageReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 频道素材分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImChannelMaterialPageReqVO extends PageParam { + + @Schema(description = "频道编号", example = "1") + private Long channelId; + + @Schema(description = "内容类型", example = "1") + private Integer type; // 参见 ImChannelMaterialTypeEnum 枚举类 + + @Schema(description = "标题", example = "活动") + private String title; + + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialRespVO.java new file mode 100644 index 0000000000..55aebed92c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 频道素材 Response VO") +@Data +public class ImChannelMaterialRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "频道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long channelId; + + @Schema(description = "频道名称(关联查询填充)") + private String channelName; + + @Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer type; // 参见 ImChannelMaterialTypeEnum 枚举类 + + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED) + private String title; + + @Schema(description = "封面图") + private String coverUrl; + + @Schema(description = "摘要") + private String summary; + + @Schema(description = "正文;富文本 HTML") + private String content; + + @Schema(description = "跳转链接") + private String url; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialSaveReqVO.java new file mode 100644 index 0000000000..e30f35b91d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/vo/material/ImChannelMaterialSaveReqVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 频道素材新增 / 修改 Request VO") +@Data +public class ImChannelMaterialSaveReqVO { + + @Schema(description = "编号(修改时必填)", example = "1024") + private Long id; + + @Schema(description = "频道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "频道编号不能为空") + private Long channelId; + + @Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "内容类型不能为空") + private Integer type; // 参见 ImChannelMaterialTypeEnum 枚举类 + + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "双十一活动来啦") + @NotBlank(message = "标题不能为空") + @Size(max = 128, message = "标题长度不能超过 128") + private String title; + + @Schema(description = "封面图", example = "https://cdn.example.com/cover.png") + @Size(max = 512, message = "封面图长度不能超过 512") + private String coverUrl; + + @Schema(description = "摘要", example = "全场五折,戳详情看玩法") + @Size(max = 255, message = "摘要长度不能超过 255") + private String summary; + + @Schema(description = "正文;富文本 HTML") + private String content; + + @Schema(description = "跳转链接;为空表示走客户端内置详情页", example = "https://example.com/activity/123") + @Size(max = 512, message = "跳转链接长度不能超过 512") + private String url; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackItemManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackItemManagerController.java new file mode 100644 index 0000000000..4a25431791 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackItemManagerController.java @@ -0,0 +1,85 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face; + +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.im.controller.admin.manager.face.vo.item.ImFacePackItemPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item.ImFacePackItemRespVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item.ImFacePackItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import cn.iocoder.yudao.module.im.service.face.ImFacePackItemService; +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 jakarta.validation.constraints.Size; +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; + +@Tag(name = "管理后台 - IM 表情包项") +@RestController +@RequestMapping("/im/manager/face-pack-item") +@Validated +public class ImFacePackItemManagerController { + + @Resource + private ImFacePackItemService facePackItemService; + + @PostMapping("/create") + @Operation(summary = "新增表情") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:create')") + public CommonResult createFacePackItem(@Valid @RequestBody ImFacePackItemSaveReqVO reqVO) { + return success(facePackItemService.createFacePackItem(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改表情") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:update')") + public CommonResult updateFacePackItem(@Valid @RequestBody ImFacePackItemSaveReqVO reqVO) { + facePackItemService.updateFacePackItem(reqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除表情") + @Parameter(name = "id", description = "编号", required = true, example = "2048") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:delete')") + public CommonResult deleteFacePackItem(@RequestParam("id") Long id) { + facePackItemService.deleteFacePackItem(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除表情") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:delete')") + public CommonResult deleteFacePackItemList( + @RequestParam("ids") @Size(max = 100, message = "批量删除最多 100 条") List ids) { + facePackItemService.deleteFacePackItemList(ids); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得表情分页") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:query')") + public CommonResult> getFacePackItemPage(@Valid ImFacePackItemPageReqVO pageReqVO) { + PageResult pageResult = facePackItemService.getFacePackItemPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ImFacePackItemRespVO.class)); + } + + @GetMapping("/get") + @Operation(summary = "获得表情详情") + @Parameter(name = "id", description = "编号", required = true, example = "2048") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack-item:query')") + public CommonResult getFacePackItem(@RequestParam("id") Long id) { + ImFacePackItemDO item = facePackItemService.getFacePackItem(id); + return success(BeanUtils.toBean(item, ImFacePackItemRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackManagerController.java new file mode 100644 index 0000000000..ad2f8ff72e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFacePackManagerController.java @@ -0,0 +1,85 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face; + +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.im.controller.admin.manager.face.vo.pack.ImFacePackPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack.ImFacePackRespVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack.ImFacePackSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import cn.iocoder.yudao.module.im.service.face.ImFacePackService; +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 jakarta.validation.constraints.Size; +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; + +@Tag(name = "管理后台 - IM 表情包") +@RestController +@RequestMapping("/im/manager/face-pack") +@Validated +public class ImFacePackManagerController { + + @Resource + private ImFacePackService facePackService; + + @PostMapping("/create") + @Operation(summary = "新增表情包") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:create')") + public CommonResult createFacePack(@Valid @RequestBody ImFacePackSaveReqVO reqVO) { + return success(facePackService.createFacePack(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改表情包") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:update')") + public CommonResult updateFacePack(@Valid @RequestBody ImFacePackSaveReqVO reqVO) { + facePackService.updateFacePack(reqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除表情包") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:delete')") + public CommonResult deleteFacePack(@RequestParam("id") Long id) { + facePackService.deleteFacePack(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除表情包") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:delete')") + public CommonResult deleteFacePackList(@RequestParam("ids") + @Size(max = 100, message = "批量删除最多 100 条") List ids) { + facePackService.deleteFacePackList(ids); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得表情包分页") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:query')") + public CommonResult> getFacePackPage(@Valid ImFacePackPageReqVO pageReqVO) { + PageResult pageResult = facePackService.getFacePackPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ImFacePackRespVO.class)); + } + + @GetMapping("/get") + @Operation(summary = "获得表情包详情") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:face-pack:query')") + public CommonResult getFacePack(@RequestParam("id") Long id) { + ImFacePackDO pack = facePackService.getFacePack(id); + return success(BeanUtils.toBean(pack, ImFacePackRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFaceUserItemManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFaceUserItemManagerController.java new file mode 100644 index 0000000000..df4a06d6eb --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/ImFaceUserItemManagerController.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; +import cn.iocoder.yudao.module.im.service.face.ImFaceUserItemService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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 java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IM 用户表情") +@RestController +@RequestMapping("/im/manager/face-user-item") +@Validated +public class ImFaceUserItemManagerController { + + @Resource + private ImFaceUserItemService faceUserItemService; + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/page") + @Operation(summary = "获得用户表情分页") + @PreAuthorize("@ss.hasPermission('im:manager:face-user-item:query')") + public CommonResult> getFaceUserItemPage( + @Valid ImFaceUserItemManagerPageReqVO pageReqVO) { + PageResult pageResult = faceUserItemService.getFaceUserItemPage(pageReqVO); + // 关联回填用户昵称 + Map userMap = adminUserApi.getUserMap( + CollectionUtils.convertSet(pageResult.getList(), ImFaceUserItemDO::getUserId)); + List voList = CollectionUtils.convertList(pageResult.getList(), item -> { + ImFaceUserItemManagerRespVO vo = BeanUtils.toBean(item, ImFaceUserItemManagerRespVO.class); + AdminUserRespDTO user = userMap.get(item.getUserId()); + if (user != null) { + vo.setUserNickname(user.getNickname()); + } + return vo; + }); + return success(new PageResult<>(voList, pageResult.getTotal())); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除用户表情") + @Parameter(name = "id", description = "编号", required = true, example = "4096") + @PreAuthorize("@ss.hasPermission('im:manager:face-user-item:delete')") + public CommonResult deleteFaceUserItem(@RequestParam("id") Long id) { + faceUserItemService.deleteFaceUserItem(id); + return success(true); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemPageReqVO.java new file mode 100644 index 0000000000..eefbbed955 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemPageReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - IM 表情包项分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImFacePackItemPageReqVO extends PageParam { + + @Schema(description = "所属表情包编号", example = "1024") + private Long packId; + + @Schema(description = "表情名,模糊匹配", example = "狗") + private String name; + + @Schema(description = "状态", example = "0") + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; // 参见 CommonStatusEnum 枚举类 + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemRespVO.java new file mode 100644 index 0000000000..817348f101 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemRespVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 表情包项 Response VO") +@Data +public class ImFacePackItemRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long id; + + @Schema(description = "所属表情包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long packId; + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/pack/cat-001.png") + private String url; + + @Schema(description = "表情名", example = "狗头") + private String name; + + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer width; + + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer height; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemSaveReqVO.java new file mode 100644 index 0000000000..de6842bea9 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/item/ImFacePackItemSaveReqVO.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 表情包项新增 / 修改 Request VO") +@Data +public class ImFacePackItemSaveReqVO { + + @Schema(description = "编号(修改时必填)", example = "2048") + private Long id; + + @Schema(description = "所属表情包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "表情包编号不能为空") + private Long packId; + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/pack/cat-001.png") + @NotBlank(message = "表情图 URL 不能为空") + @Size(max = 512, message = "表情图 URL 长度不能超过 512") + private String url; + + @Schema(description = "表情名", example = "狗头") + @Size(max = 64, message = "表情名长度不能超过 64") + private String name; + + @Schema(description = "渲染宽度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "渲染宽度不能为空") + @Min(value = 1, message = "渲染宽度不能小于 1 像素") + @Max(value = 2048, message = "渲染宽度不能大于 2048 像素") + private Integer width; + + @Schema(description = "渲染高度(像素)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "渲染高度不能为空") + @Min(value = 1, message = "渲染高度不能小于 1 像素") + @Max(value = 2048, message = "渲染高度不能大于 2048 像素") + private Integer height; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackPageReqVO.java new file mode 100644 index 0000000000..e0220afb70 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackPageReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 表情包分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImFacePackPageReqVO extends PageParam { + + @Schema(description = "表情包名称,模糊匹配", example = "猫") + private String name; + + @Schema(description = "状态", example = "0") + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackRespVO.java new file mode 100644 index 0000000000..bdc53d2d43 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackRespVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 表情包 Response VO") +@Data +public class ImFacePackRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子") + private String name; + + @Schema(description = "表情包图标", example = "https://cdn.example.com/face/pack/cat.png") + private String icon; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackSaveReqVO.java new file mode 100644 index 0000000000..86be43d357 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/pack/ImFacePackSaveReqVO.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 表情包新增 / 修改 Request VO") +@Data +public class ImFacePackSaveReqVO { + + @Schema(description = "编号(修改时必填)", example = "1024") + private Long id; + + @Schema(description = "表情包名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "猫主子") + @NotBlank(message = "表情包名称不能为空") + @Size(max = 64, message = "表情包名称长度不能超过 64") + private String name; + + @Schema(description = "表情包图标", example = "https://cdn.example.com/face/pack/cat.png") + @Size(max = 512, message = "图标长度不能超过 512") + private String icon; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/useritem/ImFaceUserItemManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/useritem/ImFaceUserItemManagerPageReqVO.java new file mode 100644 index 0000000000..dec8f1f393 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/useritem/ImFaceUserItemManagerPageReqVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 用户表情分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImFaceUserItemManagerPageReqVO extends PageParam { + + @Schema(description = "所属用户编号", example = "1024") + private Long userId; + + @Schema(description = "表情名,模糊匹配", example = "狗") + private String name; + + @Schema(description = "添加时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/useritem/ImFaceUserItemManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/useritem/ImFaceUserItemManagerRespVO.java new file mode 100644 index 0000000000..723fc48f71 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/face/vo/useritem/ImFaceUserItemManagerRespVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 用户表情 Response VO") +@Data +public class ImFaceUserItemManagerRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096") + private Long id; + + @Schema(description = "所属用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long userId; + + @Schema(description = "所属用户昵称", example = "张三") + private String userNickname; + + @Schema(description = "表情图 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://cdn.example.com/face/user/abc.gif") + private String url; + + @Schema(description = "表情名", example = "狗头") + private String name; + + @Schema(description = "渲染宽度(像素)", example = "200") + private Integer width; + + @Schema(description = "渲染高度(像素)", example = "200") + private Integer height; + + @Schema(description = "添加时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java new file mode 100644 index 0000000000..b2f6ae7095 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.friend; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import io.swagger.v3.oas.annotations.Operation; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; + +@Tag(name = "管理后台 - IM 好友管理") +@RestController +@RequestMapping("/im/manager/friend") +@Validated +public class ImFriendManagerController { + + @Resource + private ImFriendService friendService; + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/page") + @Operation(summary = "获得好友关系分页") + @PreAuthorize("@ss.hasPermission('im:manager:friend:query')") + public CommonResult> getFriendPage( + @Valid ImFriendManagerPageReqVO pageReqVO) { + // 1. 分页查询 + PageResult pageResult = friendService.getFriendPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 2.1 一次性批量查询用户 + 好友的昵称 + Set userIds = convertSetByFlatMap(pageResult.getList(), + f -> Stream.of(f.getUserId(), f.getFriendUserId())); + Map userMap = adminUserApi.getUserMap(userIds); + // 2.2 转换为 VO,填充昵称 + return success(BeanUtils.toBean(pageResult, ImFriendManagerRespVO.class, vo -> { + MapUtils.findAndThen(userMap, vo.getUserId(), + user -> vo.setUserNickname(user.getNickname())); + MapUtils.findAndThen(userMap, vo.getFriendUserId(), + user -> vo.setFriendNickname(user.getNickname())); + })); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendRequestManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendRequestManagerController.java new file mode 100644 index 0000000000..eac0c9a9fe --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendRequestManagerController.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.friend; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendRequestManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendRequestManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; +import cn.iocoder.yudao.module.im.service.friend.ImFriendRequestService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import io.swagger.v3.oas.annotations.Operation; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; + +@Tag(name = "管理后台 - IM 好友申请管理") +@RestController +@RequestMapping("/im/manager/friend-request") +@Validated +public class ImFriendRequestManagerController { + + @Resource + private ImFriendRequestService friendRequestService; + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/page") + @Operation(summary = "获得好友申请分页") + @PreAuthorize("@ss.hasPermission('im:manager:friend-request:query')") + public CommonResult> getFriendRequestPage( + @Valid ImFriendRequestManagerPageReqVO pageReqVO) { + // 1. 分页查询 + PageResult pageResult = friendRequestService.getFriendRequestPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + + // 2.1 一次性批量查询发起方 + 接收方的昵称 + Set userIds = convertSetByFlatMap(pageResult.getList(), + request -> Stream.of(request.getFromUserId(), request.getToUserId())); + Map userMap = adminUserApi.getUserMap(userIds); + // 2.2 转换为 VO,填充昵称 + return success(BeanUtils.toBean(pageResult, ImFriendRequestManagerRespVO.class, vo -> { + MapUtils.findAndThen(userMap, vo.getFromUserId(), + user -> vo.setFromNickname(user.getNickname())); + MapUtils.findAndThen(userMap, vo.getToUserId(), + user -> vo.setToNickname(user.getNickname())); + })); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java new file mode 100644 index 0000000000..54b5dcce1d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 好友关系分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImFriendManagerPageReqVO extends PageParam { + + @Schema(description = "用户编号", example = "1024") + private Long userId; + + @Schema(description = "好友用户编号", example = "2048") + private Long friendUserId; + + @Schema(description = "好友状态", example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "是否免打扰", example = "false") + private Boolean silent; + + @Schema(description = "添加好友时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] addTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java new file mode 100644 index 0000000000..6664f24353 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 好友关系 Response VO") +@Data +public class ImFriendManagerRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long userId; + + @Schema(description = "用户昵称", example = "张三") + private String userNickname; + + @Schema(description = "好友用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long friendUserId; + + @Schema(description = "好友昵称", example = "李四") + private String friendNickname; + + @Schema(description = "好友展示备注") + private String displayName; + + @Schema(description = "添加来源", example = "1") + private Integer addSource; // 参见 ImFriendAddSourceEnum 枚举类 + + @Schema(description = "是否免打扰", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean silent; + + @Schema(description = "是否置顶联系人", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean pinned; + + @Schema(description = "是否拉黑", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean blocked; + + @Schema(description = "好友状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "添加好友时间") + private LocalDateTime addTime; + + @Schema(description = "删除好友时间") + private LocalDateTime deleteTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendRequestManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendRequestManagerPageReqVO.java new file mode 100644 index 0000000000..a078909281 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendRequestManagerPageReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 好友申请分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImFriendRequestManagerPageReqVO extends PageParam { + + @Schema(description = "发起方用户编号", example = "1024") + private Long fromUserId; + + @Schema(description = "接收方用户编号", example = "2048") + private Long toUserId; + + @Schema(description = "处理结果", example = "0") + private Integer handleResult; // 参见 ImFriendRequestHandleResultEnum 枚举类 + + @Schema(description = "添加来源", example = "1") + private Integer addSource; // 参见 ImFriendAddSourceEnum 枚举类 + + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendRequestManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendRequestManagerRespVO.java new file mode 100644 index 0000000000..9fa9d4d454 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendRequestManagerRespVO.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 好友申请 Response VO") +@Data +public class ImFriendRequestManagerRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "发起方用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long fromUserId; + + @Schema(description = "发起方昵称", example = "张三") + private String fromNickname; + + @Schema(description = "接收方用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long toUserId; + + @Schema(description = "接收方昵称", example = "李四") + private String toNickname; + + @Schema(description = "申请理由", example = "我是芋艿") + private String applyContent; + + @Schema(description = "发起方对接收方的备注") + private String displayName; + + @Schema(description = "添加来源", example = "1") + private Integer addSource; // 参见 ImFriendAddSourceEnum 枚举类 + + @Schema(description = "处理结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer handleResult; // 参见 ImFriendRequestHandleResultEnum 枚举类 + + @Schema(description = "处理理由", example = "暂不通过") + private String handleContent; + + @Schema(description = "处理时间") + private LocalDateTime handleTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java new file mode 100644 index 0000000000..46fd4eb119 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.group; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; +import cn.iocoder.yudao.module.im.service.group.ImGroupService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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 java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - IM 群聊管理") +@RestController +@RequestMapping("/im/manager/group") +@Validated +public class ImGroupManagerController { + + @Resource + private ImGroupService groupService; + @Resource + private ImGroupMemberService groupMemberService; + + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/page") + @Operation(summary = "获得群分页") + @PreAuthorize("@ss.hasPermission('im:manager:group:query')") + public CommonResult> getGroupPage(@Valid ImGroupManagerPageReqVO pageReqVO) { + // 1. 分页查询群 + PageResult pageResult = groupService.getGroupPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 2.1 批量查询相关数据 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), ImGroupDO::getOwnerUserId)); + Map memberCountMap = groupMemberService.getActiveMemberCountMap( + convertSet(pageResult.getList(), ImGroupDO::getId)); + // 2.2 转换为 VO,填充群主昵称、群成员数量 + return success(BeanUtils.toBean(pageResult, ImGroupManagerRespVO.class, vo -> { + MapUtils.findAndThen(userMap, vo.getOwnerUserId(), + user -> vo.setOwnerNickname(user.getNickname())); + vo.setMemberCount(memberCountMap.getOrDefault(vo.getId(), 0L).intValue()); + })); + } + + @GetMapping("/get") + @Operation(summary = "获得群详情") + @Parameter(name = "id", description = "群编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:group:query')") + public CommonResult getGroup(@RequestParam("id") Long id) { + ImGroupDO group = groupService.getGroup(id); + return success(BeanUtils.toBean(group, ImGroupManagerRespVO.class)); + } + + @PutMapping("/ban") + @Operation(summary = "封禁群") + @PreAuthorize("@ss.hasPermission('im:manager:group:ban')") + public CommonResult banGroup(@Valid @RequestBody ImGroupManagerBanReqVO reqVO) { + groupService.banGroup(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/unban") + @Operation(summary = "解封群") + @Parameter(name = "id", description = "群编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:group:ban')") + public CommonResult unbanGroup(@RequestParam("id") Long id) { + groupService.unbanGroup(getLoginUserId(), id); + return success(true); + } + + @DeleteMapping("/dissolve") + @Operation(summary = "解散群") + @Parameter(name = "id", description = "群编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:group:dissolve')") + public CommonResult dissolveGroup(@RequestParam("id") Long id) { + groupService.dissolveGroupByManager(getLoginUserId(), id); + return success(true); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java new file mode 100644 index 0000000000..e1fa1f328d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java @@ -0,0 +1,60 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.group; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +@Tag(name = "管理后台 - IM 群成员管理") +@RestController +@RequestMapping("/im/manager/group/member") +@Validated +public class ImGroupMemberManagerController { + + @Resource + private ImGroupMemberService groupMemberService; + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/list") + @Operation(summary = "获得群成员列表(含已退群成员,由前端按需过滤)") + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:group:query')") + public CommonResult> getGroupMemberList(@RequestParam("groupId") Long groupId) { + // 1. 查询群全部成员(含已退群) + List members = groupMemberService.getGroupMemberListByGroupId(groupId); + if (CollUtil.isEmpty(members)) { + return success(Collections.emptyList()); + } + // 2.1 批量查询用户信息 + Map userMap = adminUserApi.getUserMap( + convertSet(members, ImGroupMemberDO::getUserId)); + // 2.2 转换为 VO,填充昵称、头像 + return success(BeanUtils.toBean(members, ImGroupMemberManagerRespVO.class, vo -> + MapUtils.findAndThen(userMap, vo.getUserId(), user -> + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar())))); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupRequestManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupRequestManagerController.java new file mode 100644 index 0000000000..8010312402 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupRequestManagerController.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.group; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupRequestManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupRequestManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; +import cn.iocoder.yudao.module.im.service.group.ImGroupRequestService; +import cn.iocoder.yudao.module.im.service.group.ImGroupService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import io.swagger.v3.oas.annotations.Operation; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; + +@Tag(name = "管理后台 - IM 加群申请管理") +@RestController +@RequestMapping("/im/manager/group-request") +@Validated +public class ImGroupRequestManagerController { + + @Resource + private ImGroupRequestService groupRequestService; + @Resource + private ImGroupService groupService; + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/page") + @Operation(summary = "获得加群申请分页") + @PreAuthorize("@ss.hasPermission('im:manager:group-request:query')") + public CommonResult> getGroupRequestPage( + @Valid ImGroupRequestManagerPageReqVO pageReqVO) { + // 1. 分页查询 + PageResult pageResult = groupRequestService.getGroupRequestPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + + // 2.1 批量聚合 user / inviter / handler 用户昵称 + Set userIds = convertSetByFlatMap(pageResult.getList(), + request -> Stream.of(request.getUserId(), request.getInviterUserId(), request.getHandleUserId()) + .filter(Objects::nonNull)); + Map userMap = adminUserApi.getUserMap(userIds); + // 2.2 批量聚合群信息(取群名) + Set groupIds = convertSet(pageResult.getList(), ImGroupRequestDO::getGroupId); + Map groupMap = groupService.getGroupMap(groupIds); + return success(BeanUtils.toBean(pageResult, ImGroupRequestManagerRespVO.class, vo -> { + MapUtils.findAndThen(userMap, vo.getUserId(), user -> vo.setUserNickname(user.getNickname())); + MapUtils.findAndThen(userMap, vo.getInviterUserId(), user -> vo.setInviterNickname(user.getNickname())); + MapUtils.findAndThen(userMap, vo.getHandleUserId(), user -> vo.setHandleNickname(user.getNickname())); + MapUtils.findAndThen(groupMap, vo.getGroupId(), group -> vo.setGroupName(group.getName())); + })); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerBanReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerBanReqVO.java new file mode 100644 index 0000000000..a4fb81961d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerBanReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 群聊封禁 Request VO") +@Data +public class ImGroupManagerBanReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "群编号不能为空") + private Long id; + + @Schema(description = "封禁原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "违规内容") + @NotBlank(message = "封禁原因不能为空") + @Size(max = 200, message = "封禁原因长度不能超过 200") + private String reason; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerPageReqVO.java new file mode 100644 index 0000000000..e7da76b1b6 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerPageReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.group.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 群聊分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImGroupManagerPageReqVO extends PageParam { + + @Schema(description = "群名称,模糊匹配", example = "技术交流群") + private String name; + + @Schema(description = "群主用户编号", example = "1024") + private Long ownerUserId; + + @Schema(description = "群状态", example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类(0 正常 / 1 已解散) + + @Schema(description = "是否封禁", example = "false") + private Boolean banned; + + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerRespVO.java new file mode 100644 index 0000000000..c1de792322 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerRespVO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 群聊 Response VO") +@Data +public class ImGroupManagerRespVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "群名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "技术交流群") + private String name; + + @Schema(description = "群头像") + private String avatar; + + @Schema(description = "群公告") + private String notice; + + @Schema(description = "群主用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long ownerUserId; + + @Schema(description = "群主昵称", example = "张三") + private String ownerNickname; + + @Schema(description = "群成员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "12") + private Integer memberCount; + + @Schema(description = "群状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "解散时间") + private LocalDateTime dissolvedTime; + + @Schema(description = "是否封禁", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean banned; + + @Schema(description = "是否全群禁言") + private Boolean mutedAll; + + @Schema(description = "封禁原因") + private String bannedReason; + + @Schema(description = "封禁时间") + private LocalDateTime bannedTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupRequestManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupRequestManagerPageReqVO.java new file mode 100644 index 0000000000..e51d066857 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupRequestManagerPageReqVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.group.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 加群申请分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImGroupRequestManagerPageReqVO extends PageParam { + + @Schema(description = "群编号", example = "1024") + private Long groupId; + + @Schema(description = "申请人 / 被邀请人用户编号", example = "2048") + private Long userId; + + @Schema(description = "邀请人用户编号", example = "31460") + private Long inviterUserId; + + @Schema(description = "处理结果", example = "0") + private Integer handleResult; // 参见 ImGroupRequestHandleResultEnum 枚举类 + + @Schema(description = "加入来源", example = "1") + private Integer addSource; // 参见 ImGroupAddSourceEnum 枚举类 + + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupRequestManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupRequestManagerRespVO.java new file mode 100644 index 0000000000..6f8684e909 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupRequestManagerRespVO.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 加群申请 Response VO") +@Data +public class ImGroupRequestManagerRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long groupId; + + @Schema(description = "群名称", example = "芋道技术交流群") + private String groupName; + + @Schema(description = "申请人 / 被邀请人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long userId; + + @Schema(description = "申请人 / 被邀请人昵称", example = "张三") + private String userNickname; + + @Schema(description = "邀请人用户编号;NULL 表示用户主动申请", example = "200") + private Long inviterUserId; + + @Schema(description = "邀请人昵称", example = "老张") + private String inviterNickname; + + @Schema(description = "申请理由", example = "我想加入这个群") + private String applyContent; + + @Schema(description = "加入来源", example = "1") + private Integer addSource; // 参见 ImGroupAddSourceEnum 枚举类 + + @Schema(description = "处理结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer handleResult; // 参见 ImGroupRequestHandleResultEnum 枚举类 + + @Schema(description = "处理人用户编号", example = "31460") + private Long handleUserId; + + @Schema(description = "处理人昵称", example = "管理员") + private String handleNickname; + + @Schema(description = "处理理由", example = "暂不通过") + private String handleContent; + + @Schema(description = "处理时间") + private LocalDateTime handleTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/member/ImGroupMemberManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/member/ImGroupMemberManagerRespVO.java new file mode 100644 index 0000000000..b5d1346d96 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/member/ImGroupMemberManagerRespVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 群成员 Response VO") +@Data +public class ImGroupMemberManagerRespVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long userId; + + @Schema(description = "用户昵称", example = "张三") + private String nickname; + + @Schema(description = "用户头像") + private String avatar; + + @Schema(description = "组内显示名", example = "三哥") + private String displayUserName; + + @Schema(description = "群备注", example = "技术交流群") + private String groupRemark; + + @Schema(description = "是否免打扰", example = "false") + private Boolean silent; + + @Schema(description = "成员状态", example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "成员角色;1=群主 2=管理员 3=普通成员", example = "3") + private Integer role; // 参见 ImGroupMemberRoleEnum 枚举类 + + @Schema(description = "入群时间") + private LocalDateTime joinTime; + + @Schema(description = "退群时间") + private LocalDateTime quitTime; + + @Schema(description = "禁言到期时间") + private LocalDateTime muteEndTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImChannelMessageManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImChannelMessageManagerController.java new file mode 100644 index 0000000000..36a3b60bc8 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImChannelMessageManagerController.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessagePageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessageRespVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService; +import cn.iocoder.yudao.module.im.service.message.ImChannelMessageService; +import cn.iocoder.yudao.module.im.service.channel.ImChannelService; +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 java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +@Tag(name = "管理后台 - IM 频道消息") +@RestController +@RequestMapping("/im/manager/channel-message") +@Validated +public class ImChannelMessageManagerController { + + @Resource + private ImChannelMessageService channelMessageService; + @Resource + private ImChannelService channelService; + @Resource + private ImChannelMaterialService channelMaterialService; + + @PostMapping("/send") + @Operation(summary = "立即推送频道消息") + @PreAuthorize("@ss.hasPermission('im:manager:channel-message:send')") + public CommonResult sendMessage(@Valid @RequestBody ImChannelMessageSendReqVO reqVO) { + return success(channelMessageService.sendMessage(reqVO)); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除频道消息") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:channel-message:delete')") + public CommonResult deleteMessage(@RequestParam("id") Long id) { + channelMessageService.deleteMessage(id); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得频道消息分页;回填频道名 / 素材标题") + @PreAuthorize("@ss.hasPermission('im:manager:channel-message:query')") + public CommonResult> getMessagePage(@Valid ImChannelMessagePageReqVO pageReqVO) { + PageResult pageResult = channelMessageService.getMessagePage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 批量查询频道和素材,并回填频道名 / 素材标题 + Map channelMap = channelService.getChannelMap( + convertSet(pageResult.getList(), ImChannelMessageDO::getChannelId)); + Map materialMap = channelMaterialService.getMaterialMap( + convertSet(pageResult.getList(), ImChannelMessageDO::getMaterialId)); + return success(BeanUtils.toBean(pageResult, ImChannelMessageRespVO.class, vo -> { + MapUtils.findAndThen(channelMap, vo.getChannelId(), c -> vo.setChannelName(c.getName())); + MapUtils.findAndThen(materialMap, vo.getMaterialId(), + material -> vo.setMaterialTitle(material.getTitle()).setMaterialCoverUrl(material.getCoverUrl())); + })); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java new file mode 100644 index 0000000000..da49a9c16e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.service.group.ImGroupService; +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; +import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.AT_USER_ID_ALL; + +@Tag(name = "管理后台 - IM 群聊消息") +@RestController +@RequestMapping("/im/manager/message/group") +@Validated +public class ImGroupMessageManagerController { + + @Resource + private ImGroupMessageService groupMessageService; + @Resource + private ImGroupService groupService; + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/page") + @Operation(summary = "获得群聊消息分页") + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") + public CommonResult> getGroupMessagePage( + @Valid ImGroupMessageManagerPageReqVO pageReqVO) { + // 1. 分页查询 + PageResult pageResult = groupMessageService.getGroupMessagePage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 2.1 批量查询群名称、发送人昵称、@ 用户昵称(-1 表示 @所有人,跳过查询,由前端判断渲染) + Map groupMap = groupService.getGroupMap( + convertSet(pageResult.getList(), ImGroupMessageDO::getGroupId)); + Set userIds = convertSetByFlatMap(pageResult.getList(), m -> Stream.concat( + Stream.of(m.getSenderId()), + CollUtil.emptyIfNull(m.getAtUserIds()).stream() + .filter(id -> !Objects.equals(id, AT_USER_ID_ALL)))); + Map userMap = adminUserApi.getUserMap(userIds); + // 2.2 转换为 VO,填充群名 / 发送人昵称 / @ 用户昵称(-1 位置留 null,由前端展示「@所有人」) + return success(BeanUtils.toBean(pageResult, ImGroupMessageManagerRespVO.class, vo -> { + MapUtils.findAndThen(groupMap, vo.getGroupId(), group -> vo.setGroupName(group.getName())); + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname())); + if (CollUtil.isNotEmpty(vo.getAtUserIds())) { + vo.setAtUserNicknames(convertList(vo.getAtUserIds(), id -> { + AdminUserRespDTO user = userMap.get(id); + return user != null ? user.getNickname() : null; + })); + } + })); + } + + @GetMapping("/get") + @Operation(summary = "获得群聊消息详情") + @Parameter(name = "id", description = "消息编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") + public CommonResult getGroupMessage(@RequestParam("id") Long id) { + ImGroupMessageDO message = groupMessageService.getGroupMessage(id); + return success(BeanUtils.toBean(message, ImGroupMessageManagerRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java new file mode 100644 index 0000000000..8db5b698b8 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; + +@Tag(name = "管理后台 - IM 私聊消息") +@RestController +@RequestMapping("/im/manager/message/private") +@Validated +public class ImPrivateMessageManagerController { + + @Resource + private ImPrivateMessageService privateMessageService; + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/page") + @Operation(summary = "获得私聊消息分页") + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") + public CommonResult> getPrivateMessagePage( + @Valid ImPrivateMessageManagerPageReqVO pageReqVO) { + // 1. 分页查询 + PageResult pageResult = privateMessageService.getPrivateMessagePage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 2.1 一次性批量查询发送人 + 接收人昵称 + Map userMap = adminUserApi.getUserMap(convertSetByFlatMap(pageResult.getList(), + m -> Stream.of(m.getSenderId(), m.getReceiverId()))); + // 2.2 转换为 VO,填充昵称 + return success(BeanUtils.toBean(pageResult, ImPrivateMessageManagerRespVO.class, vo -> { + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname())); + MapUtils.findAndThen(userMap, vo.getReceiverId(), user -> vo.setReceiverNickname(user.getNickname())); + })); + } + + @GetMapping("/get") + @Operation(summary = "获得私聊消息详情") + @Parameter(name = "id", description = "消息编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") + public CommonResult getPrivateMessage(@RequestParam("id") Long id) { + ImPrivateMessageDO message = privateMessageService.getPrivateMessage(id); + return success(BeanUtils.toBean(message, ImPrivateMessageManagerRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessagePageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessagePageReqVO.java new file mode 100644 index 0000000000..48e1a44c53 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessagePageReqVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 频道消息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImChannelMessagePageReqVO extends PageParam { + + @Schema(description = "频道编号", example = "1") + private Long channelId; + + @Schema(description = "素材编号", example = "1024") + private Long materialId; + + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] sendTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageRespVO.java new file mode 100644 index 0000000000..cf1bd64dbd --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageRespVO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - IM 频道消息 Response VO") +@Data +public class ImChannelMessageRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "频道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long channelId; + + @Schema(description = "频道名称(关联查询填充)") + private String channelName; + + @Schema(description = "素材编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long materialId; + + @Schema(description = "素材标题(关联查询填充)") + private String materialTitle; + + @Schema(description = "素材封面 URL(关联查询填充)") + private String materialCoverUrl; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "125") + private Integer type; // 参见 ImContentTypeEnum 枚举类 + + @Schema(description = "消息内容;payload JSON 快照") + private String content; + + @Schema(description = "接收人编号列表;为空表示全员") + private List receiverUserIds; + + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime sendTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageSendReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageSendReqVO.java new file mode 100644 index 0000000000..a69ff16c3d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageSendReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - IM 频道消息推送 Request VO") +@Data +public class ImChannelMessageSendReqVO { + + @Schema(description = "素材编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "素材编号不能为空") + private Long materialId; + + @Schema(description = "接收人编号列表;为空表示全员", example = "[1024, 2048]") + private List receiverUserIds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java new file mode 100644 index 0000000000..121ac900c0 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 群聊消息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImGroupMessageManagerPageReqVO extends PageParam { + + @Schema(description = "群编号", example = "1024") + private Long groupId; + + @Schema(description = "发送人编号", example = "1024") + private Long senderId; + + @Schema(description = "消息类型", example = "1") + private Integer type; // 参见 ImContentTypeEnum 枚举类 + + @Schema(description = "消息内容", example = "你好") + private String content; + + @Schema(description = "消息状态", example = "0") + private Integer status; // 参见 ImMessageStatusEnum 枚举类 + + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] sendTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java new file mode 100644 index 0000000000..fcc964af06 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - IM 群聊消息 Response VO") +@Data +public class ImGroupMessageManagerRespVO { + + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "客户端消息编号", example = "c-uuid-xxx") + private String clientMessageId; + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long groupId; + + @Schema(description = "群名称", example = "技术交流群") + private String groupName; + + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long senderId; + + @Schema(description = "发送人昵称", example = "张三") + private String senderNickname; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer type; // 参见 ImContentTypeEnum 枚举类 + + @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 ImMessageStatusEnum 枚举类 + + @Schema(description = "@ 目标用户编号列表(-1 表示 @所有人)") + private List atUserIds; + @Schema(description = "@ 目标用户昵称列表(-1 位置为 null,前端根据 atUserIds 自行展示「@所有人」)") + private List atUserNicknames; + + @Schema(description = "回执状态", example = "0") + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类 + + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime sendTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java new file mode 100644 index 0000000000..6fa711b455 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 私聊消息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImPrivateMessageManagerPageReqVO extends PageParam { + + @Schema(description = "发送人编号", example = "1024") + private Long senderId; + + @Schema(description = "接收人编号", example = "2048") + private Long receiverId; + + @Schema(description = "消息类型", example = "1") + private Integer type; // 参见 ImContentTypeEnum 枚举类 + + @Schema(description = "消息内容", example = "你好") + private String content; + + @Schema(description = "消息状态", example = "0") + private Integer status; // 参见 ImMessageStatusEnum 枚举类 + + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] sendTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java new file mode 100644 index 0000000000..941ef8ce5e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 私聊消息 Response VO") +@Data +public class ImPrivateMessageManagerRespVO { + + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "客户端消息编号", example = "c-uuid-xxx") + private String clientMessageId; + + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long senderId; + + @Schema(description = "发送人昵称", example = "张三") + private String senderNickname; + + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long receiverId; + + @Schema(description = "接收人昵称", example = "李四") + private String receiverNickname; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer type; // 参见 ImContentTypeEnum 枚举类 + + @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 ImMessageStatusEnum 枚举类 + + @Schema(description = "回执状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类 + + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime sendTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/ImRtcCallManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/ImRtcCallManagerController.java new file mode 100644 index 0000000000..417e58f0c5 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/ImRtcCallManagerController.java @@ -0,0 +1,113 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.rtc; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo.ImRtcCallManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo.ImRtcCallManagerRespVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo.ImRtcParticipantManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcParticipantDO; +import cn.iocoder.yudao.module.im.service.group.ImGroupService; +import cn.iocoder.yudao.module.im.service.rtc.ImRtcCallService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +@Tag(name = "管理后台 - IM 通话记录") +@RestController +@RequestMapping("/im/manager/rtc") +@Validated +public class ImRtcCallManagerController { + + @Resource + private ImRtcCallService rtcCallService; + @Resource + private ImGroupService groupService; + + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/page") + @Operation(summary = "获得通话记录分页") + @PreAuthorize("@ss.hasPermission('im:manager:rtc:query')") + public CommonResult> getCallPage(@Valid ImRtcCallManagerPageReqVO pageReqVO) { + PageResult pageResult = rtcCallService.getCallPage(pageReqVO); + return success(buildCallRespVOPage(pageResult)); + } + + @GetMapping("/get") + @Operation(summary = "获得通话记录详情") + @Parameter(name = "id", description = "通话编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:rtc:query')") + public CommonResult getCall(@RequestParam("id") Long id) { + ImRtcCallDO call = rtcCallService.getCall(id); + if (call == null) { + return success(null); + } + return success(CollUtil.getFirst(buildCallRespVOList(Collections.singletonList(call)))); + } + + @GetMapping("/participant-list") + @Operation(summary = "获得通话参与者列表") + @Parameter(name = "id", description = "通话编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:rtc:query')") + public CommonResult> getCallParticipantList(@RequestParam("id") Long id) { + List participants = rtcCallService.getCallParticipantListByCallId(id); + if (CollUtil.isEmpty(participants)) { + return success(Collections.emptyList()); + } + // 查询用户信息 + Map userMap = adminUserApi.getUserMap( + convertSet(participants, ImRtcParticipantDO::getUserId)); + // 组装返回 + return success(BeanUtils.toBean(participants, ImRtcParticipantManagerRespVO.class, vo -> + MapUtils.findAndThen(userMap, vo.getUserId(), + user -> vo.setUserNickname(user.getNickname())))); + } + + // ========== 私有方法:VO 组装 ========== + + private PageResult buildCallRespVOPage(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + return new PageResult<>(buildCallRespVOList(pageResult.getList()), pageResult.getTotal()); + } + + private List buildCallRespVOList(List calls) { + // 查询用户信息 + Map userMap = adminUserApi.getUserMap( + convertSet(calls, ImRtcCallDO::getInviterUserId)); + Map groupMap = groupService.getGroupMap( + convertSet(calls, ImRtcCallDO::getGroupId)); + // 组装返回 + return BeanUtils.toBean(calls, ImRtcCallManagerRespVO.class, vo -> { + MapUtils.findAndThen(userMap, vo.getInviterUserId(), + user -> vo.setInviterNickname(user.getNickname())); + MapUtils.findAndThen(groupMap, vo.getGroupId(), + group -> vo.setGroupName(group.getName())); + }); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcCallManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcCallManagerPageReqVO.java new file mode 100644 index 0000000000..f7878fa024 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcCallManagerPageReqVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 通话记录分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImRtcCallManagerPageReqVO extends PageParam { + + @Schema(description = "发起人用户编号", example = "1024") + private Long inviterUserId; + + @Schema(description = "会话类型", example = "1") + private Integer conversationType; // 参见 ImConversationTypeEnum 枚举类 + + @Schema(description = "媒体类型", example = "1") + private Integer mediaType; // 参见 ImRtcCallMediaTypeEnum 枚举类 + + @Schema(description = "通话状态", example = "10") + private Integer status; // 参见 ImRtcCallStatusEnum 枚举类 + + @Schema(description = "结束原因", example = "1") + private Integer endReason; // 参见 ImRtcCallEndReasonEnum 枚举类 + + @Schema(description = "发起时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] startTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcCallManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcCallManagerRespVO.java new file mode 100644 index 0000000000..0151581dee --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcCallManagerRespVO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 通话记录 Response VO") +@Data +public class ImRtcCallManagerRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "业务通话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "uuid-xxx") + private String room; + + @Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer conversationType; // 参见 ImConversationTypeEnum 枚举类 + + @Schema(description = "媒体类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer mediaType; // 参见 ImRtcCallMediaTypeEnum 枚举类 + + @Schema(description = "发起人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long inviterUserId; + + @Schema(description = "发起人昵称", example = "张三") + private String inviterNickname; + + @Schema(description = "群编号;私聊为空", example = "999") + private Long groupId; + + @Schema(description = "群名称;私聊为空", example = "测试群") + private String groupName; + + @Schema(description = "通话状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer status; // 参见 ImRtcCallStatusEnum 枚举类 + + @Schema(description = "结束原因", example = "1") + private Integer endReason; // 参见 ImRtcCallEndReasonEnum 枚举类 + + @Schema(description = "发起时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime startTime; + + @Schema(description = "接通时间;未接通为空") + private LocalDateTime acceptTime; + + @Schema(description = "结束时间;未结束为空") + private LocalDateTime endTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcParticipantManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcParticipantManagerRespVO.java new file mode 100644 index 0000000000..a990577db0 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/rtc/vo/ImRtcParticipantManagerRespVO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 通话参与者 Response VO") +@Data +public class ImRtcParticipantManagerRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "通话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long callId; + + @Schema(description = "参与者用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long userId; + + @Schema(description = "参与者昵称", example = "张三") + private String userNickname; + + @Schema(description = "参与角色", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer role; // 参见 ImRtcParticipantRoleEnum 枚举类 + + @Schema(description = "参与状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + private Integer status; // 参见 ImRtcParticipantStatusEnum 枚举类 + + @Schema(description = "被邀请时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime inviteTime; + + @Schema(description = "接听时间;未接听为空") + private LocalDateTime acceptTime; + + @Schema(description = "离开时间;未加入为空") + private LocalDateTime leaveTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java new file mode 100644 index 0000000000..d69248d343 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java @@ -0,0 +1,106 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordRespVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; +import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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 jakarta.validation.constraints.Size; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +@Tag(name = "管理后台 - IM 敏感词") +@RestController +@RequestMapping("/im/manager/sensitive-word") +@Validated +public class ImSensitiveWordManagerController { + + @Resource + private ImSensitiveWordService sensitiveWordService; + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "新增敏感词") + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:create')") + public CommonResult createSensitiveWord(@Valid @RequestBody ImSensitiveWordSaveReqVO reqVO) { + return success(sensitiveWordService.createSensitiveWord(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改敏感词") + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:update')") + public CommonResult updateSensitiveWord(@Valid @RequestBody ImSensitiveWordSaveReqVO reqVO) { + sensitiveWordService.updateSensitiveWord(reqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除敏感词") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')") + public CommonResult deleteSensitiveWord(@RequestParam("id") Long id) { + sensitiveWordService.deleteSensitiveWord(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除敏感词") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')") + public CommonResult deleteSensitiveWordList( + @RequestParam("ids") + @Size(max = 100, message = "批量删除最多 100 条") List ids) { + sensitiveWordService.deleteSensitiveWordList(ids); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得敏感词分页") + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:query')") + public CommonResult> getSensitiveWordPage( + @Valid ImSensitiveWordPageReqVO pageReqVO) { + // 1. 分页查询 + PageResult pageResult = sensitiveWordService.getSensitiveWordPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 2.1 批量查询创建人昵称 + Map userMap = adminUserApi.getUserMap(convertSet(pageResult.getList(), + word -> NumberUtils.parseLong(word.getCreator()))); + // 2.2 转换为 VO,填充创建人昵称 + return success(BeanUtils.toBean(pageResult, ImSensitiveWordRespVO.class, vo -> + MapUtils.findAndThen(userMap, NumberUtils.parseLong(vo.getCreator()), + user -> vo.setCreatorName(user.getNickname())))); + } + + @GetMapping("/get") + @Operation(summary = "获得敏感词详情") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:query')") + public CommonResult getSensitiveWord(@RequestParam("id") Long id) { + ImSensitiveWordDO word = sensitiveWordService.getSensitiveWord(id); + return success(BeanUtils.toBean(word, ImSensitiveWordRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java new file mode 100644 index 0000000000..4fc21f225e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 敏感词分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImSensitiveWordPageReqVO extends PageParam { + + @Schema(description = "敏感词,模糊匹配", example = "敏感") + private String word; + + @Schema(description = "状态", example = "0") + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) + + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java new file mode 100644 index 0000000000..578703fe42 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 敏感词 Response VO") +@Data +public class ImSensitiveWordRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "敏感词", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏感词内容") + private String word; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "创建人") + private String creator; + @Schema(description = "创建人昵称", example = "张三") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java new file mode 100644 index 0000000000..5d421406af --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 敏感词新增 / 修改 Request VO") +@Data +public class ImSensitiveWordSaveReqVO { + + @Schema(description = "编号(修改时必填)", example = "1024") + private Long id; + + @Schema(description = "敏感词", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏感词内容") + @NotBlank(message = "敏感词不能为空") + @Size(max = 64, message = "敏感词长度不能超过 64") + private String word; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/ImStatisticsManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/ImStatisticsManagerController.java new file mode 100644 index 0000000000..0a5f1d5c08 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/ImStatisticsManagerController.java @@ -0,0 +1,160 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.statistics; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.statistics.vo.*; +import cn.iocoder.yudao.module.im.service.statistics.ImStatisticsManagerService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.constraints.Max; +import jakarta.validation.constraints.Min; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + +@Tag(name = "管理后台 - IM 数据看板") +@RestController +@RequestMapping("/im/manager/statistics") +@Validated +public class ImStatisticsManagerController { + + /** + * 群规模分桶名称的展示顺序 + */ + private static final List GROUP_SIZE_BUCKETS = Arrays.asList("1-9 人", "10-49 人", "50-199 人", "200+ 人"); + /** + * 看板分布默认时间窗口(天) + */ + private static final int DISTRIBUTION_WINDOW_DAYS = 30; + /** + * TOP 发送者数量 + */ + private static final int TOP_SENDER_LIMIT = 10; + + @Resource + private ImStatisticsManagerService statisticsService; + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/overview") + @Operation(summary = "获得数据概览") + @PreAuthorize("@ss.hasPermission('im:manager:statistics:query')") + public CommonResult getOverview() { + LocalDateTime todayBegin = LocalDate.now().atStartOfDay(); + LocalDateTime tomorrowBegin = todayBegin.plusDays(1); + LocalDateTime yesterdayBegin = todayBegin.minusDays(1); + // 周活/月活定义为 N 天滚动窗口(含今天) + LocalDateTime weekBegin = todayBegin.minusDays(6); + LocalDateTime monthBegin = todayBegin.minusDays(29); + return success(new ImStatisticsManagerOverviewRespVO() + .setTotalUser(statisticsService.getTotalUserCount()) + .setNewUserToday(statisticsService.getNewUserCount(todayBegin, tomorrowBegin)) + .setTotalGroup(statisticsService.getTotalGroupCount()) + .setNewGroupToday(statisticsService.getNewGroupCount(todayBegin, tomorrowBegin)) + .setActiveUserDaily(statisticsService.getActiveUserCount(todayBegin, tomorrowBegin)) + .setActiveUserWeekly(statisticsService.getActiveUserCount(weekBegin, tomorrowBegin)) + .setActiveUserMonthly(statisticsService.getActiveUserCount(monthBegin, tomorrowBegin)) + .setPrivateMessageToday(statisticsService.getPrivateMessageCount(todayBegin, tomorrowBegin)) + .setGroupMessageToday(statisticsService.getGroupMessageCount(todayBegin, tomorrowBegin)) + .setPrivateMessageYesterday(statisticsService.getPrivateMessageCount(yesterdayBegin, todayBegin)) + .setGroupMessageYesterday(statisticsService.getGroupMessageCount(yesterdayBegin, todayBegin))); + } + + @GetMapping("/message-trend") + @Operation(summary = "获得消息趋势(私聊 / 群聊双线)") + @Parameter(name = "days", description = "回看天数(含今日)", example = "7") + @PreAuthorize("@ss.hasPermission('im:manager:statistics:query')") + public CommonResult getMessageTrend( + @RequestParam(value = "days", defaultValue = "7") @Min(1) @Max(90) int days) { + List dates = LocalDateTimeUtils.getLatestDays(days); + LocalDateTime beginTime = dates.get(0); + LocalDateTime endTime = dates.get(days - 1).plusDays(1); + Map privateMap = statisticsService.getPrivateMessageDailyCountMap(beginTime, endTime); + Map groupMap = statisticsService.getGroupMessageDailyCountMap(beginTime, endTime); + // 转换格式 + Map> series = new LinkedHashMap<>(); + series.put("private", alignSeries(dates, privateMap)); + series.put("group", alignSeries(dates, groupMap)); + return success(new ImStatisticsManagerTrendRespVO().setDates(dates).setSeries(series)); + } + + @GetMapping("/user-trend") + @Operation(summary = "获得用户趋势(新增注册 / 日活双线)") + @Parameter(name = "days", description = "回看天数(含今日)", example = "7") + @PreAuthorize("@ss.hasPermission('im:manager:statistics:query')") + public CommonResult getUserTrend( + @RequestParam(value = "days", defaultValue = "7") @Min(1) @Max(90) int days) { + List dates = LocalDateTimeUtils.getLatestDays(days); + LocalDateTime beginTime = dates.get(0); + LocalDateTime endTime = dates.get(days - 1).plusDays(1); + Map registerMap = statisticsService.getNewUserDailyCountMap(beginTime, endTime); + Map activeMap = statisticsService.getActiveUserDailyCountMap(beginTime, endTime); + // 转换格式 + Map> series = new LinkedHashMap<>(); + series.put("register", alignSeries(dates, registerMap)); + series.put("active", alignSeries(dates, activeMap)); + return success(new ImStatisticsManagerTrendRespVO().setDates(dates).setSeries(series)); + } + + @GetMapping("/message-type-distribution") + @Operation(summary = "获得内容类型分布(最近 30 天)") + @PreAuthorize("@ss.hasPermission('im:manager:statistics:query')") + public CommonResult> getMessageTypeDistribution() { + LocalDateTime endTime = LocalDate.now().plusDays(1).atStartOfDay(); + LocalDateTime beginTime = endTime.minusDays(DISTRIBUTION_WINDOW_DAYS); + Map typeCountMap = statisticsService.getMessageTypeCountMap(beginTime, endTime); + // 转换格式 + return success(convertList(typeCountMap.entrySet(), entry -> new ImStatisticsManagerMessageTypeRespVO() + .setType(entry.getKey()).setValue(entry.getValue()))); + } + + @GetMapping("/group-size-distribution") + @Operation(summary = "获得群规模分布") + @PreAuthorize("@ss.hasPermission('im:manager:statistics:query')") + public CommonResult> getGroupSizeDistribution() { + Map groupSizeMap = statisticsService.getGroupSizeCountMap(); + // 转换格式 + return success(convertList(GROUP_SIZE_BUCKETS, bucket -> new ImStatisticsManagerGroupSizeRespVO() + .setRange(bucket).setCount(groupSizeMap.getOrDefault(bucket, 0L)))); + } + + @GetMapping("/top-senders") + @Operation(summary = "获得消息 TOP 发送者(最近 30 天)") + @PreAuthorize("@ss.hasPermission('im:manager:statistics:query')") + public CommonResult> getTopSenders() { + LocalDateTime endTime = LocalDate.now().plusDays(1).atStartOfDay(); + LocalDateTime beginTime = endTime.minusDays(DISTRIBUTION_WINDOW_DAYS); + Map topSenderMap = statisticsService.getTopSenderCountMap(beginTime, endTime, TOP_SENDER_LIMIT); + // TOP 发送者:批量回填昵称 + Map userMap = adminUserApi.getUserMap(topSenderMap.keySet()); + return success(convertList(topSenderMap.entrySet(), entry -> { + ImStatisticsManagerTopSenderRespVO item = new ImStatisticsManagerTopSenderRespVO() + .setUserId(entry.getKey()).setMessageCount(entry.getValue()); + MapUtils.findAndThen(userMap, entry.getKey(), user -> item.setNickname(user.getNickname())); + return item; + })); + } + + /** + * 把每日聚合 Map 对齐到 dates 序列;缺失天补 0 + */ + private static List alignSeries(List dates, Map dailyMap) { + return convertList(dates, date -> dailyMap.getOrDefault(date, 0L)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerGroupSizeRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerGroupSizeRespVO.java new file mode 100644 index 0000000000..064f0dec02 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerGroupSizeRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.statistics.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - IM 数据看板群规模分布项 Response VO") +@Data +public class ImStatisticsManagerGroupSizeRespVO { + + @Schema(description = "区间名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1-9 人") + private String range; + + @Schema(description = "群数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "320") + private Long count; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerMessageTypeRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerMessageTypeRespVO.java new file mode 100644 index 0000000000..fce2275536 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerMessageTypeRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.statistics.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - IM 数据看板内容类型分布项 Response VO") +@Data +public class ImStatisticsManagerMessageTypeRespVO { + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer type; // 参见 ImContentTypeEnum 枚举类 + + @Schema(description = "消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000") + private Long value; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerOverviewRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerOverviewRespVO.java new file mode 100644 index 0000000000..dc9e713f3d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerOverviewRespVO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.statistics.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - IM 数据看板概览 Response VO") +@Data +public class ImStatisticsManagerOverviewRespVO { + + @Schema(description = "用户总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "12345") + private Long totalUser; + + @Schema(description = "今日新增用户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "23") + private Long newUserToday; + + @Schema(description = "群总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "678") + private Long totalGroup; + + @Schema(description = "今日新建群数", requiredMode = Schema.RequiredMode.REQUIRED, example = "4") + private Long newGroupToday; + + @Schema(description = "日活用户(今日发过消息的去重用户数)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1023") + private Long activeUserDaily; + + @Schema(description = "周活用户(最近 7 天发过消息的去重用户数)", requiredMode = Schema.RequiredMode.REQUIRED, example = "4567") + private Long activeUserWeekly; + + @Schema(description = "月活用户(最近 30 天发过消息的去重用户数)", requiredMode = Schema.RequiredMode.REQUIRED, example = "8901") + private Long activeUserMonthly; + + @Schema(description = "今日私聊消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "8765") + private Long privateMessageToday; + + @Schema(description = "今日群聊消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "3210") + private Long groupMessageToday; + + @Schema(description = "昨日私聊消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "7890") + private Long privateMessageYesterday; + + @Schema(description = "昨日群聊消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "3000") + private Long groupMessageYesterday; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerTopSenderRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerTopSenderRespVO.java new file mode 100644 index 0000000000..2f06158707 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerTopSenderRespVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.statistics.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - IM 数据看板 TOP 发送者项 Response VO") +@Data +public class ImStatisticsManagerTopSenderRespVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long userId; + + @Schema(description = "用户昵称", example = "张三") + private String nickname; + + @Schema(description = "消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1500") + private Long messageCount; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerTrendRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerTrendRespVO.java new file mode 100644 index 0000000000..e08d980cdd --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerTrendRespVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.statistics.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - IM 数据看板趋势 Response VO") +@Data +public class ImStatisticsManagerTrendRespVO { + + @Schema(description = "横轴日期序列(每天 00:00:00,由前端按需取日期部分)", requiredMode = Schema.RequiredMode.REQUIRED) + private List dates; + + @Schema(description = "数据系列:key 为系列名(如 private/group 或 register/active),value 为与 dates 等长的计数数组", + requiredMode = Schema.RequiredMode.REQUIRED) + private Map> series; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImChannelMessageController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImChannelMessageController.java new file mode 100644 index 0000000000..41a8c0165e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImChannelMessageController.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.im.controller.admin.message; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.channel.ImChannelMessagePullRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.service.message.ImChannelMessageService; +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.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.PositiveOrZero; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +@Tag(name = "用户 APP - IM 频道消息") +@RestController +@RequestMapping("/im/channel/message") +@Validated +public class ImChannelMessageController { + + @Resource + private ImChannelMessageService channelMessageService; + + @GetMapping("/pull") + @Operation(summary = "拉取频道消息(离线增量);按 minId 游标分页") + public CommonResult> pullChannelMessageList( + @RequestParam(value = "minId", defaultValue = "0") @PositiveOrZero(message = "minId 不能小于 0") Long minId, + @RequestParam(value = "size", defaultValue = "100") + @Min(value = 1, message = "size 必须大于 0") + @Max(value = 200, message = "size 一次最多 200 条") Integer size) { + // 1. 拉取消息列表 + Long userId = getLoginUserId(); + List list = channelMessageService.pullChannelMessageList(userId, minId, size); + if (CollUtil.isEmpty(list)) { + return success(Collections.emptyList()); + } + // 2. 按已读游标补 receiptStatus(已读 DONE / 未读 PENDING) + Map readMaxByChannel = channelMessageService.getChannelReadMaxMessageIdMap( + userId, convertSet(list, ImChannelMessageDO::getChannelId)); + return success(BeanUtils.toBean(list, ImChannelMessagePullRespVO.class, vo -> { + Long readMax = readMaxByChannel.get(vo.getChannelId()); + vo.setReceiptStatus(readMax != null && readMax >= vo.getId() + ? ImMessageReceiptStatusEnum.DONE.getStatus() + : ImMessageReceiptStatusEnum.PENDING.getStatus()); + })); + } + + @PutMapping("/read") + @Operation(summary = "标记频道消息已读") + @Parameter(name = "channelId", description = "频道编号", required = true, example = "1") + @Parameter(name = "messageId", description = "已读到的消息编号", required = true, example = "100") + public CommonResult readChannelMessages(@RequestParam("channelId") Long channelId, + @RequestParam("messageId") Long messageId) { + channelMessageService.readChannelMessages(getLoginUserId(), channelId, messageId); + return success(true); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java new file mode 100644 index 0000000000..4bc719e8a6 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImGroupMessageController.java @@ -0,0 +1,85 @@ +package cn.iocoder.yudao.module.im.controller.admin.message; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageRespVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; +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 jakarta.validation.constraints.Min; +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; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - IM 群聊消息") +@RestController +@RequestMapping("/im/message/group") +@Validated +public class ImGroupMessageController { + + @Resource + private ImGroupMessageService groupMessageService; + + @PostMapping("/send") + @Operation(summary = "发送群聊消息") + public CommonResult sendGroupMessage(@Valid @RequestBody ImGroupMessageSendReqVO reqVO) { + ImGroupMessageDO message = groupMessageService.sendGroupMessage(getLoginUserId(), reqVO); + return success(BeanUtils.toBean(message, ImGroupMessageRespVO.class)); + } + + @GetMapping("/pull") + @Operation(summary = "拉取群聊消息(增量)") + @Parameter(name = "minId", description = "最小消息 id", required = true, example = "0") + @Parameter(name = "size", description = "拉取数量", required = true, example = "100") + public CommonResult> pullGroupMessageList( + @RequestParam("minId") Long minId, + @RequestParam("size") @Min(value = 1, message = "拉取数量最小值为 1") Integer size) { + List messages = groupMessageService.pullGroupMessageList(getLoginUserId(), minId, size); + return success(BeanUtils.toBean(messages, ImGroupMessageRespVO.class)); + } + + @PutMapping("/read") + @Operation(summary = "标记群聊消息已读") + @Parameter(name = "groupId", description = "群编号", required = true, example = "1") + @Parameter(name = "messageId", description = "已读到的消息编号", required = true, example = "100") + public CommonResult readGroupMessages(@RequestParam("groupId") Long groupId, + @RequestParam("messageId") Long messageId) { + groupMessageService.readGroupMessages(getLoginUserId(), groupId, messageId); + return success(true); + } + + @DeleteMapping("/recall") + @Operation(summary = "撤回群聊消息") + @Parameter(name = "id", description = "消息编号", required = true, example = "1") + public CommonResult recallGroupMessage(@RequestParam("id") Long id) { + ImGroupMessageDO message = groupMessageService.recallGroupMessage(getLoginUserId(), id); + return success(BeanUtils.toBean(message, ImGroupMessageRespVO.class)); + } + + @GetMapping("/get-read-user-ids") + @Operation(summary = "获取群消息已读用户列表") + @Parameter(name = "groupId", description = "群编号", required = true, example = "1") + @Parameter(name = "messageId", description = "消息编号", required = true, example = "1") + public CommonResult> getGroupReadUserIds(@RequestParam("groupId") Long groupId, + @RequestParam("messageId") Long messageId) { + return success(groupMessageService.getGroupReadUserIds(getLoginUserId(), groupId, messageId)); + } + + @GetMapping("/list") + @Operation(summary = "查询群聊历史消息") + public CommonResult> getGroupMessageList(@Valid ImGroupMessageListReqVO reqVO) { + List messages = groupMessageService.getGroupMessageList(getLoginUserId(), reqVO); + return success(BeanUtils.toBean(messages, ImGroupMessageRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImMessageController.http b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImMessageController.http new file mode 100644 index 0000000000..83d177313f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImMessageController.http @@ -0,0 +1,18 @@ +### 请求 /send 接口 => 成功 +POST {{baseUrl}}/im/message/send +Authorization: Bearer {{token}} +Content-Type: application/json +tenant-id: {{adminTenentId}} + +{ + "clientMessageId": "123", + "receiverId": 1, + "conversationType": 1, + "contentType": 101, + "content": "你好1" +} + +### 请求 /pull 接口 => 成功 +GET {{baseUrl}}/im/message/pull?sequence=0&size=2 +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} \ No newline at end of file diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java new file mode 100644 index 0000000000..277dc976b3 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java @@ -0,0 +1,85 @@ +package cn.iocoder.yudao.module.im.controller.admin.message; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageRespVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; +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 jakarta.validation.constraints.Min; +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; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - IM 私聊消息") +@RestController +@RequestMapping("/im/message/private") +@Validated +public class ImPrivateMessageController { + + @Resource + private ImPrivateMessageService privateMessageService; + + @PostMapping("/send") + @Operation(summary = "发送私聊消息") + public CommonResult sendPrivateMessage( + @Valid @RequestBody ImPrivateMessageSendReqVO reqVO) { + ImPrivateMessageDO message = privateMessageService.sendPrivateMessage(getLoginUserId(), reqVO); + return success(BeanUtils.toBean(message, ImPrivateMessageRespVO.class)); + } + + @GetMapping("/pull") + @Operation(summary = "拉取私聊消息(增量)") + @Parameter(name = "minId", description = "最小消息 id", required = true, example = "0") + @Parameter(name = "size", description = "拉取数量", required = true, example = "100") + public CommonResult> pullPrivateMessageList( + @RequestParam("minId") Long minId, + @RequestParam("size") @Min(value = 1, message = "拉取数量最小值为 1") Integer size) { + List messages = privateMessageService.pullPrivateMessageList(getLoginUserId(), minId, size); + return success(BeanUtils.toBean(messages, ImPrivateMessageRespVO.class)); + } + + @PutMapping("/read") + @Operation(summary = "标记私聊消息已读") + @Parameter(name = "receiverId", description = "接收方用户编号(对方)", required = true, example = "2") + @Parameter(name = "messageId", description = "已读位置(含),通常是会话内最大消息编号", required = true, example = "100") + public CommonResult readPrivateMessages(@RequestParam("receiverId") Long receiverId, + @RequestParam("messageId") Long messageId) { + privateMessageService.readPrivateMessages(getLoginUserId(), receiverId, messageId); + return success(true); + } + + @GetMapping("/max-read-message-id") + @Operation(summary = "查询对方已读到我发的最大消息 id", + description = "用于多端 / 离线场景下的已读位置补齐:进入会话或断线重连后调用,据此把本地自发消息更新为已读") + @Parameter(name = "peerId", description = "对方用户编号", required = true, example = "2") + public CommonResult getMaxReadMessageId(@RequestParam("peerId") Long peerId) { + return success(privateMessageService.getMaxReadMessageId(getLoginUserId(), peerId)); + } + + @DeleteMapping("/recall") + @Operation(summary = "撤回私聊消息") + @Parameter(name = "id", description = "消息编号", required = true, example = "1") + public CommonResult recallPrivateMessage(@RequestParam("id") Long id) { + ImPrivateMessageDO message = privateMessageService.recallPrivateMessage(getLoginUserId(), id); + return success(BeanUtils.toBean(message, ImPrivateMessageRespVO.class)); + } + + @GetMapping("/list") + @Operation(summary = "查询私聊历史消息") + public CommonResult> getPrivateMessageList(@Valid ImPrivateMessageListReqVO reqVO) { + List messages = privateMessageService.getPrivateMessageList(getLoginUserId(), reqVO); + return success(BeanUtils.toBean(messages, ImPrivateMessageRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java new file mode 100644 index 0000000000..8a30fcb795 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.im.controller.admin.message.vo.channel; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "用户 APP - IM 频道消息拉取 Response VO") +@Data +public class ImChannelMessagePullRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9001") + private Long id; + + @Schema(description = "频道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long channelId; + + @Schema(description = "素材编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long materialId; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "125") + private Integer type; + + @Schema(description = "消息内容;payload JSON 快照", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "当前用户回执 / 已读态;按 Redis 读位置计算(已读 DONE,未读 PENDING)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类 + + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime sendTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageListReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageListReqVO.java new file mode 100644 index 0000000000..76aa9586c1 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageListReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.im.controller.admin.message.vo.group; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 群聊历史消息列表 Request VO") +@Data +public class ImGroupMessageListReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "群编号不能为空") + private Long groupId; + + @Schema(description = "起始消息编号,从该 id 往前拉取(不含)。为空则从最新消息开始", example = "1024") + private Long maxId; + + @Schema(description = "拉取数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + @NotNull(message = "拉取数量不能为空") + @Min(value = 1, message = "拉取数量最小值为 1") + @Max(value = 200, message = "拉取数量最大值为 200") + private Integer limit; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageRespVO.java new file mode 100644 index 0000000000..ee8a164616 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageRespVO.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.im.controller.admin.message.vo.group; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 群聊消息 Response VO + */ +@Schema(description = "管理后台 - 群聊消息 Response VO") +@Data +public class ImGroupMessageRespVO { + + @Schema(description = "消息编号", example = "1") + private Long id; + + @Schema(description = "客户端消息编号", example = "uuid-xxx") + private String clientMessageId; + + @Schema(description = "发送人编号", example = "1") + private Long senderId; + + @Schema(description = "群编号", example = "1") + private Long groupId; + + @Schema(description = "消息类型", example = "0") + private Integer type; + + @Schema(description = "消息内容", example = "{\"content\":\"你好\"}") + private String content; + + @Schema(description = "消息状态", example = "0") + private Integer status; + + @Schema(description = "发送时间") + private LocalDateTime sendTime; + + @Schema(description = "@目标用户编号列表", example = "[1,2,3]") + private List atUserIds; + + @Schema(description = "定向接收用户编号列表", example = "[1,2]") + private List receiverUserIds; + + @Schema(description = "回执状态", example = "0") + private Integer receiptStatus; + + @Schema(description = "已读人数(回执消息、且发送人为当前用户时有值)", example = "3") + private Integer readCount; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java new file mode 100644 index 0000000000..813e35ad60 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.im.controller.admin.message.vo.group; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 群聊消息发送 Request VO + */ +@Schema(description = "管理后台 - 群聊消息发送 Request VO") +@Data +public class ImGroupMessageSendReqVO { + + @Schema(description = "客户端消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "uuid-xxx") + @NotEmpty(message = "客户端消息编号不能为空") + private String clientMessageId; + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "群编号不能为空") + private Long groupId; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "消息类型不能为空") + @InEnum(ImContentTypeEnum.class) + private Integer type; + + @Schema(description = "消息内容,JSON 格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"content\":\"你好\"}") + @NotEmpty(message = "消息内容不能为空") + private String content; + + @Schema(description = "@目标用户编号列表", example = "[1,2,3]") + private List atUserIds; + + @Schema(description = "是否需要回执", example = "false") + private Boolean receipt; + + /** + * 仅允许用户消息(normal)类型 + */ + @AssertTrue(message = "消息类型不允许") + @JsonIgnore + public boolean isTypeNormal() { + return type == null || ImContentTypeEnum.validate(type).isNormal(); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageListReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageListReqVO.java new file mode 100644 index 0000000000..c9259d9129 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageListReqVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.im.controller.admin.message.vo.privates; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 私聊历史消息列表 Request VO") +@Data +public class ImPrivateMessageListReqVO { + + @Schema(description = "接收人编号(对方)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "接收人编号不能为空") + private Long receiverId; + + @Schema(description = "起始消息编号,从该 id 往前拉取(不含)。为空则从最新消息开始", example = "1024") + private Long maxId; + + @Schema(description = "拉取数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + @NotNull(message = "拉取数量不能为空") + @Min(value = 1, message = "拉取数量最小值为 1") + @Max(value = 200, message = "拉取数量最大值为 200") + private Integer limit; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessagePageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessagePageReqVO.java new file mode 100644 index 0000000000..b353e77d3f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessagePageReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.im.controller.admin.message.vo.privates; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 私聊消息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ImPrivateMessagePageReqVO extends PageParam { + + @Schema(description = "接收人编号(对方)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "接收人编号不能为空") + private Long receiverId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java new file mode 100644 index 0000000000..9b626896c6 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.im.controller.admin.message.vo.privates; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 私聊消息 Response VO + */ +@Schema(description = "管理后台 - 私聊消息 Response VO") +@Data +public class ImPrivateMessageRespVO { + + @Schema(description = "消息编号", example = "1") + private Long id; + + @Schema(description = "客户端消息编号", example = "uuid-xxx") + private String clientMessageId; + + @Schema(description = "发送人编号", example = "1") + private Long senderId; + + @Schema(description = "接收人编号", example = "2") + private Long receiverId; + + @Schema(description = "消息类型", example = "0") + private Integer type; + + @Schema(description = "消息内容", example = "{\"content\":\"你好\"}") + private String content; + + @Schema(description = "消息状态", example = "0") + private Integer status; // 参见 ImMessageStatusEnum 枚举类 + + @Schema(description = "回执状态", example = "0") + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类 + + @Schema(description = "发送时间") + private LocalDateTime sendTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java new file mode 100644 index 0000000000..655223420c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.im.controller.admin.message.vo.privates; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 私聊消息发送 Request VO + */ +@Schema(description = "管理后台 - 私聊消息发送 Request VO") +@Data +public class ImPrivateMessageSendReqVO { + + @Schema(description = "客户端消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "uuid-xxx") + @NotEmpty(message = "客户端消息编号不能为空") + private String clientMessageId; + + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "接收人编号不能为空") + private Long receiverId; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "消息类型不能为空") + @InEnum(ImContentTypeEnum.class) + private Integer type; + + @Schema(description = "消息内容,JSON 格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"content\":\"你好\"}") + @NotEmpty(message = "消息内容不能为空") + private String content; + + @Schema(description = "是否需要回执", example = "false") + private Boolean receipt; + + /** + * 仅允许用户消息(normal)类型 + */ + @AssertTrue(message = "消息类型不允许") + @JsonIgnore + public boolean isTypeNormal() { + return type == null || ImContentTypeEnum.validate(type).isNormal(); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/ImRtcCallController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/ImRtcCallController.java new file mode 100644 index 0000000000..bebabf6297 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/ImRtcCallController.java @@ -0,0 +1,169 @@ +package cn.iocoder.yudao.module.im.controller.admin.rtc; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.module.im.controller.admin.rtc.vo.ImRtcCallCreateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.rtc.vo.ImRtcCallInviteReqVO; +import cn.iocoder.yudao.module.im.controller.admin.rtc.vo.ImRtcCallRespVO; +import cn.iocoder.yudao.module.im.controller.admin.rtc.vo.ImRtcGroupCallRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcParticipantDO; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallStatusEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantStatusEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.rtc.ImRtcCallService; +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.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - IM 实时通话") +@RestController +@RequestMapping("/im/rtc") +@Validated +public class ImRtcCallController { + + @Resource + private ImRtcCallService rtcCallService; + @Resource + private ImProperties imProperties; + + @PostMapping("/create") + @Operation(summary = "创建新通话;按 conversationType 区分私聊 / 群聊") + public CommonResult createCall(@Valid @RequestBody ImRtcCallCreateReqVO reqVO) { + Long userId = getLoginUserId(); + ImRtcCallDO call = rtcCallService.createCall(userId, reqVO); + return success(buildCallRespVO(call, userId)); + } + + @PostMapping("/invite") + @Operation(summary = "通话中追加邀请;仅群通话可用") + public CommonResult inviteCall(@Valid @RequestBody ImRtcCallInviteReqVO reqVO) { + rtcCallService.inviteCall(getLoginUserId(), reqVO); + return success(true); + } + + @PostMapping("/join") + @Operation(summary = "加入已有群通话;用于胶囊条「加入」按钮") + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") + public CommonResult joinCall(@RequestParam("room") String room) { + Long userId = getLoginUserId(); + return success(buildCallRespVO(rtcCallService.joinCall(userId, room), userId)); + } + + @PostMapping("/accept") + @Operation(summary = "接听通话") + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") + public CommonResult accept(@RequestParam("room") String room) { + Long userId = getLoginUserId(); + return success(buildCallRespVO(rtcCallService.acceptCall(userId, room), userId)); + } + + @PostMapping("/reject") + @Operation(summary = "拒绝通话") + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") + public CommonResult reject(@RequestParam("room") String room) { + rtcCallService.rejectCall(getLoginUserId(), room); + return success(true); + } + + @PostMapping("/cancel") + @Operation(summary = "取消邀请;主叫接通前调用") + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") + public CommonResult cancel(@RequestParam("room") String room) { + rtcCallService.cancelCall(getLoginUserId(), room); + return success(true); + } + + @PostMapping("/leave") + @Operation(summary = "离开通话;接通后调用") + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") + public CommonResult leave(@RequestParam("room") String room) { + rtcCallService.leaveCall(getLoginUserId(), room); + return success(true); + } + + @PostMapping("/no-answer-call-check") + @Operation(summary = "前端 RUNNING 端 timer 兜底;触发后端立即扫描该 room 的振铃超时(接口静默)") + @Parameter(name = "room", description = "业务通话编号", required = true, example = "f47ac10b58cc4372a567") + public CommonResult noAnswerCallCheck(@RequestParam("room") String room) { + rtcCallService.noAnswerCallCheck(getLoginUserId(), room); + return success(true); + } + + @GetMapping("/get-active-call") + @Operation(summary = "查询当前进行中的通话;用于群聊顶部「N 人正在通话」胶囊条") + @Parameter(name = "groupId", description = "群编号", required = true, example = "2048") + public CommonResult getActiveCall(@RequestParam("groupId") Long groupId) { + ImRtcCallDO call = rtcCallService.getActiveCall(getLoginUserId(), groupId); + return success(buildGroupActiveRespVO(call)); + } + + // ========== VO 拼装 ========== + + /** + * 拼装 invite / join / accept / refresh-token 的响应 VO;含 token + 参与者分桶 + * + * @param call 通话主表 + * @param userId 当前用户编号;token 按该用户签发 + * @return 响应 VO;call 为空返回 null + */ + private ImRtcCallRespVO buildCallRespVO(ImRtcCallDO call, Long userId) { + if (call == null) { + return null; + } + List participants = rtcCallService.getCallParticipantList(call.getRoom()); + boolean ended = ImRtcCallStatusEnum.isEnded(call.getStatus()); + return new ImRtcCallRespVO() + .setRoom(call.getRoom()) + .setLivekitUrl(imProperties.getRtc().getLivekitUrl()) + // 仅非 ENDED 场景才签 token,ENDED 场景不签,前端根据 token 是否存在,来判断是否展示「通话已结束」的提示 + .setToken(ended ? null : rtcCallService.signCallToken(userId, call.getRoom())) + .setConversationType(call.getConversationType()).setMediaType(call.getMediaType()) + .setStatus(call.getStatus()).setEndReason(call.getEndReason()) + .setInviterId(call.getInviterUserId()).setGroupId(call.getGroupId()) + .setInviteeIds(filterUserIds(participants, ImRtcParticipantStatusEnum.INVITING)) + .setJoinedUserIds(filterUserIds(participants, ImRtcParticipantStatusEnum.JOINED)); + } + + /** + * 拼装 get-active-call 的响应 VO + * + * @param call 通话主表 + * @return 响应 VO:只用于群聊胶囊条,不含 token + */ + private ImRtcGroupCallRespVO buildGroupActiveRespVO(ImRtcCallDO call) { + if (call == null) { + return null; + } + List participants = rtcCallService.getCallParticipantList(call.getRoom()); + return new ImRtcGroupCallRespVO().setRoom(call.getRoom()).setMediaType(call.getMediaType()) + .setGroupId(call.getGroupId()).setInviterId(call.getInviterUserId()) + .setJoinedUserIds(filterUserIds(participants, ImRtcParticipantStatusEnum.JOINED)) + .setInviteeIds(filterUserIds(participants, ImRtcParticipantStatusEnum.INVITING)); + } + + /** + * 按状态过滤参与者 userId;用 LinkedHashSet 保留前端展示顺序 + * + * @param participants 参与者列表 + * @param status 目标状态 + * @return userId 集合 + */ + private static Set filterUserIds(List participants, + ImRtcParticipantStatusEnum status) { + return CollectionUtils.convertLinkedSet(participants, ImRtcParticipantDO::getUserId, + participant -> Objects.equals(participant.getStatus(), status.getStatus())); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/ImRtcLiveKitController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/ImRtcLiveKitController.java new file mode 100644 index 0000000000..b68c2ae4b9 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/ImRtcLiveKitController.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.im.controller.admin.rtc; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; +import cn.iocoder.yudao.module.im.framework.rtc.core.LiveKitClient; +import cn.iocoder.yudao.module.im.framework.rtc.core.LiveKitWebhookEventDTO; +import cn.iocoder.yudao.module.im.service.rtc.ImRtcCallService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +/** + * LiveKit 厂商对接入口(Webhook 回调) + *

+ * 安全由请求签名(JWT + body sha256)保证,不走登录鉴权;伪造请求会被签名校验直接拒绝 + * + * @author 芋道源码 + */ +@Tag(name = "LiveKit Webhook 回调") +@RestController +@RequestMapping("/im/livekit") +@Slf4j +public class ImRtcLiveKitController { + + @Resource + private LiveKitClient liveKitClient; + @Resource + private ImRtcCallService rtcCallService; + + /** + * LiveKit Webhook 回调入口 + * + * @param request HTTP 请求;用于取 Authorization 头做签名校验 + * @param rawBody 原始 JSON body;签名校验需要原始字节 + * @return 是否处理成功;伪造 / 非法事件返回 false + */ + @PostMapping("/webhook") + @Operation(summary = "LiveKit Webhook 回调;接收成员离开 / 房间结束等事件做业务态兜底清理") + @PermitAll + @TenantIgnore + public CommonResult webhook(HttpServletRequest request, @RequestBody String rawBody) { + // 1.1 校验签名;伪造请求直接 200 但忽略,避免给攻击者反馈 + if (!liveKitClient.verifyWebhookSignature(request.getHeader("Authorization"), rawBody)) { + log.warn("[webhook][签名校验失败 ip={} bodyLength={}]", + request.getRemoteAddr(), rawBody == null ? 0 : rawBody.length()); + return success(false); + } + // 1.2 解析事件载荷;非法 / 空 event 直接忽略 + LiveKitWebhookEventDTO event = JsonUtils.parseObject(rawBody, LiveKitWebhookEventDTO.class); + if (event == null || StrUtil.isBlank(event.getEvent())) { + return success(false); + } + + // 2. 交给 service 处理;幂等由 service 自己保证 + log.info("[webhook][事件处理 event={} room={}]", event.getEvent(), + event.getRoom() == null ? null : event.getRoom().getName()); + try { + rtcCallService.handleLiveKitEvent(event); + } catch (Exception e) { + log.error("[webhook][事件处理失败 event={} body={}]", event.getEvent(), rawBody, e); + } + return success(true); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallCreateReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallCreateReqVO.java new file mode 100644 index 0000000000..9dc6a756df --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallCreateReqVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.im.controller.admin.rtc.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallMediaTypeEnum; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Set; + +@Schema(description = "管理后台 - 创建通话 Request VO") +@Data +public class ImRtcCallCreateReqVO { + + @Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "会话类型不能为空") + @InEnum(ImConversationTypeEnum.class) + private Integer conversationType; + + @Schema(description = "媒体类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "媒体类型不能为空") + @InEnum(ImRtcCallMediaTypeEnum.class) + private Integer mediaType; + + @Schema(description = "群编号;群聊场景必填", example = "2048") + private Long groupId; + + @Schema(description = "被邀请的用户编号集合;私聊必传 1 个对端,群聊必传至少 1 人") + private Set<@NotNull(message = "被邀请用户编号不能为空") Long> inviteeIds; + + /** + * 通话仅支持私聊和群聊 + */ + @AssertTrue(message = "会话类型不支持") + @JsonIgnore + public boolean isConversationTypeSupported() { + return conversationType == null || ImConversationTypeEnum.isPrivate(conversationType) + || ImConversationTypeEnum.isGroup(conversationType); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallInviteReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallInviteReqVO.java new file mode 100644 index 0000000000..7fcde58d4a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallInviteReqVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.im.controller.admin.rtc.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Set; + +@Schema(description = "管理后台 - 通话中追加邀请 Request VO;仅群通话可用") +@Data +public class ImRtcCallInviteReqVO { + + @Schema(description = "业务通话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "f47ac10b58cc4372a567") + @NotBlank(message = "通话编号不能为空") + private String room; + + @Schema(description = "新邀请的用户编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "请至少选择一位成员") + private Set<@NotNull(message = "被邀请用户编号不能为空") Long> inviteeIds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallRespVO.java new file mode 100644 index 0000000000..9f3113cd01 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcCallRespVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.im.controller.admin.rtc.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Set; + +@Schema(description = "管理后台 - 通话会话 Response VO;invite / accept / refreshToken 共用") +@Data +public class ImRtcCallRespVO { + + @Schema(description = "业务通话编号") + private String room; + + @Schema(description = "LiveKit Server WebSocket 地址;前端 connect 用") + private String livekitUrl; + + @Schema(description = "LiveKit 接入 Token;需要时调 refreshToken 重新签发") + private String token; + + @Schema(description = "会话类型") + private Integer conversationType; // 参见 ImConversationTypeEnum 枚举类 + + @Schema(description = "媒体类型") + private Integer mediaType; // 参见 ImCallMediaTypeEnum 枚举类 + + @Schema(description = "状态") + private Integer status; // 参见 ImCallStatusEnum 枚举类 + + @Schema(description = "结束原因;仅 status=ENDED 时有值") + private Integer endReason; // 参见 ImRtcCallEndReasonEnum 枚举类 + + @Schema(description = "发起人编号") + private Long inviterId; + + @Schema(description = "群编号;群通话才有") + private Long groupId; + + @Schema(description = "被邀请人编号集合") + private Set inviteeIds; + + @Schema(description = "已加入房间的成员编号集合") + private Set joinedUserIds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcGroupCallRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcGroupCallRespVO.java new file mode 100644 index 0000000000..5d1d3ce8af --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/rtc/vo/ImRtcGroupCallRespVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.im.controller.admin.rtc.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Set; + +@Schema(description = "管理后台 - 群活跃通话 Response VO;不含 token,胶囊条仅展示用") +@Data +public class ImRtcGroupCallRespVO { + + @Schema(description = "业务通话编号") + private String room; + + @Schema(description = "群编号") + private Long groupId; + + @Schema(description = "媒体类型") + private Integer mediaType; // 参见 ImCallMediaTypeEnum 枚举类 + + @Schema(description = "发起人编号") + private Long inviterId; + + @Schema(description = "已加入房间的用户编号集合") + private Set joinedUserIds; + + @Schema(description = "被邀请池;用于胶囊条展开时显示待加入头像") + private Set inviteeIds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/app/package-info.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/app/package-info.java new file mode 100644 index 0000000000..31a8695242 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/app/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.im.controller.app; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/package-info.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/package-info.java new file mode 100644 index 0000000000..f2e831e7bb --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package cn.iocoder.yudao.module.im.controller; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/channel/ImChannelDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/channel/ImChannelDO.java new file mode 100644 index 0000000000..f385cfa51d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/channel/ImChannelDO.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.channel; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IM 频道 DO + *

+ * 业务语义: + * - 频道是运营单向推送的主体;C 端用户不能向频道发消息 + * - {@link #code} 是业务码(API / 字典外露),id 是数字主键给前端会话 targetId 用 + * + * @author 芋道源码 + */ +@TableName("im_channel") +@KeySequence("im_channel_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImChannelDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 频道业务码 + */ + private String code; + /** + * 频道名称 + */ + private String name; + /** + * 频道头像 + */ + private String avatar; + /** + * 排序 + */ + private Integer sort; + /** + * 状态 + *

+ * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/channel/ImChannelMaterialDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/channel/ImChannelMaterialDO.java new file mode 100644 index 0000000000..0d49721c40 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/channel/ImChannelMaterialDO.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.channel; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import cn.iocoder.yudao.module.im.enums.channel.ImChannelMaterialTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IM 频道素材 DO + *

+ * 业务语义: + * - 运营素材库,可被反复推送 + * - 一条素材 1:N 关联多条 {@link ImChannelMessageDO} + * - {@link #content} 富文本仅在素材详情接口按需返回,推送 payload 不带,避免压爆 WebSocket 通道 + * + * @author 芋道源码 + */ +@TableName("im_channel_material") +@KeySequence("im_channel_material_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImChannelMaterialDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 频道编号 + *

+ * 关联 {@link ImChannelDO#getId()} + */ + private Long channelId; + /** + * 素材内容类型 + *

+ * 枚举 {@link ImChannelMaterialTypeEnum} + */ + private Integer type; + /** + * 标题 + */ + private String title; + /** + * 封面图 + */ + private String coverUrl; + /** + * 摘要 + */ + private String summary; + /** + * 富文本 HTML;在 {@link ImChannelMaterialTypeEnum#CONTENT} 使用 + */ + private String content; + /** + * 跳转链接;在 {@link ImChannelMaterialTypeEnum#LINK} 使用 + */ + private String url; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationReadDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationReadDO.java new file mode 100644 index 0000000000..bdd9fa0696 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationReadDO.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.conversation; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * IM 会话读位置 DO + *

+ * 只表达「用户在某个会话的最大已读位置」,私聊 / 群聊 / 频道统一落这张表,是读位置的唯一权威。 + * + * @author 芋道源码 + */ +@TableName("im_conversation_read") +@KeySequence("im_conversation_read_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImConversationReadDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long userId; + /** + * 会话类型 + *

+ * 枚举 {@link ImConversationTypeEnum} + */ + private Integer conversationType; + /** + * 目标编号 + *

+ * 私聊关联 AdminUserDO 的 id;群聊关联 {@link ImGroupDO#getId()};频道关联 {@link ImChannelDO#getId()} + */ + private Long targetId; + /** + * 最大已读消息编号 + *

+ * 关联 {@link ImPrivateMessageDO#getId()}、{@link ImGroupMessageDO#getId()}、{@link ImChannelMessageDO#getId()} + */ + private Long messageId; + /** + * 最近已读时间 + */ + private LocalDateTime readTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackDO.java new file mode 100644 index 0000000000..0236218daf --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackDO.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.face; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IM 表情包 DO(运营配置的系统表情包元数据) + * + * @author 芋道源码 + */ +@TableName("im_face_pack") +@KeySequence("im_face_pack_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImFacePackDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 表情包名称 + */ + private String name; + /** + * 表情包图标 + *

+ * 面板底部 tab 栏显示 + */ + private String icon; + /** + * 排序 + */ + private Integer sort; + /** + * 状态 + *

+ * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackItemDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackItemDO.java new file mode 100644 index 0000000000..d3c56769a9 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFacePackItemDO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.face; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IM 表情包项 DO(系统表情包内的单张表情图) + * + * @author 芋道源码 + */ +@TableName("im_face_pack_item") +@KeySequence("im_face_pack_item_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImFacePackItemDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 所属表情包编号 + */ + private Long packId; + /** + * 表情图 URL + */ + private String url; + /** + * 表情名(可选;如「狗头」「捂脸」) + */ + private String name; + /** + * 渲染宽度(像素) + */ + private Integer width; + /** + * 渲染高度(像素) + */ + private Integer height; + /** + * 排序 + */ + private Integer sort; + /** + * 状态 + *

+ * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFaceUserItemDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFaceUserItemDO.java new file mode 100644 index 0000000000..0aee3bb266 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/face/ImFaceUserItemDO.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.face; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IM 用户私有表情 DO(个人表情包,对照微信「我的表情」) + * + * @author 芋道源码 + */ +@TableName("im_face_user_item") +@KeySequence("im_face_user_item_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImFaceUserItemDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 所属用户编号 + *

+ * 关联 AdminUserDO 的 id 编号 + */ + private Long userId; + /** + * 表情图 URL + */ + private String url; + /** + * 表情名(可选) + *

+ * 用户私有表情通常不带名字,留字段以备将来「重命名」交互 + */ + private String name; + /** + * 渲染宽度(像素) + */ + private Integer width; + /** + * 渲染高度(像素) + */ + private Integer height; + /** + * 排序 + */ + private Integer sort; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/friend/ImFriendDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/friend/ImFriendDO.java new file mode 100644 index 0000000000..db9543a0f1 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/friend/ImFriendDO.java @@ -0,0 +1,90 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.friend; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * IM 好友关系 DO + *

+ * 业务语义: + * - 双向关系:A-B 互为好友会存 2 条记录(userId=A, friendUserId=B 和 userId=B, friendUserId=A) + * - 状态管理:{@link #status} 使用 {@link CommonStatusEnum},ENABLE=正常,DISABLE=已删除 + * - 免打扰:{@link #silent} 控制是否屏蔽来自该好友的通知 + * - 联系人置顶:{@link #pinned} 单边,影响联系人 / 会话排序 + * - 黑名单:{@link #blocked} 弱关联 friend,单边屏蔽对方消息(必须先是好友) + * + * @author 芋道源码 + */ +@TableName("im_friend") +@KeySequence("im_friend_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImFriendDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 用户编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long userId; + /** + * 好友用户编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long friendUserId; + /** + * 是否免打扰 + */ + private Boolean silent; + /** + * 好友展示备注 + */ + private String displayName; + /** + * 添加来源 + *

+ * 枚举 {@link cn.iocoder.yudao.module.im.enums.friend.ImFriendAddSourceEnum} + */ + private Integer addSource; + /** + * 是否置顶联系人 + */ + private Boolean pinned; + /** + * 是否拉黑(弱关联 friend,单边屏蔽对方私聊消息) + */ + private Boolean blocked; + /** + * 好友状态 + *

+ * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 添加好友时间 + */ + private LocalDateTime addTime; + /** + * 删除好友时间 + *

+ * 不为 null 时表示已删除 + */ + private LocalDateTime deleteTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/friend/ImFriendRequestDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/friend/ImFriendRequestDO.java new file mode 100644 index 0000000000..ec30987368 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/friend/ImFriendRequestDO.java @@ -0,0 +1,85 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.friend; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendAddSourceEnum; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * IM 好友申请记录 DO + *

+ * 配合「申请 - 审批」流程: + * - 发起方调 apply 接口落库(handleResult=UNHANDLED) + * - 接收方调 agree / refuse 处理(更新 handleResult / handleTime / handleContent) + * - 申请通过后,displayName / addSource 同步写入 {@link ImFriendDO} + * + * @author 芋道源码 + */ +@TableName("im_friend_request") +@KeySequence("im_friend_request_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImFriendRequestDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 发起方用户编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long fromUserId; + /** + * 接收方用户编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long toUserId; + + // ========== 申请发起阶段(发起方填写) ========== + /** + * 申请理由 + */ + private String applyContent; + /** + * 发起方对接收方的备注 + *

+ * 同意后写入 A 侧 {@link ImFriendDO#getDisplayName()};B 侧不动 + */ + private String displayName; + /** + * 添加来源 + *

+ * 枚举 {@link ImFriendAddSourceEnum} + */ + private Integer addSource; + + // ========== 申请处理阶段(接收方填写) ========== + /** + * 处理结果 + *

+ * 枚举 {@link ImFriendRequestHandleResultEnum} + */ + private Integer handleResult; + /** + * 处理理由(接收方拒绝时可选填) + */ + private String handleContent; + /** + * 处理时间 + */ + private LocalDateTime handleTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java new file mode 100644 index 0000000000..f47f80d88a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java @@ -0,0 +1,94 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.group; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * IM 群信息 DO + * + * @author 芋道源码 + */ +@TableName(value = "im_group",autoResultMap = true) +@KeySequence("im_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImGroupDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 群名称 + */ + private String name; + /** + * 群主用户编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + /** + * 群头像 + */ + private String avatar; + /** + * 群公告 + */ + private String notice; + /** + * 进群是否需群主 / 管理员审批 + *

+ * false 自由进群(默认);true 需审批,所有「申请」「邀请」路径都需群主 / 管理员同意 + */ + private Boolean joinApproval; + /** + * 是否封禁 + */ + private Boolean banned; + /** + * 封禁原因 + */ + private String bannedReason; + /** + * 封禁时间 + */ + private LocalDateTime bannedTime; + /** + * 群状态 + *

+ * 枚举 {@link CommonStatusEnum} + * ENABLE = 正常,DISABLE = 已解散 + */ + private Integer status; + /** + * 解散时间 + */ + private LocalDateTime dissolvedTime; + /** + * 是否全群禁言 + */ + private Boolean mutedAll; + /** + * 群置顶消息编号列表 + *

+ * 仅存 messageId,操作人 / 置顶时间从对应 PIN 事件的消息记录里反查 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List pinnedMessageIds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupMemberDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupMemberDO.java new file mode 100644 index 0000000000..430264cccb --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupMemberDO.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.group; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * IM 群成员 DO + * + * @author 芋道源码 + */ +@TableName("im_group_member") +@KeySequence("im_group_member_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImGroupMemberDO extends BaseDO { + + /** + * 永久禁言到期时间 + */ + public static final LocalDateTime PERMANENT_MUTE_END_TIME = LocalDateTime.of(9999, 12, 31, 23, 59, 59); + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 群编号 + *

+ * 关联 {@link ImGroupDO#getId()} + */ + private Long groupId; + /** + * 用户编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long userId; + /** + * 组内显示名 + */ + private String displayUserName; + /** + * 群备注 + */ + private String groupRemark; + /** + * 是否免打扰 + */ + private Boolean silent; + /** + * 成员状态 + *

+ * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 成员角色 + *

+ * 枚举 {@link ImGroupMemberRoleEnum} + */ + private Integer role; + /** + * 入群时间 + */ + private LocalDateTime joinTime; + /** + * 加入来源 + *

+ * 枚举 {@link ImGroupAddSourceEnum} + */ + private Integer addSource; + /** + * 邀请人用户编号 + *

+ * 关联 AdminUserDO 的 id 字段;用户主动申请进群时为 NULL + */ + private Long inviterUserId; + /** + * 退群时间 + */ + private LocalDateTime quitTime; + /** + * 禁言到期时间 + *

+ * null 表示未禁言;非 null 且在未来表示禁言中; + */ + private LocalDateTime muteEndTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupRequestDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupRequestDO.java new file mode 100644 index 0000000000..6ccb2cfdd7 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupRequestDO.java @@ -0,0 +1,91 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.group; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * IM 加群申请记录 DO + *

+ * 配合「申请 - 审批」流程: + *

    + *
  • 用户主动申请:调 apply 接口落库(inviterUserId=null,handleResult=UNHANDLED),群主 / 管理员审批
  • + *
  • 普通成员邀请:群 joinType=APPLY_AND_NORMAL_INVITE 时落库(inviterUserId=操作人),群主 / 管理员审批
  • + *
  • 处理:群主 / 管理员调 agree / refuse 推进状态机;同意时把 addSource / inviterUserId 同步写入 {@link ImGroupMemberDO}
  • + *
+ * + * @author 芋道源码 + */ +@TableName("im_group_request") +@KeySequence("im_group_request_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增;MySQL 等可不写 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImGroupRequestDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 群编号 + *

+ * 关联 {@link ImGroupDO#getId()} + */ + private Long groupId; + /** + * 申请人 / 被邀请人用户编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long userId; + /** + * 邀请人用户编号 + *

+ * NULL 表示用户主动申请进群;非 NULL 表示由群成员邀请、待群主 / 管理员审批 + */ + private Long inviterUserId; + + // ========== 申请发起阶段 ========== + /** + * 申请理由 + */ + private String applyContent; + /** + * 加入来源 + *

+ * 枚举 {@link ImGroupAddSourceEnum};同意时同步写入 {@link ImGroupMemberDO#getAddSource()} + */ + private Integer addSource; + + // ========== 申请处理阶段 ========== + /** + * 处理结果 + *

+ * 枚举 {@link ImGroupRequestHandleResultEnum} + */ + private Integer handleResult; + /** + * 处理人用户编号(群主或管理员) + */ + private Long handleUserId; + /** + * 处理理由(拒绝时可选填) + */ + private String handleContent; + /** + * 处理时间 + */ + private LocalDateTime handleTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java new file mode 100644 index 0000000000..d71ee7c589 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * IM 频道消息 DO + *

+ * 业务语义: + * - 一次推送 1 行;{@link #receiverUserIds} 为空表示全员 + * - {@link #channelId} 冗余 {@link ImChannelMaterialDO#getChannelId()} 便于按频道检索 + * - {@link #content} 存推送时 payload 的 JSON 快照(title / coverUrl / summary / url);不含富文本正文 + * + * @author 芋道源码 + */ +@TableName(value = "im_channel_message", autoResultMap = true) +@KeySequence("im_channel_message_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImChannelMessageDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 频道编号 + *

+ * 关联 {@link ImChannelDO#getId()};冗余便于按频道检索 + */ + private Long channelId; + /** + * 关联素材编号 + *

+ * 关联 {@link ImChannelMaterialDO#getId()} + */ + private Long materialId; + /** + * 消息类型 + *

+ * 枚举 {@link ImContentTypeEnum} + */ + private Integer type; + /** + * 消息内容;推送时 payload 的 JSON 快照 + */ + private String content; + /** + * 接收人编号列表;为空表示全员 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List receiverUserIds; + /** + * 发送时间 + */ + private LocalDateTime sendTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java new file mode 100644 index 0000000000..bdab8a8806 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java @@ -0,0 +1,107 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * IM 群聊消息 DO + * + * @author 芋道源码 + */ +@TableName(value = "im_group_message", autoResultMap = true) +@KeySequence("im_group_message_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImGroupMessageDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 客户端消息编号,用于发送幂等 + */ + private String clientMessageId; + /** + * 发送人编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long senderId; + /** + * 群编号 + * + * 关联 {@link ImGroupDO#getId()} + */ + private Long groupId; + /** + * 消息类型 + *

+ * 枚举 {@link ImContentTypeEnum} + */ + private Integer type; + /** + * 消息内容,JSON 格式 + * + * 参考 content 包下的 TextMessage、ImageMessage 等结构化模型 + */ + private String content; + /** + * 消息状态 + *

+ * 枚举 {@link ImMessageStatusEnum} + */ + private Integer status; + /** + * 发送时间 + */ + private LocalDateTime sendTime; + /** + * 定向接收用户编号列表,以逗号分隔 + *

+ * 为空表示全员可见 + * + * 关联 AdminUserDO 的 id 字段 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List receiverUserIds; + /** + * @ 目标用户编号列表,以逗号分隔 + * + * 关联 AdminUserDO 的 id 字段 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List atUserIds; + /** + * 回执状态 + *

+ * 枚举 {@link ImMessageReceiptStatusEnum} + */ + private Integer receiptStatus; + + // ========== 非表字段 ========== + + /** + * 离线拉取等场景下回算的已读人数 + */ + @TableField(exist = false) + private Integer readCount; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java new file mode 100644 index 0000000000..808c1ee28e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java @@ -0,0 +1,79 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * IM 私聊消息 DO + * + * @author 芋道源码 + */ +@TableName("im_private_message") +@KeySequence("im_private_message_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImPrivateMessageDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 客户端消息编号,用于发送幂等 + */ + private String clientMessageId; + /** + * 发送人编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long senderId; + /** + * 接收人编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long receiverId; + /** + * 消息类型 + *

+ * 枚举 {@link ImContentTypeEnum} + */ + private Integer type; + /** + * 消息内容,JSON 格式 + * + * 参考 content 包下的 TextMessage、ImageMessage 等结构化模型 + */ + private String content; + /** + * 消息状态 + *

+ * 枚举 {@link ImMessageStatusEnum} + */ + private Integer status; + /** + * 回执状态 + *

+ * 枚举 {@link ImMessageReceiptStatusEnum} + */ + private Integer receiptStatus; + /** + * 发送时间 + */ + private LocalDateTime sendTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/AudioMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/AudioMessage.java new file mode 100644 index 0000000000..2422467c93 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/AudioMessage.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import lombok.Data; + +/** + * 语音消息内容 + */ +@Data +public class AudioMessage { + + /** + * 语音文件 URL + */ + private String url; + /** + * 语音时长(秒) + */ + private Integer duration; + /** + * 文件大小(字节) + */ + private Long size; + + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/CardMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/CardMessage.java new file mode 100644 index 0000000000..d99ed12944 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/CardMessage.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import lombok.Data; + +/** + * 名片消息内容 + *

+ * 支持两类名片,按 targetType 区分: + * 1. 用户名片(targetType = PRIVATE):targetId = 用户编号,name = 用户昵称 + * 2. 群名片(targetType = GROUP):targetId = 群编号,name = 群名,可带 memberCount + */ +@Data +public class CardMessage { + + /** + * 名片对象类型 + *

+ * 枚举 {@link ImConversationTypeEnum} + */ + private Integer targetType; + /** + * 目标对象编号:PRIVATE 时 = 用户编号;GROUP 时 = 群编号 + */ + private Long targetId; + /** + * 显示名快照:PRIVATE 时 = 用户昵称;GROUP 时 = 群名 + */ + private String name; + /** + * 头像(快照) + */ + private String avatar; + /** + * 群成员数(仅 targetType = GROUP;接收端展示「N 人群聊」) + */ + private Integer memberCount; + + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FaceMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FaceMessage.java new file mode 100644 index 0000000000..6d6a642fea --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FaceMessage.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import lombok.Data; + +/** + * 表情消息内容 + *

+ * 承载「贴图 / 自定义表情包」类消息;Unicode emoji(😀😂)仍走 TEXT + */ +@Data +public class FaceMessage { + + /** + * 表情图 URL + */ + private String url; + /** + * 渲染宽度(像素,可选) + *

+ * 客户端渲染时按此尺寸占位,避免图片加载完成后的布局抖动;缺失时由客户端 CSS max-width 兜底 + */ + private Integer width; + /** + * 渲染高度(像素,可选) + */ + private Integer height; + /** + * 表情名(可选) + *

+ * 系统表情包通常带名字(如「狗头」「捂脸」),用户私有表情包通常为空 + */ + private String name; + + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FileMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FileMessage.java new file mode 100644 index 0000000000..bd4fcc3700 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FileMessage.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import lombok.Data; + +/** + * 文件消息内容 + */ +@Data +public class FileMessage { + + /** + * 文件 URL + */ + private String url; + /** + * 文件名 + */ + private String name; + /** + * 文件大小(字节) + */ + private Long size; + /** + * 文件类型(扩展名) + */ + private String type; + + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/ImageMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/ImageMessage.java new file mode 100644 index 0000000000..052342a950 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/ImageMessage.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import lombok.Data; + +/** + * 图片消息内容 + */ +@Data +public class ImageMessage { + + /** + * 图片 URL + */ + private String url; + /** + * 缩略图 URL + */ + private String thumbnailUrl; + /** + * 图片宽度 + */ + private Integer width; + /** + * 图片高度 + */ + private Integer height; + /** + * 图片大小(字节) + */ + private Long size; + + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/LocationMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/LocationMessage.java new file mode 100644 index 0000000000..fdbb7c7066 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/LocationMessage.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import lombok.Data; + +/** + * 位置消息内容 + */ +@Data +public class LocationMessage { + + /** + * 位置描述 + */ + private String description; + /** + * 经度 + */ + private Double longitude; + /** + * 纬度 + */ + private Double latitude; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MaterialMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MaterialMessage.java new file mode 100644 index 0000000000..41fbdce203 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MaterialMessage.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 频道素材消息 payload + *

+ * 对应 {@link cn.iocoder.yudao.module.im.enums.ImContentTypeEnum#MATERIAL}(type=125)。 + * 客户端按本字段集渲染图文卡片; + * 富文本正文不在本 payload 中传递,点击详情时另调 /im/channel/material/get-content?id= 按需拉取。 + */ +@Data +@Accessors(chain = true) +public class MaterialMessage { + + /** + * 素材编号 + */ + private Long materialId; + /** + * 频道编号 + *

+ * 冗余到 payload 内;转发到私聊 / 群聊后客户端用本字段定位频道,渲染卡片底部的频道头像 + 名称 + */ + private Long channelId; + /** + * 素材内容类型 + *

+ * 枚举 {@link cn.iocoder.yudao.module.im.enums.channel.ImChannelMaterialTypeEnum} + * 客户端按本字段判定点击行为:CONTENT 走站内详情页拉富文本;LINK 跳 url + */ + private Integer type; + /** + * 标题 + */ + private String title; + /** + * 封面图 + */ + private String coverUrl; + /** + * 摘要 + */ + private String summary; + /** + * 跳转链接 + */ + private String url; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MergeMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MergeMessage.java new file mode 100644 index 0000000000..9bc675b106 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MergeMessage.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import lombok.Data; + +import java.util.List; + +/** + * 合并转发消息内容 + *

+ * payload 内嵌完整快照,原消息撤回 / 删除不影响已转发的合并消息 + */ +@Data +public class MergeMessage { + + /** + * 合并标题 + */ + private String title; + /** + * 内嵌的完整消息快照 + */ + private List messages; + + /** + * 单条内嵌消息快照 + */ + @Data + public static class Item { + + /** + * 原消息编号 + */ + private Long messageId; + /** + * 发送人编号 + */ + private Long senderId; + /** + * 发送人昵称快照;接收方可能不在原会话里,无法实时查到 + */ + private String senderNickname; + /** + * 发送人头像快照 + */ + private String senderAvatar; + /** + * 消息类型 + *

+ * 枚举 {@link ImContentTypeEnum} + */ + private Integer type; + /** + * 原消息 content(JSON) + */ + private String content; + /** + * 发送时间戳(毫秒) + */ + private Long sendTime; + + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/QuoteMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/QuoteMessage.java new file mode 100644 index 0000000000..a06d417bb8 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/QuoteMessage.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 引用消息内容 + *

+ * 客户端在 content.quote 里写入 messageId 表达"引用了哪条消息",服务端反查原消息后重算 QuoteMessage 覆盖客户端写入, + * 避免伪造发送人 / 类型 / 摘要 + * + * @see TextMessage 等等消息内容里 quote 字段 + */ +@Data +@Accessors(chain = true) +public class QuoteMessage { + + /** + * content JSON 里 quote 字段名 + */ + public static final String FIELD_NAME = "quote"; + + /** + * QuoteMessage 里 messageId 字段名 + */ + public static final String FIELD_MESSAGE_ID = "messageId"; + + /** + * 被引用消息编号 + */ + private Long messageId; + /** + * 被引用消息发送人编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long senderId; + /** + * 被引用消息类型 + *

+ * 枚举 {@link ImContentTypeEnum} + */ + private Integer type; + /** + * 原消息完整 content(JSON);服务端复制时会 removeQuote 防嵌套 + */ + private String content; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/RecallMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/RecallMessage.java new file mode 100644 index 0000000000..a9ba5756ee --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/RecallMessage.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 撤回提示消息内容 + */ +@Data +@Accessors(chain = true) +public class RecallMessage { + + /** + * 被撤回的原消息编号 + */ + private Long messageId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/TextMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/TextMessage.java new file mode 100644 index 0000000000..4d8a66c66f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/TextMessage.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import lombok.Data; + +/** + * 文本消息内容 + */ +@Data +public class TextMessage { + + /** + * 文本内容 + */ + private String content; + + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/VideoMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/VideoMessage.java new file mode 100644 index 0000000000..5bceef1f7f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/VideoMessage.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; + +import lombok.Data; + +/** + * 视频消息内容 + */ +@Data +public class VideoMessage { + + /** + * 视频文件 URL + */ + private String url; + /** + * 视频封面 URL + */ + private String coverUrl; + /** + * 视频时长(秒) + */ + private Integer duration; + /** + * 视频宽度 + */ + private Integer width; + /** + * 视频高度 + */ + private Integer height; + /** + * 文件大小(字节) + */ + private Long size; + + /** + * 引用消息(可选) + */ + private QuoteMessage quote; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/rtc/ImRtcCallDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/rtc/ImRtcCallDO.java new file mode 100644 index 0000000000..e6453d1cdd --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/rtc/ImRtcCallDO.java @@ -0,0 +1,93 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.rtc; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallEndReasonEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallMediaTypeEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * IM 通话记录 DO(房间级 / 主表) + *

+ * 一通通话一行;状态机 CREATED → RUNNING → ENDED;和明细表 {@link ImRtcParticipantDO} 通过 {@link #room} 关联 + * + * @author 芋道源码 + */ +@TableName("im_rtc_call") +@KeySequence("im_rtc_call_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增;MySQL 等数据库可不写 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImRtcCallDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 业务通话编号(UUID,同时作为 LiveKit 房间名);唯一 + */ + private String room; + /** + * 会话类型 + * + * 枚举 {@link ImConversationTypeEnum} + */ + private Integer conversationType; + /** + * 媒体类型 + * + * 枚举 {@link ImRtcCallMediaTypeEnum} + */ + private Integer mediaType; + /** + * 发起人用户编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long inviterUserId; + /** + * 群编号;私聊为 NULL + *

+ * 关联 {@link ImGroupDO#getId()} + */ + private Long groupId; + /** + * 通话状态 + * + * 枚举 {@link ImRtcCallStatusEnum} + */ + private Integer status; + /** + * 结束原因;通话未结束时为 NULL + * + * 枚举 {@link ImRtcCallEndReasonEnum} + */ + private Integer endReason; + /** + * 发起时间 + */ + private LocalDateTime startTime; + /** + * 接通时间 + * + * 首个非发起人加入时写入;未接通保持 NULL + */ + private LocalDateTime acceptTime; + /** + * 通话结束时间 + */ + private LocalDateTime endTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/rtc/ImRtcParticipantDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/rtc/ImRtcParticipantDO.java new file mode 100644 index 0000000000..4282baa1a7 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/rtc/ImRtcParticipantDO.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.rtc; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantRoleEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * IM 通话参与者 DO(用户级 / 明细表) + *

+ * 一通通话每个参与者一行;通过 {@link #room} 关联主表 {@link ImRtcCallDO} + *

+ * 终态闭合:通话 ENDED 时所有明细 status 必属 {LEFT / REJECTED / NO_ANSWER} 之一 + * + * @author 芋道源码 + */ +@TableName("im_rtc_participant") +@KeySequence("im_rtc_participant_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增;MySQL 等数据库可不写 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImRtcParticipantDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 通话编号 + *

+ * 关联 {@link ImRtcCallDO#getId()} + */ + private Long callId; + /** + * 业务通话编号 + *

+ * 关联 {@link ImRtcCallDO#getRoom()} + */ + private String room; + /** + * 参与者用户编号 + *

+ * 关联 AdminUserDO 的 id 字段 + */ + private Long userId; + /** + * 参与角色 + * + * 枚举 {@link ImRtcParticipantRoleEnum} + */ + private Integer role; + /** + * 参与状态 + * + * 枚举 {@link ImRtcParticipantStatusEnum} + */ + private Integer status; + /** + * 被邀请时间;发起人取通话 startTime + */ + private LocalDateTime inviteTime; + /** + * 接听时间;未接听 NULL + */ + private LocalDateTime acceptTime; + /** + * 离开时间;未加入 NULL + */ + private LocalDateTime leaveTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/sensitiveword/ImSensitiveWordDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/sensitiveword/ImSensitiveWordDO.java new file mode 100644 index 0000000000..4f6d1fcf76 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/sensitiveword/ImSensitiveWordDO.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IM 敏感词 DO + * + * @author 芋道源码 + */ +@TableName("im_sensitive_word") +@KeySequence("im_sensitive_word_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImSensitiveWordDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 敏感词 + */ + private String word; + /** + * 状态 + *

+ * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/channel/ImChannelMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/channel/ImChannelMapper.java new file mode 100644 index 0000000000..faab2b547c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/channel/ImChannelMapper.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.im.dal.mysql.channel; + +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.im.controller.admin.manager.channel.vo.channel.ImChannelPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * IM 频道 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImChannelMapper extends BaseMapperX { + + default ImChannelDO selectByCode(String code) { + return selectOne(ImChannelDO::getCode, code); + } + + default List selectListByStatusOrderBySort(Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(ImChannelDO::getStatus, status) + .orderByAsc(ImChannelDO::getSort)); + } + + default PageResult selectPage(ImChannelPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ImChannelDO::getCode, reqVO.getCode()) + .likeIfPresent(ImChannelDO::getName, reqVO.getName()) + .eqIfPresent(ImChannelDO::getStatus, reqVO.getStatus()) + .orderByAsc(ImChannelDO::getSort) + .orderByDesc(ImChannelDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/channel/ImChannelMaterialMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/channel/ImChannelMaterialMapper.java new file mode 100644 index 0000000000..421bae877c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/channel/ImChannelMaterialMapper.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.im.dal.mysql.channel; + +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.im.controller.admin.manager.channel.vo.material.ImChannelMaterialPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * IM 频道素材 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImChannelMaterialMapper extends BaseMapperX { + + default Long selectCountByChannelId(Long channelId) { + return selectCount(ImChannelMaterialDO::getChannelId, channelId); + } + + default List selectListByChannelId(Long channelId) { + return selectList(new LambdaQueryWrapperX() + .eq(ImChannelMaterialDO::getChannelId, channelId) + .orderByDesc(ImChannelMaterialDO::getId)); + } + + default PageResult selectPage(ImChannelMaterialPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImChannelMaterialDO::getChannelId, reqVO.getChannelId()) + .eqIfPresent(ImChannelMaterialDO::getType, reqVO.getType()) + .likeIfPresent(ImChannelMaterialDO::getTitle, reqVO.getTitle()) + .betweenIfPresent(ImChannelMaterialDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ImChannelMaterialDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapper.java new file mode 100644 index 0000000000..45a702f119 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapper.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.module.im.dal.mysql.conversation; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * IM 会话读位置 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImConversationReadMapper extends BaseMapperX { + + default ImConversationReadDO selectByUserIdAndConversation(Long userId, Integer conversationType, Long conversationId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImConversationReadDO::getUserId, userId) + .eq(ImConversationReadDO::getConversationType, conversationType) + .eq(ImConversationReadDO::getTargetId, conversationId)); + } + + default List selectListByConversation(Integer conversationType, Long conversationId) { + return selectList(new LambdaQueryWrapperX() + .eq(ImConversationReadDO::getConversationType, conversationType) + .eq(ImConversationReadDO::getTargetId, conversationId)); + } + + default List selectListByUserIdAndConversations(Long userId, Integer conversationType, + Collection conversationIds) { + return selectList(new LambdaQueryWrapperX() + .eq(ImConversationReadDO::getUserId, userId) + .eq(ImConversationReadDO::getConversationType, conversationType) + .in(ImConversationReadDO::getTargetId, conversationIds)); + } + + /** + * 增量拉取当前用户的会话读位置(按 update_time + id 正向游标) + * + * @param userId 当前用户编号 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null + * @param lastId 上次拉取到的记录编号;首次拉取传 null + * @param limit 拉取数量 + * @return 会话读位置列表 + */ + default List selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .eq(ImConversationReadDO::getUserId, userId); + if (lastUpdateTime != null && lastId != null) { + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime); + query.and(w -> w.gt(ImConversationReadDO::getUpdateTime, lastTime) + .or(n -> n.eq(ImConversationReadDO::getUpdateTime, lastTime).gt(ImConversationReadDO::getId, lastId))); + } + return selectList(query.orderByAsc(ImConversationReadDO::getUpdateTime).orderByAsc(ImConversationReadDO::getId) + .last("LIMIT " + limit)); + } + + /** + * 单调递增更新读位置:仅当新位置更大时才更新 + *

+ * 通过 {@code read_message_id < messageId} 的 CAS 条件,保证乱序 / 并发上报时读位置不会回退。 + * + * @param id 记录编号 + * @param messageId 新的最大已读消息编号 + * @param readTime 已读时间 + * @return 影响行数;0 表示新位置不大于已有位置,未更新 + */ + default int updateReadMessageIdToLarger(Long id, Long messageId, LocalDateTime readTime) { + return update(null, Wrappers.lambdaUpdate() + .set(ImConversationReadDO::getMessageId, messageId) + .set(ImConversationReadDO::getReadTime, readTime) + .set(ImConversationReadDO::getUpdateTime, readTime) + .eq(ImConversationReadDO::getId, id) + .lt(ImConversationReadDO::getMessageId, messageId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackItemMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackItemMapper.java new file mode 100644 index 0000000000..622a319fad --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackItemMapper.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.im.dal.mysql.face; + +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.im.controller.admin.manager.face.vo.item.ImFacePackItemPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * IM 表情包项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImFacePackItemMapper extends BaseMapperX { + + default List selectListByPackIdsAndStatus(Collection packIds, Integer status) { + return selectList(new LambdaQueryWrapperX() + .in(ImFacePackItemDO::getPackId, packIds) + .eq(ImFacePackItemDO::getStatus, status) + .orderByAsc(ImFacePackItemDO::getPackId) + .orderByAsc(ImFacePackItemDO::getSort) + .orderByAsc(ImFacePackItemDO::getId)); + } + + default Long selectCountByPackId(Long packId) { + return selectCount(new LambdaQueryWrapperX() + .eq(ImFacePackItemDO::getPackId, packId)); + } + + default Long selectCountByPackIds(Collection packIds) { + return selectCount(new LambdaQueryWrapperX() + .in(ImFacePackItemDO::getPackId, packIds)); + } + + default PageResult selectPage(ImFacePackItemPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImFacePackItemDO::getPackId, reqVO.getPackId()) + .likeIfPresent(ImFacePackItemDO::getName, reqVO.getName()) + .eqIfPresent(ImFacePackItemDO::getStatus, reqVO.getStatus()) + .orderByAsc(ImFacePackItemDO::getSort) + .orderByDesc(ImFacePackItemDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackMapper.java new file mode 100644 index 0000000000..2864d61739 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFacePackMapper.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.im.dal.mysql.face; + +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.im.controller.admin.manager.face.vo.pack.ImFacePackPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * IM 表情包 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImFacePackMapper extends BaseMapperX { + + default List selectListByStatusOrderBySort(Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(ImFacePackDO::getStatus, status) + .orderByAsc(ImFacePackDO::getSort) + .orderByAsc(ImFacePackDO::getId)); + } + + default PageResult selectPage(ImFacePackPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ImFacePackDO::getName, reqVO.getName()) + .eqIfPresent(ImFacePackDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(ImFacePackDO::getCreateTime, reqVO.getCreateTime()) + .orderByAsc(ImFacePackDO::getSort) + .orderByDesc(ImFacePackDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFaceUserItemMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFaceUserItemMapper.java new file mode 100644 index 0000000000..5eb2dc778b --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/face/ImFaceUserItemMapper.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.im.dal.mysql.face; + +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.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * IM 用户私有表情 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImFaceUserItemMapper extends BaseMapperX { + + default List selectListByUserId(Long userId) { + return selectList(new LambdaQueryWrapperX() + .eq(ImFaceUserItemDO::getUserId, userId) + .orderByAsc(ImFaceUserItemDO::getSort) + .orderByDesc(ImFaceUserItemDO::getId)); + } + + default ImFaceUserItemDO selectByUserIdAndUrl(Long userId, String url) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImFaceUserItemDO::getUserId, userId) + .eq(ImFaceUserItemDO::getUrl, url)); + } + + default Long selectCountByUserId(Long userId) { + return selectCount(ImFaceUserItemDO::getUserId, userId); + } + + default PageResult selectPage(ImFaceUserItemManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImFaceUserItemDO::getUserId, reqVO.getUserId()) + .likeIfPresent(ImFaceUserItemDO::getName, reqVO.getName()) + .betweenIfPresent(ImFaceUserItemDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ImFaceUserItemDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java new file mode 100644 index 0000000000..8673100b5d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.module.im.dal.mysql.friend; + +import cn.hutool.core.date.LocalDateTimeUtil; +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.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * IM 好友关系 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImFriendMapper extends BaseMapperX { + + default ImFriendDO selectByUserIdAndFriendUserId(Long userId, Long friendUserId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImFriendDO::getUserId, userId) + .eq(ImFriendDO::getFriendUserId, friendUserId)); + } + + default List selectListByUserId(Long userId) { + return selectList(new LambdaQueryWrapperX() + .eq(ImFriendDO::getUserId, userId) + .orderByDesc(ImFriendDO::getId)); + } + + /** + * 增量拉取当前用户的好友关系(含已删除,按 update_time + id 正向游标) + * + * @param userId 当前用户编号 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null + * @param lastId 上次拉取到的记录编号;首次拉取传 null + * @param limit 拉取数量 + * @return 好友关系列表 + */ + default List selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .eq(ImFriendDO::getUserId, userId); + if (lastUpdateTime != null && lastId != null) { + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime); + query.and(w -> w.gt(ImFriendDO::getUpdateTime, lastTime) + .or(n -> n.eq(ImFriendDO::getUpdateTime, lastTime).gt(ImFriendDO::getId, lastId))); + } + return selectList(query.orderByAsc(ImFriendDO::getUpdateTime).orderByAsc(ImFriendDO::getId) + .last("LIMIT " + limit)); + } + + default List selectListByUserIdAndStatus(Long userId, Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(ImFriendDO::getUserId, userId) + .eq(ImFriendDO::getStatus, status) + .orderByDesc(ImFriendDO::getId)); + } + + default List selectListByUserIdAndFriendUserIdsAndStatus(Long userId, + Collection friendUserIds, + Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(ImFriendDO::getUserId, userId) + .in(ImFriendDO::getFriendUserId, friendUserIds) + .eq(ImFriendDO::getStatus, status)); + } + + default List selectListByUserIdsAndFriendUserIdAndStatus(Collection userIds, + Long friendUserId, + Integer status) { + return selectList(new LambdaQueryWrapperX() + .in(ImFriendDO::getUserId, userIds) + .eq(ImFriendDO::getFriendUserId, friendUserId) + .eq(ImFriendDO::getStatus, status)); + } + + default PageResult selectPage(ImFriendManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImFriendDO::getUserId, reqVO.getUserId()) + .eqIfPresent(ImFriendDO::getFriendUserId, reqVO.getFriendUserId()) + .eqIfPresent(ImFriendDO::getStatus, reqVO.getStatus()) + .eqIfPresent(ImFriendDO::getSilent, reqVO.getSilent()) + .betweenIfPresent(ImFriendDO::getAddTime, reqVO.getAddTime()) + .orderByDesc(ImFriendDO::getId)); + } + + @SuppressWarnings("UnusedReturnValue") + default int updateReAddFields(Long id, Integer status, LocalDateTime addTime, + LocalDateTime updateTime, + Boolean silent, Boolean pinned, Boolean blocked, + String displayName, Integer addSource) { + return update(null, Wrappers.lambdaUpdate() + .eq(ImFriendDO::getId, id) + .set(ImFriendDO::getStatus, status) + .set(ImFriendDO::getAddTime, addTime) + .set(ImFriendDO::getUpdateTime, updateTime) + .set(ImFriendDO::getSilent, silent) + .set(ImFriendDO::getPinned, pinned) + .set(ImFriendDO::getBlocked, blocked) + .set(ImFriendDO::getDisplayName, displayName) + .set(ImFriendDO::getAddSource, addSource) + .set(ImFriendDO::getDeleteTime, null)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java new file mode 100644 index 0000000000..6dffaa5a98 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.module.im.dal.mysql.friend; + +import cn.hutool.core.date.LocalDateTimeUtil; +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.im.controller.admin.manager.friend.vo.ImFriendRequestManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * IM 好友申请记录 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImFriendRequestMapper extends BaseMapperX { + + default ImFriendRequestDO selectByFromUserIdAndToUserId(Long fromUserId, Long toUserId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImFriendRequestDO::getFromUserId, fromUserId) + .eq(ImFriendRequestDO::getToUserId, toUserId)); + } + + /** + * 拉取「我相关」的好友申请列表 + * + * @param userId 当前用户编号 + * @param maxRequestUpdateTime 起始更新时间;首次拉取传 null + * @param maxId 起始记录编号;首次拉取传 null + * @param limit 拉取数量 + * @return 好友申请列表 + */ + default List selectMyList(Long userId, LocalDateTime maxRequestUpdateTime, + Long maxId, int limit) { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX<>(); + wrapper.and(w -> w.eq(ImFriendRequestDO::getFromUserId, userId) + .or().eq(ImFriendRequestDO::getToUserId, userId)); + if (maxRequestUpdateTime != null && maxId != null) { + wrapper.and(w -> w.lt(ImFriendRequestDO::getUpdateTime, maxRequestUpdateTime) + .or(n -> n.eq(ImFriendRequestDO::getUpdateTime, maxRequestUpdateTime) + .lt(ImFriendRequestDO::getId, maxId))); + } + wrapper.orderByDesc(ImFriendRequestDO::getUpdateTime) + .orderByDesc(ImFriendRequestDO::getId) + .last("LIMIT " + limit); + return selectList(wrapper); + } + + /** + * 增量拉取「我相关」的好友申请(双向 OR,按 update_time + id 正向游标) + * + * @param userId 当前用户编号 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null + * @param lastId 上次拉取到的记录编号;首次拉取传 null + * @param limit 拉取数量 + * @return 好友申请列表 + */ + default List selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX<>(); + query.and(w -> w.eq(ImFriendRequestDO::getFromUserId, userId) + .or().eq(ImFriendRequestDO::getToUserId, userId)); + if (lastUpdateTime != null && lastId != null) { + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime); + query.and(w -> w.gt(ImFriendRequestDO::getUpdateTime, lastTime) + .or(n -> n.eq(ImFriendRequestDO::getUpdateTime, lastTime).gt(ImFriendRequestDO::getId, lastId))); + } + return selectList(query.orderByAsc(ImFriendRequestDO::getUpdateTime).orderByAsc(ImFriendRequestDO::getId) + .last("LIMIT " + limit)); + } + + default int updateByIdAndHandleResult(Long id, Integer handleResult, ImFriendRequestDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ImFriendRequestDO::getId, id).eq(ImFriendRequestDO::getHandleResult, handleResult)); + } + + /** + * 复用 (fromUserId, toUserId) 旧申请记录:覆盖申请理由 / 备注 / 来源,重置为未处理 + 清空旧处理痕迹 + *

+ * handleContent / handleTime 走 LambdaUpdateWrapper.set 显式置 null,updateById 默认会忽略 null 字段 + */ + default int updateByIdReset(Long id, String applyContent, String displayName, Integer addSource, + LocalDateTime updateTime) { + return update(null, new LambdaUpdateWrapper() + .eq(ImFriendRequestDO::getId, id) + .set(ImFriendRequestDO::getApplyContent, applyContent) + .set(ImFriendRequestDO::getDisplayName, displayName) + .set(ImFriendRequestDO::getAddSource, addSource) + .set(ImFriendRequestDO::getHandleResult, ImFriendRequestHandleResultEnum.UNHANDLED.getResult()) + .set(ImFriendRequestDO::getHandleContent, null) + .set(ImFriendRequestDO::getHandleTime, null) + .set(ImFriendRequestDO::getUpdateTime, updateTime)); + } + + default PageResult selectPage(ImFriendRequestManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImFriendRequestDO::getFromUserId, reqVO.getFromUserId()) + .eqIfPresent(ImFriendRequestDO::getToUserId, reqVO.getToUserId()) + .eqIfPresent(ImFriendRequestDO::getHandleResult, reqVO.getHandleResult()) + .eqIfPresent(ImFriendRequestDO::getAddSource, reqVO.getAddSource()) + .betweenIfPresent(ImFriendRequestDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ImFriendRequestDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java new file mode 100644 index 0000000000..7f56a1aa77 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.im.dal.mysql.group; + +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.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * IM 群 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImGroupMapper extends BaseMapperX { + + default ImGroupDO selectByIdForUpdate(Long id) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImGroupDO::getId, id) + .last("FOR UPDATE")); + } + + default PageResult selectPage(ImGroupManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ImGroupDO::getName, reqVO.getName()) + .eqIfPresent(ImGroupDO::getOwnerUserId, reqVO.getOwnerUserId()) + .eqIfPresent(ImGroupDO::getStatus, reqVO.getStatus()) + .eqIfPresent(ImGroupDO::getBanned, reqVO.getBanned()) + .betweenIfPresent(ImGroupDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ImGroupDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java new file mode 100644 index 0000000000..2afa559f49 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java @@ -0,0 +1,137 @@ +package cn.iocoder.yudao.module.im.dal.mysql.group; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * IM 群成员 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImGroupMemberMapper extends BaseMapperX { + + default List selectListByGroupId(Long groupId) { + return selectList(new LambdaQueryWrapperX().eq(ImGroupMemberDO::getGroupId, groupId)); + } + + default ImGroupMemberDO selectByGroupIdAndUserId(Long groupId, Long userId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getGroupId, groupId) + .eq(ImGroupMemberDO::getUserId, userId)); + } + + default List selectListByGroupIdAndUserIds(Long groupId, Collection userIds) { + return selectList(new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getGroupId, groupId) + .in(ImGroupMemberDO::getUserId, userIds)); + } + + default List selectListByGroupIdAndStatus(Long groupId, Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getGroupId, groupId) + .eq(ImGroupMemberDO::getStatus, status)); + } + + default List selectListByGroupIdAndStatusAndRoles(Long groupId, Integer status, + Collection roles) { + return selectList(new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getGroupId, groupId) + .eq(ImGroupMemberDO::getStatus, status) + .in(ImGroupMemberDO::getRole, roles)); + } + + default List selectListByUserIdAndStatus(Long userId, Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getUserId, userId) + .eq(ImGroupMemberDO::getStatus, status)); + } + + default List selectListByUserId(Long userId) { + return selectList(ImGroupMemberDO::getUserId, userId); + } + + @SuppressWarnings("UnusedReturnValue") + default int updateByGroupIdAndStatus(Long groupId, Integer oldStatus, ImGroupMemberDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getGroupId, groupId) + .eq(ImGroupMemberDO::getStatus, oldStatus)); + } + + @SuppressWarnings("UnusedReturnValue") + default int updateByGroupIdAndUserIdsAndStatus(Long groupId, Collection userIds, + Integer oldStatus, ImGroupMemberDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getGroupId, groupId) + .in(ImGroupMemberDO::getUserId, userIds) + .eq(ImGroupMemberDO::getStatus, oldStatus)); + } + + @SuppressWarnings("UnusedReturnValue") + default int updateListByGroupIdAndUserIds(Long groupId, Collection userIds, ImGroupMemberDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getGroupId, groupId) + .in(ImGroupMemberDO::getUserId, userIds)); + } + + default Long selectCountByGroupIdAndRoleAndStatus(Long groupId, Integer role, Integer status) { + return selectCount(new LambdaQueryWrapperX() + .eq(ImGroupMemberDO::getGroupId, groupId) + .eq(ImGroupMemberDO::getRole, role) + .eq(ImGroupMemberDO::getStatus, status)); + } + + /** + * 批量按 group_id 统计指定状态的成员数 + */ + default Map selectCountMapByGroupIdsAndStatus(Collection groupIds, Integer status) { + if (CollUtil.isEmpty(groupIds)) { + return Collections.emptyMap(); + } + List> rows = selectMaps(Wrappers.query() + .select("group_id AS groupId", "COUNT(*) AS cnt") + .in("group_id", groupIds) + .eq("status", status) + .groupBy("group_id")); + // 转换成 Map + Map result = new HashMap<>(rows.size()); + rows.forEach(row -> result.put( + ((Number) row.get("groupId")).longValue(), + ((Number) row.get("cnt")).longValue())); + return result; + } + + @SuppressWarnings("UnusedReturnValue") + default int updateMuteEndTimeNull(Long id) { + return update(null, Wrappers.lambdaUpdate() + .eq(ImGroupMemberDO::getId, id) + .set(ImGroupMemberDO::getMuteEndTime, null)); + } + + @SuppressWarnings("UnusedReturnValue") + default int updateRejoinFields(Long id, Integer status, LocalDateTime joinTime, + Integer role, Integer addSource, Long inviterUserId) { + return update(null, Wrappers.lambdaUpdate() + .eq(ImGroupMemberDO::getId, id) + .set(ImGroupMemberDO::getStatus, status) + .set(ImGroupMemberDO::getJoinTime, joinTime) + .set(ImGroupMemberDO::getRole, role) + .set(ImGroupMemberDO::getAddSource, addSource) + .set(ImGroupMemberDO::getInviterUserId, inviterUserId) + .set(ImGroupMemberDO::getQuitTime, null) + .set(ImGroupMemberDO::getMuteEndTime, null) + .set(ImGroupMemberDO::getUpdateTime, joinTime)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java new file mode 100644 index 0000000000..c344493d0c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.im.dal.mysql.group; + +import cn.hutool.core.date.LocalDateTimeUtil; +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.im.controller.admin.manager.group.vo.ImGroupRequestManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; +import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * IM 加群申请记录 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImGroupRequestMapper extends BaseMapperX { + + default ImGroupRequestDO selectByGroupIdAndUserId(Long groupId, Long userId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImGroupRequestDO::getGroupId, groupId) + .eq(ImGroupRequestDO::getUserId, userId)); + } + + default List selectListByGroupIdsAndHandleResult(Collection groupIds, Integer handleResult) { + return selectList(new LambdaQueryWrapperX() + .in(ImGroupRequestDO::getGroupId, groupIds) + .eq(ImGroupRequestDO::getHandleResult, handleResult) + .orderByDesc(ImGroupRequestDO::getUpdateTime) + .orderByDesc(ImGroupRequestDO::getId)); + } + + default List selectListByGroupId(Long groupId) { + // 同上,update_time 倒序优先于 id + return selectList(new LambdaQueryWrapperX() + .eq(ImGroupRequestDO::getGroupId, groupId) + .orderByDesc(ImGroupRequestDO::getUpdateTime) + .orderByDesc(ImGroupRequestDO::getId)); + } + + /** + * 增量拉取「我管理的群」下的加群申请(含已处理,按 update_time + id 正向游标) + * + * @param groupIds 群编号集合 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null + * @param lastId 上次拉取到的记录编号;首次拉取传 null + * @param limit 拉取数量 + * @return 加群申请列表 + */ + default List selectPullListByGroupIds(Collection groupIds, Long lastUpdateTime, + Long lastId, Integer limit) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .in(ImGroupRequestDO::getGroupId, groupIds); + if (lastUpdateTime != null && lastId != null) { + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime); + query.and(w -> w.gt(ImGroupRequestDO::getUpdateTime, lastTime) + .or(n -> n.eq(ImGroupRequestDO::getUpdateTime, lastTime).gt(ImGroupRequestDO::getId, lastId))); + } + return selectList(query.orderByAsc(ImGroupRequestDO::getUpdateTime).orderByAsc(ImGroupRequestDO::getId) + .last("LIMIT " + limit)); + } + + default int updateByIdAndHandleResult(Long id, Integer expectedHandleResult, ImGroupRequestDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, expectedHandleResult)); + } + + /** + * 复用主动申请的旧记录:覆盖申请理由 / 来源,重置为未处理 + 清空旧处理痕迹 + 刷 update_time + *

+ * update_time 显式 set,因为 update(null, wrapper) 不会触发 MetaObjectHandler.updateFill; + * 列表查询按 update_time 倒序,复用记录必须刷这一列才会排到最前 + */ + default int updateApplyByIdReset(Long id, String applyContent, Integer addSource, LocalDateTime updateTime) { + return update(null, new LambdaUpdateWrapper() + .eq(ImGroupRequestDO::getId, id) + .set(ImGroupRequestDO::getApplyContent, applyContent) + .set(ImGroupRequestDO::getAddSource, addSource) + .set(ImGroupRequestDO::getHandleResult, ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) + .set(ImGroupRequestDO::getInviterUserId, null) + .set(ImGroupRequestDO::getHandleUserId, null) + .set(ImGroupRequestDO::getHandleContent, null) + .set(ImGroupRequestDO::getHandleTime, null) + .set(ImGroupRequestDO::getUpdateTime, updateTime)); + } + + default int updateInviteByIdReset(Long id, Long inviterUserId, Integer addSource, LocalDateTime updateTime) { + return update(null, new LambdaUpdateWrapper() + .eq(ImGroupRequestDO::getId, id) + .set(ImGroupRequestDO::getInviterUserId, inviterUserId) + .set(ImGroupRequestDO::getAddSource, addSource) + .set(ImGroupRequestDO::getHandleResult, ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) + .set(ImGroupRequestDO::getHandleUserId, null) + .set(ImGroupRequestDO::getHandleContent, null) + .set(ImGroupRequestDO::getHandleTime, null) + .set(ImGroupRequestDO::getUpdateTime, updateTime)); + } + + default PageResult selectPage(ImGroupRequestManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImGroupRequestDO::getGroupId, reqVO.getGroupId()) + .eqIfPresent(ImGroupRequestDO::getUserId, reqVO.getUserId()) + .eqIfPresent(ImGroupRequestDO::getInviterUserId, reqVO.getInviterUserId()) + .eqIfPresent(ImGroupRequestDO::getHandleResult, reqVO.getHandleResult()) + .eqIfPresent(ImGroupRequestDO::getAddSource, reqVO.getAddSource()) + .betweenIfPresent(ImGroupRequestDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ImGroupRequestDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImChannelMessageMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImChannelMessageMapper.java new file mode 100644 index 0000000000..6f1cd1a1f1 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImChannelMessageMapper.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.im.dal.mysql.message; + +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.framework.mybatis.core.util.MyBatisUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessagePageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * IM 频道消息 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImChannelMessageMapper extends BaseMapperX { + + /** + * 拉取指定用户应收的频道消息 + *

+ * 命中条件:id 大于游标 + (receiver_user_ids 为空表示全员 OR 逗号分隔列表里包含当前 userId) + * + * @param userId 当前用户编号 + * @param minId 游标;返回大于此值的消息 + * @param size 返回条数 + * @return 频道消息列表;按 id 升序 + */ + default List selectListByUserAndMinId(Long userId, Long minId, Integer size) { + return selectList(new LambdaQueryWrapperX() + .gt(ImChannelMessageDO::getId, minId) + .and(w -> w.isNull(ImChannelMessageDO::getReceiverUserIds) + .or().eq(ImChannelMessageDO::getReceiverUserIds, "") + .or().apply(MyBatisUtils.findInSet("receiver_user_ids"), userId)) + .orderByAsc(ImChannelMessageDO::getId) + .last("LIMIT " + size)); + } + + default Long selectCountByMaterialId(Long materialId) { + return selectCount(ImChannelMessageDO::getMaterialId, materialId); + } + + default PageResult selectPage(ImChannelMessagePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImChannelMessageDO::getChannelId, reqVO.getChannelId()) + .eqIfPresent(ImChannelMessageDO::getMaterialId, reqVO.getMaterialId()) + .betweenIfPresent(ImChannelMessageDO::getSendTime, reqVO.getSendTime()) + .orderByDesc(ImChannelMessageDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java new file mode 100644 index 0000000000..d0d5584632 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.module.im.dal.mysql.message; + +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.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * IM 群聊消息 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImGroupMessageMapper extends BaseMapperX { + + /** + * 根据 minId + 时间窗口增量拉取群聊消息 + * + * @param userId 当前用户编号 + * @param groupIds 候选群编号集合(当前在群 ∪ 窗口内退群),不能为空 + * @param minId 最小消息 id(不含) + * @param minSendTime 最早发送时间(不含),限制离线消息时间窗口 + * @param size 拉取数量 + * @return 消息列表(按 id 升序) + */ + default List selectListByMinId(Long userId, Collection groupIds, Long minId, + LocalDateTime minSendTime, Integer size) { + QueryWrapperX wrapper = new QueryWrapperX<>(); + wrapper.in("group_id", groupIds) + .gt("id", minId) + .gt("send_time", minSendTime) + .apply(MyBatisUtils.findInSet("receiver_user_ids"), userId) + .orderByAsc("id"); + wrapper.limitN(size); + return selectList(wrapper); + } + + /** + * 查询群聊历史消息(游标拉取) + * + * @param userId 当前用户编号 + * @param groupId 群编号 + * @param maxId 起始消息 id(不含),为空则从最新开始 + * @param limit 拉取数量 + * @return 消息列表(按 id 倒序) + */ + default List selectHistoryListByUser(Long userId, Long groupId, Long maxId, Integer limit) { + QueryWrapperX wrapper = new QueryWrapperX<>(); + wrapper.eq("group_id", groupId) + .lt(maxId != null, "id", maxId) + .apply(MyBatisUtils.findInSet("receiver_user_ids"), userId) + .orderByDesc("id"); + wrapper.limitN(limit); + return selectList(wrapper); + } + + default ImGroupMessageDO selectBySenderIdAndClientMessageId(Long senderId, String clientMessageId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImGroupMessageDO::getSenderId, senderId) + .eq(ImGroupMessageDO::getClientMessageId, clientMessageId)); + } + + /** + * 查询群内指定范围内待回执的消息 + *

+ * 仅在用户"已读位置前进"时调用,避免全量扫描: + * 只有位于 (minId, maxId] 范围内、且仍处于 PENDING 的回执消息可能因本次已读而状态变化。 + * + * @param groupId 群编号 + * @param minId 起始消息 id(不含,上一次已读位置) + * @param maxId 结束消息 id(含,本次已读位置) + * @return 待回执消息列表 + */ + default List selectListByGroupIdAndPendingReceipt(Long groupId, Long minId, Long maxId) { + return selectList(new LambdaQueryWrapperX() + .eq(ImGroupMessageDO::getGroupId, groupId) + .eq(ImGroupMessageDO::getReceiptStatus, ImMessageReceiptStatusEnum.PENDING.getStatus()) + .gt(minId != null, ImGroupMessageDO::getId, minId) + .le(ImGroupMessageDO::getId, maxId) + .ne(ImGroupMessageDO::getStatus, ImMessageStatusEnum.RECALL.getStatus())); + } + + default PageResult selectPage(ImGroupMessageManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImGroupMessageDO::getGroupId, reqVO.getGroupId()) + .eqIfPresent(ImGroupMessageDO::getSenderId, reqVO.getSenderId()) + .eqIfPresent(ImGroupMessageDO::getType, reqVO.getType()) + .likeIfPresent(ImGroupMessageDO::getContent, reqVO.getContent()) + .eqIfPresent(ImGroupMessageDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(ImGroupMessageDO::getSendTime, reqVO.getSendTime()) + .orderByDesc(ImGroupMessageDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java new file mode 100644 index 0000000000..b666dfb19a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java @@ -0,0 +1,99 @@ +package cn.iocoder.yudao.module.im.dal.mysql.message; + +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.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * IM 私聊消息 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImPrivateMessageMapper extends BaseMapperX { + + /** + * 根据 minId + 时间窗口增量拉取私聊消息 + * + * @param userId 当前用户编号 + * @param minId 最小消息 id(不含) + * @param minSendTime 最早发送时间(不含),限制离线消息时间窗口 + * @param size 拉取数量 + * @return 消息列表 + */ + default List selectListByMinId(Long userId, Long minId, + LocalDateTime minSendTime, Integer size) { + QueryWrapperX wrapper = new QueryWrapperX<>(); + wrapper.and(w -> w.eq("sender_id", userId) + .or() + .eq("receiver_id", userId)) + .gt("id", minId) + .gt("send_time", minSendTime) + .orderByAsc("id"); + wrapper.limitN(size); + return selectList(wrapper); + } + + /** + * 查询私聊历史消息(游标拉取) + * + * @param userId 当前用户编号 + * @param receiverId 对方用户编号 + * @param maxId 起始消息 id(不含),为空则从最新开始 + * @param limit 拉取数量 + * @return 消息列表(按 id 倒序) + */ + default List selectHistoryList(Long userId, Long receiverId, Long maxId, Integer limit) { + QueryWrapperX wrapper = new QueryWrapperX<>(); + wrapper.and(w -> w.eq("sender_id", userId).eq("receiver_id", receiverId) + .or() + .eq("sender_id", receiverId).eq("receiver_id", userId)) + .lt(maxId != null, "id", maxId) + .orderByDesc("id"); + wrapper.limitN(limit); + return selectList(wrapper); + } + + default ImPrivateMessageDO selectBySenderIdAndClientMessageId(Long senderId, String clientMessageId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImPrivateMessageDO::getSenderId, senderId) + .eq(ImPrivateMessageDO::getClientMessageId, clientMessageId)); + } + + default int updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus(Long senderId, Long receiverId, Long maxMessageId, + Integer whereReceiptStatus, ImPrivateMessageDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(ImPrivateMessageDO::getSenderId, senderId) + .eq(ImPrivateMessageDO::getReceiverId, receiverId) + .le(ImPrivateMessageDO::getId, maxMessageId) + .eq(ImPrivateMessageDO::getReceiptStatus, whereReceiptStatus)); + } + + default PageResult selectPage(ImPrivateMessageManagerPageReqVO reqVO) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX<>(); + if (reqVO.getSenderId() != null && reqVO.getReceiverId() != null) { + query.and(w -> w.eq(ImPrivateMessageDO::getSenderId, reqVO.getSenderId()) + .eq(ImPrivateMessageDO::getReceiverId, reqVO.getReceiverId()) + .or() + .eq(ImPrivateMessageDO::getSenderId, reqVO.getReceiverId()) + .eq(ImPrivateMessageDO::getReceiverId, reqVO.getSenderId())); + } else { + query.eqIfPresent(ImPrivateMessageDO::getSenderId, reqVO.getSenderId()) + .eqIfPresent(ImPrivateMessageDO::getReceiverId, reqVO.getReceiverId()); + } + return selectPage(reqVO, query + .eqIfPresent(ImPrivateMessageDO::getType, reqVO.getType()) + .likeIfPresent(ImPrivateMessageDO::getContent, reqVO.getContent()) + .eqIfPresent(ImPrivateMessageDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(ImPrivateMessageDO::getSendTime, reqVO.getSendTime()) + .orderByDesc(ImPrivateMessageDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/rtc/ImRtcCallMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/rtc/ImRtcCallMapper.java new file mode 100644 index 0000000000..65f21a55b2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/rtc/ImRtcCallMapper.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.im.dal.mysql.rtc; + +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.im.controller.admin.manager.rtc.vo.ImRtcCallManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * IM 通话记录 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImRtcCallMapper extends BaseMapperX { + + default ImRtcCallDO selectByRoom(String room) { + return selectOne(ImRtcCallDO::getRoom, room); + } + + default ImRtcCallDO selectLastOneByGroupIdAndStatusIn(Long groupId, Collection statuses) { + return selectLastOne(new LambdaQueryWrapperX() + .eq(ImRtcCallDO::getGroupId, groupId) + .in(ImRtcCallDO::getStatus, statuses)); + } + + default int updateByIdAndStatus(Long id, Integer oldStatus, ImRtcCallDO updateObj) { + return update(updateObj, Wrappers.lambdaUpdate() + .eq(ImRtcCallDO::getId, id) + .eq(ImRtcCallDO::getStatus, oldStatus)); + } + + @SuppressWarnings("UnusedReturnValue") + default int updateByIdAndStatusIn(Long id, Collection statuses, ImRtcCallDO updateObj) { + return update(updateObj, Wrappers.lambdaUpdate() + .eq(ImRtcCallDO::getId, id) + .in(ImRtcCallDO::getStatus, statuses)); + } + + default List selectListByStatusInAndStartTimeBefore(Collection statuses, + LocalDateTime startTimeBefore) { + return selectList(new LambdaQueryWrapperX() + .in(ImRtcCallDO::getStatus, statuses) + .lt(ImRtcCallDO::getStartTime, startTimeBefore)); + } + + default PageResult selectPage(ImRtcCallManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImRtcCallDO::getInviterUserId, reqVO.getInviterUserId()) + .eqIfPresent(ImRtcCallDO::getConversationType, reqVO.getConversationType()) + .eqIfPresent(ImRtcCallDO::getMediaType, reqVO.getMediaType()) + .eqIfPresent(ImRtcCallDO::getStatus, reqVO.getStatus()) + .eqIfPresent(ImRtcCallDO::getEndReason, reqVO.getEndReason()) + .betweenIfPresent(ImRtcCallDO::getStartTime, reqVO.getStartTime()) + .orderByDesc(ImRtcCallDO::getId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/rtc/ImRtcParticipantMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/rtc/ImRtcParticipantMapper.java new file mode 100644 index 0000000000..49d8c12ff7 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/rtc/ImRtcParticipantMapper.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.im.dal.mysql.rtc; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcParticipantDO; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * IM 通话参与者 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImRtcParticipantMapper extends BaseMapperX { + + default ImRtcParticipantDO selectByRoomAndUserId(String room, Long userId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImRtcParticipantDO::getRoom, room) + .eq(ImRtcParticipantDO::getUserId, userId)); + } + + default List selectListByRoom(String room) { + return selectList(ImRtcParticipantDO::getRoom, room); + } + + default List selectListByCallId(Long callId) { + return selectList(ImRtcParticipantDO::getCallId, callId); + } + + default List selectListByStatusAndInviteTimeBefore(Integer status, LocalDateTime threshold) { + return selectList(new LambdaQueryWrapperX() + .eq(ImRtcParticipantDO::getStatus, status) + .lt(ImRtcParticipantDO::getInviteTime, threshold)); + } + + default List selectListByRoomAndStatusAndInviteTimeBefore(String room, Integer status, LocalDateTime threshold) { + return selectList(new LambdaQueryWrapperX() + .eq(ImRtcParticipantDO::getRoom, room) + .eq(ImRtcParticipantDO::getStatus, status) + .lt(ImRtcParticipantDO::getInviteTime, threshold)); + } + + default ImRtcParticipantDO selectLastOneByUserIdAndStatus(Long userId, Collection statuses) { + return selectLastOne(new LambdaQueryWrapperX() + .eq(ImRtcParticipantDO::getUserId, userId) + .in(ImRtcParticipantDO::getStatus, statuses)); + } + + default ImRtcParticipantDO selectLastOneByUserIdAndStatusInAndRoomNot(Long userId, Collection statuses, String room) { + return selectLastOne(new LambdaQueryWrapperX() + .eq(ImRtcParticipantDO::getUserId, userId) + .in(ImRtcParticipantDO::getStatus, statuses) + .ne(ImRtcParticipantDO::getRoom, room)); + } + + @SuppressWarnings("UnusedReturnValue") + default int updateByIdAndStatus(Long id, Integer oldStatus, ImRtcParticipantDO updateObj) { + return update(updateObj, Wrappers.lambdaUpdate() + .eq(ImRtcParticipantDO::getId, id) + .eq(ImRtcParticipantDO::getStatus, oldStatus)); + } + + @SuppressWarnings("UnusedReturnValue") + default int updateByRoomAndStatus(String room, Integer oldStatus, ImRtcParticipantDO updateObj) { + return update(updateObj, Wrappers.lambdaUpdate() + .eq(ImRtcParticipantDO::getRoom, room) + .eq(ImRtcParticipantDO::getStatus, oldStatus)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java new file mode 100644 index 0000000000..ccf3f2bb46 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.im.dal.mysql.sensitiveword; + +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.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * IM 敏感词 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImSensitiveWordMapper extends BaseMapperX { + + default List selectListByStatus(Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(ImSensitiveWordDO::getStatus, status)); + } + + default ImSensitiveWordDO selectByWord(String word) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImSensitiveWordDO::getWord, word)); + } + + default PageResult selectPage(ImSensitiveWordPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ImSensitiveWordDO::getWord, reqVO.getWord()) + .eqIfPresent(ImSensitiveWordDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(ImSensitiveWordDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ImSensitiveWordDO::getId)); + } + + @Select("SELECT MAX(update_time) FROM im_sensitive_word") + LocalDateTime selectMaxUpdateTime(@Param("tenantId") Long tenantId); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java new file mode 100644 index 0000000000..26e3d982a5 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java @@ -0,0 +1,191 @@ +package cn.iocoder.yudao.module.im.dal.mysql.statistics; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * IM 数据看板 Mapper + *

+ * 独立于业务 Mapper:所有统计 SQL 集中在此,仅服务于 manager 看板,不被其它业务调用,保持各业务 Mapper / Service 不受统计需求污染。 + * + * @author 芋道源码 + */ +@Mapper +public interface ImStatisticsManagerMapper { + + String NORMAL_MESSAGE_CONDITION = "type IN (101,102,103,104,105,107,108,115,125) AND status <> 2"; + + // ==================== 用户 ==================== + + /** + * 用户总数(system_users) + */ + @Select("SELECT COUNT(*) FROM system_users WHERE deleted = 0") + Long selectTotalUserCount(); + + /** + * 区间内新增用户数 + */ + @Select("SELECT COUNT(*) FROM system_users " + + "WHERE deleted = 0 AND create_time >= #{beginTime} AND create_time < #{endTime}") + Long selectNewUserCount(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 区间内活跃用户数:私聊或群聊发过消息的去重用户数 + */ + @Select("SELECT COUNT(DISTINCT user_id) FROM (" + + " SELECT sender_id AS user_id FROM im_private_message " + + " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime}" + + " UNION ALL " + + " SELECT sender_id AS user_id FROM im_group_message " + + " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime}" + + ") t") + Long selectActiveUserCount(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 区间内每日新增用户数(按天分组) + * + * @return [{date: "yyyy-MM-dd", count: 123}, ...] + */ + @Select("SELECT DATE(create_time) AS date, COUNT(*) AS count FROM system_users " + + "WHERE deleted = 0 AND create_time >= #{beginTime} AND create_time < #{endTime} " + + "GROUP BY DATE(create_time)") + List> selectNewUserDailyCount(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 区间内每日活跃用户数(按天分组、跨私聊+群聊去重) + */ + @Select("SELECT day AS date, COUNT(DISTINCT user_id) AS count FROM (" + + " SELECT DATE(send_time) AS day, sender_id AS user_id FROM im_private_message " + + " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime}" + + " UNION ALL " + + " SELECT DATE(send_time) AS day, sender_id AS user_id FROM im_group_message " + + " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime}" + + ") t GROUP BY day") + List> selectActiveUserDailyCount(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); + + // ==================== 群 ==================== + + /** + * 当前有效群总数 + */ + @Select("SELECT COUNT(*) FROM im_group WHERE deleted = 0 AND status = 0") + Long selectTotalGroupCount(); + + /** + * 区间内新建群数 + */ + @Select("SELECT COUNT(*) FROM im_group " + + "WHERE deleted = 0 AND create_time >= #{beginTime} AND create_time < #{endTime}") + Long selectNewGroupCount(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); + + /** + * 群规模分布(按群成员数分桶) + * + * @return [{range: "1-9 人", count: 123}, ...] + */ + @Select("SELECT " + + " CASE " + + " WHEN cnt < 10 THEN '1-9 人' " + + " WHEN cnt < 50 THEN '10-49 人' " + + " WHEN cnt < 200 THEN '50-199 人' " + + " ELSE '200+ 人' " + + " END AS `range`, COUNT(*) AS count " + + "FROM (" + + " SELECT g.id, COUNT(m.id) AS cnt " + + " FROM im_group g " + + " LEFT JOIN im_group_member m ON m.group_id = g.id AND m.status = 0 AND m.deleted = 0 " + + " WHERE g.deleted = 0 AND g.status = 0 " + + " GROUP BY g.id" + + ") t " + + "GROUP BY `range`") + List> selectGroupSizeDistribution(); + + // ==================== 消息 ==================== + + /** + * 区间内私聊消息数 + */ + @Select("SELECT COUNT(*) FROM im_private_message " + + "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime}") + Long selectPrivateMessageCount(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 区间内群聊消息数 + */ + @Select("SELECT COUNT(*) FROM im_group_message " + + "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime}") + Long selectGroupMessageCount(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 区间内每日私聊消息数(按天分组) + */ + @Select("SELECT DATE(send_time) AS date, COUNT(*) AS count FROM im_private_message " + + "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime} " + + "GROUP BY DATE(send_time)") + List> selectPrivateMessageDailyCount(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 区间内每日群聊消息数(按天分组) + */ + @Select("SELECT DATE(send_time) AS date, COUNT(*) AS count FROM im_group_message " + + "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime} " + + "GROUP BY DATE(send_time)") + List> selectGroupMessageDailyCount(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 区间内内容类型分布(私聊+群聊合并) + * + * @return [{type: 0, count: 123}, ...] + */ + @Select("SELECT type, COUNT(*) AS count FROM (" + + " SELECT type FROM im_private_message " + + " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime}" + + " UNION ALL " + + " SELECT type FROM im_group_message " + + " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime}" + + ") t GROUP BY type") + List> selectMessageTypeDistribution(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 区间内 TOP 发送者(私聊+群聊合并,按消息数倒序) + * + * @return [{userId: 1024, messageCount: 1500}, ...] + */ + @Select("SELECT user_id AS userId, COUNT(*) AS messageCount FROM (" + + " SELECT sender_id AS user_id FROM im_private_message " + + " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime}" + + " UNION ALL " + + " SELECT sender_id AS user_id FROM im_group_message " + + " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + + " AND send_time >= #{beginTime} AND send_time < #{endTime}" + + ") t GROUP BY user_id ORDER BY messageCount DESC LIMIT #{limit}") + List> selectTopSenders(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime, + @Param("limit") int limit); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java new file mode 100644 index 0000000000..92d38902c2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.im.dal.redis; + + +/** + * IM Redis Key 枚举类 + * + * @author 芋道源码 + */ +public interface RedisKeyConstants { + + /** + * 好友关系状态缓存(合并「是否好友」+「是否拉黑」两态) + *

+ * KEY 格式:friend_state:{userId}_{friendUserId} + * VALUE 数据类型:{@link cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum} + */ + String FRIEND_STATE = "friend_state"; + + /** + * 群信息缓存 + *

+ * KEY 格式:group:{groupId} + * VALUE 数据类型:ImGroupDO + */ + String GROUP = "group"; + + /** + * 群有效成员 userId 列表缓存(仅 ENABLE 状态) + *

+ * KEY 格式:group_member_ids:{groupId} + * VALUE 数据类型:List + *

+ * 说明:只缓存轻量的 userId 列表,适合「群消息推送目标」这类只关心 userId 的场景 + */ + String GROUP_MEMBER_IDS = "group_member_ids"; + + /** + * 通话同对 / 同群活跃唯一性的分布式锁 + *

+ * KEY 格式:im_rtc_call:{conversationType}:{suffix} + * 私聊(conversationType=1):suffix = {小 userId}_{大 userId} + * 群聊(conversationType=2):suffix = {groupId} + * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构 + * 过期时间:不固定(lock 时显式传 timeoutMillis) + */ + String IM_RTC_CALL_LOCK = "im_rtc_call:%d:%s"; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/rtc/ImRtcCallLockRedisDAO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/rtc/ImRtcCallLockRedisDAO.java new file mode 100644 index 0000000000..0384bf4718 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/rtc/ImRtcCallLockRedisDAO.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.im.dal.redis.rtc; + +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Repository; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.IM_RTC_CALL_LOCK; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.RTC_INVITE_BUSY; + +/** + * IM 通话同对 / 同群活跃唯一性的锁 Redis DAO + *

+ * invite 入口包一层;锁内做「SELECT 已有活跃通话 → 命中即加入分支;否则 INSERT 新通话」 + * + * @author 芋道源码 + */ +@Repository +@Slf4j +public class ImRtcCallLockRedisDAO { + + /** + * 等待获取锁的最长时间;超时抛 RTC_INVITE_BUSY + */ + private static final long LOCK_WAIT_MS = 5_000L; + /** + * 持有锁的最长时间;自动释放兜底;给 LiveKit / DB / 推送偶发慢留余地 + */ + private static final long LOCK_LEASE_MS = 30_000L; + + @Resource + private RedissonClient redissonClient; + + /** + * 私聊通话锁;按 userId 排序拼 key 保对称(同对呼叫和反向呼叫共用一把锁) + */ + public V lockPrivate(Long userIdA, Long userIdB, Callable callable) throws Exception { + String key = String.format(IM_RTC_CALL_LOCK, ImConversationTypeEnum.PRIVATE.getType(), + Math.min(userIdA, userIdB)+ "_" + Math.max(userIdA, userIdB)); + return doLock(key, callable); + } + + /** + * 群通话锁;同群所有 invite 串行 + */ + public V lockGroup(Long groupId, Callable callable) throws Exception { + String key = String.format(IM_RTC_CALL_LOCK, ImConversationTypeEnum.GROUP.getType(), groupId); + return doLock(key, callable); + } + + /** + * tryLock(waitTime, leaseTime, unit):waitTime 内拿不到锁直接抛繁忙;拿到后 leaseTime 自动释放 + *

+ * unlock 前用 isHeldByCurrentThread 兜底;业务超过 leaseTime 时锁已自动释放,不再抛 IllegalMonitorStateException + */ + private V doLock(String lockKey, Callable callable) throws Exception { + RLock lock = redissonClient.getLock(lockKey); + boolean acquired = lock.tryLock(LOCK_WAIT_MS, LOCK_LEASE_MS, TimeUnit.MILLISECONDS); + if (!acquired) { + log.error("[doLock][lockKey={} 等待 {}ms 仍未获取到锁]", lockKey, LOCK_WAIT_MS); + throw exception(RTC_INVITE_BUSY); + } + try { + return callable.call(); + } finally { + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } else { + log.error("[doLock][lockKey={} 业务超过 leaseTime={}ms,锁已被 Redisson 自动释放]", lockKey, LOCK_LEASE_MS); + } + } + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java new file mode 100644 index 0000000000..eb34dabc01 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java @@ -0,0 +1,108 @@ +package cn.iocoder.yudao.module.im.enums; + +import cn.iocoder.yudao.framework.common.exception.ErrorCode; + +/** + * IM 错误码枚举类 + *

+ * im 系统,使用 1-040-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== 消息 (1-040-300-000) ========== + ErrorCode MESSAGE_NOT_EXISTS = new ErrorCode(1_040_300_000, "消息不存在"); + ErrorCode MESSAGE_RECALL_DENIED = new ErrorCode(1_040_300_002, "只能撤回自己发送的消息"); + ErrorCode MESSAGE_ALREADY_RECALLED = new ErrorCode(1_040_300_003, "消息已撤回"); + ErrorCode MESSAGE_SENSITIVE_WORD_BLOCKED = new ErrorCode(1_040_300_004, "消息包含敏感词,无法发送"); + ErrorCode MESSAGE_PULL_SIZE_EXCEEDED = new ErrorCode(1_040_300_005, "单次拉取消息数量不能超过 {} 条"); + ErrorCode MESSAGE_RECALL_TIMEOUT = new ErrorCode(1_040_300_007, "超过 {} 分钟的消息无法撤回"); + ErrorCode MESSAGE_QUOTE_INVALID = new ErrorCode(1_040_300_008, "引用的消息不可用"); + ErrorCode MESSAGE_NOT_IN_GROUP = new ErrorCode(1_040_300_009, "消息不属于该群"); + ErrorCode MESSAGE_PRIVATE_READ_DISABLED = new ErrorCode(1_040_300_010, "私聊已读功能已关闭"); + ErrorCode MESSAGE_GROUP_READ_DISABLED = new ErrorCode(1_040_300_011, "群聊已读功能已关闭"); + ErrorCode MESSAGE_CONTENT_INVALID = new ErrorCode(1_040_300_013, "消息内容格式不正确"); + + // ========== 群 (1-040-400-000) ========== + ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1_040_400_000, "群不存在"); + ErrorCode GROUP_BANNED = new ErrorCode(1_040_400_001, "群已被封禁"); + ErrorCode GROUP_DISSOLVED = new ErrorCode(1_040_400_002, "群已解散"); + ErrorCode GROUP_NOT_OWNER = new ErrorCode(1_040_400_003, "仅群主可执行该操作"); + ErrorCode GROUP_NOT_OWNER_OR_ADMIN = new ErrorCode(1_040_400_004, "仅群主或管理员可执行该操作"); + ErrorCode GROUP_TRANSFER_OWNER_TO_SELF = new ErrorCode(1_040_400_005, "不能将群主转让给自己"); + ErrorCode GROUP_MESSAGE_PIN_MAX_LIMIT = new ErrorCode(1_040_400_006, "群置顶消息数量不能超过 {} 条"); + ErrorCode GROUP_MESSAGE_ALREADY_PINNED = new ErrorCode(1_040_400_007, "该消息已置顶"); + ErrorCode GROUP_MESSAGE_NOT_PINNED = new ErrorCode(1_040_400_008, "该消息未置顶"); + ErrorCode GROUP_MESSAGE_PIN_DIRECTED_DENIED = new ErrorCode(1_040_400_009, "定向消息不支持置顶"); + + // ========== 群成员 (1-040-500-000) ========== + ErrorCode GROUP_MEMBER_NOT_IN_GROUP = new ErrorCode(1_040_500_001, "您已不在该群中"); + ErrorCode GROUP_OWNER_CANNOT_QUIT = new ErrorCode(1_040_500_003, "群主不能退出群聊,请先转让群主或解散群聊"); + ErrorCode GROUP_CANNOT_REMOVE_SELF = new ErrorCode(1_040_500_004, "不能将自己移出群聊"); + ErrorCode GROUP_MEMBER_EXCEED = new ErrorCode(1_040_500_005, "群聊人数不能超过 {} 人"); + ErrorCode GROUP_INVITE_NOT_FRIEND = new ErrorCode(1_040_500_006, "'{}' 不是您的好友,邀请失败"); + ErrorCode GROUP_ADMIN_TARGET_NOT_IN_GROUP = new ErrorCode(1_040_500_007, "目标用户已不在该群中"); + ErrorCode GROUP_ADMIN_TARGET_IS_OWNER = new ErrorCode(1_040_500_008, "群主无法被设为或撤销管理员"); + ErrorCode GROUP_ADMIN_MAX_LIMIT = new ErrorCode(1_040_500_009, "群管理员数量不能超过 {} 人"); + ErrorCode GROUP_REMOVE_OWNER_DENIED = new ErrorCode(1_040_500_010, "群主无法被移出群聊"); + ErrorCode GROUP_REMOVE_ADMIN_DENIED = new ErrorCode(1_040_500_011, "管理员无法移出其他管理员,请先由群主撤销其管理员身份"); + ErrorCode GROUP_MUTED_CANNOT_SEND = new ErrorCode(1_040_500_012, "群已全局禁言,仅群主和管理员可发送消息"); + ErrorCode GROUP_MEMBER_MUTED_CANNOT_SEND = new ErrorCode(1_040_500_013, "您已被禁言,解除时间:{}"); + ErrorCode GROUP_MUTE_MEMBER_SELF = new ErrorCode(1_040_500_014, "不能禁言自己"); + ErrorCode GROUP_MUTE_OWNER_DENIED = new ErrorCode(1_040_500_015, "群主无法被禁言"); + ErrorCode GROUP_MUTE_ADMIN_DENIED = new ErrorCode(1_040_500_016, "管理员无法禁言其他管理员"); + + // ========== 好友 (1-040-600-000) ========== + ErrorCode FRIEND_NOT_FRIEND = new ErrorCode(1_040_600_001, "对方不是您的好友"); + ErrorCode FRIEND_ADD_SELF = new ErrorCode(1_040_600_002, "不允许添加自己为好友"); + ErrorCode FRIEND_NOT_BLOCKED = new ErrorCode(1_040_600_003, "对方未在黑名单中"); + ErrorCode FRIEND_BLOCKED_BY_PEER = new ErrorCode(1_040_600_004, "您已被对方拉入黑名单,无法发送消息"); + + // ========== 加群申请 (1-040-510-000) ========== + ErrorCode GROUP_REQUEST_NOT_EXISTS = new ErrorCode(1_040_510_001, "加群申请不存在"); + ErrorCode GROUP_REQUEST_HANDLED = new ErrorCode(1_040_510_002, "加群申请已处理"); + ErrorCode GROUP_REQUEST_NOT_TO_ME = new ErrorCode(1_040_510_003, "仅群主或管理员可处理加群申请"); + ErrorCode GROUP_REQUEST_ALREADY_MEMBER = new ErrorCode(1_040_510_004, "您已在该群中,无需重复申请"); + + // ========== 好友申请 (1-040-610-000) ========== + ErrorCode FRIEND_REQUEST_NOT_EXISTS = new ErrorCode(1_040_610_001, "好友申请不存在"); + ErrorCode FRIEND_REQUEST_HANDLED = new ErrorCode(1_040_610_002, "好友申请已处理"); + ErrorCode FRIEND_REQUEST_NOT_TO_ME = new ErrorCode(1_040_610_003, "不能处理别人的好友申请"); + ErrorCode FRIEND_REQUEST_ALREADY_FRIEND = new ErrorCode(1_040_610_005, "您已是 TA 的好友,无需重复添加"); + ErrorCode FRIEND_REQUEST_BLOCKED_BY_PEER = new ErrorCode(1_040_610_006, "您已被对方拉入黑名单,无法添加为好友"); + + // ========== 敏感词 (1-040-700-000) ========== + ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1_040_700_000, "敏感词不存在"); + ErrorCode SENSITIVE_WORD_DUPLICATED = new ErrorCode(1_040_700_001, "敏感词 '{}' 已存在"); + + // ========== 表情包 (1-040-800-000) ========== + ErrorCode FACE_PACK_NOT_EXISTS = new ErrorCode(1_040_800_000, "表情包不存在"); + ErrorCode FACE_PACK_HAS_ITEMS = new ErrorCode(1_040_800_001, "表情包下还有表情,无法删除"); + ErrorCode FACE_PACK_ITEM_NOT_EXISTS = new ErrorCode(1_040_800_002, "表情不存在"); + ErrorCode FACE_USER_ITEM_NOT_EXISTS = new ErrorCode(1_040_800_010, "个人表情不存在"); + ErrorCode FACE_USER_ITEM_NOT_OWN = new ErrorCode(1_040_800_011, "不能操作他人的表情"); + ErrorCode FACE_USER_ITEM_DUPLICATED = new ErrorCode(1_040_800_013, "该表情已添加到个人表情"); + ErrorCode FACE_USER_ITEM_MAX_LIMIT = new ErrorCode(1_040_800_014, "个人表情数量不能超过 {} 个"); + + // ========== 频道 (1-040-810-000) ========== + ErrorCode IM_CHANNEL_NOT_EXISTS = new ErrorCode(1_040_810_000, "频道不存在"); + ErrorCode IM_CHANNEL_CODE_DUPLICATED = new ErrorCode(1_040_810_001, "频道编码 '{}' 已存在"); + ErrorCode IM_CHANNEL_HAS_MATERIAL = new ErrorCode(1_040_810_002, "频道下还有素材,无法删除"); + ErrorCode IM_CHANNEL_MATERIAL_NOT_EXISTS = new ErrorCode(1_040_810_010, "素材不存在"); + ErrorCode IM_CHANNEL_MATERIAL_USED = new ErrorCode(1_040_810_011, "素材已被推送过,无法删除"); + ErrorCode IM_CHANNEL_MESSAGE_NOT_EXISTS = new ErrorCode(1_040_810_020, "频道消息不存在"); + + // ========== 实时通话 (1-040-900-000) ========== + ErrorCode RTC_NOT_ENABLED = new ErrorCode(1_040_900_000, "通话功能未开启"); + ErrorCode RTC_SESSION_NOT_EXISTS = new ErrorCode(1_040_900_001, "通话已结束"); + ErrorCode RTC_PEER_BUSY = new ErrorCode(1_040_900_002, "对方正在通话中"); + ErrorCode RTC_SELF_BUSY = new ErrorCode(1_040_900_003, "您正在通话中"); + ErrorCode RTC_NOT_PARTICIPANT = new ErrorCode(1_040_900_004, "您不在该通话中"); + ErrorCode RTC_INVITE_SELF = new ErrorCode(1_040_900_005, "不能呼叫自己"); + ErrorCode RTC_PRIVATE_INVITEE_REQUIRED = new ErrorCode(1_040_900_006, "私聊通话必须指定对方"); + ErrorCode RTC_GROUP_REQUIRED = new ErrorCode(1_040_900_007, "群聊通话必须指定群编号"); + ErrorCode RTC_INVITE_BUSY = new ErrorCode(1_040_900_008, "通话发起繁忙,请稍后再试"); + ErrorCode RTC_GROUP_CALL_ACTIVE = new ErrorCode(1_040_900_009, "该群已有进行中通话,请通过胶囊条加入"); + ErrorCode RTC_GROUP_INVITEE_OVER_LIMIT = new ErrorCode(1_040_900_010, "群通话邀请人数超过最大值"); + ErrorCode RTC_GROUP_INVITEE_REQUIRED = new ErrorCode(1_040_900_011, "群通话必须选择被邀请人"); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java new file mode 100644 index 0000000000..404d2fb330 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.im.enums; + +/** + * IM 通用常量 + * + * @author 芋道源码 + */ +public interface ImCommonConstants { + + /** + * 群消息 @ 所有人的特殊用户编号 + *

+ * 前后端协议契约值;atUserIds 数组里出现该值表示 @ 全体成员 + */ + long AT_USER_ID_ALL = -1L; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImContentTypeEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImContentTypeEnum.java new file mode 100644 index 0000000000..2f68ad80d4 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImContentTypeEnum.java @@ -0,0 +1,422 @@ +package cn.iocoder.yudao.module.im.enums; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Set; + +/** + * IM 内容类型枚举 + * + * @author 芋道源码 + */ +@Getter +@RequiredArgsConstructor +public enum ImContentTypeEnum implements ArrayValuable { + + // ========== 用户聊天消息(101-105 直接复用 OpenIM 段位编号) ========== + /** + * 对应 OpenIM:Text 101 + * 对应自己的类:TextMessage + */ + TEXT(101, "文本", true, true), + /** + * 对应 OpenIM:Picture 102 + * 对应自己的类:ImageMessage + */ + IMAGE(102, "图片", true, true), + /** + * 对应 OpenIM:Sound 103 + * 对应自己的类:AudioMessage + */ + VOICE(103, "语音", true, true), + /** + * 对应 OpenIM:Video 104 + * 对应自己的类:VideoMessage + */ + VIDEO(104, "视频", true, true), + /** + * 对应 OpenIM:File 105 + * 对应自己的类:FileMessage + */ + FILE(105, "文件", true, true), + /** + * 对应 OpenIM:Merger 107 + * 对应自己的类:MergeMessage + */ + MERGE(107, "合并转发", true, true), + /** + * 对应 OpenIM:Card 108(OpenIM 仅用户名片;本系统扩展为用户 / 群双类型,按 targetType 区分) + * 对应自己的类:CardMessage + * 场景:把用户名片 / 群名片推荐给其他会话;用户名片点击打开 UserInfoCard,群名片点击「已加群跳会话 / 未加群弹申请加群」 + */ + CARD(108, "名片", true, true), + /** + * 对应 OpenIM:Face 115 + * 对应自己的类:FaceMessage + * 场景:表情贴图(运营配置的系统表情包 + 用户私有表情包);Unicode emoji 仍走 TEXT + */ + FACE(115, "表情", true, true), + + // ========== 频道消息扩展段(125+;OpenIM 122 之后未占用,本系统在 125 起步给频道 / 公众号类消息扩展) ========== + /** + * 对应 OpenIM:无(125 段位 OpenIM 未占用,作为频道消息扩展起始位) + * 对应自己的类:MaterialMessage + * 场景:频道运营推送的素材消息;当前形态为图文卡片(title + coverUrl + summary + url) + * 详情:url 非空跳 url;url 为空时客户端按 materialId 拉 /get-content 渲染富文本正文 + */ + MATERIAL(125, "素材", true, true), + + // ========== 信号类(2101 / 2200 直接复用 OpenIM 段位编号;2201 自有扩展) ========== + /** + * 对应 OpenIM:RevokeNotification 2101 + * 对应自己的类:RecallMessage + */ + RECALL(2101, "撤回", true, false), + /** + * 对应 OpenIM:HasReadReceipt 2200 + * 对应自己的类:无(payload 走 ImXxxMessageDTO 顶层字段) + */ + RECEIPT(2200, "回执", false, false), + /** + * 对应 OpenIM:无(自有扩展,OpenIM 走 ConversationChangeNotification 1300 路径) + * 对应自己的类:无(payload 走 ImXxxMessageDTO 顶层字段) + */ + READ(2201, "已读", false, false), + + // ========== 实时通话信令(1601-1605 段位与 OpenIM 对齐;1610+ 自有扩展) ========== + /** + * 对应 OpenIM:SignalingNotification 1601(通话信令统一入口) + * 对应自己的类:ImRtcCallNotification + * 场景:通话信令;不入库,走 imWebSocketService 仅推参与方;status 复用参与者状态枚举区分 INVITING / JOINED / REJECTED / NO_ANSWER / LEFT + */ + RTC_CALL(1601, "通话信令", false, false), + /** + * 对应 OpenIM:RoomParticipantsConnectedNotification 1602 + * 对应自己的类:ImRtcParticipantConnectedNotification + * 场景:通话参与者加入;LiveKit webhook participant_joined 触发;私聊推 peer 多端 + inviter 多端,群聊全群广播;不入库 + */ + RTC_PARTICIPANT_CONNECTED(1602, "通话参与者加入", false, false), + /** + * 对应 OpenIM:RoomParticipantsDisconnectedNotification 1603 + * 对应自己的类:ImRtcParticipantDisconnectedNotification + * 场景:通话参与者离开;LiveKit webhook participant_left 触发;推送范围同 1602;不入库 + */ + RTC_PARTICIPANT_DISCONNECTED(1603, "通话参与者离开", false, false), + // 1604-1609 OpenIM 已用 / 留作扩展,本系统暂不使用 + /** + * 对应 OpenIM:无(自有扩展,OpenIM 通话事件不入消息流) + * 对应自己的类:ImRtcCallStartNotification + * 场景:通话开始;群聊入 im_group_message 全群广播,前端渲染聊天 tip「{inviterNickname} 发起了语音通话」; + * 私聊入 im_private_message 定向给被叫,仅用于会话列表预览展示「[语音通话]」(不渲染聊天 tip) + *

+ * 与 RTC_CALL_END(1611) 两段式配对:START 一定先于 END 入库(START 在 invite 接口事务里、END 在 cancel/leave 接口事务里,自然按请求顺序串行) + */ + RTC_CALL_START(1610, "通话开始", true, false), + /** + * 对应 OpenIM:无(自有扩展,OpenIM 通话事件不入消息流) + * 对应自己的类:ImRtcCallEndNotification + * 场景:通话结束;入 im_private_message / im_group_message;私聊渲染准气泡,群聊渲染 tip「语音通话已经结束」 + *

+ * 与 RTC_CALL_START(1610) 两段式配对 + */ + RTC_CALL_END(1611, "通话结束", true, false), + + // ========== 好友通知(1201-1210 直接复用 OpenIM 段位编号) ========== + /** + * 对应 OpenIM:FriendApplicationApprovedNotification 1201 + * 对应自己的类:FriendRequestApprovedNotification + * 场景:B 同意 A 的好友申请,推给 A 多端 + */ + FRIEND_REQUEST_APPROVED(1201, "好友申请被同意", false, false), + /** + * 对应 OpenIM:FriendApplicationRejectedNotification 1202 + * 对应自己的类:FriendRequestRejectedNotification + * 场景:B 拒绝 A 的好友申请,推给 A 多端 + */ + FRIEND_REQUEST_REJECTED(1202, "好友申请被拒绝", false, false), + /** + * 对应 OpenIM:FriendApplicationNotification 1203 + * 对应自己的类:FriendRequestNotification + * 场景:A 申请加 B,推给 B 多端,前端落到「新的朋友」列表 + */ + FRIEND_REQUEST_RECEIVED(1203, "收到新的好友申请", false, false), + /** + * 对应 OpenIM:FriendAddedNotification 1204(OpenIM friendAdded.isSendMsg=false 默认不入消息流;本系统改为入库当会话气泡) + * 对应自己的类:FriendAddNotification + * 场景:双方建立好友关系,单条入库(sender=fromUserId, receiver=toUserId);双向 WebSocket 自动覆盖双方多端 + * 注意:silentReAddFriend 单边语义场景,发送时显式 setPersistent(false) 覆盖默认值 + */ + FRIEND_ADD(1204, "新增好友", true, false), + /** + * 对应 OpenIM:FriendDeletedNotification 1205 + * 对应自己的类:FriendDeleteNotification + * 场景:A 删除 B,推给 A、B 双方多端 + */ + FRIEND_DELETE(1205, "好友被删除", false, false), + // 1206 对应 OpenIM FriendRemarkSetNotification;本系统并入 FRIEND_UPDATE(1210) 统一推送,单一字段变更不再独立通道 + /** + * 对应 OpenIM:BlackAddedNotification 1207 + * 对应自己的类:FriendBlockNotification + * 场景:A 拉黑 B,仅推 A 多端 + */ + FRIEND_BLOCK(1207, "加入黑名单", false, false), + /** + * 对应 OpenIM:BlackDeletedNotification 1208 + * 对应自己的类:FriendUnblockNotification + * 场景:A 移出 B 的黑名单,仅推 A 多端 + */ + FRIEND_UNBLOCK(1208, "移出黑名单", false, false), + /** + * 对应 OpenIM:FriendInfoUpdatedNotification 1209 + * 对应自己的类:FriendInfoUpdatedNotification + * 场景:B 改了昵称 / 头像后,推给 B 的所有好友 + * 触发:system 模块发 AdminUserProfileUpdateMessage,IM 消费者 AdminUserProfileUpdateConsumer 批量推此通知 + */ + FRIEND_INFO_UPDATED(1209, "好友资料变更", false, false), + /** + * 对应 OpenIM:FriendsInfoUpdateNotification 1210(窄化到 silent / pinned 单边属性) + * 对应自己的类:FriendUpdateNotification + * 场景:A 改了 silent / pinned 等单边属性,推 A 多端 + */ + FRIEND_UPDATE(1210, "好友信息批量更新", false, false), + + // ========== 群事件(1501-1520 直接复用 OpenIM 段位编号;1530+ 我们独有扩展) ========== + // 1500 对应 OpenIM GroupNotificationBegin 起始位,仅作占位,不使用 + /** + * 对应 OpenIM:sdkws.GroupCreatedTips(GroupCreatedNotification 1501) + * 对应自己的类:GroupCreateNotification + * 场景:用户创建群(同时邀请初始成员),全员广播(含创建者多端同步 + 初始成员) + */ + GROUP_CREATE(1501, "群创建", true, false), + /** + * 对应 OpenIM:sdkws.GroupInfoSetTips(GroupInfoSetNotification 1502,NAME / NOTICE 之外字段的 generic 兜底) + * 对应自己的类:GroupInfoUpdateNotification + * 场景:群主修改群头像 / 简介等字段后全员广播 + */ + GROUP_INFO_UPDATE(1502, "群信息变更", true, false), + /** + * 对应 OpenIM:sdkws.JoinGroupApplicationTips(JoinGroupApplicationNotification 1503) + * 对应自己的类:GroupRequestReceivedNotification + * 场景:用户申请加群 / 普通成员邀请待审批,定向私聊推送给群主 + 全部管理员(多端同步);不入群消息流 + */ + GROUP_REQUEST_RECEIVED(1503, "收到新的入群申请", false, false), + /** + * 对应 OpenIM:sdkws.MemberQuitTips(MemberQuitNotification 1504) + * 对应自己的类:GroupMemberQuitNotification + * 场景:成员主动退群(send-before-remove),全员广播(含 quitter);quitter 自判 operatorUserId === self → removeGroup + */ + GROUP_MEMBER_QUIT(1504, "成员退群", true, false), + /** + * 对应 OpenIM:sdkws.GroupApplicationAcceptedTips(GroupApplicationAcceptedNotification 1505) + * 对应自己的类:GroupRequestApprovedNotification + * 场景:群主 / 管理员同意申请,定向私聊推送给申请人 + 群主 + 全部管理员;申请人侧弹 toast,admin 侧 pendingRequestCount-1;不入群消息流 + */ + GROUP_REQUEST_APPROVED(1505, "入群申请被同意", false, false), + /** + * 对应 OpenIM:sdkws.GroupApplicationRejectedTips(GroupApplicationRejectedNotification 1506) + * 对应自己的类:GroupRequestRejectedNotification + * 场景:群主 / 管理员拒绝申请,定向私聊推送给申请人 + 群主 + 全部管理员;不入群消息流 + */ + GROUP_REQUEST_REJECTED(1506, "入群申请被拒绝", false, false), + /** + * 对应 OpenIM:sdkws.GroupOwnerTransferredTips(GroupOwnerTransferredNotification 1507) + * 对应自己的类:GroupOwnerTransferNotification + * 场景:群主转让,全员广播;前端 transferOwner 把 ownerUserId 切到新值 + 旧群主 role → NORMAL / 新群主 role → OWNER + */ + GROUP_OWNER_TRANSFER(1507, "群主转让", true, false), + /** + * 对应 OpenIM:sdkws.MemberKickedTips(MemberKickedNotification 1508) + * 对应自己的类:GroupMemberKickNotification + * 场景:群主 / 管理员移出成员(send-before-remove),全员广播(含被踢者);被踢者自判 memberUserIds 含 self → removeGroup + */ + GROUP_MEMBER_KICK(1508, "成员被移出", true, false), + /** + * 对应 OpenIM:sdkws.MemberInvitedTips(MemberInvitedNotification 1509) + * 对应自己的类:GroupMemberInviteNotification + * 场景:成员邀请新人入群,全员广播(含被邀请者);被邀请人前端按 memberUserIds 含自己自判,初次拉取 fetchGroupInfo + fetchGroupMembers + */ + GROUP_MEMBER_INVITE(1509, "成员加入", true, false), + /** + * 对应 OpenIM:sdkws.MemberEnterTips(MemberEnterNotification 1510) + * 对应自己的类:GroupMemberEnterNotification + * 场景:用户经搜索 / 二维码 / 分享链接自由进群(FREE 模式或审批通过后),全员广播;前端按 entrantUserId 局部添加成员 + */ + GROUP_MEMBER_ENTER(1510, "自由进群", true, false), + /** + * 对应 OpenIM:sdkws.GroupDismissedTips(GroupDismissedNotification 1511) + * 对应自己的类:GroupDissolveNotification + * 场景:群主解散群(send-before-remove),全员广播(含群主多端同步);前端 removeGroup 清群;离场用户离线 pull 通过 quit 路径(send_time < quit_time)也能拉到 + */ + GROUP_DISSOLVE(1511, "群解散", true, false), + /** + * 对应 OpenIM:sdkws.GroupMemberMutedTips(GroupMemberMutedNotification 1512) + * 对应自己的类:GroupMemberMutedNotification + * 场景:群主 / 管理员禁言某成员,全员广播 + */ + GROUP_MEMBER_MUTED(1512, "成员禁言", true, false), + /** + * 对应 OpenIM:sdkws.GroupMemberCancelMutedTips(GroupMemberCancelMutedNotification 1513) + * 对应自己的类:GroupMemberCancelMutedNotification + * 场景:群主 / 管理员取消某成员禁言,全员广播 + */ + GROUP_MEMBER_CANCEL_MUTED(1513, "成员取消禁言", true, false), + /** + * 对应 OpenIM:sdkws.GroupMutedTips(GroupMutedNotification 1514) + * 对应自己的类:GroupMutedNotification + * 场景:群主 / 管理员开启全群禁言,全员广播 + */ + GROUP_MUTED(1514, "全群禁言", true, false), + /** + * 对应 OpenIM:sdkws.GroupCancelMutedTips(GroupCancelMutedNotification 1515) + * 对应自己的类:GroupCancelMutedNotification + * 场景:群主 / 管理员取消全群禁言,全员广播 + */ + GROUP_CANCEL_MUTED(1515, "全群取消禁言", true, false), + /** + * 对应 OpenIM:sdkws.GroupMemberInfoSetTips(GroupMemberInfoSetNotification 1516,窄化到 displayUserName) + * 对应自己的类:GroupMemberNicknameUpdateNotification + * 场景:成员修改自己在群里的昵称,在线成员同步对应 member + */ + GROUP_MEMBER_NICKNAME_UPDATE(1516, "成员昵称变更", false, false), + /** + * 对应 OpenIM:GroupMemberSetToAdminNotification 1517 + * 对应自己的类:GroupAdminAddNotification + * 场景:群主设置管理员,全员广播;前端 updateMembersRole 把对应成员 role 提升为 ADMIN + */ + GROUP_ADMIN_ADD(1517, "添加管理员", true, false), + /** + * 对应 OpenIM:GroupMemberSetToOrdinaryUserNotification 1518 + * 对应自己的类:GroupAdminRemoveNotification + * 场景:群主撤销管理员,全员广播;前端 updateMembersRole 把对应成员 role 降级为 NORMAL + */ + GROUP_ADMIN_REMOVE(1518, "撤销管理员", true, false), + /** + * 对应 OpenIM:sdkws.GroupInfoSetAnnouncementTips(GroupInfoSetAnnouncementNotification 1519) + * 对应自己的类:GroupNoticeUpdateNotification + * 场景:群主修改群公告后全员广播 + */ + GROUP_NOTICE_UPDATE(1519, "群公告变更", true, false), + /** + * 对应 OpenIM:sdkws.GroupInfoSetNameTips(GroupInfoSetNameNotification 1520) + * 对应自己的类:GroupNameUpdateNotification + * 场景:群主修改群名后全员广播 + */ + GROUP_NAME_UPDATE(1520, "群名变更", true, false), + + // 1530+ 我们独有扩展段(OpenIM 1500-1520 段位无对应物) + /** + * 对应 OpenIM:无直接对应(OpenIM 走 ConversationChangeNotification 1300 单聊路径) + * 对应自己的类:GroupMemberSettingUpdateNotification + * 场景:用户改自己的群免打扰 / 群备注,仅推该用户其他在线终端做多端同步 + */ + GROUP_MEMBER_SETTING_UPDATE(1530, "群成员个人设置变更", false, false), + /** + * 对应 OpenIM:无(OpenIM 无群消息置顶功能,自有扩展) + * 对应自己的类:GroupMessagePinNotification + * 场景:群主 / 管理员置顶一条群消息,全员广播;payload 直接带消息对象,前端把 message push 进 group.pinnedMessages + */ + GROUP_MESSAGE_PIN(1531, "群消息置顶", true, false), + /** + * 对应 OpenIM:无(OpenIM 无群消息置顶功能,自有扩展) + * 对应自己的类:GroupMessageUnpinNotification + * 场景:群主 / 管理员取消置顶,全员广播;前端按 messageId 从 group.pinnedMessages 移除 + */ + GROUP_MESSAGE_UNPIN(1532, "群消息取消置顶", true, false), + /** + * 对应 OpenIM:无(OpenIM 无群封禁概念,自有扩展) + * 对应自己的类:GroupBannedNotification + * 场景:管理后台封禁 / 解封群,全员广播;前端按 banned 字段切换输入栏封禁覆盖层 + */ + GROUP_BANNED(1533, "群封禁变更", true, false); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImContentTypeEnum::getType).toArray(Integer[]::new); + + private static final Set FRIEND_NOTIFICATION_TYPES = CollUtil.newHashSet( + FRIEND_REQUEST_APPROVED.type, + FRIEND_REQUEST_REJECTED.type, + FRIEND_REQUEST_RECEIVED.type, + FRIEND_ADD.type, + FRIEND_DELETE.type, + FRIEND_BLOCK.type, + FRIEND_UNBLOCK.type, + FRIEND_INFO_UPDATED.type, + FRIEND_UPDATE.type); + + private static final Set GROUP_REQUEST_NOTIFICATION_TYPES = CollUtil.newHashSet( + GROUP_REQUEST_RECEIVED.type, + GROUP_REQUEST_APPROVED.type, + GROUP_REQUEST_REJECTED.type); + + private static final Set RTC_NOTIFICATION_TYPES = CollUtil.newHashSet( + RTC_CALL.type, + RTC_PARTICIPANT_CONNECTED.type, + RTC_PARTICIPANT_DISCONNECTED.type); + + /** + * 类型 + */ + private final Integer type; + /** + * 名字 + */ + private final String name; + /** + * 是否入库 + *

+ * true:插入 im_xxx_message 消息表,离线 pull 能拉到; + * false:仅 WebSocket 推送,离线丢弃;状态由专用存储维护(如 READ 走 Redis 游标) + */ + private final boolean persistent; + /** + * 是不是用户聊天消息(normal vs event 二分) + *

+ * true:用户主动发的聊天消息,计入会话未读数(接收方非激活会话时 unreadCount + 1);用户发送入口仅允许这类; + * false:系统事件 / 信号 / 提示,不参与未读计数 + */ + private final boolean normal; + + @Override + public Integer[] array() { + return ARRAYS; + } + + /** + * 校验 type 已注册,并返回对应枚举;未注册立刻抛异常,避免新增 type 时漏配 persistent / normal 属性 + * + * @param type 消息类型 + * @return 枚举实例 + */ + public static ImContentTypeEnum validate(Integer type) { + ImContentTypeEnum result = ArrayUtil.firstMatch(item -> item.type.equals(type), values()); + Assert.notNull(result, "未注册的消息类型 type={}", type); + return result; + } + + /** + * 判断是否为好友通知 + */ + public static boolean isFriendNotification(Integer type) { + return type != null && FRIEND_NOTIFICATION_TYPES.contains(type); + } + + /** + * 判断是否为群申请定向通知 + */ + public static boolean isGroupRequestNotification(Integer type) { + return type != null && GROUP_REQUEST_NOTIFICATION_TYPES.contains(type); + } + + /** + * 判断是否为通话信令通知 + */ + public static boolean isRtcNotification(Integer type) { + return type != null && RTC_NOTIFICATION_TYPES.contains(type); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImConversationTypeEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImConversationTypeEnum.java new file mode 100644 index 0000000000..eb3c0538cd --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImConversationTypeEnum.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.im.enums; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Objects; + +/** + * IM 会话类型枚举 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImConversationTypeEnum implements ArrayValuable { + + NONE(0, "无会话"), // 无会话 + PRIVATE(1, "私聊"), // 私聊 + GROUP(2, "群聊"), // 群聊 + CHANNEL(3, "频道"); // 频道 / 公众号 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImConversationTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isNone(Integer type) { + return Objects.equals(NONE.type, type); + } + + public static boolean isPrivate(Integer type) { + return Objects.equals(PRIVATE.type, type); + } + + public static boolean isGroup(Integer type) { + return Objects.equals(GROUP.type, type); + } + + public static boolean isChannel(Integer type) { + return Objects.equals(CHANNEL.type, type); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/channel/ImChannelMaterialTypeEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/channel/ImChannelMaterialTypeEnum.java new file mode 100644 index 0000000000..55982c7529 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/channel/ImChannelMaterialTypeEnum.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.enums.channel; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IM 频道素材内容类型枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum ImChannelMaterialTypeEnum implements ArrayValuable { + + /** + * 站内富文本;点击素材在客户端内置详情页拉 content 渲染 + */ + CONTENT(1, "站内富文本"), + /** + * 外链;点击素材跳 url 打开浏览器 + */ + LINK(2, "外链"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImChannelMaterialTypeEnum::getType).toArray(Integer[]::new); + + private final Integer type; + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendAddSourceEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendAddSourceEnum.java new file mode 100644 index 0000000000..55ad7ac00e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendAddSourceEnum.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.im.enums.friend; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * IM 好友添加来源枚举 + *

+ * 由发起方调用 apply 接口时传入;同意后同步写入 im_friend.add_source(双向) + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImFriendAddSourceEnum implements ArrayValuable { + + SEARCH(1, "搜索"), // FriendAddDialog 搜索流程 + GROUP(2, "群聊"), // 群成员主页 → UserInfo「加为好友」入口 + QR_CODE(3, "扫码"), // TODO @芋艿:后续实现扫码加好友 + CARD(4, "名片"); // TODO @芋艿:后续实现通过名片加好友,类似微信的「扫一扫 - 名片」功能,或者「通讯录 - 推荐好友」功能 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImFriendAddSourceEnum::getSource).toArray(Integer[]::new); + + /** + * 来源 + */ + private final Integer source; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendRequestHandleResultEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendRequestHandleResultEnum.java new file mode 100644 index 0000000000..d5d2bf8a18 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendRequestHandleResultEnum.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.im.enums.friend; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Objects; + +/** + * IM 好友申请处理结果枚举 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImFriendRequestHandleResultEnum implements ArrayValuable { + + UNHANDLED(0, "未处理"), + AGREED(1, "同意"), + REFUSED(2, "拒绝"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImFriendRequestHandleResultEnum::getResult).toArray(Integer[]::new); + + /** + * 结果 + */ + private final Integer result; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + /** + * 判断申请是否还未处理 + */ + public static boolean isUnhandled(Integer result) { + return Objects.equals(UNHANDLED.result, result); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendStateEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendStateEnum.java new file mode 100644 index 0000000000..888f6a9dc4 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/friend/ImFriendStateEnum.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.im.enums.friend; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Objects; + +/** + * IM 好友关系状态(合并「是否好友」+「是否拉黑」两态) + *

+ * 用 state 而非 status:避免和 ImFriendDO 的 status 物理字段(CommonStatusEnum:ENABLE / DISABLE)混淆。 + *

+ * 用于 ImFriendService.getFriendState 返回值与 FRIEND_STATE 缓存值:私聊发送热点路径下,sender 调缓存即可同时判定「能否发」和「是否屏蔽接收方」 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImFriendStateEnum implements ArrayValuable { + + NONE(0, "非好友 / 已删除"), + FRIEND(1, "好友"), + BLOCKED(2, "好友且已被对方拉黑"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImFriendStateEnum::getState).toArray(Integer[]::new); + + /** + * 状态值 + */ + private final Integer state; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isNone(Integer state) { + return Objects.equals(NONE.state, state); + } + + public static boolean isFriend(Integer state) { + return Objects.equals(FRIEND.state, state); + } + + public static boolean isBlocked(Integer state) { + return Objects.equals(BLOCKED.state, state); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupAddSourceEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupAddSourceEnum.java new file mode 100644 index 0000000000..d12e098c23 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupAddSourceEnum.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.im.enums.group; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * IM 加入群聊来源枚举 + *

+ * 由发起方在申请 / 邀请时传入;同意后同步写入 ImGroupMemberDO 的 addSource 字段 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImGroupAddSourceEnum implements ArrayValuable { + + SEARCH(1, "搜索"), // TODO @芋艿:SEARCH 暂未实现,原因 - 搜群入口尚未开发 + INVITE(2, "邀请"), + QR_CODE(3, "扫码"), // TODO @芋艿:QR_CODE 暂未实现,原因 - 群二维码扫码进群入口尚未开发 + SHARE_LINK(4, "分享链接"); // TODO @芋艿:SHARE_LINK 暂未实现,原因 - 群分享链接进群入口尚未开发 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImGroupAddSourceEnum::getSource).toArray(Integer[]::new); + + /** + * 来源 + */ + private final Integer source; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupMemberRoleEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupMemberRoleEnum.java new file mode 100644 index 0000000000..48a4e790fa --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupMemberRoleEnum.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.im.enums.group; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Objects; + +/** + * IM 群成员角色枚举 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImGroupMemberRoleEnum implements ArrayValuable { + + OWNER(1, "群主"), + ADMIN(2, "管理员"), + NORMAL(3, "普通成员"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImGroupMemberRoleEnum::getRole).toArray(Integer[]::new); + + /** + * 角色 + */ + private final Integer role; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isOwner(Integer role) { + return Objects.equals(OWNER.role, role); + } + + public static boolean isAdmin(Integer role) { + return Objects.equals(ADMIN.role, role); + } + + public static boolean isOwnerOrAdmin(Integer role) { + return isOwner(role) || isAdmin(role); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupRequestHandleResultEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupRequestHandleResultEnum.java new file mode 100644 index 0000000000..1be6df5338 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/group/ImGroupRequestHandleResultEnum.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.im.enums.group; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Objects; + +/** + * IM 加群申请处理结果枚举 + *

+ * 取值与 {@link cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum} 平行,便于复用心智模型 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImGroupRequestHandleResultEnum implements ArrayValuable { + + UNHANDLED(0, "未处理"), + AGREED(1, "同意"), + REFUSED(2, "拒绝"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImGroupRequestHandleResultEnum::getResult).toArray(Integer[]::new); + + /** + * 结果 + */ + private final Integer result; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + /** + * 判断申请是否还未处理 + */ + public static boolean isUnhandled(Integer result) { + return Objects.equals(UNHANDLED.result, result); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageReceiptStatusEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageReceiptStatusEnum.java new file mode 100644 index 0000000000..41e3cf086e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageReceiptStatusEnum.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.im.enums.message; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IM 消息回执状态枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum ImMessageReceiptStatusEnum implements ArrayValuable { + + NO_RECEIPT(0, "不需要回执"), + PENDING(1, "待完成"), + DONE(2, "已完成"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageReceiptStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态 + */ + private final Integer status; + + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageStatusEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageStatusEnum.java new file mode 100644 index 0000000000..537f95c06a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageStatusEnum.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.im.enums.message; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * IM 消息状态枚举 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImMessageStatusEnum implements ArrayValuable { + + SENDING(-1, "发送中"), // 仅客户端使用 + NORMAL(0, "正常"), // 私聊 / 群聊的正常(初始)状态 + RECALL(2, "已撤回"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态 + */ + private final Integer status; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} \ No newline at end of file diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallEndReasonEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallEndReasonEnum.java new file mode 100644 index 0000000000..51099b4f46 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallEndReasonEnum.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.im.enums.rtc; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * IM 通话结束原因枚举;落历史消息时计算 content 文案 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImRtcCallEndReasonEnum implements ArrayValuable { + + HANGUP(1, "通话结束"), // 接通后任一方主动挂断 + REJECT(2, "已拒绝"), // 被叫接通前点拒接 + CANCEL(3, "已取消"), // 主叫接通前主动取消 + NO_ANSWER(4, "无人接听"), // 振铃超时未接通;由参与者超时 Job 触发 + BUSY(5, "对方正忙"), // 私聊呼叫时对方在另一通话 + ERROR(9, "通话异常"); // 网络中断、设备失败等 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImRtcCallEndReasonEnum::getReason).toArray(Integer[]::new); + + /** + * 原因值 + */ + private final Integer reason; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallMediaTypeEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallMediaTypeEnum.java new file mode 100644 index 0000000000..9f767b90dc --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallMediaTypeEnum.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.im.enums.rtc; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Objects; + +/** + * IM 通话媒体类型枚举 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImRtcCallMediaTypeEnum implements ArrayValuable { + + VOICE(1, "语音"), // 仅音频 + VIDEO(2, "视频"); // 音频 + 视频 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImRtcCallMediaTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isVoice(Integer type) { + return Objects.equals(VOICE.type, type); + } + + public static boolean isVideo(Integer type) { + return Objects.equals(VIDEO.type, type); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallStatusEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallStatusEnum.java new file mode 100644 index 0000000000..42b3cdaa43 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcCallStatusEnum.java @@ -0,0 +1,60 @@ +package cn.iocoder.yudao.module.im.enums.rtc; + +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * IM 通话状态枚举(主表 status) + *

+ * 状态机:CREATED → RUNNING → ENDED;CREATED 直接到 ENDED 表示无人接听 / 主叫取消 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImRtcCallStatusEnum implements ArrayValuable { + + CREATED(10, "创建"), // 通话已创建;私聊:等被叫接听;群聊:发起人已进房,等其他人加入 + RUNNING(20, "进行中"), // 第一个非发起人接通后进入 + ENDED(30, "已结束"); // 任一方挂断 / 主叫 cancel / Webhook 兜底 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImRtcCallStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 活跃状态集合;通话未结束(CREATED / RUNNING) + */ + public static final List ACTIVE_STATUSES = ListUtil.of(CREATED.getStatus(), RUNNING.getStatus()); + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isCreated(Integer status) { + return Objects.equals(CREATED.status, status); + } + + public static boolean isRunning(Integer status) { + return Objects.equals(RUNNING.status, status); + } + + public static boolean isEnded(Integer status) { + return Objects.equals(ENDED.status, status); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantRoleEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantRoleEnum.java new file mode 100644 index 0000000000..125c5f0b40 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantRoleEnum.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.im.enums.rtc; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * IM 通话参与者角色枚举 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImRtcParticipantRoleEnum implements ArrayValuable { + + INVITER(1, "发起人"), + INVITEE(2, "被邀请者"), + JOINER(3, "主动加入者"); // 仅群通话场景:旁观者点胶囊条加入已有通话 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImRtcParticipantRoleEnum::getRole).toArray(Integer[]::new); + + /** + * 角色 + */ + private final Integer role; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java new file mode 100644 index 0000000000..c4af81ba6e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.im.enums.rtc; + +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * IM 通话参与者状态枚举(明细表 status) + *

+ * 终态闭合:通话 ENDED 时所有明细 status 必属 {LEFT / REJECTED / NO_ANSWER} + *

+ * 1、INVITING → JOINED → LEFT(接通后挂断 / 离开); + * 2、INVITING → REJECTED(接通前点拒接); + * 3、INVITING → NO_ANSWER(通话结束仍未应答) + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ImRtcParticipantStatusEnum implements ArrayValuable { + + INVITING(10, "邀请中"), // 已发出 invite,等被叫响应 + JOINED(20, "已加入"), // 已 connect 进 LiveKit 房间 + REJECTED(30, "已拒绝"), // 接通前点拒接 + NO_ANSWER(40, "未应答"), // 通话已结束仍未应答;endSession 批量改 + LEFT(50, "已离开"); // 接通后挂断 / Webhook 兜底 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImRtcParticipantStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 活跃状态集合;尚未离开通话(INVITING / JOINED) + */ + public static final List ACTIVE_STATUSES = ListUtil.of(INVITING.getStatus(), JOINED.getStatus()); + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isInviting(Integer status) { + return Objects.equals(INVITING.status, status); + } + + public static boolean isJoined(Integer status) { + return Objects.equals(JOINED.status, status); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/config/ImProperties.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/config/ImProperties.java new file mode 100644 index 0000000000..8a8192f3e5 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/config/ImProperties.java @@ -0,0 +1,195 @@ +package cn.iocoder.yudao.module.im.framework.config; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +/** + * IM 模块全局配置 + *

+ * 各子模块用嵌套 inner class 区分(friend / group / face / message / rtc 等), + * yaml 路径保持 yudao.im.{module}.{key} 与原有部署保持兼容 + * + * @author 芋道源码 + */ +@Component +@ConfigurationProperties(prefix = "yudao.im") +@Validated +@Data +public class ImProperties { + + private Friend friend = new Friend(); + + private Group group = new Group(); + + private Face face = new Face(); + + private Message message = new Message(); + + @Valid + private Rtc rtc = new Rtc(); + + /** + * 好友模块配置 + */ + @Data + public static class Friend { + + /** + * 是否自动通过所有好友申请(全局开关) + *

+ * 默认 false,普通用户必须走申请-审批流程;开启后所有用户的好友申请会立即同意,主要用于全员开放型 IM 部署。 + * 如需细化到「仅特定用户自动通过」(如机器人 / AI 账号),请在 system 用户表加字段,并在 applyFriend 内按用户级开关短路 + */ + private boolean autoAccept = false; + + } + + /** + * 群模块配置 + */ + @Data + public static class Group { + + /** + * 群最大成员人数 + */ + private int maxMember = 500; + + /** + * 单群管理员人数上限 + */ + private int adminMaxCount = 3; + + /** + * 单群置顶消息条数上限 + */ + private int pinMaxCount = 5; + + } + + /** + * 表情模块配置 + */ + @Data + public static class Face { + + /** + * 个人表情数量上限 + */ + private int userItemMaxCount = 200; + + } + + /** + * 消息模块配置 + */ + @Data + public static class Message { + + /** + * 是否启用私聊已读功能 + *

+ * 关闭后:private read 接口直接抛业务异常;服务端不再下发私聊 READ / RECEIPT 事件信号。 + * 客户端侧需镜像此开关,隐藏私聊气泡的「已读 / 未读」标签 + */ + private boolean privateReadEnabled = true; + + /** + * 是否启用群聊已读功能(含群消息回执) + *

+ * 关闭后:group read 接口直接抛业务异常;服务端不再下发群 READ / RECEIPT 事件信号; + * 群消息回执 receiptStatus 一并停用(即使发送方传 receipt=true 也强制落 NO_RECEIPT,不再算「N 人已读」)。 + * 客户端侧需镜像此开关,隐藏群回执 popover 与「发送回执消息」入口 + */ + private boolean groupReadEnabled = true; + + /** + * pull 最大拉取数量 + */ + private int maxPullSize = 1000; + + /** + * 消息撤回时间限制(分钟) + */ + private int recallTimeoutMinutes = 5; + + /** + * 私聊离线消息最大拉取天数 + *

+ * 客户端通过 pull 接口增量拉取私聊离线消息时,仅返回最近 N 天内产生的消息, + * 超过该窗口的老消息不再主动推送(可通过历史消息接口按需倒翻)。 + */ + private int privatePullMaxDays = 30; + + /** + * 群聊离线消息最大拉取天数 + *

+ * 客户端通过 pull 接口增量拉取群聊离线消息时,仅返回最近 N 天内产生的消息; + * 退群前消息的补齐也以该窗口为基准(早于窗口的退群群不再扫描),避免老用户首次 + * 拉取时对历史退群群做大量查询。 + */ + private int groupPullMaxDays = 30; + + } + + /** + * 实时通话模块配置 + *

+ * 媒体走 LiveKit SFU;后端只签 Token + 通过 IM 长连接推送来电 / 接通 / 结束三种信令。 + * 关闭后所有 RTC 接口直接抛 RTC_NOT_ENABLED;前端可据此隐藏通话按钮。 + */ + @Data + public static class Rtc { + + /** + * 是否启用实时通话功能 + */ + private boolean enabled = true; + + /** + * LiveKit Server WebSocket 地址;客户端 connect 时使用,通常 ws://host:7880 或 wss://host + */ + @NotBlank(message = "LiveKit URL 不能为空") + private String livekitUrl = "ws://127.0.0.1:7880"; + + /** + * LiveKit API Key + */ + @NotBlank(message = "LiveKit API Key 不能为空") + private String apiKey = "devkey"; + + /** + * LiveKit API Secret;生产必须改为强随机值 + */ + @NotBlank(message = "LiveKit API Secret 不能为空") + @Size(min = 32, message = "LiveKit API Secret 长度需 ≥ 32 位") + private String apiSecret = "secret-poc-key-min-32-chars-required-here"; + + /** + * 单次签发的 Token 有效期(小时) + */ + private int tokenTtlHours = 6; + + /** + * 群通话最大同时在房成员数;超过 invite 直接拒绝 + */ + private int groupMaxParticipants = 16; + + /** + * 僵尸通话清理阈值(分钟);通话创建超过此值仍未结束才纳入扫描,避开「刚发起还在响铃」的合理零人态 + */ + private int cleanupZombieThresholdMinutes = 5; + + /** + * 振铃超时阈值(分钟);被叫 INVITING 超过此值未接通 → 标 NO_ANSWER + 推 RTC_CALL(REJECT) 让 banner 收敛 + */ + private int inviteTimeoutMinutes = 1; + + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/package-info.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/package-info.java new file mode 100644 index 0000000000..61b6dae9e8 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 erp 模块的 framework 封装 + * + * @author 芋道源码 + */ +package cn.iocoder.yudao.module.im.framework; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/config/ImRtcConfiguration.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/config/ImRtcConfiguration.java new file mode 100644 index 0000000000..12bf2e1128 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/config/ImRtcConfiguration.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.im.framework.rtc.config; + +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.framework.rtc.core.LiveKitClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * im 模块的 RTC 组件的 Configuration + * + * @author 芋道源码 + */ +@Configuration(proxyBeanMethods = false) +public class ImRtcConfiguration { + + /** + * LiveKit 客户端 + */ + @Bean + public LiveKitClient liveKitClient(ImProperties imProperties) { + return new LiveKitClient(imProperties); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java new file mode 100644 index 0000000000..9aea1839c2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java @@ -0,0 +1,244 @@ +package cn.iocoder.yudao.module.im.framework.rtc.core; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONUtil; +import cn.hutool.jwt.JWT; +import cn.hutool.jwt.signers.JWTSignerUtil; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import lombok.extern.slf4j.Slf4j; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * LiveKit 客户端 + *

+ * 厂商绑定层封装:Token 签发(join / admin)、Server API(DeleteRoom / ListParticipants)、identity 拼接 / 解析 + *

+ * 由 {@link cn.iocoder.yudao.module.im.framework.rtc.config.ImRtcConfiguration} 注册为 Bean,本类不带 {@code @Component} + * + * @author 芋道源码 + */ +@Slf4j +public class LiveKitClient { + + /** + * Twirp 端点路径 + */ + private static final String TWIRP_DELETE_ROOM = "/twirp/livekit.RoomService/DeleteRoom"; + private static final String TWIRP_LIST_PARTICIPANTS = "/twirp/livekit.RoomService/ListParticipants"; + + /** + * 管理 Token 有效期;Server API 一次调用即弃,10 秒足够 + */ + private static final Duration ADMIN_TOKEN_TTL = Duration.ofSeconds(10); + + /** + * Server API HTTP 调用的超时上限;超时后直接报错,避免 LiveKit 异常 / 网络抖动时业务长时间阻塞 + */ + private static final int SERVER_API_TIMEOUT_MS = 10_000; + + private final ImProperties imProperties; + + /** + * @param imProperties IM 全局配置;从中读 livekitUrl / apiKey / apiSecret / tokenTtlHours + */ + public LiveKitClient(ImProperties imProperties) { + this.imProperties = imProperties; + } + + /** + * 签发客户端进房 Token;有效期从 {@link ImProperties.Rtc#getTokenTtlHours()} 读取 + * + * @param identity 用户唯一标识;写入 sub claim;同 identity 重连会踢前一个连接 + * @param displayName 客户端展示名;可空 + * @param room 房间名 + * @return JWT 字符串 + */ + public String signJoinToken(String identity, String displayName, String room) { + Assert.notBlank(identity, "identity 不可为空"); + Assert.notBlank(room, "room 不可为空"); + ImProperties.Rtc cfg = imProperties.getRtc(); + + // video claim:限定客户端能在该房间内做什么 + Map video = new HashMap<>(); + video.put("roomJoin", true); // 允许加入房间 + video.put("room", room); // 限定只能加入这个房间 + video.put("canPublish", true); // 允许发布媒体(推流) + video.put("canSubscribe", true); // 允许订阅媒体(拉流) + video.put("canPublishData", true); // 允许发送 data channel 消息 + + long nowSec = Instant.now().getEpochSecond(); + long ttlSec = Duration.ofHours(cfg.getTokenTtlHours()).getSeconds(); + JWT jwt = JWT.create() + .setIssuer(cfg.getApiKey()) + .setSubject(identity) + .setNotBefore(new Date(nowSec * 1000)) + .setExpiresAt(new Date((nowSec + ttlSec) * 1000)) + .setPayload("video", video) + .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes(StandardCharsets.UTF_8))); + if (StrUtil.isNotEmpty(displayName)) { + jwt.setPayload("name", displayName); + } + return jwt.sign(); + } + + /** + * 调用 LiveKit Server API 删除房间:用于通话结束时,强制断开异常残留客户端 + * + * @param room 房间名 + */ + @SuppressWarnings("EmptyTryBlock") + public void deleteRoom(String room) { + try (HttpResponse ignored = postTwirp(TWIRP_DELETE_ROOM, room)) { + // 状态码不区分;调用方失败即兜底 + } + } + + /** + * 调用 LiveKit Server API 查询某房间内的参与者数量:用于定时扫描僵尸通话 + *

+ * 房间不存在 LiveKit 返回 404,视同 0 人 + * + * @param room 房间名 + * @return 参与者数量;HTTP 失败返回 -1 + */ + public int listParticipants(String room) { + try (HttpResponse response = postTwirp(TWIRP_LIST_PARTICIPANTS, room)) { + if (response.getStatus() == 404) { + return 0; + } + if (!response.isOk()) { + log.warn("[listParticipants][LiveKit 返回非 2xx status={} room={} body={}]", + response.getStatus(), room, response.body()); + return -1; + } + JSONArray participants = JSONUtil.parseObj(response.body()).getJSONArray("participants"); + return CollUtil.size(participants); + } + } + + /** + * 拼接 LiveKit identity;当前单端 = userId 字符串 + *

+ * 多端扩展时改 {@code userId + "#" + terminal} 格式,调用方无需改 + * + * @param userId 用户编号 + * @return identity 字符串 + */ + public String buildIdentity(Long userId) { + Assert.notNull(userId, "userId 不可为空"); + return String.valueOf(userId); + } + + /** + * 从 LiveKit identity 解析业务 userId + *

+ * 当前 identity 直接是 userId 字符串;预留 {@code userId#terminal} 多端格式 + * + * @param identity LiveKit identity + * @return 用户编号;解析失败返回 null + */ + public Long parseUserId(String identity) { + if (StrUtil.isBlank(identity)) { + return null; + } + int sep = identity.indexOf('#'); + String idPart = sep >= 0 ? identity.substring(0, sep) : identity; + try { + return Long.parseLong(idPart); + } catch (NumberFormatException e) { + return null; + } + } + + /** + * 校验 LiveKit Webhook 签名;流程参见 + * webhook 文档 + *

+ * 校验两步: + * 1.1 JWT HS256 签名验证;密钥使用 LiveKit API Secret + * 1.2 body 的 sha256 与 JWT 内 claim 一致;防止抓到 token 后篡改 body + * + * @param authHeader 请求头 Authorization 原值(含 "Bearer " 前缀) + * @param rawBody 请求原始 body + * @return 是否通过;签名异常一律视为不通过 + */ + public boolean verifyWebhookSignature(String authHeader, String rawBody) { + if (StrUtil.isBlank(authHeader)) { + return false; + } + String token = StrUtil.removePrefix(authHeader, "Bearer ").trim(); + ImProperties.Rtc cfg = imProperties.getRtc(); + try { + JWT jwt = JWT.of(token); + // JWT HS256 签名验证 + if (!jwt.setKey(cfg.getApiSecret().getBytes(StandardCharsets.UTF_8)).verify()) { + return false; + } + // body sha256 一致性校验 + Object expectedSha = jwt.getPayload("sha256"); + if (expectedSha == null) { + return false; + } + // 计算 body 的 sha256,并与 JWT 内 claim 对比 + String actualSha = Base64.encode(DigestUtil.sha256(rawBody)); + return Objects.equals(expectedSha.toString(), actualSha); + } catch (Exception e) { + log.warn("[verifyWebhookSignature][签名解析异常 bodyLength={}]", + rawBody == null ? 0 : rawBody.length(), e); + return false; + } + } + + /** + * 签发管理 Token;用于调 Server API(DeleteRoom / ListParticipants / RemoveParticipant 等) + * + * @return JWT 字符串 + */ + private String signAdminToken() { + ImProperties.Rtc cfg = imProperties.getRtc(); + // roomAdmin claim 给管理类 API 必备 + long nowSec = Instant.now().getEpochSecond(); + return JWT.create() + .setIssuer(cfg.getApiKey()) + .setNotBefore(new Date(nowSec * 1000)) + .setExpiresAt(new Date((nowSec + ADMIN_TOKEN_TTL.getSeconds()) * 1000)) + .setPayload("video", MapUtil.of("roomAdmin", true)) + .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes(StandardCharsets.UTF_8))) + .sign(); + } + + /** + * Twirp 协议 POST 调用;统一处理 ws→http 协议切换、签 admin token、Bearer 头、JSON body、超时 + * + * @param path Twirp 端点路径,例如 {@code /twirp/livekit.RoomService/DeleteRoom} + * @param room 房间名;写入 JSON body 的 room 字段 + * @return HTTP 响应;调用方负责 close 与状态码判断 + */ + private HttpResponse postTwirp(String path, String room) { + Assert.notBlank(room, "room 不可为空"); + String token = signAdminToken(); + return HttpRequest.post(HttpUtils.wsUrlToHttp(imProperties.getRtc().getLivekitUrl()) + path) + .header("Authorization", "Bearer " + token) + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(MapUtil.of("room", room))) + .timeout(SERVER_API_TIMEOUT_MS) + .execute(); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitWebhookEventDTO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitWebhookEventDTO.java new file mode 100644 index 0000000000..bb535017a2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitWebhookEventDTO.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.module.im.framework.rtc.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * LiveKit Webhook 事件载荷;只反序列化我们关心的字段,其余忽略 + *

+ * 文档参考:webhook 文档 + * + * @author 芋道源码 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class LiveKitWebhookEventDTO { + + /** + * 房间创建:首个 participant 加入时触发 + */ + public static final String EVENT_ROOM_STARTED = "room_started"; + /** + * 房间销毁:最后一个 participant 离开 / 显式 DeleteRoom 后触发;用于业务侧兜底关房 + */ + public static final String EVENT_ROOM_FINISHED = "room_finished"; + /** + * 参与者加入房间 + */ + public static final String EVENT_PARTICIPANT_JOINED = "participant_joined"; + /** + * 参与者离开房间:关 tab / 网络断 / 显式 disconnect 都会触发;用于业务侧兜底清理 + */ + public static final String EVENT_PARTICIPANT_LEFT = "participant_left"; + /** + * 参与者发布媒体轨道(摄像头 / 麦克风 / 屏幕共享) + */ + public static final String EVENT_TRACK_PUBLISHED = "track_published"; + + /** + * 事件 id:用于幂等去重 + */ + private String id; + + /** + * 事件类型;取值参见本类 EVENT_* 常量 + */ + private String event; + + /** + * 房间元信息:room_started / room_finished 必填 + */ + private RoomInfo room; + + /** + * 参与者元信息:participant_* 事件必填 + */ + private ParticipantInfo participant; + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class RoomInfo { + + private String sid; + private String name; + private Long creationTime; + private Integer numParticipants; + + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ParticipantInfo { + + private String sid; + /** + * 用户身份:签 token 时写入 userId 字符串,后续支持多端会拼成 userId#terminal + */ + private String identity; + private String name; + private Long joinedAt; + + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/web/config/ImWebConfiguration.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/web/config/ImWebConfiguration.java new file mode 100644 index 0000000000..7e2b2ed5cc --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/web/config/ImWebConfiguration.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.framework.web.config; + +import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * im 模块的 web 组件的 Configuration + */ +@Configuration(proxyBeanMethods = false) +public class ImWebConfiguration { + + /** + * im 模块的 API 分组 + */ + @Bean + public GroupedOpenApi imGroupedOpenApi() { + return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("im"); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/web/package-info.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/web/package-info.java new file mode 100644 index 0000000000..b21d0fe03a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/web/package-info.java @@ -0,0 +1,4 @@ +/** + * IM 模块的 web 配置 + */ +package cn.iocoder.yudao.module.im.framework.web; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/job/rtc/ImRtcCallCleanupJob.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/job/rtc/ImRtcCallCleanupJob.java new file mode 100644 index 0000000000..664e3cfeab --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/job/rtc/ImRtcCallCleanupJob.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.im.job.rtc; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.rtc.ImRtcCallService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 僵尸通话清理 Job:兜底 LiveKit Webhook 丢失 / 客户端异常关闭等未调 leave 的场景 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class ImRtcCallCleanupJob implements JobHandler { + + @Resource + private ImRtcCallService rtcCallService; + + @Resource + private ImProperties imProperties; + + /** + * 执行清理 + * + * @param param 阈值(分钟);为空 / 非法走 {@link ImProperties.Rtc#getCleanupZombieThresholdMinutes()} 默认值 + * @return 清理数量描述 + */ + @Override + @TenantJob + public String execute(String param) { + int thresholdMinutes = resolveThresholdMinutes(param); + int cleaned = rtcCallService.cleanupZombieCalls(thresholdMinutes); + log.info("[execute][清理僵尸通话数量 ({}) 个]", cleaned); + return String.format("清理僵尸通话 %s 个", cleaned); + } + + /** + * 解析 quartz param 为分钟阈值;非法 / 非正数走配置默认值 + * + * @param param quartz 调度入参字符串 + * @return 分钟阈值 + */ + private int resolveThresholdMinutes(String param) { + int defaultMinutes = imProperties.getRtc().getCleanupZombieThresholdMinutes(); + if (StrUtil.isBlank(param) || !NumberUtil.isInteger(param)) { + return defaultMinutes; + } + int minutes = Integer.parseInt(param); + return minutes > 0 ? minutes : defaultMinutes; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/job/rtc/ImRtcParticipantTimeoutJob.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/job/rtc/ImRtcParticipantTimeoutJob.java new file mode 100644 index 0000000000..f551dee0fe --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/job/rtc/ImRtcParticipantTimeoutJob.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.im.job.rtc; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.rtc.ImRtcCallService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 振铃超时 Job:扫 INVITING 超过阈值的参与者,单人粒度标 NO_ANSWER + 推 RTC_CALL(REJECT) 让前端 banner 收敛 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class ImRtcParticipantTimeoutJob implements JobHandler { + + @Resource + private ImRtcCallService rtcCallService; + + @Resource + private ImProperties imProperties; + + /** + * 执行超时扫描 + * + * @param param 阈值(分钟);为空 / 非法走 {@link ImProperties.Rtc#getInviteTimeoutMinutes()} 默认值 + * @return 超时处理数量描述 + */ + @Override + @TenantJob + public String execute(String param) { + int thresholdMinutes = resolveThresholdMinutes(param); + int timedOut = rtcCallService.timeoutInvitingParticipants(thresholdMinutes); + log.info("[execute][振铃超时参与者数量 ({}) 个]", timedOut); + return String.format("振铃超时 %s 个", timedOut); + } + + /** + * 解析 quartz param 为分钟阈值;非法 / 非正数走配置默认值 + * + * @param param quartz 调度入参字符串 + * @return 分钟阈值 + */ + private int resolveThresholdMinutes(String param) { + int defaultMinutes = imProperties.getRtc().getInviteTimeoutMinutes(); + if (StrUtil.isBlank(param) || !NumberUtil.isInteger(param)) { + return defaultMinutes; + } + int minutes = Integer.parseInt(param); + return minutes > 0 ? minutes : defaultMinutes; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java new file mode 100644 index 0000000000..13eeee6deb --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.im.mq.consumer.friend; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendInfoUpdatedNotification; +import cn.iocoder.yudao.module.system.api.message.user.AdminUserProfileUpdateMessage; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import java.util.List; + +/** + * 监听 system 模块的 {@link AdminUserProfileUpdateMessage} 消息,向「资料被改的人」的所有好友推送 FRIEND_INFO_UPDATED 通知 + * + * @author 芋道源码 + */ +@Slf4j +@Component +public class AdminUserProfileUpdateConsumer { + + @Resource + private ImFriendService friendService; + @Resource + private ImWebSocketService websocketService; + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true) + @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步;事务提交后触发,避免回滚误推幽灵通知 / Consumer 抢在 commit 前读旧值 + public void onMessage(AdminUserProfileUpdateMessage message) { + try { + log.info("[onMessage][消息内容({})]", message); + // 1. 过滤双向有效好友 + if (message == null || message.getUserId() == null) { + return; + } + Long userId = message.getUserId(); + List friends = friendService.getMutualEnableFriendList(userId); + if (CollUtil.isEmpty(friends)) { + return; + } + + // 2. 给每个好友的多端推 FRIEND_INFO_UPDATED;payload 里 operatorUserId / friendUserId 都是「资料被改的人」 + int successCount = 0; + for (ImFriendDO friend : friends) { + try { + FriendInfoUpdatedNotification payload = (FriendInfoUpdatedNotification) new FriendInfoUpdatedNotification() + .setOperatorUserId(userId).setFriendUserId(userId); + websocketService.sendNotificationAsync(friend.getFriendUserId(), + ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_INFO_UPDATED.getType(), payload); + successCount++; + } catch (Exception e) { + log.warn("[onMessage][userId({}) friendUserId({}) 推送失败]", + userId, friend.getFriendUserId(), e); + } + } + log.info("[onMessage][userId({}) 推送 FRIEND_INFO_UPDATED 给 {} 位好友]", userId, successCount); + } catch (Exception e) { + log.error("[onMessage][消息内容({}) 处理失败]", message, e); + } + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/package-info.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/package-info.java new file mode 100644 index 0000000000..9bf6bc368d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/package-info.java @@ -0,0 +1,8 @@ +/** + * im 模块,我们放即时通讯业务。 + * 例如说:单聊、群聊、消息收发、消息撤回、消息已读等等 + * + * 1. Controller URL:以 /im/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 im_ 开头,方便在数据库中区分 + */ +package cn.iocoder.yudao.module.im; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelMaterialService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelMaterialService.java new file mode 100644 index 0000000000..44fc204635 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelMaterialService.java @@ -0,0 +1,106 @@ +package cn.iocoder.yudao.module.im.service.channel; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * IM 频道素材 Service 接口 + * + * @author 芋道源码 + */ +public interface ImChannelMaterialService { + + // ==================== 用户端 ==================== + + /** + * 校验素材存在 + * + * @param id 素材编号 + * @return 素材 DO + */ + ImChannelMaterialDO validateMaterialExists(Long id); + + /** + * 按编号批量查询素材 + * + * @param ids 素材编号列表 + * @return 素材列表 + */ + List getMaterialList(Collection ids); + + /** + * 按编号批量查询素材 Map + * + * @param ids 素材编号列表 + * @return id -> 素材 Map + */ + default Map getMaterialMap(Collection ids) { + return convertMap(getMaterialList(ids), ImChannelMaterialDO::getId); + } + + /** + * 统计指定频道下的素材数量 + * + * @param channelId 频道编号 + * @return 数量 + */ + Long getMaterialCountByChannelId(Long channelId); + + // ==================== 管理后台 ==================== + + /** + * 按频道查询素材精简列表 + * + * @param channelId 频道编号 + * @return 素材列表 + */ + List getMaterialListByChannelId(Long channelId); + + /** + * 分页查询素材 + * + * @param reqVO 分页查询条件 + * @return 素材分页 + */ + PageResult getMaterialPage(ImChannelMaterialPageReqVO reqVO); + + /** + * 获取素材详情(含 content 富文本) + * + * @param id 素材编号 + * @return 素材 DO + */ + ImChannelMaterialDO getMaterial(Long id); + + /** + * 新增素材 + * + * @param reqVO 新增请求 + * @return 新增素材编号 + */ + Long createMaterial(@Valid ImChannelMaterialSaveReqVO reqVO); + + /** + * 修改素材 + * + * @param reqVO 修改请求 + */ + void updateMaterial(@Valid ImChannelMaterialSaveReqVO reqVO); + + /** + * 删除素材;素材已被推送过时拒绝,避免历史消息无法回查 + * + * @param id 素材编号 + */ + void deleteMaterial(Long id); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelMaterialServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelMaterialServiceImpl.java new file mode 100644 index 0000000000..ec1d8c3cf6 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelMaterialServiceImpl.java @@ -0,0 +1,113 @@ +package cn.iocoder.yudao.module.im.service.channel; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.material.ImChannelMaterialSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; +import cn.iocoder.yudao.module.im.dal.mysql.channel.ImChannelMaterialMapper; +import cn.iocoder.yudao.module.im.dal.mysql.message.ImChannelMessageMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.IM_CHANNEL_MATERIAL_NOT_EXISTS; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.IM_CHANNEL_MATERIAL_USED; + +/** + * IM 频道素材 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImChannelMaterialServiceImpl implements ImChannelMaterialService { + + @Resource + private ImChannelMaterialMapper channelMaterialMapper; + @Resource + private ImChannelService channelService; + @Resource + private ImChannelMessageMapper channelMessageMapper; + + // ==================== 用户端 ==================== + + @Override + public ImChannelMaterialDO validateMaterialExists(Long id) { + ImChannelMaterialDO material = channelMaterialMapper.selectById(id); + if (material == null) { + throw exception(IM_CHANNEL_MATERIAL_NOT_EXISTS); + } + return material; + } + + @Override + public List getMaterialList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return channelMaterialMapper.selectByIds(ids); + } + + @Override + public Long getMaterialCountByChannelId(Long channelId) { + return channelMaterialMapper.selectCountByChannelId(channelId); + } + + // ==================== 管理后台 ==================== + + @Override + public List getMaterialListByChannelId(Long channelId) { + return channelMaterialMapper.selectListByChannelId(channelId); + } + + @Override + public PageResult getMaterialPage(ImChannelMaterialPageReqVO reqVO) { + return channelMaterialMapper.selectPage(reqVO); + } + + @Override + public ImChannelMaterialDO getMaterial(Long id) { + return channelMaterialMapper.selectById(id); + } + + @Override + public Long createMaterial(ImChannelMaterialSaveReqVO reqVO) { + // 1. 校验所属频道存在 + channelService.validateChannelExists(reqVO.getChannelId()); + + // 2. 插入素材 + ImChannelMaterialDO material = BeanUtils.toBean(reqVO, ImChannelMaterialDO.class); + channelMaterialMapper.insert(material); + return material.getId(); + } + + @Override + public void updateMaterial(ImChannelMaterialSaveReqVO reqVO) { + // 1.1 校验存在 + validateMaterialExists(reqVO.getId()); + // 1.2 校验所属频道存在 + channelService.validateChannelExists(reqVO.getChannelId()); + + // 2. 更新素材 + ImChannelMaterialDO updateObj = BeanUtils.toBean(reqVO, ImChannelMaterialDO.class); + channelMaterialMapper.updateById(updateObj); + } + + @Override + public void deleteMaterial(Long id) { + validateMaterialExists(id); + // 防止删除素材导致历史 channel_message 反查不到内容 + if (channelMessageMapper.selectCountByMaterialId(id) > 0) { + throw exception(IM_CHANNEL_MATERIAL_USED); + } + channelMaterialMapper.deleteById(id); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelService.java new file mode 100644 index 0000000000..b318743b9c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelService.java @@ -0,0 +1,99 @@ +package cn.iocoder.yudao.module.im.service.channel; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel.ImChannelPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel.ImChannelSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * IM 频道 Service 接口 + * + * @author 芋道源码 + */ +public interface ImChannelService { + + // ==================== 用户端 ==================== + + /** + * 按状态查询频道列表,按 sort 升序 + * + * @param status 状态;对应 CommonStatusEnum + * @return 频道列表 + */ + List getChannelListByStatus(Integer status); + + /** + * 按编号批量查询频道 + * + * @param ids 频道编号列表 + * @return 频道列表 + */ + List getChannelList(Collection ids); + + /** + * 按编号批量查询频道 Map + * + * @param ids 频道编号列表 + * @return id -> 频道 Map + */ + default Map getChannelMap(Collection ids) { + return convertMap(getChannelList(ids), ImChannelDO::getId); + } + + /** + * 校验频道存在 + * + * @param id 频道编号 + * @return 频道 DO + */ + @SuppressWarnings("UnusedReturnValue") + ImChannelDO validateChannelExists(Long id); + + // ==================== 管理后台 ==================== + + /** + * 分页查询频道 + * + * @param reqVO 分页查询条件 + * @return 频道分页 + */ + PageResult getChannelPage(ImChannelPageReqVO reqVO); + + /** + * 获取频道详情 + * + * @param id 频道编号 + * @return 频道 DO + */ + ImChannelDO getChannel(Long id); + + /** + * 新增频道 + * + * @param reqVO 新增请求 + * @return 新增频道编号 + */ + Long createChannel(@Valid ImChannelSaveReqVO reqVO); + + /** + * 修改频道 + * + * @param reqVO 修改请求 + */ + void updateChannel(@Valid ImChannelSaveReqVO reqVO); + + /** + * 删除频道;频道下有素材或消息时拒绝 + * + * @param id 频道编号 + */ + void deleteChannel(Long id); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelServiceImpl.java new file mode 100644 index 0000000000..6df27e1fb9 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/channel/ImChannelServiceImpl.java @@ -0,0 +1,120 @@ +package cn.iocoder.yudao.module.im.service.channel; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel.ImChannelPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.channel.vo.channel.ImChannelSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; +import cn.iocoder.yudao.module.im.dal.mysql.channel.ImChannelMapper; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; + +/** + * IM 频道 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImChannelServiceImpl implements ImChannelService { + + @Resource + private ImChannelMapper channelMapper; + @Resource + @Lazy // 延迟加载,解决循环依赖 + private ImChannelMaterialService channelMaterialService; + + // ==================== 用户端 ==================== + + @Override + public List getChannelListByStatus(Integer status) { + return channelMapper.selectListByStatusOrderBySort(status); + } + + @Override + public List getChannelList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return channelMapper.selectByIds(ids); + } + + @Override + public ImChannelDO validateChannelExists(Long id) { + ImChannelDO channel = channelMapper.selectById(id); + if (channel == null) { + throw exception(IM_CHANNEL_NOT_EXISTS); + } + return channel; + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getChannelPage(ImChannelPageReqVO reqVO) { + return channelMapper.selectPage(reqVO); + } + + @Override + public ImChannelDO getChannel(Long id) { + return channelMapper.selectById(id); + } + + @Override + public Long createChannel(ImChannelSaveReqVO reqVO) { + // 校验 code 唯一 + validateCodeUnique(null, reqVO.getCode()); + + // 插入 + ImChannelDO channel = BeanUtils.toBean(reqVO, ImChannelDO.class); + channelMapper.insert(channel); + return channel.getId(); + } + + @Override + public void updateChannel(ImChannelSaveReqVO reqVO) { + // 1.1 校验存在 + validateChannelExists(reqVO.getId()); + // 1.2 校验 code 唯一 + validateCodeUnique(reqVO.getId(), reqVO.getCode()); + + // 2. 更新 + ImChannelDO updateObj = BeanUtils.toBean(reqVO, ImChannelDO.class); + channelMapper.updateById(updateObj); + } + + @Override + public void deleteChannel(Long id) { + // 1.1 校验存在 + validateChannelExists(id); + // 1.2 防止误删频道导致历史素材 / 消息回查不到归属 + if (channelMaterialService.getMaterialCountByChannelId(id) > 0) { + throw exception(IM_CHANNEL_HAS_MATERIAL); + } + + // 2. 删除频道 + channelMapper.deleteById(id); + } + + private void validateCodeUnique(Long id, String code) { + ImChannelDO exist = channelMapper.selectByCode(code); + if (exist == null) { + return; + } + if (id == null || ObjUtil.notEqual(exist.getId(), id)) { + throw exception(IM_CHANNEL_CODE_DUPLICATED, code); + } + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadService.java new file mode 100644 index 0000000000..9c057ae62a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadService.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.im.service.conversation; + +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * IM 会话读位置 Service 接口 + * + * @author 芋道源码 + */ +public interface ImConversationReadService { + + /** + * 更新用户在某会话的最大已读位置(单调递增,乱序 / 并发上报不会回退) + * + * @param userId 用户编号 + * @param conversationType 会话类型 + * @param conversationId 会话编号 + * @param readMessageId 已读到的最大消息编号 + * @return 读位置是否前进;true 时调用方才需要下发已读 / 回执事件 + */ + boolean updateConversationReadPosition(Long userId, Integer conversationType, Long conversationId, Long readMessageId); + + /** + * 获取用户在某会话的最大已读位置 + * + * @param userId 用户编号 + * @param conversationType 会话类型 + * @param conversationId 会话编号 + * @return 最大已读消息编号;不存在则返回 null + */ + Long getConversationReadMessageId(Long userId, Integer conversationType, Long conversationId); + + /** + * 获取某会话内所有用户的读位置(用于群回执人数聚合) + * + * @param conversationType 会话类型 + * @param conversationId 会话编号 + * @return userId → 最大已读消息编号 + */ + Map getUserReadMessageIdMap(Integer conversationType, Long conversationId); + + /** + * 批量获取某用户在多个会话的读位置(用于频道批量读位置、重连后按活跃会话补偿) + * + * @param userId 用户编号 + * @param conversationType 会话类型 + * @param conversationIds 会话编号集合 + * @return conversationId → 最大已读消息编号 + */ + Map getConversationReadMessageIdMap(Long userId, Integer conversationType, Collection conversationIds); + + /** + * 增量拉取当前用户的会话读位置(重连 / 离线补偿:按 update_time + id 游标) + * + * @param userId 用户编号 + * @param lastUpdateTime 上次拉取到的最新更新时间(毫秒时间戳);首次拉取传 null + * @param lastId 上次拉取到的最后一条记录 id;首次拉取传 null + * @param limit 单次拉取条数 + * @return 会话读位置列表,按更新时间、id 正序 + */ + List pullConversationReadList(Long userId, Long lastUpdateTime, Long lastId, Integer limit); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadServiceImpl.java new file mode 100644 index 0000000000..a22d6166e8 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadServiceImpl.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.im.service.conversation; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO; +import cn.iocoder.yudao.module.im.dal.mysql.conversation.ImConversationReadMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * IM 会话读位置 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class ImConversationReadServiceImpl implements ImConversationReadService { + + @Resource + private ImConversationReadMapper conversationReadMapper; + + @Override + public boolean updateConversationReadPosition(Long userId, Integer conversationType, Long conversationId, Long readMessageId) { + LocalDateTime now = LocalDateTime.now(); + // 1. 不存在则插入;并发下唯一键冲突,降级为回查 + CAS 更新 + ImConversationReadDO existing = conversationReadMapper.selectByUserIdAndConversation( + userId, conversationType, conversationId); + if (existing == null) { + try { + conversationReadMapper.insert(new ImConversationReadDO().setUserId(userId) + .setConversationType(conversationType).setTargetId(conversationId) + .setMessageId(readMessageId).setReadTime(now)); + return true; + } catch (DuplicateKeyException e) { + log.warn("[updateConversationReadPosition][userId({}) type({}) conversationId({}) 并发插入冲突,回查更新]", + userId, conversationType, conversationId); + existing = conversationReadMapper.selectByUserIdAndConversation(userId, conversationType, conversationId); + } + } + if (existing == null) { + return false; + } + + // 2. CAS 单调更新:mapper 内 WHERE message_id < ? 保证乱序 / 并发不回退;影响行数 > 0 即读位置前进 + return conversationReadMapper.updateReadMessageIdToLarger(existing.getId(), readMessageId, now) > 0; + } + + @Override + public Long getConversationReadMessageId(Long userId, Integer conversationType, Long conversationId) { + ImConversationReadDO read = conversationReadMapper.selectByUserIdAndConversation( + userId, conversationType, conversationId); + return read != null ? read.getMessageId() : null; + } + + @Override + public Map getUserReadMessageIdMap(Integer conversationType, Long conversationId) { + return convertMap(conversationReadMapper.selectListByConversation(conversationType, conversationId), + ImConversationReadDO::getUserId, ImConversationReadDO::getMessageId); + } + + @Override + public Map getConversationReadMessageIdMap(Long userId, Integer conversationType, + Collection conversationIds) { + if (CollUtil.isEmpty(conversationIds)) { + return Collections.emptyMap(); + } + List list = conversationReadMapper.selectListByUserIdAndConversations( + userId, conversationType, conversationIds); + return convertMap(list, ImConversationReadDO::getTargetId, ImConversationReadDO::getMessageId); + } + + @Override + public List pullConversationReadList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + return conversationReadMapper.selectPullListByUserId(userId, lastUpdateTime, lastId, limit); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemService.java new file mode 100644 index 0000000000..c9f913e5ac --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemService.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item.ImFacePackItemPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item.ImFacePackItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * IM 表情包项 Service 接口 + * + * @author 芋道源码 + */ +public interface ImFacePackItemService { + + // ==================== 用户端 ==================== + + /** + * 按 packIds 批量取启用项,给前端聚合接口用 + * + * @param packIds 表情包编号列表 + * @return 启用状态的表情列表 + */ + List getEnabledItemListByPackIds(Collection packIds); + + /** + * 取某个表情包下的表情数量;ImFacePackService 删除前校验「包下无项」用 + * + * @param packId 表情包编号 + * @return 数量 + */ + Long getFacePackItemCount(Long packId); + + /** + * 取多个表情包下的表情数量合计;ImFacePackService 批量删除前校验用 + * + * @param packIds 表情包编号列表 + * @return 数量合计 + */ + Long getFacePackItemCount(Collection packIds); + + // ==================== 管理后台 ==================== + + /** + * 分页查询表情包项 + * + * @param reqVO 分页查询条件 + * @return 表情包项分页 + */ + PageResult getFacePackItemPage(ImFacePackItemPageReqVO reqVO); + + /** + * 获取表情包项详情 + * + * @param id 表情包项编号 + * @return 表情包项 DO + */ + ImFacePackItemDO getFacePackItem(Long id); + + /** + * 新增表情包项 + * + * @param reqVO 新增请求 + * @return 新增表情包项编号 + */ + Long createFacePackItem(@Valid ImFacePackItemSaveReqVO reqVO); + + /** + * 修改表情包项 + * + * @param reqVO 修改请求 + */ + void updateFacePackItem(@Valid ImFacePackItemSaveReqVO reqVO); + + /** + * 删除表情包项 + * + * @param id 表情包项编号 + */ + void deleteFacePackItem(Long id); + + /** + * 批量删除表情包项 + * + * @param ids 表情包项编号列表 + */ + void deleteFacePackItemList(List ids); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImpl.java new file mode 100644 index 0000000000..9f8d54563e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImpl.java @@ -0,0 +1,117 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item.ImFacePackItemPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item.ImFacePackItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFacePackItemMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_ITEM_NOT_EXISTS; + +/** + * IM 表情包项 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImFacePackItemServiceImpl implements ImFacePackItemService { + + @Resource + private ImFacePackItemMapper facePackItemMapper; + @Resource + private ImFacePackService facePackService; + + // ==================== 用户端 ==================== + + @Override + public List getEnabledItemListByPackIds(Collection packIds) { + if (CollUtil.isEmpty(packIds)) { + return Collections.emptyList(); + } + return facePackItemMapper.selectListByPackIdsAndStatus(packIds, CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public Long getFacePackItemCount(Long packId) { + return facePackItemMapper.selectCountByPackId(packId); + } + + @Override + public Long getFacePackItemCount(Collection packIds) { + if (CollUtil.isEmpty(packIds)) { + return 0L; + } + return facePackItemMapper.selectCountByPackIds(packIds); + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getFacePackItemPage(ImFacePackItemPageReqVO reqVO) { + return facePackItemMapper.selectPage(reqVO); + } + + @Override + public ImFacePackItemDO getFacePackItem(Long id) { + return facePackItemMapper.selectById(id); + } + + @Override + public Long createFacePackItem(ImFacePackItemSaveReqVO reqVO) { + // 1. 校验所属表情包存在 + facePackService.validateFacePackExists(reqVO.getPackId()); + + // 2. 入库 + ImFacePackItemDO item = BeanUtils.toBean(reqVO, ImFacePackItemDO.class); + facePackItemMapper.insert(item); + return item.getId(); + } + + @Override + public void updateFacePackItem(ImFacePackItemSaveReqVO reqVO) { + // 1.1 校验存在 + validateFacePackItemExists(reqVO.getId()); + // 1.2 校验所属表情包存在 + facePackService.validateFacePackExists(reqVO.getPackId()); + + // 2. 更新 + ImFacePackItemDO updateObj = BeanUtils.toBean(reqVO, ImFacePackItemDO.class); + facePackItemMapper.updateById(updateObj); + } + + @Override + public void deleteFacePackItem(Long id) { + // 1. 校验存在 + validateFacePackItemExists(id); + + // 2. 删除 + facePackItemMapper.deleteById(id); + } + + @Override + public void deleteFacePackItemList(List ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + facePackItemMapper.deleteByIds(ids); + } + + private void validateFacePackItemExists(Long id) { + if (facePackItemMapper.selectById(id) == null) { + throw exception(FACE_PACK_ITEM_NOT_EXISTS); + } + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackService.java new file mode 100644 index 0000000000..d579fbfbb0 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackService.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack.ImFacePackPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack.ImFacePackSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * IM 表情包 Service 接口 + * + * @author 芋道源码 + */ +public interface ImFacePackService { + + // ==================== 用户端 ==================== + + /** + * 获取所有启用的表情包,按 sort 升序 + * + * @return 启用的表情包列表 + */ + List getEnabledFacePackList(); + + /** + * 校验表情包存在 + * + * @param id 表情包编号 + * @return 表情包 DO + */ + @SuppressWarnings("UnusedReturnValue") + ImFacePackDO validateFacePackExists(Long id); + + // ==================== 管理后台 ==================== + + /** + * 分页查询表情包 + * + * @param reqVO 分页查询条件 + * @return 表情包分页 + */ + PageResult getFacePackPage(ImFacePackPageReqVO reqVO); + + /** + * 获取表情包详情 + * + * @param id 表情包编号 + * @return 表情包 DO + */ + ImFacePackDO getFacePack(Long id); + + /** + * 新增表情包 + * + * @param reqVO 新增请求 + * @return 新增表情包编号 + */ + Long createFacePack(@Valid ImFacePackSaveReqVO reqVO); + + /** + * 修改表情包 + * + * @param reqVO 修改请求 + */ + void updateFacePack(@Valid ImFacePackSaveReqVO reqVO); + + /** + * 删除表情包;包下存在表情时拒绝,避免历史 face 消息无法回查归属 + * + * @param id 表情包编号 + */ + void deleteFacePack(Long id); + + /** + * 批量删除表情包;任一包下存在表情时整批拒绝,避免「只删一半」中间态 + * + * @param ids 表情包编号列表 + */ + void deleteFacePackList(List ids); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImpl.java new file mode 100644 index 0000000000..e86a3977d9 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImpl.java @@ -0,0 +1,113 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack.ImFacePackPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack.ImFacePackSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFacePackMapper; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_HAS_ITEMS; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_NOT_EXISTS; + +/** + * IM 表情包 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImFacePackServiceImpl implements ImFacePackService { + + @Resource + private ImFacePackMapper facePackMapper; + + /** + * @Lazy 解决与 ImFacePackItemServiceImpl 的循环依赖(item.create / update 校验所属包存在 → 反向调用本类) + */ + @Resource + @Lazy + private ImFacePackItemService facePackItemService; + + // ==================== 用户端 ==================== + + @Override + public List getEnabledFacePackList() { + return facePackMapper.selectListByStatusOrderBySort(CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public ImFacePackDO validateFacePackExists(Long id) { + ImFacePackDO pack = facePackMapper.selectById(id); + if (pack == null) { + throw exception(FACE_PACK_NOT_EXISTS); + } + return pack; + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getFacePackPage(ImFacePackPageReqVO reqVO) { + return facePackMapper.selectPage(reqVO); + } + + @Override + public ImFacePackDO getFacePack(Long id) { + return facePackMapper.selectById(id); + } + + @Override + public Long createFacePack(ImFacePackSaveReqVO reqVO) { + ImFacePackDO pack = BeanUtils.toBean(reqVO, ImFacePackDO.class); + facePackMapper.insert(pack); + return pack.getId(); + } + + @Override + public void updateFacePack(ImFacePackSaveReqVO reqVO) { + // 1. 校验存在 + validateFacePackExists(reqVO.getId()); + + // 2. 更新 + ImFacePackDO updateObj = BeanUtils.toBean(reqVO, ImFacePackDO.class); + facePackMapper.updateById(updateObj); + } + + @Override + public void deleteFacePack(Long id) { + // 1.1 校验存在 + validateFacePackExists(id); + // 1.2 校验表情包下没有表情;防止误删表情包导致历史 face 消息无法回查归属 + if (facePackItemService.getFacePackItemCount(id) > 0) { + throw exception(FACE_PACK_HAS_ITEMS); + } + + // 2. 删除 + facePackMapper.deleteById(id); + } + + @Override + public void deleteFacePackList(List ids) { + // 1. 任一存在表情则拒绝整批删除,避免「只删一半」的中间态 + if (CollUtil.isEmpty(ids)) { + return; + } + if (facePackItemService.getFacePackItemCount(ids) > 0) { + throw exception(FACE_PACK_HAS_ITEMS); + } + + // 2. 删除 + facePackMapper.deleteByIds(ids); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemService.java new file mode 100644 index 0000000000..68ae78cf19 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemService.java @@ -0,0 +1,60 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem.ImFaceUserItemSaveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * IM 用户私有表情 Service 接口 + * + * @author 芋道源码 + */ +public interface ImFaceUserItemService { + + /** + * 获取指定用户的个人表情列表 + * + * @param userId 用户编号 + * @return 个人表情列表 + */ + List getFaceUserItemList(Long userId); + + /** + * 添加个人表情 + * + * @param userId 用户编号 + * @param reqVO 添加请求 + * @return 新增表情编号 + */ + Long createFaceUserItem(Long userId, @Valid ImFaceUserItemSaveReqVO reqVO); + + /** + * 删除指定用户的某条个人表情 + * + * @param userId 用户编号 + * @param id 表情编号 + */ + void deleteFaceUserItem(Long userId, Long id); + + // ==================== 管理后台 ==================== + + /** + * 分页查询所有用户的个人表情;管理后台审计 / 删除违规图用 + * + * @param reqVO 分页查询条件 + * @return 个人表情分页 + */ + PageResult getFaceUserItemPage(ImFaceUserItemManagerPageReqVO reqVO); + + /** + * 管理后台直接删除某条个人表情;不做归属校验 + * + * @param id 表情编号 + */ + void deleteFaceUserItem(Long id); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImpl.java new file mode 100644 index 0000000000..e5598059b3 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImpl.java @@ -0,0 +1,99 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem.ImFaceUserItemSaveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.useritem.ImFaceUserItemManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFaceUserItemMapper; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import jakarta.annotation.Resource; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_DUPLICATED; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_MAX_LIMIT; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_NOT_EXISTS; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_NOT_OWN; + +/** + * IM 用户私有表情 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImFaceUserItemServiceImpl implements ImFaceUserItemService { + + @Resource + private ImFaceUserItemMapper faceUserItemMapper; + @Resource + private ImProperties imProperties; + + @Override + public List getFaceUserItemList(Long userId) { + return faceUserItemMapper.selectListByUserId(userId); + } + + @Override + public Long createFaceUserItem(Long userId, ImFaceUserItemSaveReqVO reqVO) { + // 1.1 同 URL 已存在则报错 + if (faceUserItemMapper.selectByUserIdAndUrl(userId, reqVO.getUrl()) != null) { + throw exception(FACE_USER_ITEM_DUPLICATED); + } + // 1.2 超过最大数量限制则报错 + int maxCount = imProperties.getFace().getUserItemMaxCount(); + if (faceUserItemMapper.selectCountByUserId(userId) >= maxCount) { + throw exception(FACE_USER_ITEM_MAX_LIMIT, maxCount); + } + + // 2. 入库 + ImFaceUserItemDO item = BeanUtils.toBean(reqVO, ImFaceUserItemDO.class).setUserId(userId); + try { + faceUserItemMapper.insert(item); + } catch (DuplicateKeyException ex) { + throw exception(FACE_USER_ITEM_DUPLICATED); + } + return item.getId(); + } + + @Override + public void deleteFaceUserItem(Long userId, Long id) { + // 1.1 校验存在 + ImFaceUserItemDO item = faceUserItemMapper.selectById(id); + if (item == null) { + throw exception(FACE_USER_ITEM_NOT_EXISTS); + } + // 1.2 校验归属:防止 A 用户传 B 用户的表情 id 删别人的 + if (ObjectUtil.notEqual(item.getUserId(), userId)) { + throw exception(FACE_USER_ITEM_NOT_OWN); + } + + // 2. 删除 + faceUserItemMapper.deleteById(id); + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getFaceUserItemPage(ImFaceUserItemManagerPageReqVO reqVO) { + return faceUserItemMapper.selectPage(reqVO); + } + + @Override + public void deleteFaceUserItem(Long id) { + // 1. 校验存在 + if (faceUserItemMapper.selectById(id) == null) { + throw exception(FACE_USER_ITEM_NOT_EXISTS); + } + + // 2. 删除 + faceUserItemMapper.deleteById(id); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestService.java new file mode 100644 index 0000000000..847020611d --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestService.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.im.service.friend; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.request.ImFriendRequestApplyReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendRequestManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; + +import java.util.List; + +/** + * IM 好友申请 Service 接口 + * + * @author 芋道源码 + */ +public interface ImFriendRequestService { + + /** + * 发起好友申请 + * + * @param fromUserId 发起方用户编号 + * @param reqVO 申请请求 + * @return 申请记录 + */ + ImFriendRequestDO applyFriend(Long fromUserId, ImFriendRequestApplyReqVO reqVO); + + /** + * 同意好友申请 + * + * @param userId 操作人用户编号 + * @param requestId 申请记录编号 + */ + void agreeFriendRequest(Long userId, Long requestId); + + /** + * 拒绝好友申请 + * + * @param userId 操作人用户编号 + * @param requestId 申请记录编号 + * @param handleContent 拒绝理由 + */ + void refuseFriendRequest(Long userId, Long requestId, String handleContent); + + /** + * 查询「我相关」的申请列表(含我发起的、别人加我的);游标分页:传 maxId 拉更早一页 + * + * @param userId 用户编号 + * @param maxId 当前列表最旧记录的 id;首页传 null + * @param limit 单次拉取条数(page size,由前端常量控制) + * @return 申请记录列表,按更新时间、id 倒序 + */ + List getMyFriendRequestList(Long userId, Long maxId, Integer limit); + + /** + * 增量拉取「我相关」的好友申请(重连 / 离线补偿:双向 OR,按 update_time + id 游标) + * + * @param userId 用户编号 + * @param lastUpdateTime 上次拉取到的最新更新时间(毫秒时间戳);首次拉取传 null + * @param lastId 上次拉取到的最后一条记录 id;首次拉取传 null + * @param limit 单次拉取条数 + * @return 申请记录列表,按更新时间、id 正序 + */ + List pullFriendRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit); + + /** + * 按 id 单查申请记录;通用读接口,调用方自行做越权过滤 + */ + ImFriendRequestDO getFriendRequest(Long id); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询好友申请记录 + */ + PageResult getFriendRequestPage(ImFriendRequestManagerPageReqVO reqVO); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java new file mode 100644 index 0000000000..eeed45797e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java @@ -0,0 +1,270 @@ +package cn.iocoder.yudao.module.im.service.friend; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.request.ImFriendRequestApplyReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendRequestManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; +import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendRequestMapper; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendRequestApprovedNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendRequestNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendRequestRejectedNotification; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; + +/** + * IM 好友申请 Service 实现类 + * + * @author 芋道源码 + */ +@Slf4j +@Service +@Validated +public class ImFriendRequestServiceImpl implements ImFriendRequestService { + + @Resource + private ImFriendRequestMapper friendRequestMapper; + + @Resource + private ImFriendService friendService; + @Resource + private ImWebSocketService websocketService; + + @Resource + private ImProperties imProperties; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public ImFriendRequestDO applyFriend(Long fromUserId, ImFriendRequestApplyReqVO reqVO) { + Long toUserId = reqVO.getToUserId(); + // 1.1 校验:不能加自己 + if (Objects.equals(fromUserId, toUserId)) { + throw exception(FRIEND_ADD_SELF); + } + // 1.2 校验对方存在且启用 + adminUserApi.validateUser(toUserId); + // 1.3 已是好友 / 被对方拉黑:直接报错(state 一次拿到双向状态,省两次单边查询) + // 错误码与 ImFriendService#validateFriend 不同(语义为「申请被拒」),故保留 inline + Integer state = friendService.getFriendState(fromUserId, toUserId); + if (ImFriendStateEnum.isFriend(state)) { + throw exception(FRIEND_REQUEST_ALREADY_FRIEND); + } + if (ImFriendStateEnum.isBlocked(state)) { + throw exception(FRIEND_REQUEST_BLOCKED_BY_PEER); + } + // 1.4 单向好友(我已删 + 对方仍把我当好友):静默重新启用我侧关系,避免对方感知我曾删除 + ImFriendDO peerFriend = friendService.getFriend(toUserId, fromUserId); + if (peerFriend != null && CommonStatusEnum.isEnable(peerFriend.getStatus())) { + // 对方已拉黑:静默恢复等于绕过拉黑回到好友列表,必须先拒掉; + // getFriendState 在我侧 DISABLE 时直接返回 NONE,拿不到 BLOCKED 信号,这里显式补一次校验 + if (BooleanUtil.isTrue(peerFriend.getBlocked())) { + throw exception(FRIEND_REQUEST_BLOCKED_BY_PEER); + } + friendService.silentReAddFriend(fromUserId, toUserId, reqVO.getDisplayName(), reqVO.getAddSource()); + return null; + } + + // 2. 落库:同一申请人和接收人唯一,已有记录覆盖申请内容并重置为未处理 + ImFriendRequestDO request = createOrResetRequest(fromUserId, reqVO); + + // 3. 推送 FRIEND_REQUEST_RECEIVED 给 toUser 多端;payload 携带申请方昵称 / 头像,前端按 requestId 直推 push 进列表 + AdminUserRespDTO fromUser = adminUserApi.getUser(fromUserId); + FriendRequestNotification payload = (FriendRequestNotification) new FriendRequestNotification() + .setRequestId(request.getId()).setApplyContent(request.getApplyContent()).setAddSource(request.getAddSource()) + .setOperatorUserId(fromUserId).setFriendUserId(fromUserId); + if (fromUser != null) { + payload.setFromNickname(fromUser.getNickname()).setFromAvatar(fromUser.getAvatar()); + } + websocketService.sendNotificationAsync(toUserId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_REQUEST_RECEIVED.getType(), payload); + + // 4. 全局自动通过开关:注册 afterCommit 回调,事务提交后再走同意流程 + // 回调内 try/catch 兜底 —— afterCommit 异常会被 Spring 静默吞掉,否则同意失败时申请方永远等不到 APPROVED + if (imProperties.getFriend().isAutoAccept()) { + Long requestId = request.getId(); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCommit() { + try { + getSelf().agreeFriendRequest(toUserId, requestId); + } catch (Exception e) { + log.error("[applyFriend][autoAccept fromUserId={} toUserId={} requestId={} 自动通过失败]", + fromUserId, toUserId, requestId, e); + } + } + + }); + } + return request; + } + + /** + * 创建或重置好友申请 + * + * @param fromUserId 申请人用户编号 + * @param reqVO 申请请求 + * @return 申请记录 + */ + private ImFriendRequestDO createOrResetRequest(Long fromUserId, ImFriendRequestApplyReqVO reqVO) { + Long toUserId = reqVO.getToUserId(); + ImFriendRequestDO request = friendRequestMapper.selectByFromUserIdAndToUserId(fromUserId, toUserId); + if (request == null) { + // 1. 无旧申请:创建新申请;唯一键冲突时回查并复用并发写入的记录 + request = BeanUtils.toBean(reqVO, ImFriendRequestDO.class) + .setFromUserId(fromUserId).setToUserId(toUserId) + .setHandleResult(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()); + try { + friendRequestMapper.insert(request); + return request; + } catch (DuplicateKeyException ex) { + request = friendRequestMapper.selectByFromUserIdAndToUserId(fromUserId, toUserId); + if (request == null) { + throw ex; + } + } + } + + // 2. 复用旧申请:覆盖本次申请内容,并重置为未处理 + LocalDateTime now = LocalDateTime.now(); + friendRequestMapper.updateByIdReset(request.getId(), + reqVO.getApplyContent(), reqVO.getDisplayName(), reqVO.getAddSource(), now); + // 同步内存对象,后续通知和自动通过直接复用 + request.setApplyContent(reqVO.getApplyContent()).setDisplayName(reqVO.getDisplayName()) + .setAddSource(reqVO.getAddSource()) + .setHandleResult(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()) + .setHandleContent(null).setHandleTime(null).setUpdateTime(now); + return request; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void agreeFriendRequest(Long userId, Long requestId) { + // 1.1 校验申请存在、未处理、操作人是接收方 + ImFriendRequestDO request = validateRequestForHandle(userId, requestId); + + // 2. 乐观锁更新申请处理结果 + ImFriendRequestDO updateObj = new ImFriendRequestDO() + .setHandleResult(ImFriendRequestHandleResultEnum.AGREED.getResult()).setHandleTime(LocalDateTime.now()); + int affected = friendRequestMapper.updateByIdAndHandleResult(request.getId(), + ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), updateObj); + if (affected == 0) { + throw exception(FRIEND_REQUEST_HANDLED); + } + request.setHandleResult(ImFriendRequestHandleResultEnum.AGREED.getResult()).setHandleTime(updateObj.getHandleTime()); + + // 3. 双向建立好友关系 + friendService.becomeFriends(request); + + // 4. 推 FRIEND_REQUEST_APPROVED 给 fromUser 多端 + FriendRequestApprovedNotification payload = (FriendRequestApprovedNotification) + new FriendRequestApprovedNotification().setRequestId(request.getId()) + .setOperatorUserId(userId).setFriendUserId(userId); + websocketService.sendNotificationAsync(request.getFromUserId(), ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_REQUEST_APPROVED.getType(), payload); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void refuseFriendRequest(Long userId, Long requestId, String handleContent) { + // 1. 校验申请存在 + 未处理 + 操作人是接收方(fail-fast;并发场景仍由下面的乐观锁兜底) + ImFriendRequestDO request = validateRequestForHandle(userId, requestId); + + // 2. 乐观锁更新申请:handleResult=REFUSED + handleContent + handleTime;并发拒绝会有一方 affectedRows=0 + ImFriendRequestDO updateObj = new ImFriendRequestDO() + .setHandleResult(ImFriendRequestHandleResultEnum.REFUSED.getResult()) + .setHandleContent(handleContent).setHandleTime(LocalDateTime.now()); + int affected = friendRequestMapper.updateByIdAndHandleResult(request.getId(), + ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), updateObj); + if (affected == 0) { + throw exception(FRIEND_REQUEST_HANDLED); + } + + // 3. 推 FRIEND_REQUEST_REJECTED 给 fromUser 多端 + FriendRequestRejectedNotification payload = (FriendRequestRejectedNotification) + new FriendRequestRejectedNotification().setRequestId(request.getId()) + .setHandleContent(handleContent) + .setOperatorUserId(userId).setFriendUserId(userId); + websocketService.sendNotificationAsync(request.getFromUserId(), ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_REQUEST_REJECTED.getType(), payload); + } + + @Override + public List getMyFriendRequestList(Long userId, Long maxId, Integer limit) { + ImFriendRequestDO maxRequest = maxId != null ? friendRequestMapper.selectById(maxId) : null; + if (maxId != null && maxRequest == null) { + return ListUtil.of(); + } + return friendRequestMapper.selectMyList(userId, + maxRequest != null ? maxRequest.getUpdateTime() : null, + maxRequest != null ? maxRequest.getId() : null, + limit); + } + + @Override + public List pullFriendRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + return friendRequestMapper.selectPullListByUserId(userId, lastUpdateTime, lastId, limit); + } + + @Override + public ImFriendRequestDO getFriendRequest(Long id) { + return friendRequestMapper.selectById(id); + } + + @Override + public PageResult getFriendRequestPage(ImFriendRequestManagerPageReqVO reqVO) { + return friendRequestMapper.selectPage(reqVO); + } + + /** + * 校验申请可被「当前用户」处理:申请存在 + 未处理 + 操作人 = 接收方 + */ + private ImFriendRequestDO validateRequestForHandle(Long userId, Long requestId) { + ImFriendRequestDO request = friendRequestMapper.selectById(requestId); + if (request == null) { + throw exception(FRIEND_REQUEST_NOT_EXISTS); + } + if (ObjUtil.notEqual(request.getToUserId(), userId)) { + throw exception(FRIEND_REQUEST_NOT_TO_ME); + } + if (!ImFriendRequestHandleResultEnum.isUnhandled(request.getHandleResult())) { + throw exception(FRIEND_REQUEST_HANDLED); + } + return request; + } + + private ImFriendRequestServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java new file mode 100644 index 0000000000..1d1fc5755b --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java @@ -0,0 +1,129 @@ +package cn.iocoder.yudao.module.im.service.friend; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; + +import java.util.Collection; +import java.util.List; + +/** + * IM 好友关系 Service 接口 + *

+ * 注意:用户端「加好友」走 {@link ImFriendRequestService#applyFriend} 申请-审批流程, + * 不再开放直接 add 接口;只有 {@link #becomeFriends} 是内部入口(被 agree 同意 / 管理员 import 触发)。 + * + * @author 芋道源码 + */ +public interface ImFriendService { + + /** + * 获取 userId 视角下与 friendUserId 的好友关系状态(私聊发送热点路径) + *

+ * 参见 {@link ImFriendStateEnum} 枚举类 + */ + Integer getFriendState(Long userId, Long friendUserId); + + /** + * 校验「能否对 peerUserId 发起私聊语义动作」(消息发送 / RTC 邀请) + *

+ * 好友 / 黑名单校验:和私聊消息发送同一套语义;NONE 已删 / 未加,BLOCKED 被对方拉黑 + * + * @param userId 当前用户编号 + * @param peerUserId 对方用户编号 + */ + void validateFriend(Long userId, Long peerUserId); + + /** + * 获得当前用户的好友列表(含已删除状态) + */ + List getFriendList(Long userId); + + /** + * 增量拉取当前用户的好友关系(重连 / 离线补偿:含已删除,按 update_time + id 游标) + */ + List pullFriendList(Long userId, Long lastUpdateTime, Long lastId, Integer limit); + + /** + * 获得当前用户的有效好友列表(仅 ENABLE 状态) + */ + List getEnableFriendList(Long userId); + + /** + * 获得当前用户的双向有效好友列表(双方均 ENABLE 状态) + */ + List getMutualEnableFriendList(Long userId); + + /** + * 获得当前用户与指定用户之间的有效好友列表(仅 ENABLE 状态) + */ + List getActiveFriendList(Long userId, Collection friendUserIds); + + /** + * 查询一个好友关系记录 + */ + ImFriendDO getFriend(Long userId, Long friendUserId); + + // ==================== 内部入口 ==================== + + /** + * 双向建立好友关系(内部入口) + *

+ * 由 {@link ImFriendRequestService#agreeFriendRequest} 同意申请 / 管理后台导入触发; + * A 侧 displayName / addSource 取自申请记录;B 侧 displayName 为空、addSource 同来源。 + * 写库后推送 FRIEND_ADD 通知给 A、B 双方多端,并下发 TIP 系统消息。 + * + * @param request 已同意的申请记录(决定 fromUserId / toUserId / addSource / displayName) + */ + void becomeFriends(ImFriendRequestDO request); + + /** + * 单向静默重新建立好友关系 + *

+ * 仅用于 {@link ImFriendRequestService#applyFriend} 在「我已删除 + 对方仍把我当好友」场景: + * 直接恢复 userId 这边的 friend 记录,不走申请审批;不下发 TIP / 不通知对方,仅 FRIEND_ADD 给 userId 多端,避免对方感知我曾删除。 + * + * @param userId 当前用户编号 + * @param friendUserId 对方用户编号 + * @param displayName 备注(取自申请 VO) + * @param addSource 添加来源(取自申请 VO) + */ + void silentReAddFriend(Long userId, Long friendUserId, String displayName, Integer addSource); + + // ==================== 用户端 ==================== + + /** + * 删除好友(单向软删除) + *

+ * 仅删除 userId 视角下的好友关系;对端 friendUserId 的视角不受影响(单边删除语义) + * + * @param clear 是否级联清理本端相关数据(当前包含私聊会话;通过 FRIEND_DELETE 通知透传给多端) + */ + void deleteFriend(Long userId, Long friendUserId, Boolean clear); + + /** + * 更新好友单边属性(备注 / 免打扰 / 联系人置顶) + */ + void updateFriend(Long userId, ImFriendUpdateReqVO reqVO); + + /** + * 拉黑好友(必须先是好友) + */ + void blockFriend(Long userId, Long friendUserId); + + /** + * 移出黑名单 + */ + void unblockFriend(Long userId, Long friendUserId); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询好友关系 + */ + PageResult getFriendPage(ImFriendManagerPageReqVO reqVO); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java new file mode 100644 index 0000000000..f9dd4523c8 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java @@ -0,0 +1,339 @@ +package cn.iocoder.yudao.module.im.service.friend; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; +import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; +import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.*; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; +import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND_STATE; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_BLOCKED_BY_PEER; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_BLOCKED; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; + +/** + * IM 好友关系 Service 实现类 + * + * @author 芋道源码 + */ +@Slf4j +@Service +@Validated +public class ImFriendServiceImpl implements ImFriendService { + + @Resource + private ImFriendMapper friendMapper; + + @Resource + private ImWebSocketService websocketService; + @Resource + @Lazy + private ImPrivateMessageService privateMessageService; + + @Override + @Cacheable(cacheNames = FRIEND_STATE, key = "#userId + '_' + #friendUserId", unless = "#result == null") + public Integer getFriendState(Long userId, Long friendUserId) { + // 1.1 我侧记录:我方删了,都算非好友 + ImFriendDO mine = friendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); + if (mine == null || !CommonStatusEnum.isEnable(mine.getStatus())) { + return ImFriendStateEnum.NONE.getState(); + } + // 1.2 对方侧记录:对方删了 = 不是好友 + ImFriendDO peer = friendMapper.selectByUserIdAndFriendUserId(friendUserId, userId); + if (peer == null || !CommonStatusEnum.isEnable(peer.getStatus())) { + return ImFriendStateEnum.NONE.getState(); + } + // 2. 仅当双方都是 ENABLE 状态,才算好友关系;此时对方拉黑我,则是 BLOCKED + return BooleanUtil.isTrue(peer.getBlocked()) ? ImFriendStateEnum.BLOCKED.getState() : ImFriendStateEnum.FRIEND.getState(); + } + + @Override + public void validateFriend(Long userId, Long peerUserId) { + // 好友 / 黑名单校验:和私聊消息发送同一套语义;NONE 已删 / 未加,BLOCKED 被对方拉黑 + Integer state = getSelf().getFriendState(userId, peerUserId); + if (ImFriendStateEnum.isNone(state)) { + throw exception(FRIEND_NOT_FRIEND); + } + if (ImFriendStateEnum.isBlocked(state)) { + throw exception(FRIEND_BLOCKED_BY_PEER); + } + } + + private ImFriendService getSelf() { + return SpringUtil.getBean(getClass()); + } + + @Override + public List getFriendList(Long userId) { + return friendMapper.selectListByUserId(userId); + } + + @Override + public List pullFriendList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + return friendMapper.selectPullListByUserId(userId, lastUpdateTime, lastId, limit); + } + + @Override + public List getEnableFriendList(Long userId) { + return friendMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public List getMutualEnableFriendList(Long userId) { + // 1. 查询本端启用好友 + List friends = getEnableFriendList(userId); + if (CollUtil.isEmpty(friends)) { + return Collections.emptyList(); + } + + // 2. 查询对端启用好友关系 + Set friendUserIds = convertSet(friends, ImFriendDO::getFriendUserId); + List mutualFriends = friendMapper.selectListByUserIdsAndFriendUserIdAndStatus(friendUserIds, userId, + CommonStatusEnum.ENABLE.getStatus()); + Set mutualUserIds = convertSet(mutualFriends, ImFriendDO::getUserId); + + // 3. 过滤双向启用好友 + return filterList(friends, friend -> mutualUserIds.contains(friend.getFriendUserId())); + } + + @Override + public List getActiveFriendList(Long userId, Collection friendUserIds) { + if (CollUtil.isEmpty(friendUserIds)) { + return Collections.emptyList(); + } + return friendMapper.selectListByUserIdAndFriendUserIdsAndStatus(userId, friendUserIds, + CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public ImFriendDO getFriend(Long userId, Long friendUserId) { + return friendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); + } + + @Override + public void updateFriend(Long userId, ImFriendUpdateReqVO reqVO) { + // 1.1 校验:至少改一个字段(无字段变更,直接结束) + if (reqVO.getDisplayName() == null && reqVO.getSilent() == null && reqVO.getPinned() == null) { + return; + } + // 1.2 校验好友关系启用 + ImFriendDO friend = friendMapper.selectByUserIdAndFriendUserId(userId, reqVO.getFriendUserId()); + if (friend == null || !CommonStatusEnum.isEnable(friend.getStatus())) { + throw exception(FRIEND_NOT_FRIEND); + } + + // 2. 更新好友属性(备注 / 免打扰 / 联系人置顶) + friendMapper.updateById(new ImFriendDO().setId(friend.getId()) + .setSilent(reqVO.getSilent()).setDisplayName(reqVO.getDisplayName()).setPinned(reqVO.getPinned())); + + // 3. 推 FRIEND_UPDATE 给 A 多端:所有单边属性变更合并为单条通知,避免多通知顺序竞争 + FriendUpdateNotification payload = (FriendUpdateNotification) new FriendUpdateNotification() + .setDisplayName(reqVO.getDisplayName()).setSilent(reqVO.getSilent()).setPinned(reqVO.getPinned()) + .setOperatorUserId(userId).setFriendUserId(reqVO.getFriendUserId()); + websocketService.sendNotificationAsync(userId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_UPDATE.getType(), payload); + } + + @Override + @Caching(evict = { + @CacheEvict(cacheNames = FRIEND_STATE, key = "#request.fromUserId + '_' + #request.toUserId"), + @CacheEvict(cacheNames = FRIEND_STATE, key = "#request.toUserId + '_' + #request.fromUserId") + }) + @Transactional(rollbackFor = Exception.class) + public void becomeFriends(ImFriendRequestDO request) { + Long fromUserId = request.getFromUserId(); + Long toUserId = request.getToUserId(); + // 1. 双向建立关系:A 侧带申请的 displayName / addSource;B 侧 displayName 为空、addSource 同来源 + // FRIEND_STATE 双向失效由方法上的 @Caching 注解处理;framework 已开 transactionAware 自动延迟到 afterCommit + addFriend0(fromUserId, toUserId, request.getDisplayName(), request.getAddSource()); + addFriend0(toUserId, fromUserId, null, request.getAddSource()); + + // 2. 发送 FRIEND_ADD 入库(双方拉历史都能看到「你们已成为好友」会话气泡)+ 双向 WebSocket 自动覆盖双方多端 + // operatorUserId=fromUserId 标记申请发起方;前端按 (currentUserId === operatorUserId) 区分视角,文案固定不依赖此字段 + FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification() + .setOperatorUserId(fromUserId).setFriendUserId(toUserId); + privateMessageService.sendPrivateMessage(fromUserId, new ImPrivateMessageSendDTO() + .setReceiverId(toUserId).setType(ImContentTypeEnum.FRIEND_ADD.getType()).setContent(payload)); + } + + @Override + @Caching(evict = { + @CacheEvict(cacheNames = FRIEND_STATE, key = "#userId + '_' + #friendUserId"), + @CacheEvict(cacheNames = FRIEND_STATE, key = "#friendUserId + '_' + #userId") + }) + @Transactional(rollbackFor = Exception.class) + public void silentReAddFriend(Long userId, Long friendUserId, String displayName, Integer addSource) { + // 1. 单边重新启用我侧好友关系 + addFriend0(userId, friendUserId, displayName, addSource); + + // 2. 走 sendPrivateMessage + persistent=false:不入库 + 仅推 userId 多端(对方完全不感知,保持「对方一直把我当好友」错觉) + // operatorUserId 填 friendUserId(对方):让 userId 多端 UI 呈现「对方加了我」视角,与 silent 语义对齐 + // 前端按 type=FRIEND_ADD 渲染会话气泡(瞬时,不入库刷新即消失) + FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification() + .setOperatorUserId(friendUserId).setFriendUserId(friendUserId); + privateMessageService.sendPrivateMessage(userId, new ImPrivateMessageSendDTO() + .setReceiverId(friendUserId).setType(ImContentTypeEnum.FRIEND_ADD.getType()) + .setContent(payload).setPersistent(false)); + } + + @Override + @Caching(evict = { + @CacheEvict(cacheNames = FRIEND_STATE, key = "#userId + '_' + #friendUserId"), + @CacheEvict(cacheNames = FRIEND_STATE, key = "#friendUserId + '_' + #userId") + }) + @Transactional(rollbackFor = Exception.class) + public void deleteFriend(Long userId, Long friendUserId, Boolean clear) { + // 1. 单边软删:仅 userId 视角的关系置 DISABLE;friendUserId 视角不动 + if (!deleteFriend0(userId, friendUserId)) { + return; + } + + // 2. 走 sendPrivateMessage + persistent=false:不入库 + 仅推 userId 多端(friendUserId 不感知);clear 透传让多端清理动作一致 + // clear=false 时前端按 type=FRIEND_DELETE 渲染「你已删除好友」会话气泡(瞬时); + // clear=true 时前端按 clear 字段直接清会话,跳过气泡渲染 + FriendDeleteNotification payload = ((FriendDeleteNotification) new FriendDeleteNotification() + .setOperatorUserId(userId).setFriendUserId(friendUserId)).setClear(clear); + privateMessageService.sendPrivateMessage(userId, new ImPrivateMessageSendDTO() + .setReceiverId(friendUserId).setType(ImContentTypeEnum.FRIEND_DELETE.getType()) + .setContent(payload).setPersistent(false)); + } + + @Override + @Caching(evict = { + @CacheEvict(cacheNames = FRIEND_STATE, key = "#userId + '_' + #friendUserId"), + @CacheEvict(cacheNames = FRIEND_STATE, key = "#friendUserId + '_' + #userId") + }) + public void blockFriend(Long userId, Long friendUserId) { + // 1.1 校验是好友 + ImFriendDO friend = friendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); + if (friend == null || !CommonStatusEnum.isEnable(friend.getStatus())) { + throw exception(FRIEND_NOT_FRIEND); + } + // 1.2 已拉黑直接返回,幂等 + if (BooleanUtil.isTrue(friend.getBlocked())) { + return; + } + + // 2. 单边更新 + friendMapper.updateById(new ImFriendDO().setId(friend.getId()).setBlocked(true)); + + // 3. 推 FRIEND_BLOCK 给 A 多端 + FriendBlockNotification payload = (FriendBlockNotification) new FriendBlockNotification() + .setOperatorUserId(userId).setFriendUserId(friendUserId); + websocketService.sendNotificationAsync(userId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_BLOCK.getType(), payload); + } + + @Override + @Caching(evict = { + @CacheEvict(cacheNames = FRIEND_STATE, key = "#userId + '_' + #friendUserId"), + @CacheEvict(cacheNames = FRIEND_STATE, key = "#friendUserId + '_' + #userId") + }) + public void unblockFriend(Long userId, Long friendUserId) { + // 1.1 校验是好友 + ImFriendDO friend = friendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); + if (friend == null || !CommonStatusEnum.isEnable(friend.getStatus())) { + throw exception(FRIEND_NOT_FRIEND); + } + // 1.2 未拉黑则报错 + if (!BooleanUtil.isTrue(friend.getBlocked())) { + throw exception(FRIEND_NOT_BLOCKED); + } + + // 2. 单边更新 + friendMapper.updateById(new ImFriendDO().setId(friend.getId()).setBlocked(false)); + + // 3. 推 FRIEND_UNBLOCK 给 A 多端 + FriendUnblockNotification payload = (FriendUnblockNotification) new FriendUnblockNotification() + .setOperatorUserId(userId).setFriendUserId(friendUserId); + websocketService.sendNotificationAsync(userId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_UNBLOCK.getType(), payload); + } + + /** + * 单向绑定好友关系(内部方法,被 {@link #becomeFriends} / {@link #silentReAddFriend} 调用): + * - 情况一:已存在 ENABLE 记录 → 已是好友,幂等跳过;不重置 silent / pinned / blocked,避免历史未处理申请再被同意时清掉用户的拉黑 / 置顶 / 免打扰设置 + * - 情况二:已存在 DISABLE 记录 → 复用并恢复 ENABLE,silent / pinned / blocked 一并重置为 false,对齐"重新加好友"语义 + * - 情况三:不存在记录 → 直接插入新记录 + *

+ * 并发安全:agree 路径由 {@code friend_request.handle_result} 的乐观锁单边推进; + * 极端并发下若插入唯一键冲突,让 DuplicateKeyException 向外抛出,外层事务回滚。 + *

+ * FRIEND_STATE 缓存失效由调用方的 @Caching 注解统一处理,本方法不主动 evict + */ + public void addFriend0(Long userId, Long friendUserId, String displayName, Integer addSource) { + ImFriendDO exists = friendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); + // 情况一:已是 ENABLE 好友,幂等跳过;防御历史未处理申请被二次同意时把 blocked / silent / pinned 清回 false + if (exists != null && CommonStatusEnum.isEnable(exists.getStatus())) { + return; + } + // 情况二:复用 DISABLE 旧记录 → 恢复 ENABLE + 重置 silent / pinned / blocked,对齐"重新加好友"语义 + if (exists != null) { + LocalDateTime now = LocalDateTime.now(); + friendMapper.updateReAddFields(exists.getId(), CommonStatusEnum.ENABLE.getStatus(), now, now, + false, false, false, displayName, addSource); + return; + } + // 情况三:不存在记录 → 直接插入新记录 + ImFriendDO friend = ImFriendDO.builder().userId(userId).friendUserId(friendUserId) + .silent(false).pinned(false).blocked(false) + .displayName(displayName).addSource(addSource) + .status(CommonStatusEnum.ENABLE.getStatus()).addTime(LocalDateTime.now()).build(); + friendMapper.insert(friend); + } + + /** + * 单向解除好友关系(status 设为 DISABLE,记录 deleteTime) + *

+ * blocked 不主动重置:删好友期间保留拉黑状态;如果未来再 addFriend0,由 addFriend0 统一重置 + *

+ * FRIEND_STATE 缓存失效由调用方的 @Caching 注解统一处理,本方法不主动 evict + */ + public boolean deleteFriend0(Long userId, Long friendUserId) { + ImFriendDO exists = friendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); + if (exists == null || CommonStatusEnum.isDisable(exists.getStatus())) { + return false; + } + friendMapper.updateById(new ImFriendDO().setId(exists.getId()) + .setStatus(CommonStatusEnum.DISABLE.getStatus()).setDeleteTime(LocalDateTime.now())); + return true; + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getFriendPage(ImFriendManagerPageReqVO reqVO) { + return friendMapper.selectPage(reqVO); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java new file mode 100644 index 0000000000..a99c0f3154 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java @@ -0,0 +1,244 @@ +package cn.iocoder.yudao.module.im.service.group; + +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import jakarta.validation.Valid; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 群成员 Service 接口 + * + * @author 芋道源码 + */ +public interface ImGroupMemberService { + + /** + * 获得群成员 + * + * @param id 编号 + * @return 群成员 + */ + ImGroupMemberDO getGroupMember(Long id); + + /** + * 获得群成员 + * + * @param groupId 群编号 + * @param userId 用户编号 + * @return 群成员 + */ + ImGroupMemberDO getGroupMember(Long groupId, Long userId); + + /** + * 批量查询群成员(包含所有状态) + * + * @param groupId 群编号 + * @param userIds 用户编号集合 + * @return 群成员列表 + */ + List getGroupMembers(Long groupId, Collection userIds); + + /** + * 根据群组 id 查询群成员(包含所有状态) + * + * @param groupId 群组id + * @return 群成员列表 + */ + List getGroupMemberListByGroupId(Long groupId); + + /** + * 根据群编号查询有效成员列表(仅 ENABLE 状态) + * + * @param groupId 群编号 + * @return 有效群成员列表 + */ + List getActiveGroupMemberListByGroupId(Long groupId); + + /** + * 获取群里活跃的群主 + 管理员;用于审批通知定向推送 + * + * @param groupId 群编号 + * @return 群主 / 管理员的成员记录列表(按入群时间倒序的天然成员表顺序) + */ + List getGroupMemberListByOwnerAndAdmin(Long groupId); + + /** + * 根据群编号查询有效成员的 userId 列表(仅 ENABLE 状态) + *

+ * 相比 {@link #getActiveGroupMemberListByGroupId(Long)},只返回 userId,结果体积小,并带有 Redis 缓存。 + * 适用于"群消息推送目标"等只需要 userId 的场景; + * 需要 joinTime/quitTime/status 等字段做历史消息可见性判断时,仍应使用完整列表方法。 + * + * @param groupId 群编号 + * @return 有效群成员 userId 列表 + */ + List getActiveGroupMemberUserIdsByGroupId(Long groupId); + + /** + * 查询用户所在的所有群的有效成员记录(仅 ENABLE 状态) + * + * @param userId 用户编号 + * @return 有效群成员记录列表 + */ + List getActiveGroupMemberListByUserId(Long userId); + + /** + * 查询用户曾经加入的所有群成员记录(含已退群) + * + * @param userId 用户编号 + * @return 群成员记录列表 + */ + List getGroupMemberListByUserId(Long userId); + + /** + * 添加群成员(入群),角色默认 MEMBER + * + * @param groupId 群编号 + * @param userId 用户编号 + * @return 群成员记录 + */ + @SuppressWarnings("UnusedReturnValue") + ImGroupMemberDO addGroupMember(Long groupId, Long userId); + + /** + * 添加群成员(入群),并指定角色 + *

+ * 重置旧成员行也会强制重置 role,避免离群期间残留管理员身份被复用。 + * + * @param groupId 群编号 + * @param userId 用户编号 + * @param role 成员角色,见 {@link cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum} + * @return 群成员记录 + */ + @SuppressWarnings("UnusedReturnValue") + ImGroupMemberDO addGroupMember(Long groupId, Long userId, Integer role); + + /** + * 添加群成员(入群),并指定角色 / 加入来源 / 邀请人 + *

+ * 重置旧成员行也会强制重置 role / addSource / inviterUserId,确保留痕反映「本次入群」事件 + * + * @param groupId 群编号 + * @param userId 用户编号 + * @param role 成员角色 + * @param addSource 加入来源,见 {@link cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum} + * @param inviterUserId 邀请人用户编号;NULL 表示主动申请 + * @return 群成员记录 + */ + @SuppressWarnings("UnusedReturnValue") + ImGroupMemberDO addGroupMember(Long groupId, Long userId, Integer role, Integer addSource, Long inviterUserId); + + /** + * 批量添加群成员(入群) + * + * @param groupId 群编号 + * @param userIds 用户编号集合 + */ + void addGroupMembers(Long groupId, Collection userIds); + + /** + * 批量添加群成员(入群),统一携带加入来源 / 邀请人 + * + * @param groupId 群编号 + * @param userIds 用户编号集合 + * @param addSource 加入来源 + * @param inviterUserId 邀请人用户编号;NULL 表示主动申请 + */ + void addGroupMembers(Long groupId, Collection userIds, Integer addSource, Long inviterUserId); + + /** + * 校验用户是否为群的有效成员 + * + * @param groupId 群编号 + * @param userId 用户编号 + * @return 群成员记录 + */ + ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId); + + /** + * 批量校验用户都是该群的有效成员;任一缺失 / 禁用即抛 {@code GROUP_MEMBER_NOT_IN_GROUP} + * + * @param groupId 群编号 + * @param userIds 用户编号集合;为空直接返回 + */ + void validateMembersInGroup(Long groupId, Collection userIds); + + /** + * 更新群成员信息(群内昵称、群名备注、免打扰等) + *

+ * 内部会校验用户是否为群的有效成员 + * + * @param userId 当前登录用户编号 + * @param updateReqVO 更新信息 + */ + void updateGroupMember(Long userId, @Valid ImGroupMemberUpdateReqVO updateReqVO); + + /** + * 批量更新群成员角色 + * + * @param groupId 群编号 + * @param userIds 用户编号集合 + * @param role 新角色,见 {@link cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum} + */ + int updateGroupMemberRole(Long groupId, Collection userIds, Integer role); + + /** + * 统计群内指定 role 的活跃成员数量 + * + * @param groupId 群编号 + * @param role 成员角色 + * @return 数量 + */ + Long getGroupMemberCountByRole(Long groupId, Integer role); + + /** + * 移除指定群成员(设置为 DISABLE 状态) + *

+ * 用于退群、踢出场景 + * + * @param groupId 群编号 + * @param userId 用户编号 + */ + void removeGroupMember(Long groupId, Long userId); + + /** + * 批量移除指定群成员(设置为 DISABLE 状态) + *

+ * 用于批量踢出场景 + * + * @param groupId 群编号 + * @param userIds 用户编号集合 + */ + void removeGroupMembers(Long groupId, Collection userIds); + + /** + * 移除群的全部成员(设置为 DISABLE 状态) + *

+ * 用于群解散场景 + * + * @param groupId 群编号 + */ + void removeGroupMembersByGroupId(Long groupId); + + /** + * 批量按 group 统计活跃成员数:(group_id → count) + * + * @param groupIds 群编号集合 + * @return 群成员数 Map + */ + Map getActiveMemberCountMap(Collection groupIds); + + /** + * 更新成员禁言到期时间 + * + * @param groupId 群编号 + * @param userId 用户编号 + * @param muteEndTime 禁言到期时间;null 表示取消禁言 + */ + void updateGroupMemberMuteEndTime(Long groupId, Long userId, LocalDateTime muteEndTime); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java new file mode 100644 index 0000000000..c712a0b337 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java @@ -0,0 +1,323 @@ +package cn.iocoder.yudao.module.im.service.group; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; +import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MEMBER_IDS; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; + +/** + * 群成员 Service 实现类 + * + * @author 芋道源码 + */ +@Slf4j +@Service +@Validated +public class ImGroupMemberServiceImpl implements ImGroupMemberService { + + @Resource + private ImGroupMemberMapper groupMemberMapper; + + @Resource + @Lazy // 避免循环依赖 + private ImGroupMessageService groupMessageService; + + @Override + public ImGroupMemberDO getGroupMember(Long id) { + return groupMemberMapper.selectById(id); + } + + @Override + public ImGroupMemberDO getGroupMember(Long groupId, Long userId) { + return groupMemberMapper.selectByGroupIdAndUserId(groupId, userId); + } + + @Override + public List getGroupMembers(Long groupId, Collection userIds) { + if (CollUtil.isEmpty(userIds)) { + return Collections.emptyList(); + } + return groupMemberMapper.selectListByGroupIdAndUserIds(groupId, userIds); + } + + @Override + public List getGroupMemberListByGroupId(Long groupId) { + return groupMemberMapper.selectListByGroupId(groupId); + } + + @Override + public List getActiveGroupMemberListByGroupId(Long groupId) { + return groupMemberMapper.selectListByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public List getGroupMemberListByOwnerAndAdmin(Long groupId) { + return groupMemberMapper.selectListByGroupIdAndStatusAndRoles(groupId, CommonStatusEnum.ENABLE.getStatus(), + ListUtil.of(ImGroupMemberRoleEnum.OWNER.getRole(), ImGroupMemberRoleEnum.ADMIN.getRole())); + } + + /** + * 只缓存 userId 列表而非整个 {@link ImGroupMemberDO},理由: + *

    + *
  • 体积小:500 人群约 4KB,失效/序列化成本低;
  • + *
  • 失效面窄:仅 {@link #addGroupMember}/{@link #addGroupMembers}/ + * {@link #removeGroupMember}/{@link #removeGroupMembers}/{@link #removeGroupMembersByGroupId} + * 这类影响成员集合的写操作需要失效; + * {@link #updateGroupMember}(昵称/备注/免打扰)不修改集合成员,不需要失效。
  • + *
+ */ + @Override + @Cacheable(cacheNames = GROUP_MEMBER_IDS, key = "#groupId") + public List getActiveGroupMemberUserIdsByGroupId(Long groupId) { + List members = groupMemberMapper.selectListByGroupIdAndStatus( + groupId, CommonStatusEnum.ENABLE.getStatus()); + return convertList(members, ImGroupMemberDO::getUserId); + } + + @Override + public List getActiveGroupMemberListByUserId(Long userId) { + return groupMemberMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public List getGroupMemberListByUserId(Long userId) { + return groupMemberMapper.selectListByUserId(userId); + } + + @Override + public ImGroupMemberDO addGroupMember(Long groupId, Long userId) { + return addGroupMember(groupId, userId, ImGroupMemberRoleEnum.NORMAL.getRole(), null, null); + } + + @Override + public ImGroupMemberDO addGroupMember(Long groupId, Long userId, Integer role) { + return addGroupMember(groupId, userId, role, null, null); + } + + /** + * 并发安全:依靠 im_group_member 表的唯一索引 uk_im_group_member_group_user(group_id, user_id) 保证幂等, + * 当并发 insert 触发 {@link DuplicateKeyException} 时降级为 select + update。 + *

+ * 重置旧成员行时强制重置 role / addSource / inviterUserId / quitTime / muteEndTime + */ + @Override + @CacheEvict(cacheNames = GROUP_MEMBER_IDS, key = "#groupId") + public ImGroupMemberDO addGroupMember(Long groupId, Long userId, Integer role, + Integer addSource, Long inviterUserId) { + LocalDateTime now = LocalDateTime.now(); + // 情况一:已存在记录 → 重置或跳过 + ImGroupMemberDO exists = groupMemberMapper.selectByGroupIdAndUserId(groupId, userId); + if (exists != null) { + if (CommonStatusEnum.isDisable(exists.getStatus())) { + groupMemberMapper.updateRejoinFields(exists.getId(), CommonStatusEnum.ENABLE.getStatus(), now, + role, addSource, inviterUserId); + exists.setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(now).setRole(role) + .setAddSource(addSource).setInviterUserId(inviterUserId) + .setQuitTime(null).setMuteEndTime(null); + } + return exists; + } + // 情况二:新增成员 + ImGroupMemberDO member = new ImGroupMemberDO() + .setGroupId(groupId).setUserId(userId) + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(now) + .setRole(role).setAddSource(addSource).setInviterUserId(inviterUserId); + try { + groupMemberMapper.insert(member); + return member; + } catch (DuplicateKeyException e) { + // 并发场景:另一个请求已先一步插入,且其插入的必然是 ENABLE 状态(DISABLE 场景在上方分支已处理),查询返回 + log.warn("[addGroupMember][groupId({}) userId({}) 并发插入冲突,查询返回]", groupId, userId); + return groupMemberMapper.selectByGroupIdAndUserId(groupId, userId); + } + } + + @Override + public void addGroupMembers(Long groupId, Collection userIds) { + addGroupMembers(groupId, userIds, null, null); + } + + @Override + @CacheEvict(cacheNames = GROUP_MEMBER_IDS, key = "#groupId") + public void addGroupMembers(Long groupId, Collection userIds, Integer addSource, Long inviterUserId) { + LocalDateTime now = LocalDateTime.now(); + Integer role = ImGroupMemberRoleEnum.NORMAL.getRole(); + // 1.1 查询已有记录(含已退群的 DISABLE 记录) + List existMembers = groupMemberMapper.selectListByGroupIdAndUserIds(groupId, userIds); + Map existMap = convertMap(existMembers, ImGroupMemberDO::getUserId); + // 1.2 分类:已有记录 → UPDATE,新成员 → INSERT + List inserts = new ArrayList<>(); + List updates = new ArrayList<>(); + for (Long userId : userIds) { + ImGroupMemberDO exist = existMap.get(userId); + if (exist == null) { + inserts.add(new ImGroupMemberDO().setGroupId(groupId).setUserId(userId) + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setRole(role).setJoinTime(now) + .setAddSource(addSource).setInviterUserId(inviterUserId)); + } else if (CommonStatusEnum.DISABLE.getStatus().equals(exist.getStatus())) { + updates.add(new ImGroupMemberDO().setId(exist.getId()) + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setRole(role).setJoinTime(now) + .setAddSource(addSource).setInviterUserId(inviterUserId)); + } + } + + // 2.1 先做 update,update 没有并发冲突风险 + if (CollUtil.isNotEmpty(updates)) { + for (ImGroupMemberDO update : updates) { + groupMemberMapper.updateRejoinFields(update.getId(), update.getStatus(), update.getJoinTime(), + update.getRole(), update.getAddSource(), update.getInviterUserId()); + } + } + // 2.2 批量 insert。并发场景下若其它请求已先一步插入同一 (groupId, userId), + // 会触发唯一索引冲突,此时降级为逐个 addGroupMember(利用其兜底逻辑幂等处理)。 + if (CollUtil.isNotEmpty(inserts)) { + try { + groupMemberMapper.insertBatch(inserts); + } catch (DuplicateKeyException e) { + log.warn("[addGroupMembers][groupId({}) userIds({}) 批量插入冲突,降级为逐个处理]", groupId, userIds); + for (ImGroupMemberDO insert : inserts) { + addGroupMember(groupId, insert.getUserId(), role, addSource, inviterUserId); + } + } + } + } + + @Override + public ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId) { + ImGroupMemberDO member = groupMemberMapper.selectByGroupIdAndUserId(groupId, userId); + if (member == null || CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) { + throw exception(GROUP_MEMBER_NOT_IN_GROUP); + } + return member; + } + + @Override + public void validateMembersInGroup(Long groupId, Collection userIds) { + if (CollUtil.isEmpty(userIds)) { + return; + } + // 一次性拉取目标 userId 的成员记录,仅保留活跃状态 + List members = groupMemberMapper.selectListByGroupIdAndUserIds(groupId, userIds); + Set activeUserIds = convertSet(members, ImGroupMemberDO::getUserId, + member -> CommonStatusEnum.ENABLE.getStatus().equals(member.getStatus())); + // 任一 userId 不在活跃集合即抛 + for (Long userId : userIds) { + if (!activeUserIds.contains(userId)) { + throw exception(GROUP_MEMBER_NOT_IN_GROUP); + } + } + } + + @Override + public int updateGroupMemberRole(Long groupId, Collection userIds, Integer role) { + if (CollUtil.isEmpty(userIds)) { + return 0; + } + return groupMemberMapper.updateListByGroupIdAndUserIds(groupId, userIds, new ImGroupMemberDO().setRole(role)); + } + + @Override + public Long getGroupMemberCountByRole(Long groupId, Integer role) { + return groupMemberMapper.selectCountByGroupIdAndRoleAndStatus( + groupId, role, CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public void updateGroupMember(Long userId, ImGroupMemberUpdateReqVO updateReqVO) { + Long groupId = updateReqVO.getGroupId(); + // 1. 校验是群的有效成员 + ImGroupMemberDO member = validateMemberInGroup(groupId, userId); + + // 2. 更新群成员信息 + ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class) + .setId(member.getId()); + groupMemberMapper.updateById(updateObj); + + // 3.1 displayUserName 是公开字段,单独走 GROUP_MEMBER_NICKNAME_UPDATE 在线同步给全员;空串视为「清空昵称」也要同步;与旧值相同跳过 + if (updateReqVO.getDisplayUserName() != null + && ObjUtil.notEqual(updateReqVO.getDisplayUserName(), member.getDisplayUserName())) { + groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupMemberNicknameUpdate( + groupId, userId, updateReqVO.getDisplayUserName())); + } + // 3.2 silent / groupRemark 是个人字段,仅推自己做多端同步;与旧值都相同跳过 + boolean silentChanged = updateReqVO.getSilent() != null + && ObjUtil.notEqual(updateReqVO.getSilent(), member.getSilent()); + boolean groupRemarkChanged = updateReqVO.getGroupRemark() != null + && ObjUtil.notEqual(updateReqVO.getGroupRemark(), member.getGroupRemark()); + if (silentChanged || groupRemarkChanged) { + groupMessageService.sendGroupMessage(userId, ListUtil.of(userId), ImGroupMessageSendDTO.ofGroupMemberSettingUpdate( + groupId, userId, updateReqVO.getSilent(), updateReqVO.getGroupRemark())); + } + } + + @Override + @CacheEvict(cacheNames = GROUP_MEMBER_IDS, key = "#groupId") + public void removeGroupMember(Long groupId, Long userId) { + // 1. 校验是群的有效成员 + ImGroupMemberDO member = validateMemberInGroup(groupId, userId); + // 2. 更新为退群状态 + groupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId()) + .setStatus(CommonStatusEnum.DISABLE.getStatus()) + .setQuitTime(LocalDateTime.now())); + } + + @Override + @CacheEvict(cacheNames = GROUP_MEMBER_IDS, key = "#groupId") + public void removeGroupMembers(Long groupId, Collection userIds) { + groupMemberMapper.updateByGroupIdAndUserIdsAndStatus(groupId, userIds, + CommonStatusEnum.ENABLE.getStatus(), + new ImGroupMemberDO().setStatus(CommonStatusEnum.DISABLE.getStatus()) + .setQuitTime(LocalDateTime.now())); + } + + @Override + @CacheEvict(cacheNames = GROUP_MEMBER_IDS, key = "#groupId") + public void removeGroupMembersByGroupId(Long groupId) { + groupMemberMapper.updateByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus(), + new ImGroupMemberDO().setStatus(CommonStatusEnum.DISABLE.getStatus()) + .setQuitTime(LocalDateTime.now())); + } + + @Override + public Map getActiveMemberCountMap(Collection groupIds) { + return groupMemberMapper.selectCountMapByGroupIdsAndStatus(groupIds, CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public void updateGroupMemberMuteEndTime(Long groupId, Long userId, LocalDateTime muteEndTime) { + ImGroupMemberDO member = validateMemberInGroup(groupId, userId); + if (muteEndTime != null) { + // 禁言:直接更新到期时间 + groupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId()).setMuteEndTime(muteEndTime)); + } else { + // 取消禁言 + groupMemberMapper.updateMuteEndTimeNull(member.getId()); + } + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java new file mode 100644 index 0000000000..1c15d9b134 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java @@ -0,0 +1,105 @@ +package cn.iocoder.yudao.module.im.service.group; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.request.ImGroupRequestApplyReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupRequestManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * IM 加群申请 Service 接口 + * + * @author 芋道源码 + */ +public interface ImGroupRequestService { + + /** + * 用户主动申请加群 + *

+ * 群未开启审批时直接入群 + 1510 全员广播;开启审批则创建或复用一条待审批记录 + 1503 推送 + * + * @param userId 申请人用户编号 + * @param reqVO 申请请求 + * @return 申请记录;自由进群直进时返回 null + */ + ImGroupRequestDO applyJoinGroup(Long userId, @Valid ImGroupRequestApplyReqVO reqVO); + + /** + * 同意加群申请(群主或管理员);处理前校验入群人数上限 + * + * @param userId 操作人用户编号 + * @param requestId 申请记录编号 + */ + void agreeGroupRequest(Long userId, Long requestId); + + /** + * 拒绝加群申请(群主或管理员) + * + * @param userId 操作人用户编号 + * @param requestId 申请记录编号 + * @param handleContent 拒绝理由 + */ + void refuseGroupRequest(Long userId, Long requestId, String handleContent); + + /** + * 邀请创建审批申请;inviteGroupMember 在群开启审批时调用,每个被邀请人创建或复用一条待审批记录 + * + * @param groupId 群编号 + * @param inviterUserId 邀请人用户编号 + * @param invitedUserIds 被邀请人用户编号集合 + */ + void createInviteRequestList(Long groupId, Long inviterUserId, Collection invitedUserIds); + + /** + * 拉取「我管理的所有群」下的未处理申请列表 + *

+ * 前端 store 据此派生:每个群的未处理总数(用于群顶部横幅红点)+ 列表内容(用于 Drawer) + * + * @param userId 当前用户编号;后端按 ImGroupMember.role 过滤出我作为 OWNER / ADMIN 的群 + * @return 未处理申请列表(不分页) + */ + List getUnhandledRequestListByOwnerOrAdmin(Long userId); + + /** + * 增量拉取「我管理的群」下的加群申请(重连 / 离线补偿:含已处理,按 update_time + id 游标) + *

+ * 作用域与 {@link #getUnhandledRequestListByOwnerOrAdmin} 一致:按 ImGroupMember.role 取我作为 OWNER / ADMIN 的群 + * + * @param userId 当前用户编号 + * @param lastUpdateTime 游标:上次拉取的最后一条更新时间戳(毫秒) + * @param lastId 游标:上次拉取的最后一条申请编号 + * @param limit 单次拉取条数 + * @return 申请记录列表 + */ + List pullGroupRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit); + + /** + * 拉取指定群下的全部加群申请(含已处理);仅群主 / 管理员可查 + *

+ * 用于群「进群申请」子页:最新一条卡片化突出 + 历史申请按 id 倒序 + * + * @param userId 当前用户编号;用于校验 owner / admin 身份 + * @param groupId 群编号 + * @return 申请记录列表,按 id 倒序 + */ + List getGroupRequestListByGroupId(Long userId, Long groupId); + + /** + * 按 id 单查申请记录;通用读接口,越权过滤交由调用方 + * + * @param id 申请记录编号 + * @return 申请记录 + */ + ImGroupRequestDO getGroupRequest(Long id); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询加群申请记录 + */ + PageResult getGroupRequestPage(ImGroupRequestManagerPageReqVO reqVO); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java new file mode 100644 index 0000000000..40d1cadf92 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java @@ -0,0 +1,425 @@ +package cn.iocoder.yudao.module.im.service.group; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.request.ImGroupRequestApplyReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupRequestManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; +import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupRequestMapper; +import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; +import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.group.BaseGroupNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.group.GroupRequestApprovedNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.group.GroupRequestReceivedNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.group.GroupRequestRejectedNotification; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; + +/** + * IM 加群申请 Service 实现类 + * + * @author 芋道源码 + */ +@Slf4j +@Service +@Validated +public class ImGroupRequestServiceImpl implements ImGroupRequestService { + + @Resource + private ImGroupRequestMapper groupRequestMapper; + + @Resource + @Lazy // 避免循环依赖 + private ImGroupService groupService; + @Resource + @Lazy // 避免循环依赖 + private ImGroupMemberService groupMemberService; + @Resource + @Lazy // 避免循环依赖 + private ImGroupMessageService groupMessageService; + + @Resource + private ImWebSocketService websocketService; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public ImGroupRequestDO applyJoinGroup(Long userId, ImGroupRequestApplyReqVO reqVO) { + Long groupId = reqVO.getGroupId(); + // 1.1 校验群存在 + 未封禁 / 未解散 + ImGroupDO group = groupService.validateGroupExists(groupId); + // 1.2 校验未在群中 + ImGroupMemberDO member = groupMemberService.getGroupMember(groupId, userId); + if (member != null && !CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) { + throw exception(GROUP_REQUEST_ALREADY_MEMBER); + } + + // 2. 情况一:群未开启审批,直接入群;写群成员留痕 + 推 1510 全员广播;不落申请记录 + if (!Boolean.TRUE.equals(group.getJoinApproval())) { + // 入群前校验人数上限 + groupService.validateMemberCountLimit(groupId, 1); + // 写群成员;addSource 来自前端入口(搜索 / 二维码 / 分享链接),inviterUserId=null(主动申请) + groupMemberService.addGroupMember(groupId, userId, + ImGroupMemberRoleEnum.NORMAL.getRole(), reqVO.getAddSource(), null); + // 推 1510 给全员;payload 含进群者 + 来源,前端按 entrantUserId 局部插入新成员 + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupMemberEnter(groupId, userId, reqVO.getAddSource())); + return null; + } + + // 3. 情况二:群开启了审批,创建或复用一条主动申请记录 + ImGroupRequestDO request = createOrResetApplyRequest(groupId, userId, reqVO); + + // 4. 1503 定向推群主 + 全部管理员(多端同步);payload 携带申请方昵称 / 头像 + AdminUserRespDTO applyUser = adminUserApi.getUser(userId); + GroupRequestReceivedNotification payload = buildRequestNotification(group, request, applyUser); + for (Long receiverUserId : getGroupMemberListByOwnerAndAdminUserIds(group)) { + websocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.GROUP_REQUEST_RECEIVED.getType(), payload); + } + return request; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void agreeGroupRequest(Long userId, Long requestId) { + // 1.1 校验申请存在 + 未处理 + 操作人是 owner / admin + ImGroupRequestDO request = validateRequestForHandle(userId, requestId); + // 1.2 复核群当前状态:拒绝在封禁 / 解散的群继续放人 + groupService.validateGroupExists(request.getGroupId()); + // 1.3 复核申请人是否已在群中;幂等避免重复广播 1509 / 1510 入群事件 + ImGroupMemberDO applicant = groupMemberService.getGroupMember(request.getGroupId(), request.getUserId()); + if (applicant != null && CommonStatusEnum.ENABLE.getStatus().equals(applicant.getStatus())) { + throw exception(GROUP_REQUEST_ALREADY_MEMBER); + } + // 2. 入群前校验人数上限;群已满抛错让操作人选择拒绝 + groupService.validateMemberCountLimit(request.getGroupId(), 1); + + // 3. 乐观锁推进状态 + LocalDateTime now = LocalDateTime.now(); + ImGroupRequestDO updateObj = new ImGroupRequestDO() + .setHandleResult(ImGroupRequestHandleResultEnum.AGREED.getResult()) + .setHandleUserId(userId).setHandleTime(now); + int affected = groupRequestMapper.updateByIdAndHandleResult(request.getId(), + ImGroupRequestHandleResultEnum.UNHANDLED.getResult(), updateObj); + if (affected == 0) { + throw exception(GROUP_REQUEST_HANDLED); + } + request.setHandleResult(ImGroupRequestHandleResultEnum.AGREED.getResult()) + .setHandleUserId(userId).setHandleTime(now); + + // 4. 写群成员;addSource / inviterUserId 沿用申请记录上的来源信息 + groupMemberService.addGroupMember(request.getGroupId(), request.getUserId(), + ImGroupMemberRoleEnum.NORMAL.getRole(), request.getAddSource(), request.getInviterUserId()); + + // 5.1 1505 定向推送给申请人 + 群主 + 全部管理员(每端单推) + GroupRequestApprovedNotification payload = (GroupRequestApprovedNotification) new GroupRequestApprovedNotification() + .setRequestId(request.getId()).setGroupId(request.getGroupId()).setUserId(request.getUserId()) + .setOperatorUserId(userId); + broadcastToOwnerAdminsAndApplicant(request.getGroupId(), request.getUserId(), payload, + ImContentTypeEnum.GROUP_REQUEST_APPROVED.getType(), userId); + // 5.2 群事件:主动申请 → 1510 自由进群;被邀请 → 1509 成员加入 + if (request.getInviterUserId() == null) { + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupMemberEnter(request.getGroupId(), + request.getUserId(), request.getAddSource())); + } else { + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupMemberInvite(request.getGroupId(), + request.getInviterUserId(), Collections.singleton(request.getUserId()))); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void refuseGroupRequest(Long userId, Long requestId, String handleContent) { + // 1. 校验 + ImGroupRequestDO request = validateRequestForHandle(userId, requestId); + + // 2. 乐观锁推进 + ImGroupRequestDO updateObj = new ImGroupRequestDO() + .setHandleResult(ImGroupRequestHandleResultEnum.REFUSED.getResult()) + .setHandleContent(handleContent) + .setHandleUserId(userId).setHandleTime(LocalDateTime.now()); + int affected = groupRequestMapper.updateByIdAndHandleResult(request.getId(), + ImGroupRequestHandleResultEnum.UNHANDLED.getResult(), updateObj); + if (affected == 0) { + throw exception(GROUP_REQUEST_HANDLED); + } + + // 3. 1506 定向推送给申请人 + 群主 + 全部管理员 + GroupRequestRejectedNotification payload = (GroupRequestRejectedNotification) new GroupRequestRejectedNotification() + .setRequestId(request.getId()).setGroupId(request.getGroupId()).setUserId(request.getUserId()) + .setHandleContent(handleContent).setOperatorUserId(userId); + broadcastToOwnerAdminsAndApplicant(request.getGroupId(), request.getUserId(), payload, + ImContentTypeEnum.GROUP_REQUEST_REJECTED.getType(), userId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void createInviteRequestList(Long groupId, Long inviterUserId, Collection invitedUserIds) { + if (CollUtil.isEmpty(invitedUserIds)) { + return; + } + ImGroupDO group = groupService.validateGroupExists(groupId); + Integer inviteSource = ImGroupAddSourceEnum.INVITE.getSource(); + // 1. 逐条创建或复用邀请申请 + List requests = convertList(invitedUserIds, userId -> + createOrResetInviteRequest(groupId, inviterUserId, userId, inviteSource)); + + // 2. 推 1503 给群主 + 全部管理员;多端同步;每条申请单独推一帧 + Map userMap = adminUserApi.getUserMap(invitedUserIds); + List ownerAndAdmins = getGroupMemberListByOwnerAndAdminUserIds(group); + for (ImGroupRequestDO request : requests) { + AdminUserRespDTO applyUser = userMap.get(request.getUserId()); + GroupRequestReceivedNotification payload = buildRequestNotification(group, request, applyUser); + for (Long receiverUserId : ownerAndAdmins) { + websocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.GROUP_REQUEST_RECEIVED.getType(), payload); + } + } + } + + @Override + public List getUnhandledRequestListByOwnerOrAdmin(Long userId) { + // 1. 找出当前用户作为 OWNER / ADMIN 的所有群 + List myMembers = groupMemberService.getActiveGroupMemberListByUserId(userId); + Set ownerOrAdminGroupIds = convertSet(myMembers, + ImGroupMemberDO::getGroupId, member -> ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())); + if (CollUtil.isEmpty(ownerOrAdminGroupIds)) { + return Collections.emptyList(); + } + // 2. 一次拉所有群的未处理申请 + return groupRequestMapper.selectListByGroupIdsAndHandleResult( + ownerOrAdminGroupIds, ImGroupRequestHandleResultEnum.UNHANDLED.getResult()); + } + + @Override + public List pullGroupRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + // 1. 找出当前用户作为 OWNER / ADMIN 的所有群 + List myMembers = groupMemberService.getActiveGroupMemberListByUserId(userId); + Set ownerOrAdminGroupIds = convertSet(myMembers, + ImGroupMemberDO::getGroupId, member -> ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())); + if (CollUtil.isEmpty(ownerOrAdminGroupIds)) { + return Collections.emptyList(); + } + // 2. 按游标增量拉取这些群下的申请 + return groupRequestMapper.selectPullListByGroupIds(ownerOrAdminGroupIds, lastUpdateTime, lastId, limit); + } + + @Override + public List getGroupRequestListByGroupId(Long userId, Long groupId) { + // 1. 校验群存在 + 当前用户是群主 / 管理员 + groupService.validateGroupExists(groupId); + ImGroupMemberDO operator = groupMemberService.validateMemberInGroup(groupId, userId); + if (!ImGroupMemberRoleEnum.isOwnerOrAdmin(operator.getRole())) { + throw exception(GROUP_REQUEST_NOT_TO_ME); + } + // 2. 拉取该群下全部申请(含已处理);按 id 倒序,前端首条卡片化展示 + return groupRequestMapper.selectListByGroupId(groupId); + } + + @Override + public ImGroupRequestDO getGroupRequest(Long id) { + return groupRequestMapper.selectById(id); + } + + @Override + public PageResult getGroupRequestPage(ImGroupRequestManagerPageReqVO reqVO) { + return groupRequestMapper.selectPage(reqVO); + } + + /** + * 创建或重置主动加群申请 + * + * @param groupId 群编号 + * @param userId 申请人用户编号 + * @param reqVO 申请请求 + * @return 申请记录 + */ + private ImGroupRequestDO createOrResetApplyRequest(Long groupId, Long userId, ImGroupRequestApplyReqVO reqVO) { + // 1. 已有申请:覆盖本次申请内容,并重置为未处理 + ImGroupRequestDO request = groupRequestMapper.selectByGroupIdAndUserId(groupId, userId); + if (request != null) { + resetApplyRequest(request, reqVO); + return request; + } + // 2. 无旧申请:创建主动申请记录 + request = BeanUtils.toBean(reqVO, ImGroupRequestDO.class) + .setUserId(userId).setInviterUserId(null) + .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()); + try { + groupRequestMapper.insert(request); + return request; + } catch (DuplicateKeyException ex) { + // 3. 唯一键冲突:回查并复用并发写入的记录 + request = groupRequestMapper.selectByGroupIdAndUserId(groupId, userId); + if (request == null) { + throw ex; + } + resetApplyRequest(request, reqVO); + return request; + } + } + + /** + * 创建或重置邀请加群申请 + * + * @param groupId 群编号 + * @param inviterUserId 邀请人用户编号 + * @param userId 被邀请人用户编号 + * @param inviteSource 邀请来源 + * @return 申请记录 + */ + private ImGroupRequestDO createOrResetInviteRequest(Long groupId, Long inviterUserId, + Long userId, Integer inviteSource) { + // 1. 已有申请:覆盖邀请人和来源,并重置为未处理 + ImGroupRequestDO request = groupRequestMapper.selectByGroupIdAndUserId(groupId, userId); + if (request != null) { + resetInviteRequest(request, inviterUserId, inviteSource); + return request; + } + // 2. 无旧申请:创建邀请申请记录 + request = new ImGroupRequestDO().setGroupId(groupId).setUserId(userId).setInviterUserId(inviterUserId) + .setAddSource(inviteSource).setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()); + try { + groupRequestMapper.insert(request); + return request; + } catch (DuplicateKeyException ex) { + // 3. 唯一键冲突:回查并复用并发写入的记录 + request = groupRequestMapper.selectByGroupIdAndUserId(groupId, userId); + if (request == null) { + throw ex; + } + resetInviteRequest(request, inviterUserId, inviteSource); + return request; + } + } + + /** + * 重置主动加群申请 + * + * @param request 申请记录 + * @param reqVO 申请请求 + */ + private void resetApplyRequest(ImGroupRequestDO request, ImGroupRequestApplyReqVO reqVO) { + // 1. 更新申请内容、来源和处理状态 + LocalDateTime now = LocalDateTime.now(); + groupRequestMapper.updateApplyByIdReset(request.getId(), + reqVO.getApplyContent(), reqVO.getAddSource(), now); + // 2. 同步内存对象,后续通知构建直接复用 + request.setApplyContent(reqVO.getApplyContent()).setAddSource(reqVO.getAddSource()) + .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) + .setInviterUserId(null).setHandleUserId(null) + .setHandleContent(null).setHandleTime(null).setUpdateTime(now); + } + + /** + * 重置邀请加群申请 + * + * @param request 申请记录 + * @param inviterUserId 邀请人用户编号 + * @param inviteSource 邀请来源 + */ + private void resetInviteRequest(ImGroupRequestDO request, Long inviterUserId, Integer inviteSource) { + // 1. 更新邀请人、来源和处理状态 + LocalDateTime now = LocalDateTime.now(); + groupRequestMapper.updateInviteByIdReset(request.getId(), inviterUserId, inviteSource, now); + // 2. 同步内存对象,后续通知构建直接复用 + request.setInviterUserId(inviterUserId).setAddSource(inviteSource) + .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()) + .setHandleUserId(null).setHandleContent(null).setHandleTime(null) + .setUpdateTime(now); + } + + /** + * 校验申请可被「当前用户」处理:申请存在 + 未处理 + 操作人是群主 / 管理员 + */ + private ImGroupRequestDO validateRequestForHandle(Long userId, Long requestId) { + ImGroupRequestDO request = groupRequestMapper.selectById(requestId); + if (request == null) { + throw exception(GROUP_REQUEST_NOT_EXISTS); + } + if (!ImGroupRequestHandleResultEnum.isUnhandled(request.getHandleResult())) { + throw exception(GROUP_REQUEST_HANDLED); + } + ImGroupMemberDO operator = groupMemberService.validateMemberInGroup(request.getGroupId(), userId); + if (!ImGroupMemberRoleEnum.isOwnerOrAdmin(operator.getRole())) { + throw exception(GROUP_REQUEST_NOT_TO_ME); + } + return request; + } + + /** + * 构建 1503 通知 payload;聚合申请方昵称 / 头像供前端直接渲染 + */ + private GroupRequestReceivedNotification buildRequestNotification(ImGroupDO group, ImGroupRequestDO request, + AdminUserRespDTO applyUser) { + Long operatorUserId = request.getInviterUserId() != null ? request.getInviterUserId() : request.getUserId(); + GroupRequestReceivedNotification payload = (GroupRequestReceivedNotification) new GroupRequestReceivedNotification() + .setRequestId(request.getId()).setGroupId(group.getId()).setUserId(request.getUserId()) + .setInviterUserId(request.getInviterUserId()) + .setApplyContent(request.getApplyContent()).setAddSource(request.getAddSource()) + .setOperatorUserId(operatorUserId); + if (applyUser != null) { + payload.setUserNickname(applyUser.getNickname()).setUserAvatar(applyUser.getAvatar()); + } + return payload; + } + + /** + * 1505 / 1506 受众:申请人 + 群主 + 全部管理员 + */ + private void broadcastToOwnerAdminsAndApplicant(Long groupId, Long applicantUserId, BaseGroupNotification payload, + Integer messageType, Long operatorUserId) { + ImGroupDO group = groupService.getGroup(groupId); + if (group == null) { + return; + } + Set receivers = new LinkedHashSet<>(getGroupMemberListByOwnerAndAdminUserIds(group)); + receivers.add(applicantUserId); + for (Long receiverUserId : receivers) { + websocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(), + messageType, payload); + } + } + + /** + * 列出群主 + 全部管理员的用户编号 + * + * @param group 群信息 + * @return 群主 + 全部管理员的用户编号列表 + */ + private List getGroupMemberListByOwnerAndAdminUserIds(ImGroupDO group) { + return convertList(groupMemberService.getGroupMemberListByOwnerAndAdmin(group.getId()), + ImGroupMemberDO::getUserId); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java new file mode 100644 index 0000000000..d3b4fd48c4 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java @@ -0,0 +1,251 @@ +package cn.iocoder.yudao.module.im.service.group; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupAdminAddReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupAdminRemoveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCancelMuteMemberReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupMuteAllReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupMuteMemberReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupTransferOwnerReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 用户群群 Service 接口 + * + * @author 芋道源码 + */ +public interface ImGroupService { + + // ==================== 群的写操作 ==================== + + /** + * 创建群 + *

+ * 同时将当前登录用户设置为群主,并插入群主的群成员记录 + * + * @param createReqVO 创建信息 + * @param userId 当前登录用户编号(群主) + * @return 创建后的群信息 + */ + ImGroupDO createGroup(@Valid ImGroupCreateReqVO createReqVO, Long userId); + + /** + * 更新群信息 + * + * @param updateReqVO 更新信息 + * @param userId 当前登录用户编号 + * @return 更新后的群信息 + */ + ImGroupDO updateGroup(@Valid ImGroupUpdateReqVO updateReqVO, Long userId); + + /** + * 解散群 + *

+ * 仅群主可执行 + * + * @param id 群编号 + * @param userId 当前登录用户编号 + */ + void dissolveGroup(Long id, Long userId); + + // ==================== 群的读操作 ==================== + + /** + * 获得群 + * + * @param id 编号 + * @return 群 + */ + ImGroupDO getGroup(Long id); + + /** + * 批量获得群 Map + * + * @param ids 群编号集合 + * @return 群 Map(key = 群编号) + */ + Map getGroupMap(Collection ids); + + /** + * 校验群存在且未封禁、未解散 + * + * @param groupId 群编号 + * @return 群信息 + */ + ImGroupDO validateGroupExists(Long groupId); + + /** + * 校验入群人数上限 + *

+ * 调用方场景:自由进群 / 审批通过等不经 inviteGroupMember 的入群路径,需在写群成员前主动校验 + * + * @param groupId 群编号 + * @param addCount 即将新增的人数 + * @throws cn.iocoder.yudao.framework.common.exception.ServiceException 群已满抛 GROUP_MEMBER_EXCEED + */ + void validateMemberCountLimit(Long groupId, int addCount); + + /** + * 获取指定用户的群列表 + *

+ * 返回用户当前仍有效的群,以及最近 yudao.im.message.group-pull-max-days 天内退群的群 + * —— 退群前可能还有离线消息需要展示,前端需要把这些群信息作为缓存。 + * + * @param userId 用户编号 + * @return 群列表 + */ + List getMyGroupList(Long userId); + + // ==================== 群成员的写操作 ==================== + // 说明:群成员的写操作统一放在 ImGroupService,而非 ImGroupMemberService, + // 保持 ImGroupMemberService 无 WebSocket 推送等外部依赖,职责更单一。 + + /** + * 邀请用户加入群 + *

+ * 群成员即可执行,支持批量。 + * 校验群人数上限,邀请后推送提示消息和群创建事件给被邀请人。 + * + * @param userId 当前登录用户编号 + * @param inviteReqVO 邀请信息 + */ + void inviteGroupMember(Long userId, @Valid ImGroupMemberInviteReqVO inviteReqVO); + + /** + * 退群 + *

+ * 群主不可退群(只能解散) + * + * @param groupId 群编号 + * @param userId 当前登录用户编号 + */ + void quitGroup(Long groupId, Long userId); + + /** + * 移除群成员(踢人) + *

+ * 群主可踢管理员和普通成员;管理员仅能踢普通成员;群主不可被踢。 + * + * @param userId 当前登录用户编号 + * @param removeReqVO 移除信息 + */ + void removeGroupMember(Long userId, @Valid ImGroupMemberRemoveReqVO removeReqVO); + + /** + * 添加群管理员(仅群主可执行) + * + * @param userId 当前登录用户编号(群主) + * @param reqVO 添加信息(含群编号、目标用户编号列表) + */ + void addGroupAdmin(Long userId, @Valid ImGroupAdminAddReqVO reqVO); + + /** + * 撤销群管理员(仅群主可执行) + * + * @param userId 当前登录用户编号(群主) + * @param reqVO 撤销信息(含群编号、目标用户编号列表) + */ + void removeGroupAdmin(Long userId, @Valid ImGroupAdminRemoveReqVO reqVO); + + /** + * 转让群主(仅老群主可执行) + *

+ * 转让后:旧群主 role 降为 MEMBER,新群主 role 升为 OWNER + * + * @param userId 当前登录用户编号(旧群主) + * @param transferReqVO 转让信息 + */ + void transferGroupOwner(Long userId, @Valid ImGroupTransferOwnerReqVO transferReqVO); + + /** + * 置顶群消息(仅群主或管理员可执行) + *

+ * 上限由 yudao.im.group.pin-max-count 控制;幂等失败时抛业务异常 + * + * @param userId 当前登录用户编号 + * @param groupId 群编号 + * @param messageId 被置顶的消息编号 + */ + void pinGroupMessage(Long userId, Long groupId, Long messageId); + + /** + * 取消置顶群消息(仅群主或管理员可执行) + * + * @param userId 当前登录用户编号 + * @param groupId 群编号 + * @param messageId 被取消置顶的消息编号 + */ + void unpinGroupMessage(Long userId, Long groupId, Long messageId); + + // ==================== 群禁言 ==================== + + /** + * 全群禁言 / 取消(仅群主或管理员可执行) + * + * @param userId 当前登录用户编号 + * @param reqVO 禁言信息 + */ + void muteAll(Long userId, @Valid ImGroupMuteAllReqVO reqVO); + + /** + * 禁言单个成员(三档分层权限) + * + * @param userId 当前登录用户编号 + * @param reqVO 禁言信息 + */ + void muteMember(Long userId, @Valid ImGroupMuteMemberReqVO reqVO); + + /** + * 取消成员禁言(三档分层权限) + * + * @param userId 当前登录用户编号 + * @param reqVO 取消禁言信息 + */ + void cancelMuteMember(Long userId, @Valid ImGroupCancelMuteMemberReqVO reqVO); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询群列表 + * + * @param pageReqVO 分页查询条件 + * @return 群分页列表 + */ + PageResult getGroupPage(ImGroupManagerPageReqVO pageReqVO); + + /** + * 【管理后台】封禁群 + * + * @param operatorUserId 操作人用户编号 + * @param banReqVO 封禁信息(含群编号、封禁原因) + */ + void banGroup(Long operatorUserId, @Valid ImGroupManagerBanReqVO banReqVO); + + /** + * 【管理后台】解封群 + * + * @param operatorUserId 操作人用户编号 + * @param id 群编号 + */ + void unbanGroup(Long operatorUserId, Long id); + + /** + * 【管理后台】解散群 + * + * @param operatorUserId 操作人用户编号 + * @param id 群编号 + */ + void dissolveGroupByManager(Long operatorUserId, Long id); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java new file mode 100644 index 0000000000..93fa996831 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java @@ -0,0 +1,773 @@ +package cn.iocoder.yudao.module.im.service.group; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupAdminAddReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupAdminRemoveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCancelMuteMemberReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupMuteAllReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupMuteMemberReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupTransferOwnerReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; +import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; +import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; + +/** + * 用户群 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImGroupServiceImpl implements ImGroupService { + + @Resource + private ImGroupMapper groupMapper; + + @Resource + @Lazy // 避免循环依赖 + private ImGroupMemberService groupMemberService; + @Resource + @Lazy // 避免循环依赖 + private ImGroupMessageService groupMessageService; + @Resource + @Lazy // 避免循环依赖 + private ImGroupRequestService groupRequestService; + + @Resource + private ImFriendService friendService; + + @Resource + private AdminUserApi adminUserApi; + + @Resource + private ImProperties imProperties; + + // ==================== 群的写操作 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + public ImGroupDO createGroup(ImGroupCreateReqVO createReqVO, Long userId) { + // 1.1 处理初始成员列表(去重 + 排除创建者自己) + Set initialMemberUserIds = createReqVO.getMemberUserIds() == null + ? new HashSet<>() : new HashSet<>(createReqVO.getMemberUserIds()); + initialMemberUserIds.remove(userId); + // 1.2 校验初始成员都是创建者的好友 + if (CollUtil.isNotEmpty(initialMemberUserIds)) { + List friends = friendService.getActiveFriendList(userId, initialMemberUserIds); + Set friendUserIds = convertSet(friends, ImFriendDO::getFriendUserId); + Collection notFriendUserIds = CollUtil.subtract(initialMemberUserIds, friendUserIds); + if (CollUtil.isNotEmpty(notFriendUserIds)) { + throw exception(GROUP_INVITE_NOT_FRIEND, getUserNicknames(notFriendUserIds)); + } + } + // 1.3 校验群人数上限(创建者 + 初始成员 ≤ 群成员上限) + int maxMember = imProperties.getGroup().getMaxMember(); + if (initialMemberUserIds.size() + 1 > maxMember) { + throw exception(GROUP_MEMBER_EXCEED, maxMember); + } + + // 2.1 插入群记录 + ImGroupDO group = BeanUtils.toBean(createReqVO, ImGroupDO.class) + .setOwnerUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus()); + groupMapper.insert(group); + // 2.2 创建者作为 OWNER 入群 + groupMemberService.addGroupMember(group.getId(), userId, ImGroupMemberRoleEnum.OWNER.getRole()); + // 2.3 批量添加初始成员;标记 addSource=INVITE / inviter=创建者 + if (CollUtil.isNotEmpty(initialMemberUserIds)) { + groupMemberService.addGroupMembers(group.getId(), initialMemberUserIds, + ImGroupAddSourceEnum.INVITE.getSource(), userId); + } + + // 3. 推送 GROUP_CREATE 通知给全员(含创建者多端同步 + 初始成员) + List allMemberUserIds = CollectionUtils.of(userId, initialMemberUserIds); + groupMessageService.sendGroupMessage(userId, allMemberUserIds, + ImGroupMessageSendDTO.ofGroupCreate(group.getId(), userId, allMemberUserIds)); + return group; + } + + @Override + @CacheEvict(cacheNames = GROUP, key = "#updateReqVO.id") + @Transactional(rollbackFor = Exception.class) + public ImGroupDO updateGroup(ImGroupUpdateReqVO updateReqVO, Long userId) { + // 1.1 校验群存在:group 留作老值备份,通知里 oldXXX 字段从这里取 + ImGroupDO group = validateGroupExists(updateReqVO.getId()); + // 1.2 校验操作人是群主 + if (ObjUtil.notEqual(group.getOwnerUserId(), userId)) { + throw exception(GROUP_NOT_OWNER); + } + + // 2. 更新数据库(newGroup 仅含变更字段) + ImGroupDO newGroup = BeanUtils.toBean(updateReqVO, ImGroupDO.class); + groupMapper.updateById(newGroup); + + // 3. 按变更字段分别推送 GROUP_NAME / NOTICE / INFO_UPDATE 通知;活跃成员只查一次复用,避免 3 次 Redis GET + // name / avatar 不允许空串(业务上必须非空),notice 允许空串(清空公告也是有效操作) + Long groupId = group.getId(); + boolean nameChanged = StrUtil.isNotEmpty(updateReqVO.getName()); + boolean noticeChanged = updateReqVO.getNotice() != null; + boolean avatarChanged = StrUtil.isNotEmpty(updateReqVO.getAvatar()); + boolean joinApprovalChanged = updateReqVO.getJoinApproval() != null + && ObjUtil.notEqual(group.getJoinApproval(), updateReqVO.getJoinApproval()); + if (nameChanged || noticeChanged || avatarChanged || joinApprovalChanged) { + List memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(groupId); + if (nameChanged) { + groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupNameUpdate( + groupId, userId, group.getName(), updateReqVO.getName())); + } + if (noticeChanged) { + groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupNoticeUpdate( + groupId, userId, group.getNotice(), updateReqVO.getNotice())); + } + if (avatarChanged) { + groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupInfoUpdate( + groupId, userId, group.getAvatar(), updateReqVO.getAvatar(), null, null)); + } + if (joinApprovalChanged) { + groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupInfoUpdate( + groupId, userId, null, null, group.getJoinApproval(), updateReqVO.getJoinApproval())); + } + } + + // 4. 返回合并后的新群信息(updateReqVO 非空字段覆盖 group) + BeanUtil.copyProperties(updateReqVO, group, CopyOptions.create().ignoreNullValue()); + return group; + } + + @Override + @CacheEvict(cacheNames = GROUP, key = "#id") + @Transactional(rollbackFor = Exception.class) + public void dissolveGroup(Long id, Long userId) { + // 1. 校验群存在 + 当前用户是群主 + ImGroupDO group = validateGroupNotDissolved(id); + if (ObjUtil.notEqual(group.getOwnerUserId(), userId)) { + throw exception(GROUP_NOT_OWNER); + } + + // 2. 解散群 + dissolveGroup0(id, userId); + } + + private void dissolveGroup0(Long id, Long userId) { + // 1. 先发 GROUP_DISSOLVE 通知:放在成员移除前,sendGroupMessage 才能查到全员 + groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupDissolve(id, userId)); + + // 2.1 更新群状态为已解散 + groupMapper.updateById(new ImGroupDO().setId(id) + .setStatus(CommonStatusEnum.DISABLE.getStatus()).setDissolvedTime(LocalDateTime.now())); + // 2.2 移除全部群成员 + groupMemberService.removeGroupMembersByGroupId(id); + } + + // ==================== 群成员的写操作 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + public void inviteGroupMember(Long userId, ImGroupMemberInviteReqVO inviteReqVO) { + Long groupId = inviteReqVO.getGroupId(); + // 1.1 校验群存在 + 当前用户是群成员;同时拿到 role 供下面审批分支判断 + ImGroupDO group = validateGroupExists(groupId); + ImGroupMemberDO operator = groupMemberService.validateMemberInGroup(groupId, userId); + // 1.2 入参去重 + 排除已在群中的用户 + List activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); + Set activeMemberUserIds = convertSet(activeMembers, ImGroupMemberDO::getUserId); + List memberUserIds = CollUtil.subtractToList( + CollUtil.distinct(inviteReqVO.getMemberUserIds()), activeMemberUserIds); + if (CollUtil.isEmpty(memberUserIds)) { + return; + } + // 1.3 校验被邀请人都是当前用户的好友 + List friends = friendService.getActiveFriendList(userId, memberUserIds); + Set friendUserIds = convertSet(friends, ImFriendDO::getFriendUserId); + Collection notFriendUserIds = CollUtil.subtract(memberUserIds, friendUserIds); + if (CollUtil.isNotEmpty(notFriendUserIds)) { + throw exception(GROUP_INVITE_NOT_FRIEND, getUserNicknames(notFriendUserIds)); + } + // 1.4 校验群人数上限 + int maxMember = imProperties.getGroup().getMaxMember(); + if (activeMembers.size() + memberUserIds.size() > maxMember) { + throw exception(GROUP_MEMBER_EXCEED, maxMember); + } + + // 2. 情况一:群开启审批 + 邀请人是普通成员,落 group_request 等群主 / 管理员处理 + // 群主 / 管理员邀请,直接拉人进群 + if (Boolean.TRUE.equals(group.getJoinApproval()) + && !ImGroupMemberRoleEnum.isOwnerOrAdmin(operator.getRole())) { + groupRequestService.createInviteRequestList(groupId, userId, memberUserIds); + return; + } + + // 3. 情况二:未开审批 / 群主 / 管理员邀请,直进;批量添加群成员,写 addSource=INVITE / inviterUserId=操作人 留痕 + groupMemberService.addGroupMembers(groupId, memberUserIds, + ImGroupAddSourceEnum.INVITE.getSource(), userId); + + // 4. 发 GROUP_MEMBER_INVITE 通知给全员;本地拼 receivers(已查的 active + 新邀请)避免缓存刚 evict 后强制走 DB + Set allReceivers = new HashSet<>(memberUserIds); + allReceivers.addAll(activeMemberUserIds); + groupMessageService.sendGroupMessage(userId, allReceivers, + ImGroupMessageSendDTO.ofGroupMemberInvite(groupId, userId, memberUserIds)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void quitGroup(Long groupId, Long userId) { + // 1.1 校验群存在且未解散 + ImGroupDO group = getSelf().getGroup(groupId); + if (group == null) { + throw exception(GROUP_NOT_EXISTS); + } + if (CommonStatusEnum.DISABLE.getStatus().equals(group.getStatus())) { + throw exception(GROUP_DISSOLVED); + } + // 1.2 群主不可退群 + if (ObjUtil.equal(group.getOwnerUserId(), userId)) { + throw exception(GROUP_OWNER_CANNOT_QUIT); + } + // 1.3 校验当前用户是有效群成员;防止非成员触发广播 + 后续 remove 失败时无法回滚已推送的事件 + groupMemberService.validateMemberInGroup(groupId, userId); + + // 2. 先发广播,后移成员(见类 javadoc) + groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupMemberQuit(groupId, userId)); + + // 3. 移除群成员 + groupMemberService.removeGroupMember(groupId, userId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void removeGroupMember(Long userId, ImGroupMemberRemoveReqVO removeReqVO) { + Long groupId = removeReqVO.getGroupId(); + Set targetUserIds = new HashSet<>(removeReqVO.getMemberUserIds()); + // 1.1 校验群存在 + 操作者是群主或管理员 + ImGroupMemberDO operator = validateGroupOwnerOrAdmin(groupId, userId); + // 1.2 不能移除自己 + if (targetUserIds.contains(userId)) { + throw exception(GROUP_CANNOT_REMOVE_SELF); + } + // 1.3 仅保留仍有效的成员;已退群(DISABLE)/ 查无记录的目标直接跳过,只踢有效成员,不让整批失败 + List targets = filterList(groupMemberService.getGroupMembers(groupId, targetUserIds), + target -> CommonStatusEnum.ENABLE.getStatus().equals(target.getStatus())); + Set validTargetUserIds = convertSet(targets, ImGroupMemberDO::getUserId); + // 1.4 目标全部已不在群:无人可踢,直接返回 + if (CollUtil.isEmpty(validTargetUserIds)) { + return; + } + // 1.5 三档权限校验:群主不可被移出;管理员不能移出管理员 + boolean operatorIsAdmin = ImGroupMemberRoleEnum.isAdmin(operator.getRole()); + for (ImGroupMemberDO target : targets) { + if (ImGroupMemberRoleEnum.isOwner(target.getRole())) { + throw exception(GROUP_REMOVE_OWNER_DENIED); + } + if (operatorIsAdmin && ImGroupMemberRoleEnum.isAdmin(target.getRole())) { + throw exception(GROUP_REMOVE_ADMIN_DENIED); + } + } + + // 2. 先发 GROUP_MEMBER_KICK 通知:放在被踢者移除前,sendGroupMessage 才能查到全员(含被踢者) + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupMemberKick(groupId, userId, validTargetUserIds)); + + // 3. 批量移除群成员;不清理读位置(保留退群前历史已读,供离线补偿) + groupMemberService.removeGroupMembers(groupId, validTargetUserIds); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void addGroupAdmin(Long userId, ImGroupAdminAddReqVO reqVO) { + Long groupId = reqVO.getId(); + Set targetUserIds = new HashSet<>(reqVO.getUserIds()); + // 1.1 仅群主可操作 + validateGroupOwnerForUpdate(groupId, userId); + // 1.2 校验目标都是有效成员且非群主 + Map targetMap = convertMap( + groupMemberService.getGroupMembers(groupId, targetUserIds), ImGroupMemberDO::getUserId); + validateAdminTargets(targetUserIds, targetMap); + // 1.3 幂等过滤:跳过已是 ADMIN + Set changedUserIds = convertSet(targetUserIds, + id -> id, + id -> !ImGroupMemberRoleEnum.isAdmin(targetMap.get(id).getRole())); + if (CollUtil.isEmpty(changedUserIds)) { + return; + } + // 1.4 校验上限 + Long existAdminCount = groupMemberService.getGroupMemberCountByRole( + groupId, ImGroupMemberRoleEnum.ADMIN.getRole()); + int adminMaxCount = imProperties.getGroup().getAdminMaxCount(); + if (existAdminCount + changedUserIds.size() > adminMaxCount) { + throw exception(GROUP_ADMIN_MAX_LIMIT, adminMaxCount); + } + + // 2. 批量更新角色 + int affected = groupMemberService.updateGroupMemberRole(groupId, changedUserIds, + ImGroupMemberRoleEnum.ADMIN.getRole()); + if (affected != changedUserIds.size()) { + throw exception(GROUP_ADMIN_TARGET_NOT_IN_GROUP); + } + + // 3. 推送 GROUP_ADMIN_ADD 通知给全员 + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupAdminAdd(groupId, userId, changedUserIds)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void removeGroupAdmin(Long userId, ImGroupAdminRemoveReqVO reqVO) { + Long groupId = reqVO.getId(); + Set targetUserIds = new HashSet<>(reqVO.getUserIds()); + // 1.1 仅群主可操作 + validateGroupOwnerForUpdate(groupId, userId); + // 1.2 校验目标都是有效成员且非群主 + Map targetMap = convertMap( + groupMemberService.getGroupMembers(groupId, targetUserIds), ImGroupMemberDO::getUserId); + validateAdminTargets(targetUserIds, targetMap); + // 1.3 幂等过滤:跳过已是 MEMBER + Set changedUserIds = convertSet(targetUserIds, + id -> id, + id -> ImGroupMemberRoleEnum.isAdmin(targetMap.get(id).getRole())); + if (CollUtil.isEmpty(changedUserIds)) { + return; + } + + // 2. 批量更新角色 + int affected = groupMemberService.updateGroupMemberRole(groupId, changedUserIds, + ImGroupMemberRoleEnum.NORMAL.getRole()); + if (affected != changedUserIds.size()) { + throw exception(GROUP_ADMIN_TARGET_NOT_IN_GROUP); + } + + // 3. 推送 GROUP_ADMIN_REMOVE 通知给全员 + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupAdminRemove(groupId, userId, changedUserIds)); + } + + /** + * 校验管理员变更目标都是当前群的有效成员(status=ENABLE)且非群主 + */ + private void validateAdminTargets(Set targetUserIds, Map targetMap) { + for (Long targetUserId : targetUserIds) { + ImGroupMemberDO target = targetMap.get(targetUserId); + if (target == null || CommonStatusEnum.DISABLE.getStatus().equals(target.getStatus())) { + throw exception(GROUP_ADMIN_TARGET_NOT_IN_GROUP); + } + if (ImGroupMemberRoleEnum.isOwner(target.getRole())) { + throw exception(GROUP_ADMIN_TARGET_IS_OWNER); + } + } + } + + @Override + @CacheEvict(cacheNames = GROUP, key = "#transferReqVO.id") + @Transactional(rollbackFor = Exception.class) + public void transferGroupOwner(Long userId, ImGroupTransferOwnerReqVO transferReqVO) { + Long groupId = transferReqVO.getId(); + Long newOwnerUserId = transferReqVO.getNewOwnerUserId(); + // 1.1 仅老群主可执行 + validateGroupOwnerForUpdate(groupId, userId); + // 1.2 不能转让给自己 + if (ObjUtil.equal(userId, newOwnerUserId)) { + throw exception(GROUP_TRANSFER_OWNER_TO_SELF); + } + // 1.3 新群主必须是群的有效成员 + ImGroupMemberDO newOwner = groupMemberService.validateMemberInGroup(groupId, newOwnerUserId); + + // 2.1 更新成员角色 + int newOwnerAffected = groupMemberService.updateGroupMemberRole(groupId, CollUtil.newHashSet(newOwner.getUserId()), + ImGroupMemberRoleEnum.OWNER.getRole()); + if (newOwnerAffected != 1) { + throw exception(GROUP_MEMBER_NOT_IN_GROUP); + } + int oldOwnerAffected = groupMemberService.updateGroupMemberRole(groupId, CollUtil.newHashSet(userId), + ImGroupMemberRoleEnum.NORMAL.getRole()); + if (oldOwnerAffected != 1) { + throw exception(GROUP_MEMBER_NOT_IN_GROUP); + } + // 2.2 更新群主编号 + groupMapper.updateById(new ImGroupDO().setId(groupId).setOwnerUserId(newOwnerUserId)); + + // 3. 推送 GROUP_OWNER_TRANSFER 通知给全员 + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupOwnerTransfer(groupId, userId, newOwnerUserId)); + } + + @Override + @CacheEvict(cacheNames = GROUP, key = "#groupId") + @Transactional(rollbackFor = Exception.class) + public void pinGroupMessage(Long userId, Long groupId, Long messageId) { + // 1.1 校验群主 / 管理员;同时拿到 group 复用,避免再走一次 @Cacheable + ImGroupDO group = validateOwnerOrAdminAndGetGroupForUpdate(groupId, userId); + // 1.2 校验消息属于该群、是普通聊天消息(绕过前端菜单不允许置顶群事件 / 撤回事件)、且未被撤回 + ImGroupMessageDO message = groupMessageService.getGroupMessage(messageId); + if (message == null || ObjUtil.notEqual(message.getGroupId(), groupId)) { + throw exception(MESSAGE_NOT_IN_GROUP); + } + if (!ImContentTypeEnum.validate(message.getType()).isNormal() + || ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) { + throw exception(MESSAGE_NOT_IN_GROUP); + } + // 1.3 定向消息(receiverUserIds 非空)不允许置顶:置顶会向全群广播,泄露原本仅部分人可见的内容 + if (CollUtil.isNotEmpty(message.getReceiverUserIds())) { + throw exception(GROUP_MESSAGE_PIN_DIRECTED_DENIED); + } + + // 2. 幂等 + 上限校验 + List pinned = new ArrayList<>(CollUtil.emptyIfNull(group.getPinnedMessageIds())); + if (pinned.contains(messageId)) { + throw exception(GROUP_MESSAGE_ALREADY_PINNED); + } + int pinMaxCount = imProperties.getGroup().getPinMaxCount(); + if (pinned.size() >= pinMaxCount) { + throw exception(GROUP_MESSAGE_PIN_MAX_LIMIT, pinMaxCount); + } + pinned.add(messageId); + groupMapper.updateById(new ImGroupDO().setId(groupId).setPinnedMessageIds(pinned)); + + // 3. 推送 GROUP_MESSAGE_PIN 通知给全员 + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupMessagePin(groupId, userId, message)); + } + + @Override + @CacheEvict(cacheNames = GROUP, key = "#groupId") + @Transactional(rollbackFor = Exception.class) + public void unpinGroupMessage(Long userId, Long groupId, Long messageId) { + // 1. 校验群主 / 管理员;同时拿到 group 复用,避免再走一次 @Cacheable + ImGroupDO group = validateOwnerOrAdminAndGetGroupForUpdate(groupId, userId); + // 2. 幂等校验 + List pinned = new ArrayList<>(CollUtil.emptyIfNull(group.getPinnedMessageIds())); + if (!pinned.contains(messageId)) { + throw exception(GROUP_MESSAGE_NOT_PINNED); + } + pinned.remove(messageId); + groupMapper.updateById(new ImGroupDO().setId(groupId).setPinnedMessageIds(pinned)); + + // 3. 推送 GROUP_MESSAGE_UNPIN 通知给全员 + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupMessageUnpin(groupId, userId, messageId)); + } + + /** + * 校验登录用户是群主或管理员,同时返回群信息 + */ + private ImGroupDO validateOwnerOrAdminAndGetGroup(Long groupId, Long userId) { + ImGroupDO group = validateGroupExists(groupId); + ImGroupMemberDO member = groupMemberService.validateMemberInGroup(groupId, userId); + if (!ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())) { + throw exception(GROUP_NOT_OWNER_OR_ADMIN); + } + return group; + } + + /** + * 校验登录用户是群主或管理员,同时锁定群信息 + */ + private ImGroupDO validateOwnerOrAdminAndGetGroupForUpdate(Long groupId, Long userId) { + ImGroupDO group = validateGroupExistsForUpdate(groupId); + ImGroupMemberDO member = groupMemberService.validateMemberInGroup(groupId, userId); + if (!ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())) { + throw exception(GROUP_NOT_OWNER_OR_ADMIN); + } + return group; + } + + // ==================== 群的读操作 ==================== + + @Override + public ImGroupDO validateGroupExists(Long id) { + ImGroupDO group = getSelf().getGroup(id); + if (group == null) { + throw exception(GROUP_NOT_EXISTS); + } + if (Boolean.TRUE.equals(group.getBanned())) { + throw exception(GROUP_BANNED); + } + if (CommonStatusEnum.DISABLE.getStatus().equals(group.getStatus())) { + throw exception(GROUP_DISSOLVED); + } + return group; + } + + @Override + public void validateMemberCountLimit(Long groupId, int addCount) { + // 1. 锁定群记录 + validateGroupExistsForUpdate(groupId); + // 2. 校验成员数量上限 + int activeCount = groupMemberService.getActiveGroupMemberUserIdsByGroupId(groupId).size(); + int maxMember = imProperties.getGroup().getMaxMember(); + if (activeCount + addCount > maxMember) { + throw exception(GROUP_MEMBER_EXCEED, maxMember); + } + } + + private ImGroupDO validateGroupOwnerForUpdate(Long groupId, Long userId) { + ImGroupDO group = validateGroupExistsForUpdate(groupId); + if (ObjUtil.notEqual(group.getOwnerUserId(), userId)) { + throw exception(GROUP_NOT_OWNER); + } + return group; + } + + private ImGroupDO validateGroupExistsForUpdate(Long groupId) { + ImGroupDO group = groupMapper.selectByIdForUpdate(groupId); + if (group == null) { + throw exception(GROUP_NOT_EXISTS); + } + if (Boolean.TRUE.equals(group.getBanned())) { + throw exception(GROUP_BANNED); + } + if (CommonStatusEnum.DISABLE.getStatus().equals(group.getStatus())) { + throw exception(GROUP_DISSOLVED); + } + return group; + } + + private ImGroupDO validateGroupNotDissolved(Long id) { + ImGroupDO group = getSelf().getGroup(id); + if (group == null) { + throw exception(GROUP_NOT_EXISTS); + } + if (CommonStatusEnum.DISABLE.getStatus().equals(group.getStatus())) { + throw exception(GROUP_DISSOLVED); + } + return group; + } + + /** + * 校验当前用户是群主或管理员,否则抛 GROUP_NOT_OWNER_OR_ADMIN + */ + private ImGroupMemberDO validateGroupOwnerOrAdmin(Long groupId, Long userId) { + validateGroupExists(groupId); + ImGroupMemberDO member = groupMemberService.validateMemberInGroup(groupId, userId); + if (!ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())) { + throw exception(GROUP_NOT_OWNER_OR_ADMIN); + } + return member; + } + + @Override + @Cacheable(cacheNames = GROUP, key = "#id", unless = "#result == null") + public ImGroupDO getGroup(Long id) { + return groupMapper.selectById(id); + } + + @Override + public Map getGroupMap(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyMap(); + } + return convertMap(groupMapper.selectByIds(ids), ImGroupDO::getId); + } + + @Override + public List getMyGroupList(Long userId) { + // 1. 查用户曾经加入的所有群(含退群,前端按需过滤);退群前的离线消息也要能展示 + List members = groupMemberService.getGroupMemberListByUserId(userId); + if (CollUtil.isEmpty(members)) { + return Collections.emptyList(); + } + + // 2. 批量查询群信息(不按 status / banned 过滤,已解散 / 封禁的群也要返回,供前端展示历史消息的群名 / 头像) + Set groupIds = convertSet(members, ImGroupMemberDO::getGroupId); + return groupMapper.selectByIds(groupIds); + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getGroupPage(ImGroupManagerPageReqVO pageReqVO) { + return groupMapper.selectPage(pageReqVO); + } + + @Override + @CacheEvict(cacheNames = GROUP, key = "#banReqVO.id") + public void banGroup(Long operatorUserId, ImGroupManagerBanReqVO banReqVO) { + // 1. 校验群存在且未解散 + ImGroupDO group = getSelf().getGroup(banReqVO.getId()); + if (group == null) { + throw exception(GROUP_NOT_EXISTS); + } + if (CommonStatusEnum.DISABLE.getStatus().equals(group.getStatus())) { + throw exception(GROUP_DISSOLVED); + } + // 2. 幂等:已封禁直接返回,避免重复广播封禁通知 + if (Boolean.TRUE.equals(group.getBanned())) { + return; + } + + // 3. 更新封禁状态 + groupMapper.updateById(new ImGroupDO().setId(banReqVO.getId()) + .setBanned(true).setBannedReason(banReqVO.getReason()).setBannedTime(LocalDateTime.now())); + + // 4. 广播通知 + groupMessageService.sendGroupMessage(operatorUserId, + ImGroupMessageSendDTO.ofGroupBanned(banReqVO.getId(), operatorUserId, true)); + } + + @Override + @CacheEvict(cacheNames = GROUP, key = "#id") + public void unbanGroup(Long operatorUserId, Long id) { + // 1. 校验群存在 + if (getSelf().getGroup(id) == null) { + throw exception(GROUP_NOT_EXISTS); + } + + // 2. 解封(保留 bannedReason / bannedTime 作为历史记录) + groupMapper.updateById(new ImGroupDO().setId(id).setBanned(false)); + + // 3. 广播通知 + groupMessageService.sendGroupMessage(operatorUserId, + ImGroupMessageSendDTO.ofGroupBanned(id, operatorUserId, false)); + } + + @Override + @CacheEvict(cacheNames = GROUP, key = "#id") + @Transactional(rollbackFor = Exception.class) + public void dissolveGroupByManager(Long operatorUserId, Long id) { + // 1. 校验群存在且未解散 + validateGroupNotDissolved(id); + + // 2. 解散群 + dissolveGroup0(id, operatorUserId); + } + + // ==================== 群禁言 ==================== + + @Override + @CacheEvict(cacheNames = GROUP, key = "#reqVO.id") + @Transactional(rollbackFor = Exception.class) + public void muteAll(Long userId, ImGroupMuteAllReqVO reqVO) { + // 1. 校验群主或管理员 + validateGroupOwnerOrAdmin(reqVO.getId(), userId); + + // 2. 更新 mutedAll + groupMapper.updateById(new ImGroupDO().setId(reqVO.getId()).setMutedAll(reqVO.getMutedAll())); + + // 3. 广播通知 + ImGroupMessageSendDTO messageSendDTO = Boolean.TRUE.equals(reqVO.getMutedAll()) + ? ImGroupMessageSendDTO.ofGroupMuted(reqVO.getId(), userId) + : ImGroupMessageSendDTO.ofGroupCancelMuted(reqVO.getId(), userId); + groupMessageService.sendGroupMessage(userId, messageSendDTO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void muteMember(Long userId, ImGroupMuteMemberReqVO reqVO) { + // 1.1 不能禁言自己 + if (ObjUtil.equal(userId, reqVO.getUserId())) { + throw exception(GROUP_MUTE_MEMBER_SELF); + } + // 1.2 校验群存在且未封禁 + validateGroupExists(reqVO.getId()); + // 1.3 校验操作人和目标都在群中 + ImGroupMemberDO operatorMember = groupMemberService.validateMemberInGroup(reqVO.getId(), userId); + ImGroupMemberDO targetMember = groupMemberService.validateMemberInGroup(reqVO.getId(), reqVO.getUserId()); + // 1.4 三档权限校验 + validateMutePermission(operatorMember, targetMember); + + // 2. 设置 muteEndTime + LocalDateTime muteEndTime = reqVO.getMutedSeconds() == 0 + ? ImGroupMemberDO.PERMANENT_MUTE_END_TIME : LocalDateTime.now().plusSeconds(reqVO.getMutedSeconds()); + groupMemberService.updateGroupMemberMuteEndTime(reqVO.getId(), reqVO.getUserId(), muteEndTime); + + // 3. 广播通知 + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupMemberMuted(reqVO.getId(), userId, + reqVO.getUserId(), muteEndTime)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelMuteMember(Long userId, ImGroupCancelMuteMemberReqVO reqVO) { + // 1.1 校验群存在且未封禁 + validateGroupExists(reqVO.getId()); + // 1.2 校验操作人和目标都在群中 + ImGroupMemberDO operatorMember = groupMemberService.validateMemberInGroup(reqVO.getId(), userId); + ImGroupMemberDO targetMember = groupMemberService.validateMemberInGroup(reqVO.getId(), reqVO.getUserId()); + // 1.3 三档权限校验 + validateMutePermission(operatorMember, targetMember); + + // 2. 取消禁言(清空 muteEndTime) + groupMemberService.updateGroupMemberMuteEndTime(reqVO.getId(), reqVO.getUserId(), null); + + // 3. 广播通知 + groupMessageService.sendGroupMessage(userId, + ImGroupMessageSendDTO.ofGroupMemberCancelMuted(reqVO.getId(), userId, reqVO.getUserId())); + } + + /** + * 三档分层禁言权限校验 + */ + private void validateMutePermission(ImGroupMemberDO operator, ImGroupMemberDO target) { + // 普通成员不能禁言任何人 + if (!ImGroupMemberRoleEnum.isOwnerOrAdmin(operator.getRole())) { + throw exception(GROUP_NOT_OWNER_OR_ADMIN); + } + // 群主不可被禁言 + if (ImGroupMemberRoleEnum.isOwner(target.getRole())) { + throw exception(GROUP_MUTE_OWNER_DENIED); + } + // 管理员不能禁言其他管理员 + if (ImGroupMemberRoleEnum.isAdmin(target.getRole()) && !ImGroupMemberRoleEnum.isOwner(operator.getRole())) { + throw exception(GROUP_MUTE_ADMIN_DENIED); + } + } + + private ImGroupServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + + /** + * 根据用户编号集合,拼接用户昵称字符串(逗号分隔) + * + * @param userIds 用户编号集合 + * @return 昵称字符串,如 "张三,李四" + */ + private String getUserNicknames(Collection userIds) { + Map userMap = adminUserApi.getUserMap(userIds); + return userIds.stream() + .map(id -> userMap.containsKey(id) ? userMap.get(id).getNickname() : String.valueOf(id)) + .collect(Collectors.joining(",")); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java new file mode 100644 index 0000000000..b1e00ee76a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.im.service.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessagePageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * IM 频道消息 Service 接口 + * + * @author 芋道源码 + */ +public interface ImChannelMessageService { + + // ==================== 用户端 ==================== + + /** + * 拉取当前用户应收的频道消息(离线增量) + * + * @param userId 当前用户编号 + * @param minId 游标;返回大于此值的消息 + * @param size 返回条数 + * @return 频道消息列表;按 id 升序 + */ + List pullChannelMessageList(Long userId, Long minId, Integer size); + + /** + * 上报频道消息已读位置;同步推 READ 事件给自己多端 + * + * @param userId 当前用户编号 + * @param channelId 频道编号 + * @param messageId 已读到的最大消息编号 + */ + void readChannelMessages(Long userId, Long channelId, Long messageId); + + /** + * 批量查询用户在多个频道下的已读游标 + * + * @param userId 当前用户编号 + * @param channelIds 频道编号集合 + * @return channelId → 已读到的最大消息编号(未读到的频道不出现在返回 map 中) + */ + Map getChannelReadMaxMessageIdMap(Long userId, Collection channelIds); + + // ==================== 管理后台 ==================== + + /** + * 立即推送频道消息 + * + * @param reqVO 推送请求 + * @return 消息编号 + */ + Long sendMessage(@Valid ImChannelMessageSendReqVO reqVO); + + /** + * 分页查询消息 + * + * @param reqVO 分页查询条件 + * @return 消息分页 + */ + PageResult getMessagePage(ImChannelMessagePageReqVO reqVO); + + /** + * 删除消息 + * + * @param id 消息编号 + */ + void deleteMessage(Long id); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImpl.java new file mode 100644 index 0000000000..fc9ad8e904 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImpl.java @@ -0,0 +1,148 @@ +package cn.iocoder.yudao.module.im.service.message; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessagePageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.ImChannelMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import cn.iocoder.yudao.module.im.dal.mysql.message.ImChannelMessageMapper; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImChannelMessageNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.MaterialMessage; +import jakarta.annotation.Resource; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.Collection; +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.im.enums.ErrorCodeConstants.IM_CHANNEL_MESSAGE_NOT_EXISTS; + +/** + * IM 频道消息 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImChannelMessageServiceImpl implements ImChannelMessageService { + + @Resource + private ImChannelMessageMapper channelMessageMapper; + + @Resource + private ImChannelMaterialService channelMaterialService; + @Resource + private ImWebSocketService webSocketService; + + @Resource + private ImConversationReadService conversationReadService; + + // ==================== 用户端 ==================== + + @Override + public List pullChannelMessageList(Long userId, Long minId, Integer size) { + return channelMessageMapper.selectListByUserAndMinId(userId, minId, size); + } + + @Override + public Map getChannelReadMaxMessageIdMap(Long userId, Collection channelIds) { + return conversationReadService.getConversationReadMessageIdMap( + userId, ImConversationTypeEnum.CHANNEL.getType(), channelIds); + } + + @Override + public void readChannelMessages(Long userId, Long channelId, Long messageId) { + Assert.notNull(channelId, "频道编号不能为空"); + Assert.notNull(messageId, "已读消息编号不能为空"); + // 1.1 校验消息真实存在且属于该频道,避免未来 / 伪造 messageId 污染读位置 + ImChannelMessageDO message = channelMessageMapper.selectById(messageId); + if (message == null || ObjUtil.notEqual(message.getChannelId(), channelId)) { + return; + } + // 1.2 定向消息校验对当前用户可见(receiver_user_ids 为空表示全员可见) + if (CollUtil.isNotEmpty(message.getReceiverUserIds()) && !message.getReceiverUserIds().contains(userId)) { + return; + } + + // 2. 更新频道已读位置;读位置未前进则不推 + boolean advanced = conversationReadService.updateConversationReadPosition( + userId, ImConversationTypeEnum.CHANNEL.getType(), channelId, messageId); + if (!advanced) { + return; + } + + // 3. 异步推 READ 事件给自己多端同步 + getSelf().readChannelMessageEvent(userId, channelId, messageId); + } + + /** + * 发送频道已读 READ 事件给自己其他终端;频道无「给发送方刷回执」概念,不广播 + */ + @Async + public void readChannelMessageEvent(Long userId, Long channelId, Long readId) { + webSocketService.sendNotificationAsync(userId, ImConversationTypeEnum.CHANNEL.getType(), + ImContentTypeEnum.READ.getType(), ImMessageReadNotification.ofChannel(channelId, readId)); + } + + private ImChannelMessageServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + + // ==================== 管理后台 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + public Long sendMessage(ImChannelMessageSendReqVO reqVO) { + // 1. 校验素材存在 + ImChannelMaterialDO material = channelMaterialService.validateMaterialExists(reqVO.getMaterialId()); + + // 2.1 组装 payload(不带富文本正文);字段同名直接 BeanUtils 拷贝,materialId 单独 set 以兼容转发场景 + MaterialMessage payload = BeanUtils.toBean(material, MaterialMessage.class).setMaterialId(material.getId()); + String payloadJson = JsonUtils.toJsonString(payload); + // 2.2 落库 1 行 message;reqVO 同名字段(materialId / receiverUserIds)自动拷贝,剩余字段补 set + ImChannelMessageDO message = BeanUtils.toBean(reqVO, ImChannelMessageDO.class).setChannelId(material.getChannelId()) + .setType(ImContentTypeEnum.MATERIAL.getType()).setContent(payloadJson).setSendTime(LocalDateTime.now()); + channelMessageMapper.insert(message); + + // 3. 异步推 WebSocket:指定用户走点对点;全员(receiverUserIds 为空)走广播 + ImChannelMessageNotification dto = ImChannelMessageNotification.ofSend(message); + if (CollUtil.isNotEmpty(reqVO.getReceiverUserIds())) { + webSocketService.sendNotificationAsync(reqVO.getReceiverUserIds(), ImConversationTypeEnum.CHANNEL.getType(), + dto.getType(), dto); + } else { + webSocketService.broadcastNotificationAsync(ImConversationTypeEnum.CHANNEL.getType(), dto.getType(), dto); + } + return message.getId(); + } + + @Override + public PageResult getMessagePage(ImChannelMessagePageReqVO reqVO) { + return channelMessageMapper.selectPage(reqVO); + } + + @Override + public void deleteMessage(Long id) { + if (channelMessageMapper.selectById(id) == null) { + throw exception(IM_CHANNEL_MESSAGE_NOT_EXISTS); + } + channelMessageMapper.deleteById(id); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java new file mode 100644 index 0000000000..77426d1c31 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java @@ -0,0 +1,130 @@ +package cn.iocoder.yudao.module.im.service.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * IM 群聊消息 Service 接口 + * + * @author 芋道源码 + */ +public interface ImGroupMessageService { + + /** + * 【用户调用】发送群聊消息 + *

+ * 用户在 IM 客户端发送 TEXT / IMAGE 等消息时调用,含幂等、敏感词、quote、@ 解析等业务校验。 + * type 校验由 VO 层 {@code @InEnum} + {@code @AssertTrue} 完成(仅允许 normal 类型)。 + * + * @param senderId 发送人编号 + * @param reqVO 发送请求 + * @return 消息 + */ + ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO); + + /** + * 【系统调用】发送群聊消息:内部查 active 成员 + 推送 + *

+ * 调用方批量调用会触发多次 active 成员查询;如果调用方已经持有成员快照,优先使用 {@link #sendGroupMessage(Long, Collection, ImGroupMessageSendDTO)} 方法,避免重复查询。 + * + * @param senderId 发送人编号 + * @param dto 消息 DTO + * @return 消息 + */ + ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendDTO dto); + + /** + * 【系统调用】发送群聊消息:显式指定推送目标 + * + * @param senderId 发送人编号 + * @param targetUserIds 推送目标用户编号集合(调用方在变更成员状态前抓取的快照) + * @param dto 消息 DTO + * @return 构造的消息 DO(持久化时 id 已回填) + */ + ImGroupMessageDO sendGroupMessage(Long senderId, Collection targetUserIds, ImGroupMessageSendDTO dto); + + /** + * 【用户调用】撤回群聊消息 + * + * @param userId 当前用户编号 + * @param messageId 消息编号 + * @return 撤回后的提示消息 + */ + ImGroupMessageDO recallGroupMessage(Long userId, Long messageId); + + /** + * 拉取群聊消息(增量) + * + * @param userId 当前用户编号 + * @param minId 最小消息 id(不含) + * @param size 拉取数量 + * @return 消息列表 + */ + List pullGroupMessageList(Long userId, Long minId, Integer size); + + /** + * 标记群聊消息已读 + * + * @param userId 当前用户编号 + * @param groupId 群编号 + * @param messageId 已读到的消息编号 + */ + void readGroupMessages(Long userId, Long groupId, Long messageId); + + /** + * 获取群消息的已读用户列表 + * + * @param userId 当前用户编号 + * @param groupId 群编号 + * @param messageId 消息编号 + * @return 已读用户编号列表 + */ + List getGroupReadUserIds(Long userId, Long groupId, Long messageId); + + /** + * 查询群聊历史消息(游标拉取) + * + * @param userId 当前用户编号 + * @param reqVO 拉取请求 + * @return 消息列表(按 id 倒序) + */ + List getGroupMessageList(Long userId, ImGroupMessageListReqVO reqVO); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询群聊消息 + */ + PageResult getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO); + + /** + * 【管理后台】获取群聊消息详情 + */ + ImGroupMessageDO getGroupMessage(Long id); + + /** + * 批量按消息编号查询群聊消息 + * + * @param ids 消息编号集合 + * @return 消息列表 + */ + List getGroupMessageList(Collection ids); + + /** + * 批量按消息编号查询群聊消息,返回 messageId → DO 映射 + * + * @param ids 消息编号集合 + * @return 消息 Map(key = 消息编号) + */ + Map getGroupMessageMap(Collection ids); + +} + diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java new file mode 100644 index 0000000000..12914fbef0 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java @@ -0,0 +1,548 @@ +package cn.iocoder.yudao.module.im.service.message; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; +import cn.iocoder.yudao.module.im.service.group.ImGroupService; +import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; +import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReceiptNotification; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage; +import cn.iocoder.yudao.module.im.util.ImMessageUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; + +/** + * IM 群聊消息 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class ImGroupMessageServiceImpl implements ImGroupMessageService { + + @Resource + private ImGroupMessageMapper groupMessageMapper; + + @Resource + private ImGroupService groupService; + @Resource + private ImGroupMemberService groupMemberService; + @Resource + private ImSensitiveWordService sensitiveWordService; + @Resource + private ImConversationReadService conversationReadService; + + @Resource + private ImWebSocketService imWebSocketService; + + @Resource + private ImProperties imProperties; + + @Override + public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) { + // 1.1 幂等校验:根据 senderId + clientMessageId 查重 + ImGroupMessageDO existing = groupMessageMapper.selectBySenderIdAndClientMessageId( + senderId, reqVO.getClientMessageId()); + if (existing != null) { + log.info("[sendGroupMessage][幂等命中 senderId({}) clientMessageId({}) 已存在消息({})]", + senderId, reqVO.getClientMessageId(), existing.getId()); + return existing; + } + // 1.2 消息内容校验 + ImMessageUtils.validateUserMessageContent(reqVO.getType(), reqVO.getContent()); + // 1.3 校验群存在、发送人仍在群中 + ImGroupDO group = groupService.validateGroupExists(reqVO.getGroupId()); + ImGroupMemberDO senderMember = groupMemberService.validateMemberInGroup(reqVO.getGroupId(), senderId); + // 1.4 禁言校验 + validateMuteStatus(group, senderMember); + // 1.5 文本消息敏感词过滤 + if (ImContentTypeEnum.TEXT.getType().equals(reqVO.getType())) { + sensitiveWordService.validateText(reqVO.getContent()); + } + + // 2.1 固化发送当时可见成员快照:用户发送均为全员广播;getReceiverUserIds 兜底纳入发送者,钉死发送者必可见 + List memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(reqVO.getGroupId()); + Set receiverUserIds = getReceiverUserIds(null, senderId, memberUserIds); + // 2.2 引用 quote 消息规范化(含可见性子集校验,防止定向消息内容被广播引用泄漏) + reqVO.setContent(normalizeQuoteContent(reqVO, senderMember, receiverUserIds)); + // 2.3 构建并保存消息;唯一键冲突时回查已存在消息返回 + ImGroupMessageDO message = BeanUtils.toBean(reqVO, ImGroupMessageDO.class, m -> m + .setSenderId(senderId).setStatus(ImMessageStatusEnum.NORMAL.getStatus()).setSendTime(LocalDateTime.now()) + .setReceiverUserIds(new ArrayList<>(receiverUserIds)) + .setReceiptStatus(resolveReceiptStatus(reqVO.getReceipt()))); + try { + groupMessageMapper.insert(message); + } catch (DuplicateKeyException e) { + log.warn("[sendGroupMessage][senderId({}) clientMessageId({}) 并发插入冲突,回查返回]", + senderId, reqVO.getClientMessageId()); + return groupMessageMapper.selectBySenderIdAndClientMessageId(senderId, reqVO.getClientMessageId()); + } + + // 3. WebSocket 异步推送(快照内可见成员 + 发送方多端同步) + ImGroupMessageNotification notification = ImGroupMessageNotification.ofSend(message); + imWebSocketService.sendNotificationAsync(receiverUserIds, ImConversationTypeEnum.GROUP.getType(), + notification.getType(), notification); + return message; + } + + @Override + public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendDTO dto) { + List memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(dto.getGroupId()); + Set receiverUserIds = getReceiverUserIds(dto.getReceiverUserIds(), senderId, memberUserIds); + return sendGroupMessage(senderId, receiverUserIds, dto); + } + + @Override + public ImGroupMessageDO sendGroupMessage(Long senderId, Collection receiverUserIds, ImGroupMessageSendDTO dto) { + // 1.1 content 序列化:null / String 透传,POJO 走 JSON + Object payload = dto.getContent(); + String contentString = payload == null || payload instanceof String + ? (String) payload + : JsonUtils.toJsonString(payload); + // 1.2 构建并保存消息 + ImGroupMessageDO message = new ImGroupMessageDO().setClientMessageId(IdUtil.fastSimpleUUID()) + .setSenderId(senderId).setGroupId(dto.getGroupId()) + .setType(dto.getType()).setContent(contentString) + .setStatus(ImMessageStatusEnum.NORMAL.getStatus()).setSendTime(LocalDateTime.now()) + .setAtUserIds(dto.getAtUserIds()).setReceiverUserIds(new ArrayList<>(receiverUserIds)) + .setReceiptStatus(resolveReceiptStatus(dto.getReceipt())); + // 1.3 按 type.persistent 决定是否入库 + if (ImContentTypeEnum.validate(dto.getType()).isPersistent()) { + groupMessageMapper.insert(message); + } + + // 2. WebSocket 异步推送 + ImGroupMessageNotification notification = ImGroupMessageNotification.ofSend(message); + imWebSocketService.sendNotificationAsync(receiverUserIds, ImConversationTypeEnum.GROUP.getType(), + notification.getType(), notification); + return message; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ImGroupMessageDO recallGroupMessage(Long userId, Long messageId) { + // 1.1 校验消息存在 + ImGroupMessageDO message = validateGroupMessageExists(messageId); + // 1.2 校验撤回人仍在群中,并取角色用于权限判断 + ImGroupMemberDO operator = groupMemberService.validateMemberInGroup(message.getGroupId(), userId); + boolean isOwnerOrAdmin = ImGroupMemberRoleEnum.isOwnerOrAdmin(operator.getRole()); + // 1.3 普通成员只能撤回自己发的消息;群主 / 管理员可撤回他人违规消息 + if (ObjUtil.notEqual(message.getSenderId(), userId) && !isOwnerOrAdmin) { + throw exception(MESSAGE_RECALL_DENIED); + } + // 1.4 不能重复撤回 + if (ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) { + throw exception(MESSAGE_ALREADY_RECALLED); + } + // 1.5 撤回时间窗仅约束撤回自己的消息;群主 / 管理员治理他人违规消息不受时间限制 + if (ObjUtil.equal(message.getSenderId(), userId)) { + int recallTimeoutMinutes = imProperties.getMessage().getRecallTimeoutMinutes(); + if (message.getSendTime().plusMinutes(recallTimeoutMinutes).isBefore(LocalDateTime.now())) { + throw exception(MESSAGE_RECALL_TIMEOUT, recallTimeoutMinutes); + } + } + + // 2. 更新原消息状态为撤回 + groupMessageMapper.updateById(new ImGroupMessageDO().setId(messageId) + .setStatus(ImMessageStatusEnum.RECALL.getStatus())); + + // 3. 发送撤回事件 + return sendGroupMessage(userId, new ImGroupMessageSendDTO().setGroupId(message.getGroupId()) + .setType(ImContentTypeEnum.RECALL.getType()) + .setReceiverUserIds(message.getReceiverUserIds()) + .setContent(new RecallMessage().setMessageId(messageId))); + } + + @Override + public List pullGroupMessageList(Long userId, Long minId, Integer size) { + int maxPullSize = imProperties.getMessage().getMaxPullSize(); + if (size > maxPullSize) { + throw exception(MESSAGE_PULL_SIZE_EXCEEDED, maxPullSize); + } + + // 0. 拉取时间窗;超过窗口的老消息不再通过离线通道推送 + LocalDateTime minSendTime = LocalDateTime.now().minusDays(imProperties.getMessage().getGroupPullMaxDays()); + + // 1. 候选群 = 用户曾经加入的所有群(含退群);可见性与时间窗都交给 SQL,退群群最多贡献 0 条 + Set groupIds = convertSet(groupMemberService.getGroupMemberListByUserId(userId), + ImGroupMemberDO::getGroupId); + if (CollUtil.isEmpty(groupIds)) { + return Collections.emptyList(); + } + + // 2. 按 receiver_user_ids 快照过滤可见性,结果按 id 升序 + List messages = groupMessageMapper.selectListByMinId(userId, groupIds, minId, minSendTime, size); + + // 3. 补齐本人发送的回执消息的已读人数 + appendMessageReceipt(userId, messages); + log.info("[pullGroupMessageList][userId({}) minId({}) size({}) result({})]", userId, minId, size, messages.size()); + return messages; + } + + /** + * 补全本人发送消息的回执已读人数(readCount) + *

+ * 仅对本人发送、且需要回执(非 NO_RECEIPT)的消息,按 receiver_user_ids 快照 ∩ 当前有效成员 - 发送者 算已读人数 + */ + @SuppressWarnings("DataFlowIssue") + private void appendMessageReceipt(Long userId, List messages) { + if (CollUtil.isEmpty(messages)) { + return; + } + // 群已读关闭:不补 readCount + if (BooleanUtil.isFalse(imProperties.getMessage().isGroupReadEnabled())) { + return; + } + Map> readPositionsByGroup = new HashMap<>(); // 群 → (用户 → 已读位置) + Map> membersByGroup = new HashMap<>(); // 群 → 全部成员列表 + for (ImGroupMessageDO message : messages) { + // 仅补本人发送、且需要回执(非 NO_RECEIPT)的消息 + if (ObjUtil.notEqual(message.getSenderId(), userId) + || ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus().equals(message.getReceiptStatus())) { + continue; + } + Long groupId = message.getGroupId(); + Map positions = readPositionsByGroup.computeIfAbsent(groupId, + gid -> conversationReadService.getUserReadMessageIdMap(ImConversationTypeEnum.GROUP.getType(), gid)); + // 应读分母取当前有效成员(剔除已退群),与异步回执刷新、前端已读弹层口径一致 + List activeMembers = membersByGroup.computeIfAbsent(groupId, + groupMemberService::getActiveGroupMemberListByGroupId); + Set receiverUserIds = getReceiverUserIds(message, activeMembers); + receiverUserIds.remove(message.getSenderId()); + int readCount = getSumValue(receiverUserIds, + uid -> positions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null, + Integer::sum, 0); + message.setReadCount(readCount); + } + } + + @Override + public void readGroupMessages(Long userId, Long groupId, Long messageId) { + // 0. 全局开关校验 + if (BooleanUtil.isFalse(imProperties.getMessage().isGroupReadEnabled())) { + throw exception(MESSAGE_GROUP_READ_DISABLED); + } + Assert.notNull(messageId, "已读消息编号不能为空"); + // 1. 校验消息属于当前群,且对当前用户可见(按快照) + ImGroupMessageDO message = groupMessageMapper.selectById(messageId); + if (message == null + || ObjUtil.notEqual(message.getGroupId(), groupId) + || !isMessageReceived(message, userId)) { + throw exception(MESSAGE_NOT_IN_GROUP); + } + + // 2. 取旧读位置(用于回执刷新区间),再更新群已读位置;读位置未前进则不推 + Long prevMaxMessageId = conversationReadService.getConversationReadMessageId( + userId, ImConversationTypeEnum.GROUP.getType(), groupId); + boolean advanced = conversationReadService.updateConversationReadPosition( + userId, ImConversationTypeEnum.GROUP.getType(), groupId, messageId); + if (!advanced) { + return; + } + + // 3. 异步发送 READ 事件 + 刷新范围内的群回执 + getSelf().readGroupMessageEvent(userId, groupId, prevMaxMessageId, messageId); + } + + @Override + public List getGroupReadUserIds(Long userId, Long groupId, Long messageId) { + // 0. 全局开关校验 + if (BooleanUtil.isFalse(imProperties.getMessage().isGroupReadEnabled())) { + throw exception(MESSAGE_GROUP_READ_DISABLED); + } + // 1.1 校验用户在群中(权限校验) + groupMemberService.validateMemberInGroup(groupId, userId); + // 1.2 获取消息;并校验消息归属于该群、对调用者可见、调用者是发送方 + ImGroupMessageDO message = groupMessageMapper.selectById(messageId); + if (message == null || ObjUtil.notEqual(message.getGroupId(), groupId)) { + return Collections.emptyList(); + } + if (!isMessageReceived(message, userId)) { + return Collections.emptyList(); + } + // 1.3 仅消息发送方关心已读人数;非发送方查询直接返回空 + if (ObjUtil.notEqual(message.getSenderId(), userId)) { + return Collections.emptyList(); + } + + // 2. 获取当前有效成员和已读位置(剔除已退群,与回执分母口径一致) + List activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); + Map allPositions = conversationReadService.getUserReadMessageIdMap( + ImConversationTypeEnum.GROUP.getType(), groupId); + + // 3. 计算该消息的可见成员集合(排除发送者自己) + Set receiverUserIds = getReceiverUserIds(message, activeMembers); + receiverUserIds.remove(message.getSenderId()); + + // 4. 只返回在可见范围内且已读位置 >= messageId 的用户 + List readUserIds = new ArrayList<>(); + allPositions.forEach((uid, readMaxMessageId) -> { + if (receiverUserIds.contains(uid) && readMaxMessageId >= messageId) { + readUserIds.add(uid); + } + }); + return readUserIds; + } + + @Override + public List getGroupMessageList(Long userId, ImGroupMessageListReqVO reqVO) { + // 1. 校验用户曾经在群(当前在群或已退群),与 pull 退群窗口口径一致;内容仍由 receiver_user_ids 快照过滤 + if (groupMemberService.getGroupMember(reqVO.getGroupId(), userId) == null) { + throw exception(GROUP_MEMBER_NOT_IN_GROUP); + } + + // 2. 查询历史消息:SQL 已按 receiver_user_ids 快照过滤当前用户可见 + return groupMessageMapper.selectHistoryListByUser( + userId, reqVO.getGroupId(), reqVO.getMaxId(), reqVO.getLimit()); + } + + // ========== 异步 WebSocket 推送 ========== + + /** + * 发送已读 + 刷新群回执 WebSocket 事件 + * + * @param userId 当前用户编号 + * @param groupId 群编号 + * @param prevMaxMessageId 上次已读位置 + * @param newMaxMessageId 本次已读位置 + */ + @Async + @SuppressWarnings("DataFlowIssue") + public void readGroupMessageEvent(Long userId, Long groupId, Long prevMaxMessageId, Long newMaxMessageId) { + // 1. 发送 READ 事件给自己的其他终端(多端同步) + imWebSocketService.sendNotificationAsync(userId, ImConversationTypeEnum.GROUP.getType(), + ImContentTypeEnum.READ.getType(), + ImMessageReadNotification.ofGroup(userId, groupId, newMaxMessageId)); + + // 2. 刷新 (prevMaxMessageId, newMaxMessageId] 区间内的待回执消息 + List pendingMessages = groupMessageMapper.selectListByGroupIdAndPendingReceipt( + groupId, prevMaxMessageId, newMaxMessageId); + if (CollUtil.isEmpty(pendingMessages)) { + return; + } + List activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); + Map allPositions = conversationReadService.getUserReadMessageIdMap( + ImConversationTypeEnum.GROUP.getType(), groupId); + for (ImGroupMessageDO message : pendingMessages) { + // 2.1.1 统计可见成员中的已读人数(应读分母 = 快照 ∩ 当前有效成员 - sender) + Set receiverUserIds = getReceiverUserIds(message, activeMembers); + receiverUserIds.remove(message.getSenderId()); // 发送者自己不算已读 + if (CollUtil.isEmpty(receiverUserIds)) { + continue; + } + int readCount = getSumValue(receiverUserIds, + uid -> allPositions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null, + Integer::sum, 0); + // 2.1.2 全部已读 → 标记回执完成 + Integer newReceiptStatus = ImMessageReceiptStatusEnum.PENDING.getStatus(); + if (readCount >= receiverUserIds.size()) { + newReceiptStatus = ImMessageReceiptStatusEnum.DONE.getStatus(); + groupMessageMapper.updateById(new ImGroupMessageDO().setId(message.getId()) + .setReceiptStatus(newReceiptStatus)); + } + + // 2.2 发送 RECEIPT 事件给消息发送方(只有 ta 关心已读进度) + imWebSocketService.sendNotificationAsync(message.getSenderId(), ImConversationTypeEnum.GROUP.getType(), + ImContentTypeEnum.RECEIPT.getType(), + ImMessageReceiptNotification.ofGroup(message.getId(), groupId, readCount, newReceiptStatus)); + } + } + + // ========== 私有工具方法 ========== + + /** + * 群聊引用消息规范化 + * + * @param reqVO 发送请求 + * @param senderMember 发送人成员 + * @param newAudience 新消息可见成员集合(发送当时快照) + * @return 规范化后的 content + */ + private String normalizeQuoteContent(ImGroupMessageSendReqVO reqVO, ImGroupMemberDO senderMember, + Set newAudience) { + // 解析客户端 content 里的 quote.messageId + Long quoteMessageId = ImMessageUtils.parseQuoteMessageId(reqVO.getContent()); + + // 情况一:没有 quoteMessageId,直接 remove 掉 content 里可能伪造的 quote 字段 + if (quoteMessageId == null) { + return ImMessageUtils.removeQuote(reqVO.getContent()); + } + + // 情况二:有 quoteMessageId,加载原消息并校验 + ImGroupMessageDO original = groupMessageMapper.selectById(quoteMessageId); + if (original == null + || ImMessageStatusEnum.RECALL.getStatus().equals(original.getStatus())) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 校验是同群 + if (ObjUtil.notEqual(original.getGroupId(), reqVO.getGroupId())) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 校验对发送人可见(按快照) + if (!isMessageReceived(original, senderMember.getUserId())) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 防泄漏:新消息可见集合必须是被引用消息可见集合的子集,否则禁止引用; + // 否则只对部分成员可见的定向消息内容,会随广播引用泄漏给原本看不到的成员 + if (!new HashSet<>(original.getReceiverUserIds()).containsAll(newAudience)) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 构建 quote 对象并注入 content + QuoteMessage quote = ImMessageUtils.buildQuote(original.getId(), + original.getSenderId(), original.getType(), original.getContent()); + return ImMessageUtils.appendQuote(reqVO.getContent(), quote); + } + + /** + * 校验群聊消息存在 + * + * @param messageId 消息编号 + * @return 群聊消息 + */ + private ImGroupMessageDO validateGroupMessageExists(Long messageId) { + ImGroupMessageDO message = groupMessageMapper.selectById(messageId); + if (message == null) { + throw exception(MESSAGE_NOT_EXISTS); + } + return message; + } + + /** + * 判断一条群消息是否被某个用户接收到 + *

+ * 接收范围以发送当时固化的 receiver_user_ids 快照为准;发送者也在快照内。 + * + * @param message 消息 + * @param userId 当前用户编号 + * @return 是否接收到 + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean isMessageReceived(ImGroupMessageDO message, Long userId) { + return CollUtil.contains(message.getReceiverUserIds(), userId); + } + + /** + * 计算一条群消息的可见成员集合(快照与传入成员的交集,含发送者) + */ + private Set getReceiverUserIds(ImGroupMessageDO message, List members) { + if (CollUtil.isEmpty(message.getReceiverUserIds())) { + return new HashSet<>(); + } + Set snapshot = new HashSet<>(message.getReceiverUserIds()); + return convertSet(members, ImGroupMemberDO::getUserId, member -> snapshot.contains(member.getUserId())); + } + + /** + * 基于群成员 userId 列表,计算一条新消息的可见成员快照(含发送者)。 + *

+ * 仅适用于「新消息」发送场景:无定向接收列表则全员可见,否则取定向用户与当前成员的交集; + * 发送者始终纳入快照,保证 receiver_user_ids 非空且发送方可见。 + */ + private Set getReceiverUserIds(List directedUserIds, Long senderId, Collection memberUserIds) { + // 无定向接收列表 → 全员可见;否则取定向用户与当前成员的交集 + Set result = CollUtil.isEmpty(directedUserIds) + ? new HashSet<>(memberUserIds) + : new HashSet<>(CollUtil.intersection(memberUserIds, directedUserIds)); + // 发送者始终可见(多端同步),即便成员缓存暂未包含 + if (senderId != null) { + result.add(senderId); + } + return result; + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO) { + return groupMessageMapper.selectPage(reqVO); + } + + @Override + public ImGroupMessageDO getGroupMessage(Long id) { + return groupMessageMapper.selectById(id); + } + + @Override + public List getGroupMessageList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return groupMessageMapper.selectByIds(ids); + } + + @Override + public Map getGroupMessageMap(Collection ids) { + return convertMap(getGroupMessageList(ids), ImGroupMessageDO::getId); + } + + private ImGroupMessageServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + + /** + * 计算群消息回执 status:群已读关闭时强制 NO_RECEIPT,忽略发送方传入的 receipt(receipt 为 null 等价 false) + */ + private Integer resolveReceiptStatus(Boolean receipt) { + if (BooleanUtil.isFalse(imProperties.getMessage().isGroupReadEnabled())) { + return ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); + } + return BooleanUtil.isTrue(receipt) + ? ImMessageReceiptStatusEnum.PENDING.getStatus() + : ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); + } + + /** + * 禁言状态校验:全群禁言 → 成员禁言;群主 / 管理员豁免全群禁言 + */ + private void validateMuteStatus(ImGroupDO group, ImGroupMemberDO senderMember) { + boolean isOwnerOrAdmin = ImGroupMemberRoleEnum.isOwnerOrAdmin(senderMember.getRole()); + // 1. 全群禁言:群主 / 管理员豁免 + if (Boolean.TRUE.equals(group.getMutedAll()) && !isOwnerOrAdmin) { + throw exception(GROUP_MUTED_CANNOT_SEND); + } + // 2. 成员禁言 + if (senderMember.getMuteEndTime() != null && senderMember.getMuteEndTime().isAfter(LocalDateTime.now())) { + throw exception(GROUP_MEMBER_MUTED_CANNOT_SEND, senderMember.getMuteEndTime()); + } + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java new file mode 100644 index 0000000000..23ac9b34c2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.module.im.service.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; + +import java.util.List; + +/** + * IM 私聊消息 Service 接口 + * + * @author 芋道源码 + */ +public interface ImPrivateMessageService { + + /** + * 【用户调用】发送私聊消息 + *

+ * 用户在 IM 客户端发送 TEXT / IMAGE 等消息时调用,含幂等、好友校验、敏感词、quote 解析等业务校验。 + * type 校验由 VO 层 {@code @InEnum} + {@code @AssertTrue} 完成(仅允许 normal 类型)。 + * + * @param senderId 发送人编号 + * @param reqVO 发送请求 + * @return 消息 + */ + ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO); + + /** + * 【系统调用】发送私聊消息 + * + * @param senderId 发送人编号 + * @param dto 消息 DTO + * @return 构造的消息 DO(持久化时 id 已回填) + */ + ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendDTO dto); + + /** + * 【用户调用】撤回私聊消息 + * + * @param userId 当前用户编号 + * @param messageId 消息编号 + * @return 撤回后的消息 + */ + ImPrivateMessageDO recallPrivateMessage(Long userId, Long messageId); + + /** + * 拉取私聊消息(增量) + * + * @param userId 当前用户编号 + * @param minId 最小消息 id(不含) + * @param size 拉取数量 + * @return 消息列表 + */ + List pullPrivateMessageList(Long userId, Long minId, Integer size); + + /** + * 标记私聊消息已读 + *

+ * 语义:把「对方发给当前用户、id <= messageId 的待回执消息」一次性更新为已完成(DONE)并前进读位置, + * 与群聊 readGroupMessages 对称,避免「select-then-update」两步式带来的竞态。 + * + * @param userId 当前用户编号 + * @param receiverId 接收方用户编号(对方) + * @param messageId 已读位置(含),通常是前端会话内最大消息 id + */ + void readPrivateMessages(Long userId, Long receiverId, Long messageId); + + /** + * 查询对方已读到我发的最大消息 id + *

+ * 用于多端 / 离线场景下的已读位置补齐:客户端进入会话或断线重连后, + * 调用此接口拿到对方的 maxReadId,再按 id <= maxReadId 更新本地自发消息的回执状态,弥补离线期间错过的 RECEIPT 推送事件。 + * + * @param userId 当前用户编号 + * @param peerId 对方用户编号 + * @return 对方已读到的最大消息 id;对方一条都没读过时返回 null + */ + Long getMaxReadMessageId(Long userId, Long peerId); + + /** + * 查询私聊历史消息(游标拉取) + * + * @param userId 当前用户编号 + * @param reqVO 拉取请求 + * @return 消息列表(按 id 倒序) + */ + List getPrivateMessageList(Long userId, ImPrivateMessageListReqVO reqVO); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询私聊消息 + */ + PageResult getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO); + + /** + * 【管理后台】获取私聊消息详情 + */ + ImPrivateMessageDO getPrivateMessage(Long id); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java new file mode 100644 index 0000000000..de79f9e010 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java @@ -0,0 +1,297 @@ +package cn.iocoder.yudao.module.im.service.message; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; +import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; +import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReceiptNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImPrivateMessageNotification; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage; +import cn.iocoder.yudao.module.im.util.ImMessageUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; + +/** + * IM 私聊消息 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { + + @Resource + private ImPrivateMessageMapper privateMessageMapper; + + @Resource + private ImFriendService friendService; + @Resource + private ImSensitiveWordService sensitiveWordService; + @Resource + private ImConversationReadService conversationReadService; + + @Resource + private ImWebSocketService imWebSocketService; + + @Resource + private ImProperties imProperties; + + @Override + public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) { + // 1.1 幂等校验:根据 senderId + clientMessageId 查重 + ImPrivateMessageDO existing = privateMessageMapper.selectBySenderIdAndClientMessageId( + senderId, reqVO.getClientMessageId()); + if (existing != null) { + log.info("[sendPrivateMessage][幂等命中 senderId({}) clientMessageId({}) 已存在消息({})]", + senderId, reqVO.getClientMessageId(), existing.getId()); + return existing; + } + // 1.2 消息内容校验 + ImMessageUtils.validateUserMessageContent(reqVO.getType(), reqVO.getContent()); + // 1.3 好友校验 + friendService.validateFriend(senderId, reqVO.getReceiverId()); + // 1.4 文本消息敏感词过滤 + if (ImContentTypeEnum.TEXT.getType().equals(reqVO.getType())) { + sensitiveWordService.validateText(reqVO.getContent()); + } + + // 2.1 引用 quote 消息规范化 + reqVO.setContent(normalizeQuoteContent(reqVO, senderId)); + // 2.2 构建并保存消息;唯一键冲突时回查已存在消息返回 + // 用户私聊消息默认需要回执(receipt 不传按 true);系统通知走 DTO 通道,默认不回执 + Boolean receipt = reqVO.getReceipt() != null ? reqVO.getReceipt() : Boolean.TRUE; + ImPrivateMessageDO message = BeanUtils.toBean(reqVO, ImPrivateMessageDO.class, m -> m + .setSenderId(senderId).setStatus(ImMessageStatusEnum.NORMAL.getStatus()) + .setReceiptStatus(resolveReceiptStatus(receipt)).setSendTime(LocalDateTime.now())); + try { + privateMessageMapper.insert(message); + } catch (DuplicateKeyException e) { + log.warn("[sendPrivateMessage][senderId({}) clientMessageId({}) 并发插入冲突,回查返回]", + senderId, reqVO.getClientMessageId()); + return privateMessageMapper.selectBySenderIdAndClientMessageId(senderId, reqVO.getClientMessageId()); + } + + // 3. WebSocket 异步推送:接收方 + 发送方多端同步 + ImPrivateMessageNotification notification = ImPrivateMessageNotification.ofSend(message); + imWebSocketService.sendNotificationAsync(message.getReceiverId(), ImConversationTypeEnum.PRIVATE.getType(), + notification.getType(), notification); + imWebSocketService.sendNotificationAsync(senderId, ImConversationTypeEnum.PRIVATE.getType(), + notification.getType(), notification); + return message; + } + + @Override + public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendDTO dto) { + // 1.1 content 序列化:null / String 透传,POJO 走 JSON + Object payload = dto.getContent(); + String contentString = payload == null || payload instanceof String + ? (String) payload + : JsonUtils.toJsonString(payload); + // 1.2 构建消息 + ImPrivateMessageDO message = new ImPrivateMessageDO().setClientMessageId(IdUtil.fastSimpleUUID()) + .setSenderId(senderId).setReceiverId(dto.getReceiverId()) + .setType(dto.getType()).setContent(contentString) + .setStatus(ImMessageStatusEnum.NORMAL.getStatus()) + .setReceiptStatus(resolveReceiptStatus(dto.getReceipt())).setSendTime(LocalDateTime.now()); + // 1.3 决定是否持久化:dto.persistent 优先;为 null 时按 type 默认 + boolean persistent = dto.getPersistent() != null + ? dto.getPersistent() + : ImContentTypeEnum.validate(dto.getType()).isPersistent(); + if (persistent) { + privateMessageMapper.insert(message); + } + + // 2. WebSocket 异步推送:双向(默认);单边语义(persistent=false)下仅推 sender 多端,对方不感知 + ImPrivateMessageNotification notification = ImPrivateMessageNotification.ofSend(message); + if (persistent) { + imWebSocketService.sendNotificationAsync(dto.getReceiverId(), ImConversationTypeEnum.PRIVATE.getType(), + notification.getType(), notification); + } + imWebSocketService.sendNotificationAsync(senderId, ImConversationTypeEnum.PRIVATE.getType(), + notification.getType(), notification); + return message; + } + + /** + * 计算私聊消息回执 status:私聊已读关闭时强制 NO_RECEIPT,忽略发送方传入的 receipt(receipt 为 null 等价 false) + */ + private Integer resolveReceiptStatus(Boolean receipt) { + if (BooleanUtil.isFalse(imProperties.getMessage().isPrivateReadEnabled())) { + return ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); + } + return BooleanUtil.isTrue(receipt) + ? ImMessageReceiptStatusEnum.PENDING.getStatus() + : ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ImPrivateMessageDO recallPrivateMessage(Long userId, Long messageId) { + // 1.1 校验消息存在 + ImPrivateMessageDO message = privateMessageMapper.selectById(messageId); + if (message == null) { + throw exception(MESSAGE_NOT_EXISTS); + } + // 1.2 只能撤回自己发送的消息 + if (ObjUtil.notEqual(message.getSenderId(), userId)) { + throw exception(MESSAGE_RECALL_DENIED); + } + // 1.3 不能重复撤回 + if (ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) { + throw exception(MESSAGE_ALREADY_RECALLED); + } + // 1.4 只允许撤回限定时间内的消息 + int recallTimeoutMinutes = imProperties.getMessage().getRecallTimeoutMinutes(); + if (message.getSendTime().plusMinutes(recallTimeoutMinutes).isBefore(LocalDateTime.now())) { + throw exception(MESSAGE_RECALL_TIMEOUT, recallTimeoutMinutes); + } + + // 2. 更新原消息状态为撤回 + privateMessageMapper.updateById(new ImPrivateMessageDO().setId(messageId) + .setStatus(ImMessageStatusEnum.RECALL.getStatus())); + + // 3. 发送撤回事件 + return sendPrivateMessage(userId, new ImPrivateMessageSendDTO().setReceiverId(message.getReceiverId()) + .setType(ImContentTypeEnum.RECALL.getType()).setContent(new RecallMessage().setMessageId(messageId))); + } + + /** + * 私聊引用消息规范化 + * + * @param reqVO 发送请求 + * @param senderId 发送人编号 + * @return 规范化后的 content + */ + private String normalizeQuoteContent(ImPrivateMessageSendReqVO reqVO, Long senderId) { + // 解析客户端 content 里的 quote.messageId + Long quoteMessageId = ImMessageUtils.parseQuoteMessageId(reqVO.getContent()); + + // 情况一:没有 quoteMessageId,直接 remove 掉 content 里可能伪造的 quote 字段 + if (quoteMessageId == null) { + return ImMessageUtils.removeQuote(reqVO.getContent()); + } + + // 情况二:有 quoteMessageId,加载原消息并校验 + ImPrivateMessageDO original = privateMessageMapper.selectById(quoteMessageId); + if (original == null + || ImMessageStatusEnum.RECALL.getStatus().equals(original.getStatus())) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 校验是同对话 + boolean sameConversation = (ObjUtil.equal(original.getSenderId(), senderId) // 发送人是当前用户,接收人是对方 + && ObjUtil.equal(original.getReceiverId(), reqVO.getReceiverId())) + || (ObjUtil.equal(original.getSenderId(), reqVO.getReceiverId()) // 发送人是对方,接收人是当前用户 + && ObjUtil.equal(original.getReceiverId(), senderId)); + if (!sameConversation) { + throw exception(MESSAGE_QUOTE_INVALID); + } + // 构建 quote 对象并注入 content + QuoteMessage quote = ImMessageUtils.buildQuote(original.getId(), + original.getSenderId(), original.getType(), original.getContent()); + return ImMessageUtils.appendQuote(reqVO.getContent(), quote); + } + + @Override + public List pullPrivateMessageList(Long userId, Long minId, Integer size) { + int maxPullSize = imProperties.getMessage().getMaxPullSize(); + if (size > maxPullSize) { + throw exception(MESSAGE_PULL_SIZE_EXCEEDED, maxPullSize); + } + // 0. 拉取时间窗;超过窗口的老消息不再通过离线通道推送 + LocalDateTime minSendTime = LocalDateTime.now().minusDays(imProperties.getMessage().getPrivatePullMaxDays()); + + // 根据 minId 和 minSendTime 拉取消息,避免 minId 恰好被发出后才拉取,导致漏消息 + List messages = privateMessageMapper.selectListByMinId(userId, minId, minSendTime, size); + log.info("[pullPrivateMessageList][userId({}) minId({}) size({}) result({})]", + userId, minId, size, messages.size()); + return messages; + } + + @Override + public void readPrivateMessages(Long userId, Long receiverId, Long messageId) { + // 1. 全局开关校验 + if (BooleanUtil.isFalse(imProperties.getMessage().isPrivateReadEnabled())) { + throw exception(MESSAGE_PRIVATE_READ_DISABLED); + } + Assert.notNull(messageId, "已读消息编号不能为空"); + + // 2. 回执置 DONE:把 (receiverId → userId) 上 id <= messageId、待回执(PENDING) 的消息标记为已完成 + // status 不再表达已读(保持 NORMAL);只翻 PENDING 行,避免覆盖 NO_RECEIPT / 已 DONE + privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus( + receiverId, userId, messageId, ImMessageReceiptStatusEnum.PENDING.getStatus(), + new ImPrivateMessageDO().setReceiptStatus(ImMessageReceiptStatusEnum.DONE.getStatus())); + + // 3. 同步写 im_conversation_read(读位置唯一权威,单调递增);读位置前进才下发事件 + boolean advanced = conversationReadService.updateConversationReadPosition( + userId, ImConversationTypeEnum.PRIVATE.getType(), receiverId, messageId); + if (!advanced) { + return; + } + + // 4. 异步发送 READ + RECEIPT 事件(已读位置以前端上报为准,与多端 / 对方 UI 显示一致) + imWebSocketService.sendNotificationAsync(userId, ImConversationTypeEnum.PRIVATE.getType(), + ImContentTypeEnum.READ.getType(), ImMessageReadNotification.ofPrivate(userId, receiverId, messageId)); + imWebSocketService.sendNotificationAsync(receiverId, ImConversationTypeEnum.PRIVATE.getType(), + ImContentTypeEnum.RECEIPT.getType(), + ImMessageReceiptNotification.ofPrivate(userId, receiverId, messageId)); + } + + @Override + public Long getMaxReadMessageId(Long userId, Long peerId) { + if (BooleanUtil.isFalse(imProperties.getMessage().isPrivateReadEnabled())) { + throw exception(MESSAGE_PRIVATE_READ_DISABLED); + } + // 对端 peer 在「与 userId 的会话」里的读位置 = peer 把 userId 发的消息读到哪 + return conversationReadService.getConversationReadMessageId( + peerId, ImConversationTypeEnum.PRIVATE.getType(), userId); + } + + @Override + public List getPrivateMessageList(Long userId, ImPrivateMessageListReqVO reqVO) { + return privateMessageMapper.selectHistoryList(userId, reqVO.getReceiverId(), reqVO.getMaxId(), reqVO.getLimit()); + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO) { + return privateMessageMapper.selectPage(reqVO); + } + + @Override + public ImPrivateMessageDO getPrivateMessage(Long id) { + return privateMessageMapper.selectById(id); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImGroupMessageSendDTO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImGroupMessageSendDTO.java new file mode 100644 index 0000000000..87a7f88173 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImGroupMessageSendDTO.java @@ -0,0 +1,235 @@ +package cn.iocoder.yudao.module.im.service.message.dto; + +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.service.websocket.notification.group.*; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * IM 群聊消息发送 DTO + * + * @author 芋道源码 + */ +@Data +public class ImGroupMessageSendDTO { + + /** + * 群编号 + */ + @NotNull(message = "群编号不能为空") + private Long groupId; + /** + * 消息类型 + *

+ * 枚举 {@link ImContentTypeEnum} + */ + @NotNull(message = "消息类型不能为空") + private Integer type; + /** + * 消息内容 + *

+ * 支持 String / POJO;非 String 时由 service 序列化为 JSON + */ + private Object content; + /** + * @ 目标用户编号列表 + */ + private List atUserIds; + /** + * 定向接收用户编号列表 + *

+ * 为空表示全员可见 + */ + private List receiverUserIds; + /** + * 是否需要回执 + *

+ * 缺省视为无需回执(false) + */ + private Boolean receipt; + + // ========== 群广播事件静态工厂(对应 ImContentTypeEnum 群事件) ========== + + public static ImGroupMessageSendDTO ofGroupCreate(Long groupId, Long operatorUserId, Collection memberUserIds) { + GroupCreateNotification notification = new GroupCreateNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setMemberUserIds(new ArrayList<>(memberUserIds)); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_CREATE.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupInfoUpdate(Long groupId, Long operatorUserId, + String oldAvatar, String newAvatar, + Boolean oldJoinApproval, Boolean newJoinApproval) { + GroupInfoUpdateNotification notification = new GroupInfoUpdateNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setOldAvatar(oldAvatar); + notification.setNewAvatar(newAvatar); + notification.setOldJoinApproval(oldJoinApproval); + notification.setNewJoinApproval(newJoinApproval); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_INFO_UPDATE.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMemberQuit(Long groupId, Long operatorUserId) { + GroupMemberQuitNotification notification = new GroupMemberQuitNotification(); + notification.setOperatorUserId(operatorUserId); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MEMBER_QUIT.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupOwnerTransfer(Long groupId, Long operatorUserId, Long newOwnerUserId) { + GroupOwnerTransferNotification notification = new GroupOwnerTransferNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setNewOwnerUserId(newOwnerUserId); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_OWNER_TRANSFER.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMemberKick(Long groupId, Long operatorUserId, Collection memberUserIds) { + GroupMemberKickNotification notification = new GroupMemberKickNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setMemberUserIds(new ArrayList<>(memberUserIds)); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MEMBER_KICK.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMemberInvite(Long groupId, Long operatorUserId, Collection memberUserIds) { + GroupMemberInviteNotification notification = new GroupMemberInviteNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setMemberUserIds(new ArrayList<>(memberUserIds)); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MEMBER_INVITE.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMemberEnter(Long groupId, Long entrantUserId, Integer addSource) { + GroupMemberEnterNotification notification = new GroupMemberEnterNotification(); + notification.setOperatorUserId(entrantUserId); + notification.setEntrantUserId(entrantUserId); + notification.setAddSource(addSource); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MEMBER_ENTER.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupDissolve(Long groupId, Long operatorUserId) { + GroupDissolveNotification notification = new GroupDissolveNotification(); + notification.setOperatorUserId(operatorUserId); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_DISSOLVE.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMemberNicknameUpdate(Long groupId, Long operatorUserId, String displayUserName) { + GroupMemberNicknameUpdateNotification notification = new GroupMemberNicknameUpdateNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setDisplayUserName(displayUserName); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MEMBER_NICKNAME_UPDATE.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupAdminAdd(Long groupId, Long operatorUserId, Collection memberUserIds) { + GroupAdminAddNotification notification = new GroupAdminAddNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setMemberUserIds(new ArrayList<>(memberUserIds)); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_ADMIN_ADD.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupAdminRemove(Long groupId, Long operatorUserId, Collection memberUserIds) { + GroupAdminRemoveNotification notification = new GroupAdminRemoveNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setMemberUserIds(new ArrayList<>(memberUserIds)); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_ADMIN_REMOVE.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupNoticeUpdate(Long groupId, Long operatorUserId, String oldNotice, String newNotice) { + GroupNoticeUpdateNotification notification = new GroupNoticeUpdateNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setOldNotice(oldNotice).setNewNotice(newNotice); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_NOTICE_UPDATE.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupNameUpdate(Long groupId, Long operatorUserId, String oldName, String newName) { + GroupNameUpdateNotification notification = new GroupNameUpdateNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setOldName(oldName).setNewName(newName); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_NAME_UPDATE.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMemberSettingUpdate(Long groupId, Long operatorUserId, Boolean silent, String groupRemark) { + GroupMemberSettingUpdateNotification notification = new GroupMemberSettingUpdateNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setSilent(silent).setGroupRemark(groupRemark); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MEMBER_SETTING_UPDATE.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMessagePin(Long groupId, Long operatorUserId, ImGroupMessageDO message) { + GroupMessagePinNotification notification = new GroupMessagePinNotification(); + GroupMessagePinNotification.PinnedMessage pinnedMessage = new GroupMessagePinNotification.PinnedMessage() + .setId(message.getId()).setSenderId(message.getSenderId()).setGroupId(message.getGroupId()) + .setType(message.getType()).setContent(message.getContent()).setSendTime(message.getSendTime()) + .setAtUserIds(message.getAtUserIds()).setReceiverUserIds(message.getReceiverUserIds()); + notification.setOperatorUserId(operatorUserId); + notification.setMessageId(message.getId()).setMessage(pinnedMessage); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MESSAGE_PIN.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMessageUnpin(Long groupId, Long operatorUserId, Long messageId) { + GroupMessageUnpinNotification notification = new GroupMessageUnpinNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setMessageId(messageId); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MESSAGE_UNPIN.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMemberMuted(Long groupId, Long operatorUserId, + Long mutedUserId, + java.time.LocalDateTime muteEndTime) { + GroupMemberMutedNotification notification = new GroupMemberMutedNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setMutedUserId(mutedUserId); + notification.setMuteEndTime(muteEndTime); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MEMBER_MUTED.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMemberCancelMuted(Long groupId, Long operatorUserId, Long mutedUserId) { + GroupMemberCancelMutedNotification notification = new GroupMemberCancelMutedNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setMutedUserId(mutedUserId); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MEMBER_CANCEL_MUTED.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupMuted(Long groupId, Long operatorUserId) { + GroupMutedNotification notification = new GroupMutedNotification(); + notification.setOperatorUserId(operatorUserId); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_MUTED.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupCancelMuted(Long groupId, Long operatorUserId) { + GroupCancelMutedNotification notification = new GroupCancelMutedNotification(); + notification.setOperatorUserId(operatorUserId); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_CANCEL_MUTED.getType()).setContent(notification); + } + + public static ImGroupMessageSendDTO ofGroupBanned(Long groupId, Long operatorUserId, boolean banned) { + GroupBannedNotification notification = new GroupBannedNotification(); + notification.setOperatorUserId(operatorUserId); + notification.setBanned(banned); + return new ImGroupMessageSendDTO().setGroupId(groupId) + .setType(ImContentTypeEnum.GROUP_BANNED.getType()).setContent(notification); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImPrivateMessageSendDTO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImPrivateMessageSendDTO.java new file mode 100644 index 0000000000..5758c3bd68 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImPrivateMessageSendDTO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.im.service.message.dto; + +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import lombok.Data; + +/** + * IM 私聊消息发送 DTO + * + * @author 芋道源码 + */ +@Data +public class ImPrivateMessageSendDTO { + + /** + * 接收人编号 + */ + private Long receiverId; + /** + * 消息类型 + *

+ * 枚举 {@link ImContentTypeEnum} + */ + private Integer type; + /** + * 消息内容 + *

+ * 支持 String / POJO;非 String 时由 service 序列化为 JSON + */ + private Object content; + /** + * 是否持久化 + 推送给接收方(单边语义开关) + *

+ * null:默认按 {@link ImContentTypeEnum#isPersistent()} 决定是否入库 + 双向 WS 推送(保持原行为)
+ * false:覆盖为单边——不入库、仅推 sender 多端,对方不感知(如「你已删除 XXX」这类仅自己可见的 TIP)
+ * true:覆盖为双向 + 入库 + */ + private Boolean persistent; + /** + * 是否需要回执;null 等价 false(系统消息默认不需要回执) + */ + private Boolean receipt; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallService.java new file mode 100644 index 0000000000..5be2c7c11b --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallService.java @@ -0,0 +1,169 @@ +package cn.iocoder.yudao.module.im.service.rtc; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo.ImRtcCallManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.rtc.vo.ImRtcCallCreateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.rtc.vo.ImRtcCallInviteReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcParticipantDO; + +import java.util.List; + +/** + * IM 实时通话 Service + * + * @author 芋道源码 + */ +public interface ImRtcCallService { + + /** + * 创建新通话;同好友对 / 同群已有进行中通话直接抛错(群场景应改走 {@link #inviteCall} 追加邀请,或 {@link #joinCall} 加入旁观) + * + * @param userId 发起人编号;通常是当前登录用户 + * @param reqVO 请求参数(scene / mediaType / peerUserId 或 groupId + inviteeIds) + * @return 通话主表 + */ + ImRtcCallDO createCall(Long userId, ImRtcCallCreateReqVO reqVO); + + /** + * 通话中追加邀请:仅群通话场景可用;本人必须是房内 JOINED 参与者;给新邀请人推 RTC_CALL(INVITE) + * + * @param userId 操作人编号;必须是当前会话参与者 + * @param reqVO room + 新邀请的用户编号集合 + */ + void inviteCall(Long userId, ImRtcCallInviteReqVO reqVO); + + /** + * 加入已有群通话:用于群胶囊条「加入」按钮;旁观者作为 JOINER 加入,邀请池内成员转 JOINED + * + * @param userId 加入者用户编号 + * @param room 业务通话编号;从胶囊条 activeCall 拿 + * @return 通话主表 + */ + ImRtcCallDO joinCall(Long userId, String room); + + /** + * 接听通话:参与者 INVITING → JOINED;主表 CREATED → RUNNING(首次有非发起人接通时) + * + * @param userId 接听者用户编号 + * @param room 业务通话编号 + * @return 通话主表 + */ + ImRtcCallDO acceptCall(Long userId, String room); + + /** + * 拒绝通话;仅 INVITING 状态可拒;群通话拒绝等同于不参与,房间仍存在 + * + * @param userId 拒接者用户编号 + * @param room 业务通话编号 + */ + void rejectCall(Long userId, String room); + + /** + * 取消邀请;主叫在 INVITING 状态主动取消 + * + * @param userId 取消者用户编号(必须是主叫) + * @param room 业务通话编号 + */ + void cancelCall(Long userId, String room); + + /** + * 离开通话;RUNNING 状态下离开;私聊任一方离开 = 通话结束;群通话最后一人离开才结束 + * + * @param userId 离开者用户编号 + * @param room 业务通话编号 + */ + void leaveCall(Long userId, String room); + + /** + * 查询当前正在进行的通话;目前仅群聊场景(胶囊条),私聊未来扩展再补 peerUserId 参数 + *

+ * 鉴权:仅群活跃成员可查;防止任意用户探测群通话状态 / 拿到 inviter / inviteeIds 等敏感信息 + * + * @param userId 操作人编号;通常是当前登录用户 + * @param groupId 群编号 + * @return 通话主表;不存在返回 null + */ + ImRtcCallDO getActiveCall(Long userId, Long groupId); + + /** + * 查询某通话的全部参与者明细;交给 Controller 拼装 inviteeIds / joinedUserIds + * + * @param room 业务通话编号 + * @return 参与者明细列表 + */ + List getCallParticipantList(String room); + + /** + * 签发指定用户进入该通话的 LiveKit Token;供 Controller 拼接到响应 VO + * + * @param userId 进房用户编号;token 内 displayName 取该用户昵称 + * @param room 业务通话编号 + * @return JWT 字符串 + */ + String signCallToken(Long userId, String room); + + /** + * 处理 LiveKit Webhook 事件;用于关 tab / 强杀 / 网络断开等异常退出场景的兜底清理 + *

+ * 关键事件:participant_left(成员离开) / room_finished(房间结束)。 + * 前端正常 leave 时,也会触发同样的 LiveKit 事件;此处需做幂等处理,session 已被业务接口移除时直接忽略。 + * + * @param event LiveKit Webhook 事件 + */ + void handleLiveKitEvent(cn.iocoder.yudao.module.im.framework.rtc.core.LiveKitWebhookEventDTO event); + + /** + * 【定时任务调用】清理僵尸通话 + * + * @param thresholdMinutes 通话创建超过此分钟数才纳入扫描;调用方保证 > 0 + * @return 清理数量 + */ + int cleanupZombieCalls(int thresholdMinutes); + + /** + * 【定时任务调用】超时未接通的 INVITING 参与者:单人粒度标 NO_ANSWER + 推 RTC_CALL(NO_ANSWER) 让前端 banner 收敛; + * 若导致通话只剩主叫,由 endSessionIfTerminal 级联关房 + * + * @param thresholdMinutes 邀请时间超过此分钟数才纳入扫描;调用方保证 > 0 + * @return 超时处理数量 + */ + int timeoutInvitingParticipants(int thresholdMinutes); + + /** + * 前端 RUNNING 端 timer 兜底;立即扫描指定 room 内超时的 INVITING 参与者,等同 Job 但限定单 room; + * 实际超时阈值由后端 {@link cn.iocoder.yudao.module.im.framework.config.ImProperties.Rtc#getInviteTimeoutMinutes()} 决定, + * 避免前后端配置不一致;接口静默,所有边界(room 不存在 / 鉴权失败 / 无超时候选)都返回 false 不抛异常 + * + * @param userId 调用者用户编号;必须是该 room 的参与者 + * @param room 业务通话编号 + */ + void noAnswerCallCheck(Long userId, String room); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】获得通话记录分页 + * + * @param reqVO 分页查询条件 + * @return 通话记录分页 + */ + PageResult getCallPage(ImRtcCallManagerPageReqVO reqVO); + + /** + * 【管理后台】获得通话记录 + * + * @param id 通话编号 + * @return 通话记录 + */ + ImRtcCallDO getCall(Long id); + + /** + * 【管理后台】按通话编号查询参与者明细 + * + * @param id 通话编号 + * @return 参与者明细列表;通话不存在时返回空集合 + */ + List getCallParticipantListByCallId(Long id); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java new file mode 100644 index 0000000000..6aa4f56c63 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java @@ -0,0 +1,1186 @@ +package cn.iocoder.yudao.module.im.service.rtc; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.rtc.vo.ImRtcCallManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.rtc.vo.ImRtcCallCreateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.rtc.vo.ImRtcCallInviteReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcParticipantDO; +import cn.iocoder.yudao.module.im.dal.mysql.rtc.ImRtcCallMapper; +import cn.iocoder.yudao.module.im.dal.mysql.rtc.ImRtcParticipantMapper; +import cn.iocoder.yudao.module.im.dal.redis.rtc.ImRtcCallLockRedisDAO; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallEndReasonEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallStatusEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantRoleEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantStatusEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.framework.rtc.core.LiveKitClient; +import cn.iocoder.yudao.module.im.framework.rtc.core.LiveKitWebhookEventDTO; +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; +import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; +import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; +import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.rtc.*; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; + +/** + * IM 实时通话 Service 实现 + *

+ * 存储模型:DB 单一存储(im_rtc_call 主表 + im_rtc_participant 明细表) + *

+ * 并发幂等:同好友对 / 同群活跃唯一性走 {@link ImRtcCallLockRedisDAO} 分布式锁 + 锁内 SELECT 兜底;webhook 兜底走条件 UPDATE; + *

+ * 推送通道分流: + * 1601 RTC_CALL(INVITING / JOINED / REJECTED / NO_ANSWER / LEFT 子类型)→ {@link ImWebSocketService#sendNotificationAsync} 仅推参与方; + * 1602 / 1603 PARTICIPANT_CONNECTED / DISCONNECTED → {@link ImWebSocketService} 推参与方 + 群通话场景广播全群; + * 1610 RTC_CALL_START + 1611 RTC_CALL_END → {@link ImPrivateMessageService} / {@link ImGroupMessageService} 入消息流当聊天 tip + * (START 仅群通话;两者分别在 invite / cancel(leave) 事务里 INSERT,自增 id 自然保证顺序) + *

+ * 职责边界:媒体协商完全交给 LiveKit;后端只做会话状态机、Token 签发、来电信令推送、通话历史落消息流;房内媒体流变化交给 LiveKit 客户端事件(TrackSubscribed 等),后端不重复推 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class ImRtcCallServiceImpl implements ImRtcCallService { + + @Resource + private ImRtcCallMapper rtcCallMapper; + @Resource + private ImRtcParticipantMapper rtcParticipantMapper; + @Resource + private ImRtcCallLockRedisDAO rtcCallLockRedisDAO; + + @Resource + private ImGroupMemberService groupMemberService; + @Resource + private ImFriendService friendService; + @Resource + private ImWebSocketService webSocketService; + @Resource + private ImPrivateMessageService privateMessageService; + @Resource + private ImGroupMessageService groupMessageService; + + @Resource + private LiveKitClient liveKitClient; + + @Resource + private AdminUserApi adminUserApi; + + @Resource + private ImProperties imProperties; + + // ========== 业务接口 ========== + + @Override + @SneakyThrows + @Transactional(rollbackFor = Exception.class) + public ImRtcCallDO createCall(Long userId, ImRtcCallCreateReqVO reqVO) { + validateEnabled(); + // 1. 校验入参与场景 + validateCreateCall(userId, reqVO); + + // 2. 加锁后跑业务主体;同好友对 / 同群串行,避免并发各开一通的竞态 + if (ImConversationTypeEnum.isGroup(reqVO.getConversationType())) { + return rtcCallLockRedisDAO.lockGroup(reqVO.getGroupId(), () -> createGroupCall(userId, reqVO)); + } + Long peerUserId = CollUtil.getFirst(reqVO.getInviteeIds()); + return rtcCallLockRedisDAO.lockPrivate(userId, peerUserId, () -> createPrivateCall(userId, reqVO, peerUserId)); + } + + /** + * 群通话创建锁内主体:群有活跃通话直接抛(引导走 inviteCall / joinCall); + * 否则走完整生命周期,若发起人自身忙线立即 end(BUSY) 留下通话记录 + * + * @param userId 发起人编号 + * @param reqVO 创建请求 + * @return 通话主表(可能 status=ENDED 表示自身忙线) + */ + private ImRtcCallDO createGroupCall(Long userId, ImRtcCallCreateReqVO reqVO) { + // 1.1 同群有活跃通话 → 直接抛异常(UI 应已拦截),引导用户走 inviteCall / joinCall;避免重复开通 + ImRtcCallDO active = rtcCallMapper.selectLastOneByGroupIdAndStatusIn( + reqVO.getGroupId(), ImRtcCallStatusEnum.ACTIVE_STATUSES); + if (active != null) { + throw exception(RTC_GROUP_CALL_ACTIVE); + } + // 1.2 先检测发起人忙线状态;不抛,留给下方 end 决定 + boolean selfBusy = rtcParticipantMapper.selectLastOneByUserIdAndStatus( + userId, ImRtcParticipantStatusEnum.ACTIVE_STATUSES) != null; + + // 2. 完整生命周期:INSERT + INVITE × N + START 全推 + ImRtcCallDO call = createCall0(userId, reqVO); + + // 3. 自身忙线立即 end(BUSY);END 推送给群,群成员看到完整 START + END + if (selfBusy) { + endSession(call, userId, ImRtcCallEndReasonEnum.BUSY); + } + return call; + } + + /** + * 私聊创建锁内主体:双方忙线时仍走完整生命周期(create + 立即 end(BUSY)), + * 同一对正在通话视作数据异常直接抛 + * + * @param userId 发起人编号 + * @param reqVO 创建请求 + * @param peerUserId 对端编号;来自 reqVO.inviteeIds 的唯一元素 + * @return 通话主表(可能 status=ENDED 表示忙线) + */ + private ImRtcCallDO createPrivateCall(Long userId, ImRtcCallCreateReqVO reqVO, Long peerUserId) { + // 1.1 双方已在同一通话 → 数据异常(UI 应已拦截),直接抛 + if (getActivePrivateCallByPair(userId, peerUserId) != null) { + throw exception(RTC_SELF_BUSY); + } + // 1.2 忙线检测:self 优先(更可执行的提示);不抛,留给下方 end 决定 + boolean selfBusy = rtcParticipantMapper.selectLastOneByUserIdAndStatus( + userId, ImRtcParticipantStatusEnum.ACTIVE_STATUSES) != null; + boolean peerBusy = !selfBusy && rtcParticipantMapper.selectLastOneByUserIdAndStatus( + peerUserId, ImRtcParticipantStatusEnum.ACTIVE_STATUSES) != null; + + // 2. 完整生命周期:INSERT + INVITE 全推 + ImRtcCallDO call = createCall0(userId, reqVO); + + // 3. 忙线立即 end(BUSY);operator 决定两端看到的文案(self busy → operator=自己;peer busy → operator=对端) + if (selfBusy) { + endSession(call, userId, ImRtcCallEndReasonEnum.BUSY); + } else if (peerBusy) { + endSession(call, peerUserId, ImRtcCallEndReasonEnum.BUSY); + } + return call; + } + + @Override + @SneakyThrows + @Transactional(rollbackFor = Exception.class) + public void inviteCall(Long userId, ImRtcCallInviteReqVO reqVO) { + validateEnabled(); + // 1.1 校验通话存在且活跃 + ImRtcCallDO call = validateCallActive(reqVO.getRoom()); + // 1.2 仅群通话支持追加邀请 + if (!ImConversationTypeEnum.isGroup(call.getConversationType())) { + throw exception(RTC_GROUP_REQUIRED); + } + // 1.3 操作者必须是房内 JOINED 参与者 + ImRtcParticipantDO operator = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); + if (operator == null || !ImRtcParticipantStatusEnum.isJoined(operator.getStatus())) { + throw exception(RTC_NOT_PARTICIPANT); + } + // 2. 加群锁后执行追加邀请;避免与新建 / 重复追加的竞态 + rtcCallLockRedisDAO.lockGroup(call.getGroupId(), () -> { + addInvitees(call, userId, reqVO.getInviteeIds()); + return null; + }); + } + + /** + * 新建通话实体;INSERT 主表 + 参与表 + 推送 INVITE / START + * + * @param inviterId 发起人编号 + * @param reqVO 创建请求 + * @return 通话主表 + */ + private ImRtcCallDO createCall0(Long inviterId, ImRtcCallCreateReqVO reqVO) { + // 1. 构造参数:room 用 UUID;解析被邀请池 + String room = IdUtil.fastSimpleUUID(); + LocalDateTime now = LocalDateTime.now(); + Set invitees = resolveInvitees(reqVO, inviterId); + + // 2.1 INSERT 主表;群聊发起人即时 JOINED 但通话仍处 CREATED,等首个非发起人接通才切 RUNNING + ImRtcCallDO call = new ImRtcCallDO().setRoom(room) + .setConversationType(reqVO.getConversationType()).setMediaType(reqVO.getMediaType()) + .setInviterUserId(inviterId).setGroupId(reqVO.getGroupId()) + .setStatus(ImRtcCallStatusEnum.CREATED.getStatus()).setStartTime(now); + rtcCallMapper.insert(call); + // 2.2 批量 INSERT 参与表:发起人即时 JOINED,被邀请人 INVITING 等接通 + List participants = new ArrayList<>(invitees.size() + 1); + participants.add(new ImRtcParticipantDO().setCallId(call.getId()).setRoom(room).setUserId(inviterId) + .setRole(ImRtcParticipantRoleEnum.INVITER.getRole()) + .setStatus(ImRtcParticipantStatusEnum.JOINED.getStatus()) + .setInviteTime(now).setAcceptTime(now)); + for (Long inviteeId : invitees) { + participants.add(new ImRtcParticipantDO() + .setCallId(call.getId()).setRoom(room).setUserId(inviteeId) + .setRole(ImRtcParticipantRoleEnum.INVITEE.getRole()) + .setStatus(ImRtcParticipantStatusEnum.INVITING.getStatus()) + .setInviteTime(now)); + } + rtcParticipantMapper.insertBatch(participants); + + // 3.1 推送通知:RTC_CALL(INVITE) 给每个被邀请人 + AdminUserRespDTO inviterUser = adminUserApi.getUser(inviterId); + Map inviteeMap = adminUserApi.getUserMap(invitees); + for (Long inviteeId : invitees) { + pushCallInviteNotification(call, inviterUser, inviteeId, inviteeMap.get(inviteeId), invitees); + } + // 3.2 向消息流写入 RTC_CALL_START;群聊全群广播,私聊定向给被叫,作为会话列表预览的依据 + pushCallStartNotification(call, inviterUser, invitees); + return call; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ImRtcCallDO joinCall(Long userId, String room) { + validateEnabled(); + // 1.1 校验通话存在且活跃 + ImRtcCallDO call = validateCallActive(room); + // 1.2 仅群通话支持「旁观者加入」 + if (!ImConversationTypeEnum.isGroup(call.getConversationType())) { + throw exception(RTC_GROUP_REQUIRED); + } + // 1.3 校验当前用户是该群有效成员;防止仅凭 room 就拿到 LiveKit token 越权入会 + groupMemberService.validateMemberInGroup(call.getGroupId(), userId); + // 1.4 校验当前用户没有其它活跃通话 + validateUserNotInOtherCall(userId, call.getRoom()); + + // 2. 入参与表:已有记录切回 JOINED;不在记录则以 JOINER 角色 INSERT + LocalDateTime now = LocalDateTime.now(); + joinParticipant(call, userId, now); + + // 3. 主表 CREATED → RUNNING(首次有非发起人加入) + maybeMarkOngoing(call, userId, now); + return call; + } + + /** + * 给已存在的活跃群通话追加邀请;批量校验群成员 + 去重已在通话池 + 推送 INVITE + * + * @param call 活跃通话主表 + * @param inviterId 本次追加邀请的发起人;已是房内 JOINED 参与者 + * @param inviteeIds 本次追加的被邀请人编号 + */ + private void addInvitees(ImRtcCallDO call, Long inviterId, Collection inviteeIds) { + // 1.1 校验被邀请人都是群活跃成员 + groupMemberService.validateMembersInGroup(call.getGroupId(), inviteeIds); + // 1.2 排除已在通话池的;剩余即本次新邀请 + List existingParticipants = rtcParticipantMapper.selectListByRoom(call.getRoom()); + Set existingUserIds = CollectionUtils.convertSet(existingParticipants, ImRtcParticipantDO::getUserId); + Set incomingUserIds = new LinkedHashSet<>(inviteeIds); + incomingUserIds.removeAll(existingUserIds); + if (CollUtil.isEmpty(incomingUserIds)) { + return; + } + long activeCount = existingParticipants.stream() + .filter(participant -> ImRtcParticipantStatusEnum.ACTIVE_STATUSES.contains(participant.getStatus())) + .count(); + if (activeCount + incomingUserIds.size() > imProperties.getRtc().getGroupMaxParticipants()) { + throw exception(RTC_GROUP_INVITEE_OVER_LIMIT); + } + + // 2. 批量 INSERT 新邀请人 + LocalDateTime now = LocalDateTime.now(); + List participants = CollectionUtils.convertList(incomingUserIds, inviteeId -> + new ImRtcParticipantDO().setCallId(call.getId()).setRoom(call.getRoom()).setUserId(inviteeId) + .setRole(ImRtcParticipantRoleEnum.INVITEE.getRole()) + .setStatus(ImRtcParticipantStatusEnum.INVITING.getStatus()).setInviteTime(now)); + rtcParticipantMapper.insertBatch(participants); + + // 3. 推送通知:RTC_CALL(INVITE) 给每个新邀请人 + AdminUserRespDTO inviter = adminUserApi.getUser(inviterId); + Map inviteeMap = adminUserApi.getUserMap(incomingUserIds); + for (Long inviteeId : incomingUserIds) { + pushCallInviteNotification(call, inviter, inviteeId, inviteeMap.get(inviteeId), incomingUserIds); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ImRtcCallDO acceptCall(Long userId, String room) { + validateEnabled(); + // 1.1 校验通话存在且活跃 + ImRtcCallDO call = validateCallActive(room); + // 1.2 校验本人是该通话的参与者 + ImRtcParticipantDO participant = validateParticipant(call, userId); + + // 2.1 已 JOINED 直接幂等返回 + if (ImRtcParticipantStatusEnum.isJoined(participant.getStatus())) { + return call; + } + validateUserNotInOtherCall(userId, call.getRoom()); + // 2.2 仅 INVITING → JOINED;其它状态拒 + if (!ImRtcParticipantStatusEnum.isInviting(participant.getStatus())) { + throw exception(RTC_SESSION_NOT_EXISTS); + } + LocalDateTime now = LocalDateTime.now(); + int updated = rtcParticipantMapper.updateByIdAndStatus(participant.getId(), ImRtcParticipantStatusEnum.INVITING.getStatus(), + new ImRtcParticipantDO().setId(participant.getId()).setStatus(ImRtcParticipantStatusEnum.JOINED.getStatus()).setAcceptTime(now)); + if (updated == 0) { + throw exception(RTC_SESSION_NOT_EXISTS); + } + + // 3. 主表 CREATED → RUNNING(首次有非发起人接通) + maybeMarkOngoing(call, userId, now); + return call; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void rejectCall(Long userId, String room) { + validateEnabled(); + // 1.1 校验通话存在且活跃 + ImRtcCallDO call = validateCallActive(room); + // 1.2 校验本人是该通话的参与者 + ImRtcParticipantDO participant = validateParticipant(call, userId); + // 1.3 仅 INVITING 状态可拒 + if (!ImRtcParticipantStatusEnum.isInviting(participant.getStatus())) { + throw exception(RTC_SESSION_NOT_EXISTS); + } + + // 2. INVITING → REJECTED;并发已变更则忽略 + int updated = rtcParticipantMapper.updateByIdAndStatus(participant.getId(), ImRtcParticipantStatusEnum.INVITING.getStatus(), + new ImRtcParticipantDO().setId(participant.getId()).setStatus(ImRtcParticipantStatusEnum.REJECTED.getStatus()).setLeaveTime(LocalDateTime.now())); + if (updated == 0) { + return; + } + + // 3.1 群通话拒绝:推 RTC_CALL(REJECT) 给主叫,再判定是否要关房(全员拒接 / 只剩主叫一人时收敛) + if (ImConversationTypeEnum.isGroup(call.getConversationType())) { + pushCallRejectNotification(call, userId); + endSessionIfTerminal(call, userId); + return; + } + // 3.2 私聊拒绝:走 endSession(推 RTC_CALL_END(reason=REJECT)) + endSession(call, userId, ImRtcCallEndReasonEnum.REJECT); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelCall(Long userId, String room) { + validateEnabled(); + // 1.1 校验通话存在且活跃 + ImRtcCallDO call = validateCallActive(room); + // 1.2 仅主叫可取消 + if (ObjUtil.notEqual(call.getInviterUserId(), userId)) { + throw exception(RTC_NOT_PARTICIPANT); + } + // 1.3 仅 CREATED 状态可取消(RUNNING 应走 leave) + if (!ImRtcCallStatusEnum.isCreated(call.getStatus())) { + throw exception(RTC_SESSION_NOT_EXISTS); + } + + // 2. 关会话并推 RTC_CALL_END(reason=CANCEL) + endSession(call, userId, ImRtcCallEndReasonEnum.CANCEL); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void leaveCall(Long userId, String room) { + validateEnabled(); + // 1.1 校验通话存在且活跃 + ImRtcCallDO call = validateCallActive(room); + // 1.2 校验本人是该通话的参与者 + ImRtcParticipantDO participant = validateParticipant(call, userId); + + // 2. 当前状态 → LEFT;条件 UPDATE 防并发反复改 + LocalDateTime now = LocalDateTime.now(); + int updated = rtcParticipantMapper.updateByIdAndStatus(participant.getId(), participant.getStatus(), + new ImRtcParticipantDO().setId(participant.getId()).setStatus(ImRtcParticipantStatusEnum.LEFT.getStatus()).setLeaveTime(now)); + if (updated == 0) { + return; + } + + // 3. 群通话已入会参与者离开时推送离线通知 + if (ImConversationTypeEnum.isGroup(call.getConversationType()) + && ImRtcParticipantStatusEnum.isJoined(participant.getStatus())) { + pushParticipantDisconnectedNotification(call, userId); + } + + // 4. 触发关房判定:私聊任一方离开必关;群通话仅在「无人在房 + 无人响铃」时关 + endSessionIfTerminal(call, userId); + } + + /** + * 群通话是否应该关闭 + *

+ * 两种终态: + * 1. JOINED 数 = 0(房内已没人) + * 2. JOINED 数 = 1 且 INVITING 数 = 0(只剩 1 人独守,无后续可加入者) + *

+ * 关房后 endSession 会把残留 INVITING 批量改 NO_ANSWER 并推 RTC_CALL_END,响铃端 UI 自动收敛 + * + * @param room 业务通话编号 + * @return true 表示无法继续通话,应关房 + */ + private boolean shouldCloseGroupRoom(String room) { + int joined = 0; + int inviting = 0; + for (ImRtcParticipantDO p : rtcParticipantMapper.selectListByRoom(room)) { + if (ImRtcParticipantStatusEnum.isJoined(p.getStatus())) { + joined++; + } else if (ImRtcParticipantStatusEnum.isInviting(p.getStatus())) { + inviting++; + } + } + return joined == 0 || (joined == 1 && inviting == 0); + } + + /** + * 查询两人共同所在的活跃私聊通话 + *

+ * 拆成 3 段简单查询:拿 A 的活跃 participant → 拿主表判私聊未结束 → 看 B 是否在同 call 活跃 + * + * @param userIdA 用户 A 编号 + * @param userIdB 用户 B 编号 + * @return 活跃私聊通话;不存在返回 null + */ + private ImRtcCallDO getActivePrivateCallByPair(Long userIdA, Long userIdB) { + ImRtcParticipantDO participantA = rtcParticipantMapper.selectLastOneByUserIdAndStatus(userIdA, ImRtcParticipantStatusEnum.ACTIVE_STATUSES); + if (participantA == null) { + return null; + } + ImRtcCallDO call = rtcCallMapper.selectByRoom(participantA.getRoom()); + if (call == null + || !ImConversationTypeEnum.isPrivate(call.getConversationType()) + || ImRtcCallStatusEnum.isEnded(call.getStatus())) { + return null; + } + ImRtcParticipantDO participantB = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userIdB); + if (participantB == null + || !ImRtcParticipantStatusEnum.ACTIVE_STATUSES.contains(participantB.getStatus())) { + return null; + } + return call; + } + + @Override + public ImRtcCallDO getActiveCall(Long userId, Long groupId) { + validateEnabled(); + // 1. 鉴权:仅群活跃成员能查(走单行 SQL,不依赖成员列表缓存) + groupMemberService.validateMemberInGroup(groupId, userId); + // 2. 查询活跃通话 + return rtcCallMapper.selectLastOneByGroupIdAndStatusIn(groupId, ImRtcCallStatusEnum.ACTIVE_STATUSES); + } + + @Override + public List getCallParticipantList(String room) { + return rtcParticipantMapper.selectListByRoom(room); + } + + @Override + public String signCallToken(Long userId, String room) { + validateEnabled(); + ImRtcCallDO call = validateCallActive(room); + ImRtcParticipantDO participant = validateParticipant(call, userId); + if (!ImRtcParticipantStatusEnum.isJoined(participant.getStatus())) { + throw exception(RTC_NOT_PARTICIPANT); + } + return signToken(userId, resolveDisplayName(adminUserApi.getUser(userId), userId), room); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void handleLiveKitEvent(LiveKitWebhookEventDTO event) { + if (event == null || event.getEvent() == null) { + return; + } + switch (event.getEvent()) { + case LiveKitWebhookEventDTO.EVENT_PARTICIPANT_JOINED: + handleParticipantJoined(event); + break; + case LiveKitWebhookEventDTO.EVENT_PARTICIPANT_LEFT: + handleParticipantLeft(event); + break; + case LiveKitWebhookEventDTO.EVENT_ROOM_FINISHED: + handleRoomFinished(event); + break; + default: + // 其它事件忽略;track_published 等业务态由 LiveKit 自身分发驱动 + } + } + + /** + * 处理 LiveKit 「成员加入」事件:DB 接通态由 {@link #acceptCall(Long, String)} 接口写入,此处仅做 1602 转推 + * + * @param event 事件 + */ + private void handleParticipantJoined(LiveKitWebhookEventDTO event) { + if (ObjUtil.hasNull(event.getRoom(), event.getParticipant())) { + return; + } + // 1. 前置检查:通话存在且活跃 + String room = event.getRoom().getName(); + ImRtcCallDO call = rtcCallMapper.selectByRoom(room); + if (call == null || ImRtcCallStatusEnum.isEnded(call.getStatus())) { + return; + } + Long userId = liveKitClient.parseUserId(event.getParticipant().getIdentity()); + if (userId == null) { + return; + } + + // 2. 推 1602 通知参与方 / 全群参与方 + pushParticipantConnectedNotification(call, userId); + } + + /** + * 处理 LiveKit 「成员离开」事件:正常 {@link #leaveCall(Long, String)} 接口已经清理过的话,条件 UPDATE 自动幂等 + * + * @param event 事件 + */ + private void handleParticipantLeft(LiveKitWebhookEventDTO event) { + if (ObjUtil.hasNull(event.getRoom(), event.getParticipant())) { + return; + } + // 1. 前置检查:通话存在且活跃 + 参与者仍在 JOINED + String room = event.getRoom().getName(); + ImRtcCallDO call = rtcCallMapper.selectByRoom(room); + if (call == null || ImRtcCallStatusEnum.isEnded(call.getStatus())) { + return; + } + Long userId = liveKitClient.parseUserId(event.getParticipant().getIdentity()); + if (userId == null) { + return; + } + ImRtcParticipantDO participant = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); + if (participant == null || !ImRtcParticipantStatusEnum.isJoined(participant.getStatus())) { + return; + } + + // 2. JOINED → LEFT;正常 leave 接口已改过则 update 影响 0 行直接退出 + int updated = rtcParticipantMapper.updateByIdAndStatus(participant.getId(), ImRtcParticipantStatusEnum.JOINED.getStatus(), + new ImRtcParticipantDO().setId(participant.getId()).setStatus(ImRtcParticipantStatusEnum.LEFT.getStatus()).setLeaveTime(LocalDateTime.now())); + if (updated == 0) { + return; + } + log.info("[handleParticipantLeft][room={} userId={} 由 LiveKit Webhook 兜底]", room, userId); + + // 3. 推 1603 通知参与方 / 全群参与方 + pushParticipantDisconnectedNotification(call, userId); + // 4. 触发关房判定:私聊任一方离开必关;群通话仅在「无人在房 + 无人响铃」时关 + endSessionIfTerminal(call, userId); + } + + /** + * 处理 LiveKit 「房间结束」事件:兜底把 call 推到 ENDED + * + * @param event 事件 + */ + private void handleRoomFinished(LiveKitWebhookEventDTO event) { + if (event.getRoom() == null) { + return; + } + // 1. 前置检查:通话存在且活跃 + String room = event.getRoom().getName(); + ImRtcCallDO call = rtcCallMapper.selectByRoom(room); + if (call == null || ImRtcCallStatusEnum.isEnded(call.getStatus())) { + return; + } + // 2. 关会话 + log.info("[handleRoomFinished][room={} 由 LiveKit Webhook 兜底]", room); + endSession(call, null, ImRtcCallEndReasonEnum.HANGUP); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int cleanupZombieCalls(int thresholdMinutes) { + // 阈值由调用方(Job)保证 > 0;低于 1 分钟会误杀刚发起的合理零人态 + LocalDateTime threshold = LocalDateTime.now().minusMinutes(thresholdMinutes); + List candidates = rtcCallMapper.selectListByStatusInAndStartTimeBefore( + ImRtcCallStatusEnum.ACTIVE_STATUSES, threshold); + if (CollUtil.isEmpty(candidates)) { + return 0; + } + + // 2. 逐个查 LiveKit 房间真实 participant 数 + int cleaned = 0; + for (ImRtcCallDO call : candidates) { + int count; + try { + count = liveKitClient.listParticipants(call.getRoom()); + } catch (Exception e) { + log.warn("[cleanupZombieCalls][查询 LiveKit 失败 room={}]", call.getRoom(), e); + continue; + } + if (count != 0) { + continue; + } + log.info("[cleanupZombieCalls][清理僵尸通话 room={}]", call.getRoom()); + endSession(call, null, ImRtcCallEndReasonEnum.HANGUP); + cleaned++; + } + return cleaned; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int timeoutInvitingParticipants(int thresholdMinutes) { + // 阈值由调用方(Job)保证 > 0;低于 1 分钟可能误杀刚发起还在响铃的合理 INVITING 态 + LocalDateTime threshold = LocalDateTime.now().minusMinutes(thresholdMinutes); + return noAnswerCallCheck0(rtcParticipantMapper.selectListByStatusAndInviteTimeBefore( + ImRtcParticipantStatusEnum.INVITING.getStatus(), threshold)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void noAnswerCallCheck(Long userId, String room) { + // 鉴权:仅该 room 参与者可触发;失败静默,不暴露错误 + ImRtcParticipantDO operator = rtcParticipantMapper.selectByRoomAndUserId(room, userId); + if (operator == null) { + return; + } + // 阈值取后端配置,避免前后端配置不一致;前端 timer 仅是触发时机 + LocalDateTime threshold = LocalDateTime.now() + .minusMinutes(imProperties.getRtc().getInviteTimeoutMinutes()); + List candidates = rtcParticipantMapper.selectListByRoomAndStatusAndInviteTimeBefore( + room, ImRtcParticipantStatusEnum.INVITING.getStatus(), threshold); + noAnswerCallCheck0(candidates); + } + + /** + * 批量超时处理:循环单参与者;同 room 复用 call、批量预查 user 避免 N+1;返回成功处理数 + * + * @param candidates 已过滤的超时 INVITING 候选 + * @return 成功处理(CAS 抢占)的数量 + */ + private int noAnswerCallCheck0(List candidates) { + if (CollUtil.isEmpty(candidates)) { + return 0; + } + Map callCache = new HashMap<>(); + Map userMap = adminUserApi.getUserMap( + CollectionUtils.convertSet(candidates, ImRtcParticipantDO::getUserId)); + int timedOut = 0; + for (ImRtcParticipantDO participant : candidates) { + if (timeoutInvitingParticipant(participant, callCache, userMap)) { + timedOut++; + } + } + return timedOut; + } + + /** + * 单参与者振铃超时:CAS 把 INVITING 切到 NO_ANSWER,群通话推 RTC_CALL(NO_ANSWER) + 级联关房判定; + * 私聊场景被叫超时即整通话结束,走 endSession(NO_ANSWER) 推 RTC_CALL_END + * + * @param participant INVITING 状态的超时候选 + * @param callCache 按 room 缓存 call 对象,避免同批次多人重复查询 + * @param userMap 候选用户预查 map;避免逐个 adminUserApi.getUser 走 N+1 + * @return 是否成功处理(CAS 失败 / 通话主表缺失等场景返回 false) + */ + private boolean timeoutInvitingParticipant(ImRtcParticipantDO participant, + Map callCache, + Map userMap) { + // 1. CAS:INVITING → NO_ANSWER;并发已变(用户刚接 / 拒,或 endSession 整体改)跳过 + int updated = rtcParticipantMapper.updateByIdAndStatus(participant.getId(), + ImRtcParticipantStatusEnum.INVITING.getStatus(), + new ImRtcParticipantDO().setId(participant.getId()) + .setStatus(ImRtcParticipantStatusEnum.NO_ANSWER.getStatus()) + .setLeaveTime(LocalDateTime.now())); + if (updated == 0) { + return false; + } + Long userId = participant.getUserId(); + log.info("[timeoutInvitingParticipant][参与者振铃超时 room={} userId={}]", participant.getRoom(), userId); + + // 2. 查询通话主表;同 room 复用 callCache,避免同批次多人重复查询 + ImRtcCallDO call = callCache.computeIfAbsent(participant.getRoom(), rtcCallMapper::selectByRoom); + if (call == null) { + log.warn("[timeoutInvitingParticipant][通话主表缺失 room={} userId={}]", participant.getRoom(), userId); + return false; + } + + // 3.1 群通话:推 RTC_CALL(NO_ANSWER) 让前端 banner 移除该人 + 级联关房判定 + if (ImConversationTypeEnum.isGroup(call.getConversationType())) { + pushCallNoAnswerNotification(call, userId, userMap.get(userId)); + endSessionIfTerminal(call, userId); + return true; + } + // 3.2 私聊:被叫超时 = 整通话无人接听,走 endSession 推 RTC_CALL_END(NO_ANSWER) + endSession(call, userId, ImRtcCallEndReasonEnum.NO_ANSWER); + return true; + } + + // ========== 内部辅助 ========== + + private void validateEnabled() { + if (!imProperties.getRtc().isEnabled()) { + throw exception(RTC_NOT_ENABLED); + } + } + + /** + * 校验通话存在且活跃:不存在 / 已 ENDED 直接抛 + * + * @param room 业务通话编号 + * @return 通话主表 + */ + private ImRtcCallDO validateCallActive(String room) { + ImRtcCallDO call = rtcCallMapper.selectByRoom(room); + if (call == null || ImRtcCallStatusEnum.isEnded(call.getStatus())) { + throw exception(RTC_SESSION_NOT_EXISTS); + } + return call; + } + + /** + * 校验本人是该通话的参与者:accept / reject / leave / refreshToken 的共用前置 + * + * @param call 通话主表 + * @param userId 用户编号 + * @return 参与者记录 + */ + private ImRtcParticipantDO validateParticipant(ImRtcCallDO call, Long userId) { + ImRtcParticipantDO participant = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); + if (participant == null) { + throw exception(RTC_NOT_PARTICIPANT); + } + return participant; + } + + /** + * 校验用户不在其它活跃通话中 + * + * @param userId 用户编号 + * @param room 当前房间标识 + */ + private void validateUserNotInOtherCall(Long userId, String room) { + // 查询当前房间外的活跃参与记录 + ImRtcParticipantDO participant = rtcParticipantMapper.selectLastOneByUserIdAndStatusInAndRoomNot( + userId, ImRtcParticipantStatusEnum.ACTIVE_STATUSES, room); + // 存在活跃参与记录,则当前用户忙线 + if (participant != null) { + throw exception(RTC_SELF_BUSY); + } + } + + /** + * 加入群通话参与者列表 + * + * @param call 通话主表 + * @param userId 用户编号 + * @param now 当前时间 + */ + private void joinParticipant(ImRtcCallDO call, Long userId, LocalDateTime now) { + // 1. 已有参与记录:切回 JOINED + ImRtcParticipantDO existing = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); + if (existing != null) { + updateParticipantJoined(existing, now); + return; + } + + // 2. 无参与记录:以主动加入者身份新增 + try { + rtcParticipantMapper.insert(new ImRtcParticipantDO() + .setCallId(call.getId()).setRoom(call.getRoom()) + .setUserId(userId).setRole(ImRtcParticipantRoleEnum.JOINER.getRole()) + .setStatus(ImRtcParticipantStatusEnum.JOINED.getStatus()).setInviteTime(now).setAcceptTime(now)); + } catch (DuplicateKeyException ex) { + // 3. 唯一键冲突:回查并复用并发写入的记录 + existing = rtcParticipantMapper.selectByRoomAndUserId(call.getRoom(), userId); + if (existing == null) { + throw ex; + } + updateParticipantJoined(existing, now); + } + } + + /** + * 将参与者更新为已加入 + * + * @param participant 参与者记录 + * @param now 当前时间 + */ + private void updateParticipantJoined(ImRtcParticipantDO participant, LocalDateTime now) { + // 已是 JOINED 直接返回 + if (ImRtcParticipantStatusEnum.isJoined(participant.getStatus())) { + return; + } + // 更新状态和接听时间 + rtcParticipantMapper.updateById(new ImRtcParticipantDO().setId(participant.getId()) + .setStatus(ImRtcParticipantStatusEnum.JOINED.getStatus()).setAcceptTime(now)); + } + + /** + * 关房判定收口:私聊任一方离开必关;群通话仅在「无人在房 + 无人响铃」时关 + *

+ * end reason 按 call.status 自动推:CREATED(没人接通过)= CANCEL;RUNNING(有人接通过)= HANGUP + * + * @param call 通话主表 + * @param operatorId 操作者用户编号;webhook 兜底场景可空 + */ + private void endSessionIfTerminal(ImRtcCallDO call, Long operatorId) { + if (!ImConversationTypeEnum.isPrivate(call.getConversationType()) + && !shouldCloseGroupRoom(call.getRoom())) { + return; + } + ImRtcCallEndReasonEnum reason = ImRtcCallStatusEnum.isCreated(call.getStatus()) + ? ImRtcCallEndReasonEnum.CANCEL : ImRtcCallEndReasonEnum.HANGUP; + endSession(call, operatorId, reason); + } + + /** + * 校验创建通话入参;按场景区分必填字段,私聊补好友校验,群聊补群成员校验 + * + * @param userId 发起人编号 + * @param reqVO 创建请求 + */ + private void validateCreateCall(Long userId, ImRtcCallCreateReqVO reqVO) { + Integer conversationType = reqVO.getConversationType(); + if (ImConversationTypeEnum.isPrivate(conversationType)) { + // 私聊必须 1 个对端 + if (CollUtil.size(reqVO.getInviteeIds()) != 1) { + throw exception(RTC_PRIVATE_INVITEE_REQUIRED); + } + Long peerUserId = CollUtil.getFirst(reqVO.getInviteeIds()); + if (ObjUtil.equal(userId, peerUserId)) { + throw exception(RTC_INVITE_SELF); + } + friendService.validateFriend(userId, peerUserId); + return; + } + if (ImConversationTypeEnum.isGroup(conversationType)) { + if (reqVO.getGroupId() == null) { + throw exception(RTC_GROUP_REQUIRED); + } + // 群通话必须前端选中被邀请人(对齐微信);空集合直接拒 + if (CollUtil.isEmpty(reqVO.getInviteeIds())) { + throw exception(RTC_GROUP_INVITEE_REQUIRED); + } + groupMemberService.validateMemberInGroup(reqVO.getGroupId(), userId); + return; + } + throw new IllegalArgumentException("非法的 conversationType: " + conversationType); + } + + /** + * 解析被邀请池:私聊为 peerUserId 单元素;群聊为前端选中子集(超量抛错) + * + * @param reqVO 创建请求 + * @param inviterId 发起人编号;自己不进被邀请池 + * @return 被邀请人 userId 集合 + */ + private Set resolveInvitees(ImRtcCallCreateReqVO reqVO, Long inviterId) { + // 1. 私聊:inviteeIds 已在 validateCreateCall 校验仅 1 个对端,直接复用 + if (ImConversationTypeEnum.isPrivate(reqVO.getConversationType())) { + return new LinkedHashSet<>(reqVO.getInviteeIds()); + } + + // 2. 群聊校验:被邀请人必须是该群活跃成员,防止恶意客户端塞任意 userId + groupMemberService.validateMembersInGroup(reqVO.getGroupId(), reqVO.getInviteeIds()); + Set initial = new LinkedHashSet<>(reqVO.getInviteeIds()); + // 发起人本人不进被邀请池 + initial.remove(inviterId); + if (CollUtil.isEmpty(initial)) { + throw exception(RTC_GROUP_INVITEE_REQUIRED); + } + int max = imProperties.getRtc().getGroupMaxParticipants(); + if (initial.size() + 1 > max) { + throw exception(RTC_GROUP_INVITEE_OVER_LIMIT); + } + return initial; + } + + /** + * 主表 CREATED → RUNNING;仅当首个非发起人加入时推进;条件 UPDATE 保幂等 + * + * @param call 通话主表;推进成功后会同步内存字段 + * @param acceptorId 加入者用户编号;发起人加入不算「首次接通」 + * @param now 当前时间 + */ + private void maybeMarkOngoing(ImRtcCallDO call, Long acceptorId, LocalDateTime now) { + // 1. 已 RUNNING / ENDED 直接退出;发起人加入不算「首次有人接通」 + if (!ImRtcCallStatusEnum.isCreated(call.getStatus())) { + return; + } + if (ObjUtil.equal(call.getInviterUserId(), acceptorId)) { + return; + } + + // 2. CREATED → RUNNING 条件 UPDATE;多人并发只有一人成功 + int updated = rtcCallMapper.updateByIdAndStatus(call.getId(), ImRtcCallStatusEnum.CREATED.getStatus(), + new ImRtcCallDO().setStatus(ImRtcCallStatusEnum.RUNNING.getStatus()).setAcceptTime(now)); + if (updated == 0) { + // 3. 竞争失败:reload 看真实终态;已 ENDED 抛错,否则同步内存 + ImRtcCallDO latest = rtcCallMapper.selectById(call.getId()); + if (latest == null || ImRtcCallStatusEnum.isEnded(latest.getStatus())) { + throw exception(RTC_SESSION_NOT_EXISTS); + } + call.setStatus(latest.getStatus()).setAcceptTime(latest.getAcceptTime()); + return; + } + // 4. 推进成功:同步内存给后续判断 + call.setStatus(ImRtcCallStatusEnum.RUNNING.getStatus()).setAcceptTime(now); + } + + /** + * 关闭会话:主表条件 UPDATE 推到 ENDED → 残留 INVITING 批量改 NO_ANSWER → 推 RTC_CALL_END + * + * @param call 通话主表 + * @param operatorId 操作者用户编号;webhook 兜底 / Job 清理场景可空 + * @param reason 结束原因 + */ + private void endSession(ImRtcCallDO call, Long operatorId, ImRtcCallEndReasonEnum reason) { + // 1.1 更新通话主表为已结束;条件 UPDATE 仅在 status 还活跃时生效 + LocalDateTime now = LocalDateTime.now(); + int updated = rtcCallMapper.updateByIdAndStatusIn(call.getId(), ImRtcCallStatusEnum.ACTIVE_STATUSES, + new ImRtcCallDO().setStatus(ImRtcCallStatusEnum.ENDED.getStatus()) + .setEndReason(reason.getReason()).setEndTime(now)); + if (updated == 0) { + log.info("[endSession][已被另一路径终结,跳过 room={} operator={} reason={}]", + call.getRoom(), operatorId, reason); + return; + } + // 【特殊】同步内存 call:让 createCall 这类调用方拿到的 DO 立即反映 ENDED 终态,Controller 拼 RespVO 能直接用 + call.setStatus(ImRtcCallStatusEnum.ENDED.getStatus()).setEndReason(reason.getReason()).setEndTime(now); + // 1.2 更新参与表为已结束:残留 INVITING 改 NO_ANSWER;残留 JOINED 改 LEFT 并写 leaveTime + rtcParticipantMapper.updateByRoomAndStatus(call.getRoom(), ImRtcParticipantStatusEnum.INVITING.getStatus(), + new ImRtcParticipantDO().setStatus(ImRtcParticipantStatusEnum.NO_ANSWER.getStatus())); + rtcParticipantMapper.updateByRoomAndStatus(call.getRoom(), ImRtcParticipantStatusEnum.JOINED.getStatus(), + new ImRtcParticipantDO().setStatus(ImRtcParticipantStatusEnum.LEFT.getStatus()).setLeaveTime(now)); + + // 2. 推 RTC_CALL_END;先于 deleteRoom 异步发出,让前端按业务语义 reset(NO_ANSWER / CANCEL 等), + // 避免随后 LiveKit Disconnected 事件抢先触发前端 "通话已断开" 兜底 toast + Long durationSeconds = call.getAcceptTime() != null ? + Duration.between(call.getAcceptTime(), now).getSeconds() : null; + pushCallEndNotification(call, operatorId, reason, durationSeconds); + + // 3. 兜底删除 LiveKit 房间,强制断开异常残留客户端;失败仅记日志,不阻断业务 + try { + liveKitClient.deleteRoom(call.getRoom()); + } catch (Exception e) { + log.warn("[endSession][删除 LiveKit 房间失败 room={} operator={} reason={}]", + call.getRoom(), operatorId, reason, e); + } + log.info("[endSession][room={} operator={} reason={}]", call.getRoom(), operatorId, reason); + } + + /** + * 通话事件的收件人池:1)私聊为双方参与者;2)群聊为群活跃成员 + * + * @param call 通话主表 + * @return 收件人 userId 集合 + */ + private Collection getCallAudienceUserIdList(ImRtcCallDO call) { + if (ImConversationTypeEnum.isGroup(call.getConversationType())) { + return groupMemberService.getActiveGroupMemberUserIdsByGroupId(call.getGroupId()); + } + return CollectionUtils.convertSet( + rtcParticipantMapper.selectListByRoom(call.getRoom()), ImRtcParticipantDO::getUserId); + } + + // ========== 通知推送 ========== + + /** + * RTC_CALL(INVITE):走 webSocketService 直推到被邀请人;persistent=false 不入消息流 + * + * @param call 通话主表 + * @param inviter 发起人;可空,缺失时 payload 的 inviterNickname / Avatar 留空 + * @param inviteeId 被邀请人用户编号 + * @param invitee 被邀请人;可空,缺失时 token 内 displayName 降级为 userId + * @param inviteeIds 本次被邀请人列表;前端来电界面展示「邀请的其他人」用,包含 inviteeId 自身 + */ + private void pushCallInviteNotification(ImRtcCallDO call, AdminUserRespDTO inviter, + Long inviteeId, AdminUserRespDTO invitee, + Collection inviteeIds) { + String token = signToken(inviteeId, resolveDisplayName(invitee, inviteeId), call.getRoom()); + ImRtcCallNotification payload = ImRtcCallNotification.ofInvite( + call, inviter, imProperties.getRtc().getLivekitUrl(), token, inviteeIds); + webSocketService.sendNotificationAsync(inviteeId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.RTC_CALL.getType(), payload); + } + + /** + * RTC_CALL(REJECT):仅群通话场景;走 webSocketService 推给群通话受众 + *

+ * 私聊拒绝走 endSession → RTC_CALL_END(reason=REJECT) 入消息流,不在此推 + * + * @param call 通话主表 + * @param operatorUserId 拒接者用户编号 + */ + private void pushCallRejectNotification(ImRtcCallDO call, Long operatorUserId) { + AdminUserRespDTO operator = operatorUserId != null ? adminUserApi.getUser(operatorUserId) : null; + ImRtcCallNotification payload = ImRtcCallNotification.ofReject(call, operatorUserId, operator); + for (Long receiverUserId : getCallAudienceUserIdList(call)) { + webSocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.RTC_CALL.getType(), payload); + } + } + + /** + * RTC_CALL(NO_ANSWER):仅群通话场景;振铃超时由 Job 触发;走 webSocketService 推给群通话受众 + *

+ * 私聊未接听走 endSession → RTC_CALL_END(reason=NO_ANSWER) 入消息流,不在此推 + * + * @param call 通话主表 + * @param operatorUserId 未接听者用户编号 + * @param operator 未接听者预查结果;调用方批量查避免 N+1,可空 + */ + private void pushCallNoAnswerNotification(ImRtcCallDO call, Long operatorUserId, AdminUserRespDTO operator) { + ImRtcCallNotification payload = ImRtcCallNotification.ofNoAnswer(call, operatorUserId, operator); + for (Long receiverUserId : getCallAudienceUserIdList(call)) { + webSocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.RTC_CALL.getType(), payload); + } + } + + /** + * 通话参与者加入:LiveKit webhook participant_joined 触发;私聊推双方多端、群聊推全群成员(胶囊条 + 1) + * + * @param call 通话主表 + * @param userId 加入的参与者用户编号 + */ + private void pushParticipantConnectedNotification(ImRtcCallDO call, Long userId) { + pushParticipantNotification(call, ImContentTypeEnum.RTC_PARTICIPANT_CONNECTED.getType(), userId, + ImRtcParticipantConnectedNotification.of(call, userId)); + } + + /** + * 通话参与者离开:LiveKit webhook participant_left 触发;推送范围同 {@link #pushParticipantConnectedNotification} + * + * @param call 通话主表 + * @param userId 离开的参与者用户编号 + */ + private void pushParticipantDisconnectedNotification(ImRtcCallDO call, Long userId) { + pushParticipantNotification(call, ImContentTypeEnum.RTC_PARTICIPANT_DISCONNECTED.getType(), userId, + ImRtcParticipantDisconnectedNotification.of(call, userId)); + } + + /** + * 推送参与者事件的公共骨架;按会话类型决定收件人,单次 batch 推送扇出 + * + * @param call 通话主表 + * @param type 消息类型;1602 / 1603 + * @param actorUserId 触发本次事件的用户编号 + * @param payload 业务 payload + */ + private void pushParticipantNotification(ImRtcCallDO call, Integer type, Long actorUserId, Object payload) { + Collection receivers = getCallAudienceUserIdList(call); + if (CollUtil.isEmpty(receivers)) { + return; + } + webSocketService.sendNotificationAsync(receivers, ImConversationTypeEnum.NONE.getType(), type, payload); + } + + /** + * RTC_CALL_START:群聊走 imGroupMessageService.send 全群广播;私聊走 imPrivateMessageService.send 定向给被叫 + *

+ * 私聊 peer 直接复用 createCall 解析好的 invitees,避免再查参与表;用于会话列表预览展示「[语音通话]」 + * + * @param call 通话主表 + * @param inviter 发起人;可空 + * @param invitees 本次邀请池;私聊场景取首个作为 peer + */ + private void pushCallStartNotification(ImRtcCallDO call, AdminUserRespDTO inviter, Set invitees) { + ImRtcCallStartNotification payload = ImRtcCallStartNotification.of(call, inviter); + Long peerUserId = ImConversationTypeEnum.isGroup(call.getConversationType()) ? null : CollUtil.getFirst(invitees); + pushCallChatMessage(call, ImContentTypeEnum.RTC_CALL_START, payload, peerUserId); + } + + /** + * RTC_CALL_END:私聊走 imPrivateMessageService.send;群通话走 imGroupMessageService.send + *

+ * senderId 始终用通话发起人,让前端按「谁发起通话」决定气泡左右;操作者从 payload.operatorUserId 拿 + * + * @param call 通话主表 + * @param operatorId 操作者用户编号;webhook 兜底 / Job 清理场景可空 + * @param reason 结束原因 + * @param durationSeconds 通话时长(秒);未接通时为 null + */ + private void pushCallEndNotification(ImRtcCallDO call, Long operatorId, ImRtcCallEndReasonEnum reason, + Long durationSeconds) { + AdminUserRespDTO operator = operatorId != null ? adminUserApi.getUser(operatorId) : null; + ImRtcCallEndNotification payload = ImRtcCallEndNotification.of(call, reason, durationSeconds, operatorId, operator); + Long peerUserId = null; + if (!ImConversationTypeEnum.isGroup(call.getConversationType())) { + ImRtcParticipantDO peer = CollUtil.findOne( + rtcParticipantMapper.selectListByRoom(call.getRoom()), + p -> ObjUtil.notEqual(p.getUserId(), call.getInviterUserId())); + peerUserId = peer != null ? peer.getUserId() : null; + } + pushCallChatMessage(call, ImContentTypeEnum.RTC_CALL_END, payload, peerUserId); + } + + /** + * RTC 通话事件入消息流:群聊走 groupMessageService 全群广播,私聊走 privateMessageService 定向给 peer + *

+ * senderId 固定取 inviterUserId,让前端按「谁发起」决定气泡左右;私聊 peer 缺失时降级为发给自己作兜底 + * + * @param call 通话主表 + * @param type 消息类型;RTC_CALL_START / RTC_CALL_END + * @param payload 推送 payload + * @param peerUserId 私聊对端用户编号;群聊忽略,私聊缺失时回退为 senderId + */ + private void pushCallChatMessage(ImRtcCallDO call, ImContentTypeEnum type, Object payload, Long peerUserId) { + Long senderId = call.getInviterUserId(); + if (ImConversationTypeEnum.isGroup(call.getConversationType())) { + ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO().setGroupId(call.getGroupId()) + .setType(type.getType()).setContent(payload); + groupMessageService.sendGroupMessage(senderId, dto); + return; + } + Long receiverId = peerUserId != null ? peerUserId : senderId; + ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO().setReceiverId(receiverId) + .setType(type.getType()).setContent(payload); + privateMessageService.sendPrivateMessage(senderId, dto); + } + + // ========== Token / VO ========== + + /** + * 签 LiveKit Token + * + * @param userId 用户编号;token 内 identity 由此派生 + * @param displayName 房内显示名 + * @param room LiveKit 房间名 + * @return JWT 字符串 + */ + private String signToken(Long userId, String displayName, String room) { + return liveKitClient.signJoinToken(liveKitClient.buildIdentity(userId), displayName, room); + } + + /** + * 解析房内显示名:优先取 user.nickname,缺失时降级为 userId 字符串(LiveKit displayName 不可空) + * + * @param user 用户信息;可空 + * @param userId 用户编号;displayName 兜底 + * @return 房内显示名 + */ + private static String resolveDisplayName(AdminUserRespDTO user, Long userId) { + return StrUtil.blankToDefault( + user == null ? null : user.getNickname(), String.valueOf(userId)); + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getCallPage(ImRtcCallManagerPageReqVO reqVO) { + return rtcCallMapper.selectPage(reqVO); + } + + @Override + public ImRtcCallDO getCall(Long id) { + return rtcCallMapper.selectById(id); + } + + @Override + public List getCallParticipantListByCallId(Long id) { + return rtcParticipantMapper.selectListByCallId(id); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java new file mode 100644 index 0000000000..1a5c3aa1d7 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.im.service.sensitiveword; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * IM 敏感词 Service 接口 + * + * @author 芋道源码 + */ +public interface ImSensitiveWordService { + + /** + * 校验文本是否包含敏感词 + * + * @param text 待校验文本 + */ + void validateText(String text); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询敏感词 + */ + PageResult getSensitiveWordPage(ImSensitiveWordPageReqVO reqVO); + + /** + * 【管理后台】获取敏感词详情 + */ + ImSensitiveWordDO getSensitiveWord(Long id); + + /** + * 【管理后台】新增敏感词,返回新增 id + */ + Long createSensitiveWord(@Valid ImSensitiveWordSaveReqVO reqVO); + + /** + * 【管理后台】修改敏感词 + */ + void updateSensitiveWord(@Valid ImSensitiveWordSaveReqVO reqVO); + + /** + * 【管理后台】删除敏感词 + */ + void deleteSensitiveWord(Long id); + + /** + * 【管理后台】批量删除敏感词 + */ + void deleteSensitiveWordList(List ids); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java new file mode 100644 index 0000000000..57ed857eda --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java @@ -0,0 +1,228 @@ +package cn.iocoder.yudao.module.im.service.sensitiveword; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; +import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper; +import com.github.houbb.sensitive.word.bs.SensitiveWordBs; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_DUPLICATED; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS; + +/** + * IM 敏感词 Service 实现类 + *

+ * 词库匹配交给 houbb sensitive-word 库(trie 树 + 全/半角 / 大小写 / 繁简体 / 数字风格规范化) + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { + + @Resource + private ImSensitiveWordMapper sensitiveWordMapper; + + /** + * 缓存条目 + */ + @Data + @AllArgsConstructor + private static class SensitiveWordBsCache { + + /** + * 敏感词检测器 + */ + private SensitiveWordBs bs; + /** + * 构建本实例时数据库里的 max(update_time),作为下次刷新比对的基线 + */ + private LocalDateTime maxUpdateTime; + + } + + /** + * 租户 → SensitiveWordBs 实例的本地缓存 + *

+ * 每分钟触发一次异步 reload,先读 max(update_time),没变就复用旧实例(避免 trie 重建),变了才重新读词库 + 重建。 + * 单实例 CRUD 后另外通过 {@link #invalidateSensitiveWordBsCaches()} 立即让本机失效,多实例靠定时刷新最长 1 分钟内收敛。 + */ + @SuppressWarnings({"Convert2Diamond", "NullableProblems"}) + private final LoadingCache sensitiveWordBsCaches = CacheUtils.buildAsyncReloadingCache( + Duration.ofMinutes(1L), // 1 分钟过期 + new CacheLoader() { + + @Override + public SensitiveWordBsCache load(Long tenantId) { + return loadFresh(tenantId); + } + + @Override + public ListenableFuture reload(Long tenantId, SensitiveWordBsCache oldValue) { + // 异步刷新线程独立于业务线程,没有租户上下文;必须显式 TenantUtils.execute 设置,否则租户拦截器会按当前线程的空上下文拼 SQL + return Futures.immediateFuture(TenantUtils.execute(tenantId, () -> { + LocalDateTime currentMax = sensitiveWordMapper.selectMaxUpdateTime(tenantId); + // 没变 → 复用旧实例,避免无谓地重建 trie + if (Objects.equals(oldValue.getMaxUpdateTime(), currentMax)) { + return oldValue; + } + // 变了 → 重新读词库并重建 trie + return loadFresh(tenantId); + })); + } + + }); + + private SensitiveWordBsCache loadFresh(Long tenantId) { + return TenantUtils.execute(tenantId, () -> { + // 先取基线时间再读词库:反过来在两次查询之间出现的新插入会被漏感知 + LocalDateTime maxUpdateTime = sensitiveWordMapper.selectMaxUpdateTime(tenantId); + List words = sensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + // 构建敏感词检测器 + SensitiveWordBs bs = SensitiveWordBs.newInstance() + .wordDeny(() -> convertList(words, ImSensitiveWordDO::getWord)) + .ignoreCase(true) + .ignoreWidth(true) // 忽略全/半角 + .ignoreNumStyle(true) // 忽略数字风格(中文/阿拉伯) + .ignoreChineseStyle(true) // 忽略繁简体 + .enableWordCheck(true) + .init(); + return new SensitiveWordBsCache(bs, maxUpdateTime); + }); + } + + /** + * 强制让敏感词缓存失效,下次访问按最新 DB 重建 + *

+ * 有租户上下文:仅失效该租户。无租户上下文(如系统级 / 跨租户清理):兜底失效所有租户。 + */ + private void invalidateSensitiveWordBsCaches() { + Long tenantId = TenantContextHolder.getTenantId(); + if (tenantId != null) { + sensitiveWordBsCaches.invalidate(tenantId); + return; + } + sensitiveWordBsCaches.invalidateAll(); + } + + @Override + public void validateText(String text) { + if (StrUtil.isBlank(text)) { + return; + } + SensitiveWordBs bs = sensitiveWordBsCaches.getUnchecked(TenantContextHolder.getRequiredTenantId()).getBs(); + if (bs.contains(text)) { + throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED); + } + } + + // ==================== 管理后台 ==================== + + @Override + public PageResult getSensitiveWordPage(ImSensitiveWordPageReqVO reqVO) { + return sensitiveWordMapper.selectPage(reqVO); + } + + @Override + public ImSensitiveWordDO getSensitiveWord(Long id) { + return sensitiveWordMapper.selectById(id); + } + + @Override + public Long createSensitiveWord(ImSensitiveWordSaveReqVO reqVO) { + // 1. 校验唯一 + validateWordUnique(null, reqVO.getWord()); + + // 2.1 入库 + ImSensitiveWordDO word = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class); + sensitiveWordMapper.insert(word); + // 2.2 强制失效本机缓存(多实例靠定时刷新收敛) + invalidateSensitiveWordBsCaches(); + return word.getId(); + } + + @Override + public void updateSensitiveWord(ImSensitiveWordSaveReqVO reqVO) { + // 1.1 校验存在 + validateSensitiveWordExists(reqVO.getId()); + // 1.2 校验唯一(排除自身) + validateWordUnique(reqVO.getId(), reqVO.getWord()); + + // 2.1 更新 + ImSensitiveWordDO updateObj = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class); + sensitiveWordMapper.updateById(updateObj); + // 2.2 强制失效本机缓存 + invalidateSensitiveWordBsCaches(); + } + + @Override + public void deleteSensitiveWord(Long id) { + // 1. 校验存在 + validateSensitiveWordExists(id); + + // 2.1 删除 + sensitiveWordMapper.deleteById(id); + // 2.2 强制失效本机缓存 + invalidateSensitiveWordBsCaches(); + } + + + @Override + public void deleteSensitiveWordList(List ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + // 1. 删除 + sensitiveWordMapper.deleteByIds(ids); + // 2. 强制失效本机缓存 + invalidateSensitiveWordBsCaches(); + } + + private void validateSensitiveWordExists(Long id) { + if (sensitiveWordMapper.selectById(id) == null) { + throw exception(SENSITIVE_WORD_NOT_EXISTS); + } + } + + /** + * 校验敏感词唯一(修改时排除自身) + */ + private void validateWordUnique(Long id, String word) { + ImSensitiveWordDO exist = sensitiveWordMapper.selectByWord(word); + if (exist == null) { + return; + } + if (id == null || ObjUtil.notEqual(exist.getId(), id)) { + throw exception(SENSITIVE_WORD_DUPLICATED, word); + } + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerService.java new file mode 100644 index 0000000000..a21fc03383 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerService.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.im.service.statistics; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * IM 数据看板 Service 接口 + *

+ * 仅服务于 manager 后台统计页,独立于业务 Service,避免污染。 + * 返回的均为聚合后的简单结构,由 Controller 负责 VO 装配与昵称回填。 + * + * @author 芋道源码 + */ +public interface ImStatisticsManagerService { + + // ==================== 用户 ==================== + + /** + * 获取用户总数 + */ + Long getTotalUserCount(); + + /** + * 获取区间内新增用户数 + */ + Long getNewUserCount(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获取区间内活跃用户数(私聊+群聊去重) + */ + Long getActiveUserCount(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获取区间内每日新增用户数 Map(key 为 yyyy-MM-dd 日期) + */ + Map getNewUserDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获取区间内每日活跃用户数 Map + */ + Map getActiveUserDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime); + + // ==================== 群 ==================== + + /** + * 获取群总数 + */ + Long getTotalGroupCount(); + + /** + * 获取区间内新建群数 + */ + Long getNewGroupCount(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获取群规模分布 Map(key 为分桶名) + */ + Map getGroupSizeCountMap(); + + // ==================== 消息 ==================== + + /** + * 获取区间内私聊消息数 + */ + Long getPrivateMessageCount(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获取区间内群聊消息数 + */ + Long getGroupMessageCount(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获取区间内每日私聊消息数 Map + */ + Map getPrivateMessageDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获取区间内每日群聊消息数 Map + */ + Map getGroupMessageDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获取区间内内容类型分布 Map(key 为消息类型) + */ + Map getMessageTypeCountMap(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获取区间内 TOP 发送者 Map(key 为 userId,value 为消息数;按消息数倒序) + */ + Map getTopSenderCountMap(LocalDateTime beginTime, LocalDateTime endTime, int limit); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImpl.java new file mode 100644 index 0000000000..52516410ce --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImpl.java @@ -0,0 +1,126 @@ +package cn.iocoder.yudao.module.im.service.statistics; + +import cn.hutool.core.convert.Convert; +import cn.iocoder.yudao.module.im.dal.mysql.statistics.ImStatisticsManagerMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * IM 数据看板 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ImStatisticsManagerServiceImpl implements ImStatisticsManagerService { + + @Resource + private ImStatisticsManagerMapper statisticsMapper; + + // ==================== 用户 ==================== + + @Override + public Long getTotalUserCount() { + return statisticsMapper.selectTotalUserCount(); + } + + @Override + public Long getNewUserCount(LocalDateTime beginTime, LocalDateTime endTime) { + return statisticsMapper.selectNewUserCount(beginTime, endTime); + } + + @Override + public Long getActiveUserCount(LocalDateTime beginTime, LocalDateTime endTime) { + return statisticsMapper.selectActiveUserCount(beginTime, endTime); + } + + @Override + public Map getNewUserDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime) { + List> rows = statisticsMapper.selectNewUserDailyCount(beginTime, endTime); + return toDailyCountMap(rows); + } + + @Override + public Map getActiveUserDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime) { + List> rows = statisticsMapper.selectActiveUserDailyCount(beginTime, endTime); + return toDailyCountMap(rows); + } + + // ==================== 群 ==================== + + @Override + public Long getTotalGroupCount() { + return statisticsMapper.selectTotalGroupCount(); + } + + @Override + public Long getNewGroupCount(LocalDateTime beginTime, LocalDateTime endTime) { + return statisticsMapper.selectNewGroupCount(beginTime, endTime); + } + + @Override + public Map getGroupSizeCountMap() { + List> rows = statisticsMapper.selectGroupSizeDistribution(); + return convertMap(rows, + row -> (String) row.get("range"), + row -> Convert.toLong(row.get("count"))); + } + + // ==================== 消息 ==================== + + @Override + public Long getPrivateMessageCount(LocalDateTime beginTime, LocalDateTime endTime) { + return statisticsMapper.selectPrivateMessageCount(beginTime, endTime); + } + + @Override + public Long getGroupMessageCount(LocalDateTime beginTime, LocalDateTime endTime) { + return statisticsMapper.selectGroupMessageCount(beginTime, endTime); + } + + @Override + public Map getPrivateMessageDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime) { + List> rows = statisticsMapper.selectPrivateMessageDailyCount(beginTime, endTime); + return toDailyCountMap(rows); + } + + @Override + public Map getGroupMessageDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime) { + List> rows = statisticsMapper.selectGroupMessageDailyCount(beginTime, endTime); + return toDailyCountMap(rows); + } + + @Override + public Map getMessageTypeCountMap(LocalDateTime beginTime, LocalDateTime endTime) { + List> rows = statisticsMapper.selectMessageTypeDistribution(beginTime, endTime); + return convertMap(rows, + row -> Convert.toInt(row.get("type")), + row -> Convert.toLong(row.get("count"))); + } + + @Override + public Map getTopSenderCountMap(LocalDateTime beginTime, LocalDateTime endTime, int limit) { + List> rows = statisticsMapper.selectTopSenders(beginTime, endTime, limit); + return convertMap(rows, + row -> Convert.toLong(row.get("userId")), + row -> Convert.toLong(row.get("messageCount"))); + } + + /** + * 把 [{date, count}] 行映射为 {LocalDateTime -> Long}; + */ + private static Map toDailyCountMap(List> rows) { + return convertMap(rows, + row -> Convert.convert(LocalDate.class, row.get("date")).atStartOfDay(), + row -> Convert.toLong(row.get("count"))); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketService.java new file mode 100644 index 0000000000..2e7404cb5a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketService.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.im.service.websocket; + +import java.util.Collection; +import java.util.Collections; + +/** + * IM WebSocket 推送 Service 接口 + *

+ * 统一封装 WebSocket 通知推送,事务内调用时提交后异步执行。 + * + * @author 芋道源码 + */ +public interface ImWebSocketService { + + /** + * 异步推送 WebSocket 通知给指定用户 + * + * @param userId 目标用户编号 + * @param conversationType 会话类型,参见 ImConversationTypeEnum 枚举类 + * @param contentType 内容类型,参见 ImContentTypeEnum 枚举类 + * @param payload 通知 payload + */ + default void sendNotificationAsync(Long userId, Integer conversationType, Integer contentType, Object payload) { + sendNotificationAsync(Collections.singleton(userId), conversationType, contentType, payload); + } + + /** + * 异步批量推送 WebSocket 通知给多个用户 + * + * @param userIds 目标用户编号列表 + * @param conversationType 会话类型,参见 ImConversationTypeEnum 枚举类 + * @param contentType 内容类型,参见 ImContentTypeEnum 枚举类 + * @param payload 通知 payload + */ + void sendNotificationAsync(Collection userIds, Integer conversationType, Integer contentType, Object payload); + + /** + * 异步广播 WebSocket 通知给当前所有在线管理端用户 + * + * @param conversationType 会话类型,参见 ImConversationTypeEnum 枚举类 + * @param contentType 内容类型,参见 ImContentTypeEnum 枚举类 + * @param payload 通知 payload + */ + void broadcastNotificationAsync(Integer conversationType, Integer contentType, Object payload); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImpl.java new file mode 100644 index 0000000000..a3a107822e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImpl.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.im.service.websocket; + +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.module.im.service.websocket.notification.ImNotificationWebSocketDTO; +import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertLinkedSet; + +/** + * IM WebSocket 推送 Service 实现类 + *

+ * 当调用方处于事务中时,推送会延迟到事务提交后再异步执行, + * 避免客户端收到 WebSocket 消息时数据库变更尚未可见。 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class ImWebSocketServiceImpl implements ImWebSocketService { + + @Resource + private WebSocketSenderApi webSocketSenderApi; + + @Override + public void sendNotificationAsync(Collection userIds, Integer conversationType, Integer contentType, + Object payload) { + ImNotificationWebSocketDTO notification = buildNotification(conversationType, contentType, payload); + executeAfterTransaction(() -> getSelf().doSendNotification(userIds, notification)); + } + + @Override + public void broadcastNotificationAsync(Integer conversationType, Integer contentType, Object payload) { + ImNotificationWebSocketDTO notification = buildNotification(conversationType, contentType, payload); + executeAfterTransaction(() -> getSelf().doBroadcastNotification(notification)); + } + + private static ImNotificationWebSocketDTO buildNotification(Integer conversationType, Integer contentType, + Object payload) { + return new ImNotificationWebSocketDTO() + .setConversationType(conversationType) + .setContentType(contentType) + .setPayload(payload); + } + + /** + * 异步发送 WebSocket 通知 + */ + @Async + public void doSendNotification(Collection userIds, ImNotificationWebSocketDTO notification) { + for (Long userId : getDistinctUserIds(userIds)) { + try { + webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), userId, + ImNotificationWebSocketDTO.TYPE, notification); + } catch (Exception e) { + log.error("[doSendNotification][userId({}) notification({}) 发送失败]", userId, notification, e); + } + } + } + + /** + * 异步广播 WebSocket 通知 + */ + @Async + public void doBroadcastNotification(ImNotificationWebSocketDTO notification) { + try { + webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), + ImNotificationWebSocketDTO.TYPE, notification); + } catch (Exception e) { + log.error("[doBroadcastNotification][notification({}) 广播失败]", notification, e); + } + } + + private static Set getDistinctUserIds(Collection userIds) { + return convertLinkedSet(userIds, userId -> userId); + } + + /** + * 事务感知的任务调度 + * + * @param task 待执行的推送任务 + */ + private void executeAfterTransaction(Runnable task) { + if (!TransactionSynchronizationManager.isSynchronizationActive()) { + task.run(); + return; + } + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCommit() { + task.run(); + } + + }); + } + + /** + * 获得自身的代理对象,解决 @Async AOP 代理问题 + */ + private ImWebSocketServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/ImNotificationWebSocketDTO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/ImNotificationWebSocketDTO.java new file mode 100644 index 0000000000..38a6d0454b --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/ImNotificationWebSocketDTO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * IM WebSocket 在线通知外壳 + *

+ * conversationType 定位会话维度;contentType 定位业务内容;payload 承载对应通知对象。 + * 会进入聊天流的私聊、群聊、频道事件走 message 子包;不进入聊天流的好友、加群申请、通话信令走 NONE 会话。 + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class ImNotificationWebSocketDTO { + + public static final String TYPE = "im-notification"; + + /** + * 会话类型,参见 ImConversationTypeEnum 枚举类 + */ + private Integer conversationType; + /** + * 内容类型,参见 ImContentTypeEnum 枚举类 + */ + private Integer contentType; + /** + * 负载数据 + */ + private Object payload; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/BaseFriendNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/BaseFriendNotification.java new file mode 100644 index 0000000000..d2d3a22bdc --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/BaseFriendNotification.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; + +import lombok.Data; + +/** + * 好友事件通知基类 + *

+ * 所有好友事件 payload 共享: + * - operatorUserId 标识"谁触发的",用于多端同步时本端识别"是我自己触发的"还是"对方触发的" + * - friendUserId 标识"好友是谁",前端按它定位 / 更新本地 friend 缓存 + */ +@Data +public abstract class BaseFriendNotification { + + /** + * 操作人用户编号 + */ + private Long operatorUserId; + /** + * 好友用户编号 + */ + private Long friendUserId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendAddNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendAddNotification.java new file mode 100644 index 0000000000..781c94cc06 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendAddNotification.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; + +import lombok.Data; + +/** + * 新增好友通知 + *

+ * 双向建立好友关系后,推送给 A、B 双方多端;前端拉取好友信息入库 + */ +@Data +public class FriendAddNotification extends BaseFriendNotification { + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendBlockNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendBlockNotification.java new file mode 100644 index 0000000000..abc0913106 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendBlockNotification.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; + +import lombok.Data; + +/** + * 加入黑名单通知 + *

+ * A 拉黑 B 后仅推 A 多端;B 端不感知(B 那边 blocked=0 不变) + */ +@Data +public class FriendBlockNotification extends BaseFriendNotification { + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendDeleteNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendDeleteNotification.java new file mode 100644 index 0000000000..814c4eb935 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendDeleteNotification.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; + +import lombok.Data; + +/** + * 好友删除通知 + *

+ * 仅推送给操作人多端做同步(对端不感知,与单边删除语义对齐);前端清除本地好友 + 按 clear 决定级联清理 + */ +@Data +public class FriendDeleteNotification extends BaseFriendNotification { + + /** + * 是否级联清理本端相关数据(当前包含私聊会话;未来可能扩展更多 clear 项) + */ + private Boolean clear; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendInfoUpdatedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendInfoUpdatedNotification.java new file mode 100644 index 0000000000..60890b0735 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendInfoUpdatedNotification.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; + +import lombok.Data; + +/** + * 好友资料变更通知(对端改了昵称 / 头像) + *

+ * 由 AdminUserProfileUpdateConsumer 监听 system 模块的 AdminUserProfileUpdateMessage 后,批量推送给资料被改的人的所有好友; + * 前端 dispatcher 收到后调 loadFriendInfo 重拉资料 + *

+ * 此处 friendUserId 表示「资料被更新的那个人」(即对端),与 operatorUserId 一致 + */ +@Data +public class FriendInfoUpdatedNotification extends BaseFriendNotification { + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestApprovedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestApprovedNotification.java new file mode 100644 index 0000000000..5cb748cf55 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestApprovedNotification.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; + +import lombok.Data; + +/** + * 好友申请被同意通知 + *

+ * 推送给原申请发起方多端;前端按 requestId 把对应申请记录 handleResult 更新为「已同意」 + */ +@Data +public class FriendRequestApprovedNotification extends BaseFriendNotification { + + /** + * 已处理的申请记录编号 + */ + private Long requestId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestNotification.java new file mode 100644 index 0000000000..5d82e0ee83 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestNotification.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; + +import lombok.Data; + +/** + * 收到新的好友申请通知 + *

+ * 推送给申请的接收方多端;payload 已携带 fromUser 聚合字段,前端按 requestId 直接 push 进列表,无需回拉 + */ +@Data +public class FriendRequestNotification extends BaseFriendNotification { + + /** + * 申请记录编号 + */ + private Long requestId; + /** + * 申请理由 + */ + private String applyContent; + /** + * 添加来源 + */ + private Integer addSource; + + // ========== 聚合自 AdminUser,避免前端再调 system 接口 ========== + + /** + * 申请方昵称 + */ + private String fromNickname; + /** + * 申请方头像 + */ + private String fromAvatar; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestRejectedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestRejectedNotification.java new file mode 100644 index 0000000000..716ba4acf4 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestRejectedNotification.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; + +import lombok.Data; + +/** + * 好友申请被拒绝通知 + *

+ * 推送给原申请发起方多端;前端按 requestId 把对应申请记录 handleResult 更新为「已拒绝」 + */ +@Data +public class FriendRequestRejectedNotification extends BaseFriendNotification { + + /** + * 已处理的申请记录编号 + */ + private Long requestId; + /** + * 拒绝理由(可选) + */ + private String handleContent; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUnblockNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUnblockNotification.java new file mode 100644 index 0000000000..00c246e0be --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUnblockNotification.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; + +import lombok.Data; + +/** + * 移出黑名单通知 + *

+ * A 把 B 移出黑名单后仅推 A 多端 + */ +@Data +public class FriendUnblockNotification extends BaseFriendNotification { + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUpdateNotification.java new file mode 100644 index 0000000000..2f3df5b1cc --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUpdateNotification.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; + +import lombok.Data; + +/** + * 好友信息批量更新通知(备注 / 免打扰 / 联系人置顶等单边属性) + *

+ * A 改了对 B 的备注 / 免打扰 / 置顶等单边属性后仅推 A 多端做同步; + * 一次 update 涉及多个字段时合并为单条通知,避免多通知顺序竞争。 + */ +@Data +public class FriendUpdateNotification extends BaseFriendNotification { + + /** + * 备注;不为空则更新(空串表示清空) + */ + private String displayName; + /** + * 免打扰;不为空则更新 + */ + private Boolean silent; + /** + * 联系人置顶;不为空则更新 + */ + private Boolean pinned; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/BaseGroupNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/BaseGroupNotification.java new file mode 100644 index 0000000000..cd9775697b --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/BaseGroupNotification.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +/** + * 群事件通知基类:所有群事件 payload 共享 operatorUserId + */ +@Data +public abstract class BaseGroupNotification { + + /** + * 操作人用户编号 + */ + private Long operatorUserId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminAddNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminAddNotification.java new file mode 100644 index 0000000000..42dfa34203 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminAddNotification.java @@ -0,0 +1,7 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +/** + * 添加管理员事件通知(memberUserIds 为被设管理员的成员) + */ +public class GroupAdminAddNotification extends GroupMemberListNotification { +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminRemoveNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminRemoveNotification.java new file mode 100644 index 0000000000..39b67dc845 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminRemoveNotification.java @@ -0,0 +1,7 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +/** + * 撤销管理员事件通知(memberUserIds 为被撤销管理员的成员) + */ +public class GroupAdminRemoveNotification extends GroupMemberListNotification { +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupBannedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupBannedNotification.java new file mode 100644 index 0000000000..f72a79bd33 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupBannedNotification.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +/** + * 群封禁 / 解封通知 + */ +@Data +public class GroupBannedNotification extends BaseGroupNotification { + + /** + * 是否封禁 + */ + private Boolean banned; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCancelMutedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCancelMutedNotification.java new file mode 100644 index 0000000000..0dbc4b6a0a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCancelMutedNotification.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 全群取消禁言通知 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class GroupCancelMutedNotification extends BaseGroupNotification { + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCreateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCreateNotification.java new file mode 100644 index 0000000000..2d10bab65c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCreateNotification.java @@ -0,0 +1,7 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +/** + * 群创建事件通知(memberUserIds 含创建者 + 初始邀请成员) + */ +public class GroupCreateNotification extends GroupMemberListNotification { +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupDissolveNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupDissolveNotification.java new file mode 100644 index 0000000000..acff6b0221 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupDissolveNotification.java @@ -0,0 +1,7 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +/** + * 群解散事件通知 + */ +public class GroupDissolveNotification extends BaseGroupNotification { +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupInfoUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupInfoUpdateNotification.java new file mode 100644 index 0000000000..7dbe87c606 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupInfoUpdateNotification.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +/** + * 群信息变更事件通知(NAME / NOTICE 走独立事件) + */ +@Data +public class GroupInfoUpdateNotification extends BaseGroupNotification { + + /** + * 旧群头像 + */ + private String oldAvatar; + /** + * 新群头像 + */ + private String newAvatar; + /** + * 旧进群审批开关 + */ + private Boolean oldJoinApproval; + /** + * 新进群审批开关 + */ + private Boolean newJoinApproval; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberCancelMutedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberCancelMutedNotification.java new file mode 100644 index 0000000000..70719ef2ed --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberCancelMutedNotification.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 取消成员禁言通知 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class GroupMemberCancelMutedNotification extends BaseGroupNotification { + + /** + * 被取消禁言的用户编号 + */ + private Long mutedUserId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberEnterNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberEnterNotification.java new file mode 100644 index 0000000000..337c7440f6 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberEnterNotification.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 自由进群事件通知 + *

+ * 用户经搜索 / 二维码 / 分享链接自由进群(FREE 模式或审批通过后),全员广播; + * 进群者前端按 entrantUserId === self 自判,初次拉取 fetchGroupInfo + fetchGroupMembers;其余成员局部插入新成员 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class GroupMemberEnterNotification extends BaseGroupNotification { + + /** + * 进群者用户编号 + */ + private Long entrantUserId; + /** + * 加入来源 + */ + private Integer addSource; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberInviteNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberInviteNotification.java new file mode 100644 index 0000000000..74558450e1 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberInviteNotification.java @@ -0,0 +1,7 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +/** + * 成员加入事件通知(memberUserIds 为被邀请人) + */ +public class GroupMemberInviteNotification extends GroupMemberListNotification { +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberKickNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberKickNotification.java new file mode 100644 index 0000000000..24ff486597 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberKickNotification.java @@ -0,0 +1,7 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +/** + * 成员被移出事件通知(memberUserIds 为被移出成员) + */ +public class GroupMemberKickNotification extends GroupMemberListNotification { +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberListNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberListNotification.java new file mode 100644 index 0000000000..5a57254a4a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberListNotification.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +import java.util.List; + +/** + * 群事件成员列表通知基类 + * + * @author 芋道源码 + */ +@Data +public abstract class GroupMemberListNotification extends BaseGroupNotification { + + /** + * 受影响的成员用户编号列表 + */ + private List memberUserIds; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberMutedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberMutedNotification.java new file mode 100644 index 0000000000..a636249085 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberMutedNotification.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 成员禁言通知 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class GroupMemberMutedNotification extends BaseGroupNotification { + + /** + * 被禁言的用户编号 + */ + private Long mutedUserId; + /** + * 禁言到期时间 + */ + private LocalDateTime muteEndTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberNicknameUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberNicknameUpdateNotification.java new file mode 100644 index 0000000000..869fc2b8a2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberNicknameUpdateNotification.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +/** + * 成员组内昵称变更事件通知 + */ +@Data +public class GroupMemberNicknameUpdateNotification extends BaseGroupNotification { + + /** + * 群内昵称 + */ + private String displayUserName; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberQuitNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberQuitNotification.java new file mode 100644 index 0000000000..663f104650 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberQuitNotification.java @@ -0,0 +1,7 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +/** + * 成员退群事件通知 + */ +public class GroupMemberQuitNotification extends BaseGroupNotification { +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberSettingUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberSettingUpdateNotification.java new file mode 100644 index 0000000000..05f021573c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberSettingUpdateNotification.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +/** + * 群成员个人设置变更事件通知(个人多端同步) + *

+ * silent / groupRemark 字段 null 表示本次未变更,前端按非 null 局部更新 + */ +@Data +public class GroupMemberSettingUpdateNotification extends BaseGroupNotification { + + /** + * 群免打扰 + */ + private Boolean silent; + /** + * 群备注 + */ + private String groupRemark; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessagePinNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessagePinNotification.java new file mode 100644 index 0000000000..1717b1ba04 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessagePinNotification.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 群消息置顶事件通知 + */ +@Data +public class GroupMessagePinNotification extends BaseGroupNotification { + + /** + * 被置顶的消息编号 + */ + private Long messageId; + /** + * 被置顶的消息展示数据 + */ + private PinnedMessage message; + + /** + * 被置顶的消息展示数据 + */ + @Data + public static class PinnedMessage { + + /** + * 消息编号 + */ + private Long id; + /** + * 发送人编号 + */ + private Long senderId; + /** + * 群编号 + */ + private Long groupId; + /** + * 消息类型 + */ + private Integer type; + /** + * 消息内容 + */ + private String content; + /** + * 发送时间 + */ + private LocalDateTime sendTime; + /** + * @ 目标用户编号列表 + */ + private List atUserIds; + /** + * 定向接收用户编号列表 + */ + private List receiverUserIds; + + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessageUnpinNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessageUnpinNotification.java new file mode 100644 index 0000000000..805196759a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessageUnpinNotification.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +/** + * 群消息取消置顶事件通知 + */ +@Data +public class GroupMessageUnpinNotification extends BaseGroupNotification { + + /** + * 被取消置顶的消息编号 + */ + private Long messageId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMutedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMutedNotification.java new file mode 100644 index 0000000000..dc49a29035 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMutedNotification.java @@ -0,0 +1,11 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +/** + * 全群禁言通知 + */ +@Data +public class GroupMutedNotification extends BaseGroupNotification { + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNameUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNameUpdateNotification.java new file mode 100644 index 0000000000..00fcc74ad6 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNameUpdateNotification.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +/** + * 群名变更事件通知 + */ +@Data +public class GroupNameUpdateNotification extends BaseGroupNotification { + + /** + * 旧群名 + */ + private String oldName; + /** + * 新群名 + */ + private String newName; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNoticeUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNoticeUpdateNotification.java new file mode 100644 index 0000000000..ecd3e34890 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNoticeUpdateNotification.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +/** + * 群公告变更事件通知 + */ +@Data +public class GroupNoticeUpdateNotification extends BaseGroupNotification { + + /** + * 旧群公告 + */ + private String oldNotice; + /** + * 新群公告 + */ + private String newNotice; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupOwnerTransferNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupOwnerTransferNotification.java new file mode 100644 index 0000000000..b4e8564c17 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupOwnerTransferNotification.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; + +/** + * 群主转让事件通知 + */ +@Data +public class GroupOwnerTransferNotification extends BaseGroupNotification { + + /** + * 新群主用户编号 + */ + private Long newOwnerUserId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestApprovedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestApprovedNotification.java new file mode 100644 index 0000000000..2e083f2132 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestApprovedNotification.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 入群申请被同意通知 + *

+ * 定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class GroupRequestApprovedNotification extends BaseGroupNotification { + + /** + * 已处理的申请记录编号 + */ + private Long requestId; + /** + * 群编号 + */ + private Long groupId; + /** + * 申请人 / 被邀请人用户编号 + */ + private Long userId; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestReceivedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestReceivedNotification.java new file mode 100644 index 0000000000..29b5aa3647 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestReceivedNotification.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 收到新的入群申请通知 + *

+ * 定向推送给群主 + 全部管理员(多端同步);payload 已携带申请方昵称 / 头像,前端按 requestId 直接 push 进列表 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class GroupRequestReceivedNotification extends BaseGroupNotification { + + /** + * 申请记录编号 + */ + private Long requestId; + /** + * 群编号 + */ + private Long groupId; + /** + * 申请人 / 被邀请人用户编号 + */ + private Long userId; + /** + * 邀请人用户编号;NULL 表示用户主动申请 + */ + private Long inviterUserId; + /** + * 申请理由 + */ + private String applyContent; + /** + * 加入来源 + */ + private Integer addSource; + + // ========== 聚合自 AdminUser,避免前端再调 system 接口 ========== + + /** + * 申请方 / 被邀请人昵称 + */ + private String userNickname; + /** + * 申请方 / 被邀请人头像 + */ + private String userAvatar; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestRejectedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestRejectedNotification.java new file mode 100644 index 0000000000..dd6e153e28 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestRejectedNotification.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.group; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 入群申请被拒绝通知 + *

+ * 定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class GroupRequestRejectedNotification extends BaseGroupNotification { + + /** + * 已处理的申请记录编号 + */ + private Long requestId; + /** + * 群编号 + */ + private Long groupId; + /** + * 申请人 / 被邀请人用户编号 + */ + private Long userId; + /** + * 拒绝理由(可选) + */ + private String handleContent; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImChannelMessageNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImChannelMessageNotification.java new file mode 100644 index 0000000000..af7e4c2f68 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImChannelMessageNotification.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.message; + +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * IM 频道消息 WebSocket 推送通知 + *

+ * 单向:服务端运营推送 → C 端用户;C 端不能向频道发消息。 + * 字段分层:顶层是消息元数据 + 检索维度,content 是 MaterialMessage payload 的 JSON 串。 + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class ImChannelMessageNotification { + + /** + * 消息编号 + */ + private Long id; + /** + * 频道编号 + */ + private Long channelId; + /** + * 关联素材编号 + */ + private Long materialId; + /** + * 消息类型 + */ + private Integer type; + /** + * 消息内容;payload JSON 串 + */ + private String content; + /** + * 发送时间 + */ + private LocalDateTime sendTime; + + /** + * 由频道消息 DO 构建推送通知 + */ + public static ImChannelMessageNotification ofSend(ImChannelMessageDO message) { + return BeanUtils.toBean(message, ImChannelMessageNotification.class); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImGroupMessageNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImGroupMessageNotification.java new file mode 100644 index 0000000000..cbefa05654 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImGroupMessageNotification.java @@ -0,0 +1,77 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.message; + +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * IM 群聊消息 WebSocket 统一推送通知 + * + * @author 芋道源码 + */ +@Data +public class ImGroupMessageNotification { + + /** + * 消息编号 + */ + private Long id; + /** + * 客户端消息编号 + */ + private String clientMessageId; + /** + * 发送人编号 + */ + private Long senderId; + /** + * 群编号 + */ + private Long groupId; + /** + * 消息类型 + */ + private Integer type; + /** + * 消息内容 + */ + private String content; + /** + * 消息状态 + */ + private Integer status; + /** + * 回执状态 + */ + private Integer receiptStatus; + /** + * 已读人数 + */ + private Integer readCount; + /** + * 发送时间 + */ + private LocalDateTime sendTime; + /** + * @ 目标用户编号列表 + */ + private List atUserIds; + /** + * 定向接收用户编号列表 + */ + private List receiverUserIds; + /** + * 构建发送消息通知 + * + * @param message 群聊消息 DO + * @return 群聊通知 + */ + public static ImGroupMessageNotification ofSend(ImGroupMessageDO message) { + return BeanUtils.toBean(message, ImGroupMessageNotification.class) + .setReadCount(message.getReadCount() != null ? message.getReadCount() : 0); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReadNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReadNotification.java new file mode 100644 index 0000000000..c066fa0114 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReadNotification.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.message; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * IM 消息已读同步通知 + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class ImMessageReadNotification { + + /** + * 已读位置(最大已读消息编号) + */ + private Long id; + /** + * 发送人编号 + */ + private Long senderId; + /** + * 私聊接收人编号 + */ + private Long receiverId; + /** + * 群编号 + */ + private Long groupId; + /** + * 频道编号 + */ + private Long channelId; + /** + * 已读位置(最大已读消息编号) + */ + private Long readId; + + public static ImMessageReadNotification ofPrivate(Long senderId, Long receiverId, Long readId) { + return new ImMessageReadNotification() + .setId(readId).setReadId(readId) + .setSenderId(senderId).setReceiverId(receiverId); + } + + public static ImMessageReadNotification ofGroup(Long senderId, Long groupId, Long readId) { + return new ImMessageReadNotification() + .setId(readId).setReadId(readId) + .setSenderId(senderId).setGroupId(groupId); + } + + public static ImMessageReadNotification ofChannel(Long channelId, Long readId) { + return new ImMessageReadNotification() + .setId(readId).setReadId(readId) + .setChannelId(channelId); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReceiptNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReceiptNotification.java new file mode 100644 index 0000000000..f82df9f444 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReceiptNotification.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.message; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * IM 消息回执通知 + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class ImMessageReceiptNotification { + + /** + * 消息编号 + */ + private Long id; + /** + * 已读方的用户编号 + */ + private Long senderId; + /** + * 私聊接收人编号 + */ + private Long receiverId; + /** + * 群编号 + */ + private Long groupId; + /** + * 已读人数 + */ + private Integer readCount; + /** + * 回执状态 + */ + private Integer receiptStatus; + + public static ImMessageReceiptNotification ofPrivate(Long senderId, Long receiverId, Long readId) { + return new ImMessageReceiptNotification() + .setId(readId).setSenderId(senderId).setReceiverId(receiverId); + } + + public static ImMessageReceiptNotification ofGroup(Long messageId, Long groupId, + Integer readCount, Integer receiptStatus) { + return new ImMessageReceiptNotification() + .setId(messageId).setGroupId(groupId) + .setReadCount(readCount).setReceiptStatus(receiptStatus); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImPrivateMessageNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImPrivateMessageNotification.java new file mode 100644 index 0000000000..0a88591ea0 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImPrivateMessageNotification.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.message; + +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * IM 私聊消息 WebSocket 统一推送通知 + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class ImPrivateMessageNotification { + + /** + * 消息编号 + */ + private Long id; + /** + * 客户端消息编号 + */ + private String clientMessageId; + /** + * 发送人编号 + */ + private Long senderId; + /** + * 接收人编号 + */ + private Long receiverId; + /** + * 消息类型 + */ + private Integer type; + /** + * 消息内容 + */ + private String content; + /** + * 消息状态 + */ + private Integer status; + /** + * 回执状态 + */ + private Integer receiptStatus; + /** + * 发送时间 + */ + private LocalDateTime sendTime; + + // ========== 静态工厂方法 ========== + + /** + * 构建发送消息通知 + * + * @param message 私聊消息 DO + * @return 私聊通知 + */ + public static ImPrivateMessageNotification ofSend(ImPrivateMessageDO message) { + return BeanUtils.toBean(message, ImPrivateMessageNotification.class); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/package-info.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/package-info.java new file mode 100644 index 0000000000..7c2c251efa --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/package-info.java @@ -0,0 +1,14 @@ +/** + * IM WebSocket 在线通知对象。 + * + *

+ * 本包只放 WebSocket 外层通知对象,固定使用 im-notification 作为推送 type。 + * 具体业务 payload 放在子包中: + *

    + *
  • {@code message}:私聊、群聊、频道消息通知,以及消息内容结构;
  • + *
  • {@code friend}:好友关系和好友申请通知;
  • + *
  • {@code group}:群资料、群成员、加群申请通知;
  • + *
  • {@code rtc}:实时通话通知。
  • + *
+ */ +package cn.iocoder.yudao.module.im.service.websocket.notification; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallEndNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallEndNotification.java new file mode 100644 index 0000000000..8c616db6fc --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallEndNotification.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc; + +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallEndReasonEnum; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import lombok.Data; + +/** + * RTC_CALL_END 通话结束通知 + *

+ * 入 im_private_message / im_group_message;接收方关闭通话窗 + 渲染聊天 tip + *

+ * 文案分场景: + * 群聊:「语音通话已经结束」;发起人信息走配对的 {@link ImRtcCallStartNotification} + * 私聊:仿微信准气泡,按 endReason × selfSend 视角转换文案(HANGUP / CANCEL / REJECT / BUSY / ERROR) + *

+ * 与 {@link ImRtcCallStartNotification} 两段式配对:START 在 invite 事务里 INSERT,END 在 cancel / leave 事务里 INSERT; + * 两段位于不同请求 / 事务,自增 id 保证聊天流顺序 + * + * @author 芋道源码 + */ +@Data +public class ImRtcCallEndNotification { + + /** + * 业务通话编号 + */ + private String room; + /** + * 会话类型 + */ + private Integer conversationType; + /** + * 媒体类型 + */ + private Integer mediaType; + /** + * 结束原因 + */ + private Integer endReason; + /** + * 通话时长(秒):接通过为 endTime - acceptTime;未接通为 null + */ + private Long durationSeconds; + /** + * 操作者用户编号:HANGUP / CANCEL / REJECT 是触发结束的人;HANGUP webhook 兜底为 null + *

+ * 用于前端「被某某挂断」类文案;普通文案不依赖此字段 + */ + private Long operatorUserId; + /** + * 操作者昵称:前端按需展示(被某某挂断 / 头像 tip);操作者为空时随之为空 + */ + private String operatorNickname; + /** + * 操作者头像:前端按需展示;操作者为空时随之为空 + */ + private String operatorAvatar; + + public static ImRtcCallEndNotification of(ImRtcCallDO call, ImRtcCallEndReasonEnum reason, Long durationSeconds, + Long operatorId, AdminUserRespDTO operator) { + ImRtcCallEndNotification notification = new ImRtcCallEndNotification(); + notification.room = call.getRoom(); + notification.conversationType = call.getConversationType(); + notification.mediaType = call.getMediaType(); + notification.endReason = reason.getReason(); + notification.durationSeconds = durationSeconds; + notification.operatorUserId = operatorId; + if (operator != null) { + notification.operatorNickname = operator.getNickname(); + notification.operatorAvatar = operator.getAvatar(); + } + return notification; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallNotification.java new file mode 100644 index 0000000000..e25f389b71 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallNotification.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc; + +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantStatusEnum; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import lombok.Data; + +import java.util.Collection; +import java.util.List; + +/** + * RTC_CALL 通话信令通知(通话信令统一入口) + *

+ * 不入库;走 imWebSocketService 仅推参与方 + *

+ * status 字段复用 {@link ImRtcParticipantStatusEnum},表达「本次信令对应的参与者状态变迁」 + * + * @author 芋道源码 + */ +@Data +public class ImRtcCallNotification { + + /** + * 信令对应的参与者状态 + * + * 取值参见 {@link ImRtcParticipantStatusEnum} + */ + private Integer status; + + /** + * 业务通话编号 + */ + private String room; + /** + * 会话类型 + */ + private Integer conversationType; + /** + * 媒体类型 + */ + private Integer mediaType; + /** + * 群编号:群通话场景必填 + */ + private Long groupId; + + // ========== INVITE 专属字段 ========== + + /** + * LiveKit Server WebSocket 地址;INVITE 专属 + */ + private String livekitUrl; + /** + * 该被叫专属的 LiveKit 接听 Token;接通后直接 connect 用;INVITE 专属 + */ + private String token; + /** + * 发起人用户编号;INVITE 专属 + */ + private Long inviterUserId; + /** + * 发起人昵称;INVITE 专属,前端来电界面展示 + */ + private String inviterNickname; + /** + * 发起人头像;INVITE 专属,前端来电界面展示 + */ + private String inviterAvatar; + /** + * 本次被邀请人列表;INVITE 专属,前端来电界面展示「邀请的其他人」 + * + * 注意:包含收件人自身,前端按需过滤 + */ + private List inviteeIds; + + // ========== REJECT 专属字段 ========== + + /** + * 操作者用户编号;REJECT 触发本次状态变迁的人 + */ + private Long operatorUserId; + /** + * 操作者昵称;前端按需展示(被某某拒接);普通文案不依赖 + */ + private String operatorNickname; + /** + * 操作者头像;前端按需展示;普通文案不依赖 + */ + private String operatorAvatar; + + /** + * 构造 INVITE 信令;推被邀请人,invitee 状态变为 INVITING + * + * @param call 通话主表 + * @param inviter 发起人;可空,缺失时 inviterNickname / inviterAvatar 留空 + * @param livekitUrl LiveKit Server WebSocket 地址 + * @param token 被叫的接听 Token;按收件人单独签发 + * @param inviteeIds 本次被邀请人列表;前端来电界面展示「邀请的其他人」用 + * @return INVITE 信令 + */ + public static ImRtcCallNotification ofInvite(ImRtcCallDO call, AdminUserRespDTO inviter, + String livekitUrl, String token, + Collection inviteeIds) { + ImRtcCallNotification notification = baseOf(call, ImRtcParticipantStatusEnum.INVITING.getStatus()); + notification.livekitUrl = livekitUrl; + notification.token = token; + notification.inviterUserId = call.getInviterUserId(); + if (inviter != null) { + notification.inviterNickname = inviter.getNickname(); + notification.inviterAvatar = inviter.getAvatar(); + } + notification.inviteeIds = inviteeIds != null ? new java.util.ArrayList<>(inviteeIds) : null; + return notification; + } + + /** + * 构造 REJECT 信令;仅群通话场景;推主叫 + * + * @param call 通话主表 + * @param operatorUserId 拒接者用户编号 + * @param operator 拒接者;可空,缺失时 operatorNickname / operatorAvatar 留空 + * @return REJECT 信令 + */ + public static ImRtcCallNotification ofReject(ImRtcCallDO call, Long operatorUserId, AdminUserRespDTO operator) { + ImRtcCallNotification notification = baseOf(call, ImRtcParticipantStatusEnum.REJECTED.getStatus()); + notification.operatorUserId = operatorUserId; + if (operator != null) { + notification.operatorNickname = operator.getNickname(); + notification.operatorAvatar = operator.getAvatar(); + } + return notification; + } + + /** + * 构造 NO_ANSWER 信令;仅群通话场景;推主叫;超时未接听语义独立于 REJECT + * + * @param call 通话主表 + * @param operatorUserId 未接听者用户编号 + * @param operator 未接听者;可空,缺失时 operatorNickname / operatorAvatar 留空 + * @return NO_ANSWER 信令 + */ + public static ImRtcCallNotification ofNoAnswer(ImRtcCallDO call, Long operatorUserId, AdminUserRespDTO operator) { + ImRtcCallNotification notification = baseOf(call, ImRtcParticipantStatusEnum.NO_ANSWER.getStatus()); + notification.operatorUserId = operatorUserId; + if (operator != null) { + notification.operatorNickname = operator.getNickname(); + notification.operatorAvatar = operator.getAvatar(); + } + return notification; + } + + /** + * 公共骨架;填充 call 上下文 + status + */ + private static ImRtcCallNotification baseOf(ImRtcCallDO call, Integer status) { + ImRtcCallNotification notification = new ImRtcCallNotification(); + notification.status = status; + notification.room = call.getRoom(); + notification.conversationType = call.getConversationType(); + notification.mediaType = call.getMediaType(); + notification.groupId = call.getGroupId(); + return notification; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallStartNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallStartNotification.java new file mode 100644 index 0000000000..a7a8596e1e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallStartNotification.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc; + +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import lombok.Data; + +/** + * RTC_CALL_START 通话开始通知 + *

+ * 群聊入 im_group_message 全群广播;前端渲染聊天 tip「{inviterNickname} 发起了语音通话」 + *

+ * 私聊入 im_private_message 定向给被叫;前端不渲染聊天 tip,仅用于会话列表预览展示「[语音通话]」(刷新后仍可见) + *

+ * 与 {@link ImRtcCallEndNotification} 两段式配对: + * START 在 invite 接口事务里 INSERT,END 在 cancel / leave 接口事务里 INSERT, + * 两段位于不同请求 / 事务,自增 id 保证聊天流顺序;后续如果合并到同一事务里 push,需要额外保证 START 先于 END + * + * @author 芋道源码 + */ +@Data +public class ImRtcCallStartNotification { + + /** + * 业务通话编号 + */ + private String room; + /** + * 会话类型:当前固定 GROUP(私聊无 START) + */ + private Integer conversationType; + /** + * 媒体类型 + */ + private Integer mediaType; + /** + * 发起人用户编号 + */ + private Long inviterUserId; + /** + * 发起人昵称:用于聊天 tip 文案,可空走前端 fallback + */ + private String inviterNickname; + /** + * 发起人头像:可空,预留给点击 tip 展示发起人卡片 + */ + private String inviterAvatar; + + public static ImRtcCallStartNotification of(ImRtcCallDO call, AdminUserRespDTO inviter) { + ImRtcCallStartNotification notification = new ImRtcCallStartNotification(); + notification.room = call.getRoom(); + notification.conversationType = call.getConversationType(); + notification.mediaType = call.getMediaType(); + notification.inviterUserId = call.getInviterUserId(); + if (inviter != null) { + notification.inviterNickname = inviter.getNickname(); + notification.inviterAvatar = inviter.getAvatar(); + } + return notification; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantConnectedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantConnectedNotification.java new file mode 100644 index 0000000000..dee51c9419 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantConnectedNotification.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc; + +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import lombok.Data; + +/** + * RTC_PARTICIPANT_CONNECTED 通话参与者加入通知 + *

+ * 不入库;LiveKit webhook participant_joined 触发;私聊推 peer 多端 + inviter 多端,群聊全群广播 + *

+ * 前端 callStore 把 userId 追加进 joinedUserIds;胶囊条人数 +1;群聊场景首条 1602 携带通话元信息以便 首次填充胶囊条 + * + * @author 芋道源码 + */ +@Data +public class ImRtcParticipantConnectedNotification { + + /** + * 业务通话编号 + */ + private String room; + /** + * 加入的参与者用户编号 + */ + private Long userId; + /** + * 会话类型 + */ + private Integer conversationType; + /** + * 群编号;群通话场景必填 + */ + private Long groupId; + /** + * 媒体类型;群聊场景的非邀请成员靠这个字段 首次填充胶囊条 + */ + private Integer mediaType; + /** + * 发起人用户编号;群聊场景的非邀请成员靠这个字段 首次填充胶囊条 + */ + private Long inviterUserId; + + /** + * 构造参与者加入通知;按 {@link ImRtcCallDO} 抽通话上下文,仅 userId 是变量 + * + * @param call 通话主表 + * @param userId 加入的参与者用户编号 + * @return 通知载荷 + */ + public static ImRtcParticipantConnectedNotification of(ImRtcCallDO call, Long userId) { + ImRtcParticipantConnectedNotification notification = new ImRtcParticipantConnectedNotification(); + notification.room = call.getRoom(); + notification.userId = userId; + notification.conversationType = call.getConversationType(); + notification.groupId = call.getGroupId(); + notification.mediaType = call.getMediaType(); + notification.inviterUserId = call.getInviterUserId(); + return notification; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantDisconnectedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantDisconnectedNotification.java new file mode 100644 index 0000000000..4aec61b91b --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantDisconnectedNotification.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc; + +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import lombok.Data; + +/** + * RTC_PARTICIPANT_DISCONNECTED 通话参与者离开通知 + *

+ * 不入库;LiveKit webhook participant_left 触发;推送范围同 {@link ImRtcParticipantConnectedNotification} + *

+ * 前端 callStore 把 userId 从 joinedUserIds 移除;胶囊条人数 -1 + * + * @author 芋道源码 + */ +@Data +public class ImRtcParticipantDisconnectedNotification { + + /** + * 业务通话编号 + */ + private String room; + /** + * 离开的参与者用户编号 + */ + private Long userId; + /** + * 会话类型 + */ + private Integer conversationType; + /** + * 群编号;群通话场景必填 + */ + private Long groupId; + + /** + * 构造参与者离开通知;按 {@link ImRtcCallDO} 抽通话上下文,仅 userId 是变量 + * + * @param call 通话主表 + * @param userId 离开的参与者用户编号 + * @return 通知载荷 + */ + public static ImRtcParticipantDisconnectedNotification of(ImRtcCallDO call, Long userId) { + ImRtcParticipantDisconnectedNotification notification = new ImRtcParticipantDisconnectedNotification(); + notification.room = call.getRoom(); + notification.userId = userId; + notification.conversationType = call.getConversationType(); + notification.groupId = call.getGroupId(); + return notification; + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java new file mode 100644 index 0000000000..bdfa83f062 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java @@ -0,0 +1,218 @@ +package cn.iocoder.yudao.module.im.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_CONTENT_INVALID; + +/** + * IM 消息内容相关工具类 + */ +public class ImMessageUtils { + + /** + * 文本消息最大长度 + */ + private static final int TEXT_MAX_LENGTH = 4096; + /** + * URL 最大长度 + */ + private static final int URL_MAX_LENGTH = 2048; + /** + * 合并消息最大条数 + */ + private static final int MERGE_MESSAGE_MAX_COUNT = 100; + + /** + * 校验用户发送的消息内容 + * + * @param type 消息类型 + * @param content 消息内容 + */ + public static void validateUserMessageContent(Integer type, String content) { + ImContentTypeEnum messageType; + try { + messageType = ImContentTypeEnum.validate(type); + } catch (IllegalArgumentException e) { + throw exception(MESSAGE_CONTENT_INVALID); + } + if (!messageType.isNormal()) { + throw exception(MESSAGE_CONTENT_INVALID); + } + Map map = JsonUtils.parseMap(content); + if (map == null) { + throw exception(MESSAGE_CONTENT_INVALID); + } + if (ImContentTypeEnum.TEXT == messageType) { + validateTextContent(map); + } else if (ImContentTypeEnum.IMAGE == messageType || ImContentTypeEnum.FACE == messageType) { + validateUrl(getString(map, "url")); + } else if (ImContentTypeEnum.VOICE == messageType) { + validateUrl(getString(map, "url")); + validatePositiveNumber(map.get("duration")); + } else if (ImContentTypeEnum.VIDEO == messageType) { + validateUrl(getString(map, "url")); + } else if (ImContentTypeEnum.FILE == messageType) { + validateUrl(getString(map, "url")); + validateNotBlank(getString(map, "name")); + } else if (ImContentTypeEnum.CARD == messageType) { + validateCardContent(map); + } else if (ImContentTypeEnum.MERGE == messageType) { + validateMergeContent(map); + } else if (ImContentTypeEnum.MATERIAL == messageType) { + validateMaterialContent(map); + } + } + + private static void validateTextContent(Map map) { + String text = getString(map, "content"); + validateNotBlank(text); + if (text.length() > TEXT_MAX_LENGTH) { + throw exception(MESSAGE_CONTENT_INVALID); + } + } + + private static void validateCardContent(Map map) { + validateNumber(map.get("targetType")); + validateNumber(map.get("targetId")); + validateNotBlank(getString(map, "name")); + } + + @SuppressWarnings("PatternVariableCanBeUsed") + private static void validateMergeContent(Map map) { + validateNotBlank(getString(map, "title")); + Object messages = map.get("messages"); + if (!(messages instanceof Collection)) { + throw exception(MESSAGE_CONTENT_INVALID); + } + Collection messageList = (Collection) messages; + if (CollUtil.isEmpty(messageList) || messageList.size() > MERGE_MESSAGE_MAX_COUNT) { + throw exception(MESSAGE_CONTENT_INVALID); + } + } + + private static void validateMaterialContent(Map map) { + if (map.get("materialId") == null && StrUtil.isBlank(getString(map, "title"))) { + throw exception(MESSAGE_CONTENT_INVALID); + } + validateUrlIfPresent(getString(map, "url")); + validateUrlIfPresent(getString(map, "coverUrl")); + } + + private static String getString(Map map, String field) { + return Convert.toStr(map.get(field), null); + } + + private static void validateNotBlank(String value) { + if (StrUtil.isBlank(value)) { + throw exception(MESSAGE_CONTENT_INVALID); + } + } + + private static void validateNumber(Object value) { + if (Convert.toLong(value, null) == null) { + throw exception(MESSAGE_CONTENT_INVALID); + } + } + + private static void validatePositiveNumber(Object value) { + Integer number = Convert.toInt(value, null); + if (number == null || number < 0) { + throw exception(MESSAGE_CONTENT_INVALID); + } + } + + private static void validateUrlIfPresent(String url) { + if (StrUtil.isBlank(url)) { + return; + } + validateUrl(url); + } + + private static void validateUrl(String url) { + validateNotBlank(url); + if (url.length() > URL_MAX_LENGTH) { + throw exception(MESSAGE_CONTENT_INVALID); + } + String lowerUrl = StrUtil.trim(url).toLowerCase(); + if (StrUtil.startWithAny(lowerUrl, "javascript:", "data:", "vbscript:", "file:")) { + throw exception(MESSAGE_CONTENT_INVALID); + } + } + + /** + * 从 content 解析客户端写入的 quote.messageId + * + * @param content 原 content(JSON) + * @return messageId,不存在 / 非法返回 null + */ + public static Long parseQuoteMessageId(String content) { + Map map = JsonUtils.parseMap(content); + if (map == null) { + return null; + } + Object quoteRaw = map.get(QuoteMessage.FIELD_NAME); + if (!(quoteRaw instanceof Map)) { + return null; + } + return Convert.toLong(((Map) quoteRaw).get(QuoteMessage.FIELD_MESSAGE_ID)); + } + + /** + * 把 quote 写入 content 的 quote 字段 + * + * @param content 原 content(JSON) + * @param quote QuoteMessage 对象 + * @return 注入 quote 后的 content(JSON) + */ + public static String appendQuote(String content, QuoteMessage quote) { + Map map = JsonUtils.parseMap(content); + if (map == null) { + map = new LinkedHashMap<>(); + } + map.put(QuoteMessage.FIELD_NAME, quote); + return JsonUtils.toJsonString(map); + } + + /** + * 移除 content 里的 quote 字段 + * + * @param content 原 content(JSON) + * @return remove quote 后的 content(JSON) + */ + public static String removeQuote(String content) { + if (StrUtil.isBlank(content) || !StrUtil.contains(content, "\"" + QuoteMessage.FIELD_NAME + "\"")) { + return content; + } + Map map = JsonUtils.parseMap(content); + if (map == null) { + return content; + } + map.remove(QuoteMessage.FIELD_NAME); + return JsonUtils.toJsonString(map); + } + + /** + * 构建 QuoteMessage 对象 + * + * @param messageId 被引用消息编号 + * @param senderId 被引用消息发送人编号 + * @param type 被引用消息类型 + * @param originalContent 被引用消息原 content(JSON),服务端会 removeQuote 防止嵌套 + * @return QuoteMessage 对象 + */ + public static QuoteMessage buildQuote(Long messageId, Long senderId, Integer type, String originalContent) { + return new QuoteMessage().setMessageId(messageId).setSenderId(senderId).setType(type) + .setContent(removeQuote(originalContent)); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapperTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapperTest.java new file mode 100644 index 0000000000..b56845f4d3 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapperTest.java @@ -0,0 +1,105 @@ +package cn.iocoder.yudao.module.im.dal.mysql.conversation; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link ImConversationReadMapper} 的单元测试 + * + * @author 芋道源码 + */ +public class ImConversationReadMapperTest extends BaseDbUnitTest { + + @Resource + private ImConversationReadMapper mapper; + + @Test + public void testUpdateReadMessageIdToLarger_onlyAdvancesWhenLarger() { + // 准备:用户 1 在群 10 的读位置 = 50 + ImConversationReadDO read = buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 50L); + mapper.insert(read); + + // 1. 新位置更大 → 前进 + int advanced = mapper.updateReadMessageIdToLarger(read.getId(), 100L, LocalDateTime.now()); + assertEquals(1, advanced); + assertEquals(100L, mapper.selectById(read.getId()).getMessageId()); + + // 2. 新位置相等 → 不更新 + int equalUpdate = mapper.updateReadMessageIdToLarger(read.getId(), 100L, LocalDateTime.now()); + assertEquals(0, equalUpdate); + + // 3. 新位置更小(乱序上报)→ 不回退 + int smaller = mapper.updateReadMessageIdToLarger(read.getId(), 80L, LocalDateTime.now()); + assertEquals(0, smaller); + assertEquals(100L, mapper.selectById(read.getId()).getMessageId()); + } + + @Test + public void testSelectByUserIdAndConversation() { + ImConversationReadDO read = buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 50L); + mapper.insert(read); + // 同用户不同会话类型,不应命中 + mapper.insert(buildRead(1L, ImConversationTypeEnum.PRIVATE.getType(), 10L, 99L)); + + ImConversationReadDO result = mapper.selectByUserIdAndConversation( + 1L, ImConversationTypeEnum.GROUP.getType(), 10L); + + assertNotNull(result); + assertEquals(50L, result.getMessageId()); + } + + @Test + public void testSelectListByConversation_aggregatesAllUsers() { + // 群 10 内三个用户的读位置,用于群回执人数聚合 + mapper.insert(buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L)); + mapper.insert(buildRead(2L, ImConversationTypeEnum.GROUP.getType(), 10L, 80L)); + mapper.insert(buildRead(3L, ImConversationTypeEnum.GROUP.getType(), 10L, 50L)); + // 别的群,不应混入 + mapper.insert(buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 20L, 999L)); + + List list = mapper.selectListByConversation( + ImConversationTypeEnum.GROUP.getType(), 10L); + Map positions = convertMap(list, + ImConversationReadDO::getUserId, ImConversationReadDO::getMessageId); + + assertEquals(3, positions.size()); + assertEquals(100L, positions.get(1L)); + assertEquals(80L, positions.get(2L)); + assertEquals(50L, positions.get(3L)); + } + + @Test + public void testSelectListByUserIdAndConversations_batch() { + // 用户 1 在频道 10 / 20 / 30 的读位置 + mapper.insert(buildRead(1L, ImConversationTypeEnum.CHANNEL.getType(), 10L, 5L)); + mapper.insert(buildRead(1L, ImConversationTypeEnum.CHANNEL.getType(), 20L, 8L)); + mapper.insert(buildRead(1L, ImConversationTypeEnum.CHANNEL.getType(), 30L, 3L)); + + List list = mapper.selectListByUserIdAndConversations( + 1L, ImConversationTypeEnum.CHANNEL.getType(), List.of(10L, 20L)); + + assertEquals(2, list.size()); + } + + private ImConversationReadDO buildRead(Long userId, Integer conversationType, Long conversationId, + Long readMessageId) { + return ImConversationReadDO.builder() + .userId(userId) + .conversationType(conversationType) + .targetId(conversationId) + .messageId(readMessageId) + .readTime(LocalDateTime.now()) + .build(); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapperTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapperTest.java new file mode 100644 index 0000000000..ef7fb54642 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapperTest.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.im.dal.mysql.friend; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link ImFriendRequestMapper} 的单元测试 + * + * @author 芋道源码 + */ +public class ImFriendRequestMapperTest extends BaseDbUnitTest { + + @Resource + private ImFriendRequestMapper mapper; + + // ========== selectMyList ========== + + @Test + public void testSelectMyList_bidirectional() { + // 准备:1 既作为 from 又作为 to;3 是无关用户 + mapper.insert(buildRequest(1L, 2L)); + mapper.insert(buildRequest(2L, 1L)); + mapper.insert(buildRequest(3L, 4L)); + + // 调用:cursor 为空,拉首页 + List list = mapper.selectMyList(1L, null, null, 10); + + // 断言:双向 OR 命中两条,无关用户被排除 + assertEquals(2, list.size()); + list.forEach(r -> assertTrue(r.getFromUserId().equals(1L) || r.getToUserId().equals(1L))); + } + + @Test + public void testSelectMyList_cursorPaging() { + // 准备:三条 1 相关的申请 + ImFriendRequestDO r1 = buildRequest(1L, 2L); + ImFriendRequestDO r2 = buildRequest(1L, 3L); + ImFriendRequestDO r3 = buildRequest(1L, 4L); + mapper.insert(r1); + mapper.insert(r2); + mapper.insert(r3); + + // 调用:cursor = r3,拉比 r3 更早的下一页 + ImFriendRequestDO cursor = mapper.selectById(r3.getId()); + List next = mapper.selectMyList(1L, cursor.getUpdateTime(), cursor.getId(), 10); + + // 断言:仅含 r1 / r2,按 id 倒序 + assertEquals(2, next.size()); + assertEquals(r2.getId(), next.get(0).getId()); + assertEquals(r1.getId(), next.get(1).getId()); + } + + @Test + public void testSelectMyList_limit() { + // 准备:插入 3 条 + for (int i = 0; i < 3; i++) { + mapper.insert(buildRequest(1L, (long) (10 + i))); + } + + // 调用:limit = 2 + List list = mapper.selectMyList(1L, null, null, 2); + + // 断言:手写 LIMIT 真生效 + assertEquals(2, list.size()); + } + + private static ImFriendRequestDO buildRequest(Long fromUserId, Long toUserId) { + return new ImFriendRequestDO() + .setFromUserId(fromUserId).setToUserId(toUserId) + .setHandleResult(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapperTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapperTest.java new file mode 100644 index 0000000000..5dcee93c19 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapperTest.java @@ -0,0 +1,195 @@ +package cn.iocoder.yudao.module.im.dal.mysql.message; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link ImGroupMessageMapper} 的单元测试 + * + * @author 芋道源码 + */ +public class ImGroupMessageMapperTest extends BaseDbUnitTest { + + @Resource + private ImGroupMessageMapper mapper; + + private static final LocalDateTime FAR_PAST = LocalDateTime.of(2000, 1, 1, 0, 0, 0); + + // ========== selectListByMinId ========== + + @Test + public void testSelectListByMinId_filterByReceiverSnapshot() { + // 准备:群 10 三条消息,快照分别命中 / 不命中用户 1 + ImGroupMessageDO visible = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); + mapper.insert(visible); + ImGroupMessageDO invisible = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(2L, 3L)); + mapper.insert(invisible); + ImGroupMessageDO selfSend = buildMessage(10L, 1L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); + mapper.insert(selfSend); + + // 调用:用户 1 拉取群 10 + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100); + + // 断言:仅返回快照包含用户 1 的消息 + assertEquals(2, result.size()); + assertEquals(visible.getId(), result.get(0).getId()); + assertEquals(selfSend.getId(), result.get(1).getId()); + } + + @Test + public void testSelectListByMinId_excludeNullAndEmptySnapshot() { + // 准备:快照为 null / 空字符串的老数据,不应被任何用户拉到 + ImGroupMessageDO nullSnapshot = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, null); + mapper.insert(nullSnapshot); + ImGroupMessageDO emptySnapshot = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of()); + mapper.insert(emptySnapshot); + + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100); + + assertTrue(result.isEmpty()); + } + + @Test + public void testSelectListByMinId_onlyReturnsGroupsInIdList() { + // 准备:群 10 + 群 20 各一条,用户 1 都在快照内 + ImGroupMessageDO msg10 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); + mapper.insert(msg10); + ImGroupMessageDO msg20 = buildMessage(20L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); + mapper.insert(msg20); + + // 调用:候选群只含群 10 + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100); + + assertEquals(1, result.size()); + assertEquals(msg10.getId(), result.get(0).getId()); + } + + @Test + public void testSelectListByMinId_includesRecall() { + // 准备:撤回消息也要返回,由客户端按 status 渲染「此消息已撤回」占位 + ImGroupMessageDO normal = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(normal); + ImGroupMessageDO recalled = buildMessage(10L, 2L, ImMessageStatusEnum.RECALL, List.of(1L)); + mapper.insert(recalled); + + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100); + + assertEquals(2, result.size()); + } + + @Test + public void testSelectListByMinId_sendTimeWindow() { + // 准备:一条在窗口内,一条在窗口外 + ImGroupMessageDO inWindow = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + inWindow.setSendTime(LocalDateTime.now().minusDays(1)); + mapper.insert(inWindow); + ImGroupMessageDO outWindow = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + outWindow.setSendTime(LocalDateTime.now().minusDays(40)); + mapper.insert(outWindow); + + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, + LocalDateTime.now().minusDays(30), 100); + + assertEquals(1, result.size()); + assertEquals(inWindow.getId(), result.get(0).getId()); + } + + @Test + public void testSelectListByMinId_sortAscLimit() { + // 准备:插入 3 条可见消息 + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(m1); + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(m2); + ImGroupMessageDO m3 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(m3); + + // 调用:size=2,返回 id 最小的 2 条 + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 2); + + assertEquals(2, result.size()); + assertTrue(result.get(0).getId() < result.get(1).getId()); + assertEquals(m1.getId(), result.get(0).getId()); + assertEquals(m2.getId(), result.get(1).getId()); + } + + @Test + public void testSelectListByMinId_minIdExclusive() { + // 准备:游标之前的消息不返回 + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(m1); + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(m2); + + List result = mapper.selectListByMinId(1L, List.of(10L), m1.getId(), FAR_PAST, 100); + + assertEquals(1, result.size()); + assertEquals(m2.getId(), result.get(0).getId()); + } + + // ========== selectHistoryListByUser ========== + + @Test + public void testSelectHistoryListByUser_filterByReceiverSnapshot() { + // 准备:群 10 三条,用户 1 在前两条快照内、第三条定向给别人 + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); + mapper.insert(m1); + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); + mapper.insert(m2); + ImGroupMessageDO directedOther = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(2L, 3L)); + mapper.insert(directedOther); + // 别的群 + ImGroupMessageDO other = buildMessage(20L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(other); + + List result = mapper.selectHistoryListByUser(1L, 10L, null, 100); + + // 断言:只返回群 10 中用户 1 可见的,按 id 倒序 + assertEquals(2, result.size()); + assertEquals(m2.getId(), result.get(0).getId()); + assertEquals(m1.getId(), result.get(1).getId()); + } + + @Test + public void testSelectHistoryListByUser_maxIdCursor() { + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(m1); + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(m2); + ImGroupMessageDO m3 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(m3); + + List result = mapper.selectHistoryListByUser(1L, 10L, m3.getId(), 100); + + // 断言:只返回 id < m3.id 的 m1、m2 + assertEquals(2, result.size()); + assertTrue(result.stream().allMatch(m -> m.getId() < m3.getId())); + } + + // ========== 工具方法 ========== + + private ImGroupMessageDO buildMessage(Long groupId, Long senderId, ImMessageStatusEnum status, + List receiverUserIds) { + return ImGroupMessageDO.builder() + .clientMessageId("uuid-" + System.nanoTime()) + .senderId(senderId) + .groupId(groupId) + .type(0) + .content("{\"content\":\"test\"}") + .status(status.getStatus()) + .sendTime(LocalDateTime.now()) + .receiverUserIds(receiverUserIds) + .receiptStatus(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus()) + .build(); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapperTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapperTest.java new file mode 100644 index 0000000000..f3b5ee3bef --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapperTest.java @@ -0,0 +1,196 @@ +package cn.iocoder.yudao.module.im.dal.mysql.message; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link ImPrivateMessageMapper} 的单元测试 + * + * @author 芋道源码 + */ +public class ImPrivateMessageMapperTest extends BaseDbUnitTest { + + @Resource + private ImPrivateMessageMapper mapper; + + private static final LocalDateTime FAR_PAST = LocalDateTime.of(2000, 1, 1, 0, 0, 0); + + // ========== selectListByMinId ========== + + @Test + public void testSelectListByMinId() { + // 准备:用户 1 发给用户 2 + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg1); + // 用户 2 发给用户 1 + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg2); + // 用户 3 发给用户 4(不相关) + ImPrivateMessageDO msg3 = buildMessage(3L, 4L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg3); + + // 调用:用户 1 从 id=0 拉取 + List result = mapper.selectListByMinId(1L, 0L, FAR_PAST, 100); + + // 断言:只包含与用户 1 相关的 2 条 + assertEquals(2, result.size()); + assertTrue(result.stream().allMatch(m -> + m.getSenderId().equals(1L) || m.getReceiverId().equals(1L))); + } + + @Test + public void testSelectListByMinId_withMinId() { + // 准备 + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg1); + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg2); + + // 调用:从 msg1.id 之后拉取 + List result = mapper.selectListByMinId(1L, msg1.getId(), FAR_PAST, 100); + + // 断言:只有 msg2 + assertEquals(1, result.size()); + assertEquals(msg2.getId(), result.get(0).getId()); + } + + @Test + public void testSelectListByMinId_limitSize() { + // 准备:插入 3 条 + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL)); + mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL)); + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL)); + + // 调用:limit 2 + List result = mapper.selectListByMinId(1L, 0L, FAR_PAST, 2); + + // 断言 + assertEquals(2, result.size()); + } + + @Test + public void testSelectListByMinId_sendTimeFilter() { + // 准备:一条落在窗口内,一条落在窗口外 + ImPrivateMessageDO newMsg = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); + newMsg.setSendTime(LocalDateTime.now().minusDays(1)); + mapper.insert(newMsg); + ImPrivateMessageDO oldMsg = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); + oldMsg.setSendTime(LocalDateTime.now().minusDays(40)); + mapper.insert(oldMsg); + + // 调用:窗口起点 = 30 天前 + List result = mapper.selectListByMinId(1L, 0L, + LocalDateTime.now().minusDays(30), 100); + + // 断言:只返回窗口内的消息 + assertEquals(1, result.size()); + assertEquals(newMsg.getId(), result.get(0).getId()); + } + + // ========== selectHistoryList ========== + + @Test + public void testSelectHistoryList_basic() { + // 准备:用户 1 <-> 用户 2 的消息 + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg1); + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg2); + // 用户 1 <-> 用户 3 的消息(不相关) + ImPrivateMessageDO msg3 = buildMessage(1L, 3L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg3); + + // 调用 + List result = mapper.selectHistoryList(1L, 2L, null, 100); + + // 断言:只有与用户 2 对话的 2 条,按 id 倒序 + assertEquals(2, result.size()); + assertTrue(result.get(0).getId() > result.get(1).getId()); + } + + @Test + public void testSelectHistoryList_includeRecall() { + // 准备 + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg1); + ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.RECALL); + mapper.insert(msg2); + + // 调用 + List result = mapper.selectHistoryList(1L, 2L, null, 100); + + // 断言:撤回消息一并返回 + assertEquals(2, result.size()); + assertEquals(msg2.getId(), result.get(0).getId()); + assertEquals(msg1.getId(), result.get(1).getId()); + } + + @Test + public void testSelectHistoryList_withMaxId() { + // 准备 + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg1); + ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg2); + ImPrivateMessageDO msg3 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); + mapper.insert(msg3); + + // 调用:从 msg3 往前拉 + List result = mapper.selectHistoryList(1L, 2L, msg3.getId(), 100); + + // 断言:只有 msg1 和 msg2 + assertEquals(2, result.size()); + assertTrue(result.stream().allMatch(m -> m.getId() < msg3.getId())); + } + + @Test + public void testSelectHistoryList_limit() { + // 准备 + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL)); + mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL)); + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL)); + + // 调用:limit 2 + List result = mapper.selectHistoryList(1L, 2L, null, 2); + + // 断言 + assertEquals(2, result.size()); + } + + @Test + public void testSelectHistoryList_bidirectional() { + // 准备:验证双向查询 + ImPrivateMessageDO msg1 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL); // 对方发的 + mapper.insert(msg1); + + // 调用:以用户 1 的视角查 + List result = mapper.selectHistoryList(1L, 2L, null, 100); + + // 断言:能查到对方发来的消息 + assertEquals(1, result.size()); + assertEquals(2L, result.get(0).getSenderId()); + } + + // ========== 工具方法 ========== + + private ImPrivateMessageDO buildMessage(Long senderId, Long receiverId, ImMessageStatusEnum status) { + return ImPrivateMessageDO.builder() + .clientMessageId("uuid-" + System.nanoTime()) + .senderId(senderId) + .receiverId(receiverId) + .type(0) + .content("{\"content\":\"test\"}") + .status(status.getStatus()) + .sendTime(LocalDateTime.now()) + .build(); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapperTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapperTest.java new file mode 100644 index 0000000000..7c500332ea --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapperTest.java @@ -0,0 +1,278 @@ +package cn.iocoder.yudao.module.im.dal.mysql.statistics; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; +import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; +import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; +import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; + +import javax.sql.DataSource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link ImStatisticsManagerMapper} 的单元测试 + * + * @author 芋道源码 + */ +public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { + + private static final LocalDateTime WINDOW_BEGIN = LocalDateTime.of(2026, 1, 1, 0, 0); + private static final LocalDateTime WINDOW_END = LocalDateTime.of(2026, 2, 1, 0, 0); + + @Resource + private ImStatisticsManagerMapper mapper; + + @Resource + private ImPrivateMessageMapper privateMessageMapper; + @Resource + private ImGroupMessageMapper groupMessageMapper; + @Resource + private ImGroupMapper groupMapper; + @Resource + private ImGroupMemberMapper groupMemberMapper; + @Resource + private DataSource dataSource; + + private JdbcTemplate jdbcTemplate; + + @BeforeEach + public void initJdbcTemplate() { + jdbcTemplate = new JdbcTemplate(dataSource); + } + + // ========== 用户 ========== + + @Test + public void testSelectTotalUserCount() { + // 准备:3 个未删除用户 + insertUser(1L, LocalDateTime.now()); + insertUser(2L, LocalDateTime.now()); + insertUser(3L, LocalDateTime.now()); + + // 调用 + 断言 + assertEquals(3L, mapper.selectTotalUserCount()); + } + + @Test + public void testSelectNewUserCount_windowFilter() { + // 准备:1 个在窗口内 + 2 个在窗口外 + insertUser(1L, WINDOW_BEGIN.plusDays(3)); + insertUser(2L, WINDOW_BEGIN.minusDays(1)); + insertUser(3L, WINDOW_END.plusDays(1)); + + // 调用 + 断言:只命中窗口内 + assertEquals(1L, mapper.selectNewUserCount(WINDOW_BEGIN, WINDOW_END)); + } + + @Test + public void testSelectActiveUserCount_distinctAcrossPrivateAndGroup() { + // 准备:1 私聊;2 群聊;3 在窗口外;1 既发私聊又发群聊(去重) + privateMessageMapper.insert(buildPrivate(1L, 2L, WINDOW_BEGIN.plusDays(1))); + privateMessageMapper.insert(buildPrivate(1L, 2L, WINDOW_BEGIN.plusDays(2))); + groupMessageMapper.insert(buildGroupMessage(2L, 100L, WINDOW_BEGIN.plusDays(3))); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(4))); // 用户 1 已计一次 + privateMessageMapper.insert(buildPrivate(3L, 4L, WINDOW_BEGIN.minusDays(1))); // 窗口外 + + // 调用 + 断言:去重后 = 2 + assertEquals(2L, mapper.selectActiveUserCount(WINDOW_BEGIN, WINDOW_END)); + } + + @Test + public void testSelectNewUserDailyCount_groupByDay() { + // 准备:第 1 天 2 个;第 2 天 1 个;窗口外 1 个 + insertUser(1L, WINDOW_BEGIN.plusDays(1)); + insertUser(2L, WINDOW_BEGIN.plusDays(1)); + insertUser(3L, WINDOW_BEGIN.plusDays(2)); + insertUser(4L, WINDOW_BEGIN.minusDays(1)); + + // 调用 + List> list = mapper.selectNewUserDailyCount(WINDOW_BEGIN, WINDOW_END); + + // 断言:返回两个分组、总数 = 3 + assertEquals(2, list.size()); + long sum = list.stream().mapToLong(m -> ((Number) m.get("count")).longValue()).sum(); + assertEquals(3L, sum); + } + + @Test + public void testSelectActiveUserDailyCount_distinctPerDay() { + // 准备:同一天里用户 1 在私聊与群聊都发了;第二天用户 2 发了 + privateMessageMapper.insert(buildPrivate(1L, 9L, WINDOW_BEGIN.plusDays(1))); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1))); + groupMessageMapper.insert(buildGroupMessage(2L, 100L, WINDOW_BEGIN.plusDays(2))); + + // 调用 + List> list = mapper.selectActiveUserDailyCount(WINDOW_BEGIN, WINDOW_END); + + // 断言:两天合计 2 名活跃用户(同一天 user 1 去重 + 第二天 user 2) + assertEquals(2, list.size()); + long sum = list.stream().mapToLong(m -> ((Number) m.get("count")).longValue()).sum(); + assertEquals(2L, sum); + } + + // ========== 群 ========== + + @Test + public void testSelectTotalGroupCount_onlyEnabled() { + // 准备:2 个正常 + 1 个已解散 + groupMapper.insert(buildGroup(CommonStatusEnum.ENABLE)); + groupMapper.insert(buildGroup(CommonStatusEnum.ENABLE)); + groupMapper.insert(buildGroup(CommonStatusEnum.DISABLE)); + + // 调用 + 断言 + assertEquals(2L, mapper.selectTotalGroupCount()); + } + + @Test + public void testSelectNewGroupCount_windowFilter() { + // 准备:1 个窗口内 + 1 个窗口外 + groupMapper.insert(buildGroup(CommonStatusEnum.ENABLE)); + groupMapper.insert(buildGroup(CommonStatusEnum.ENABLE)); + // 窗口窄到只覆盖 1 秒 + LocalDateTime narrowBegin = LocalDateTime.now().plusYears(10); + LocalDateTime narrowEnd = narrowBegin.plusSeconds(1); + + // 调用 + 断言:极窄窗口不命中任何群 + assertEquals(0L, mapper.selectNewGroupCount(narrowBegin, narrowEnd)); + } + + @Test + public void testSelectGroupSizeDistribution_bucketing() { + // 准备:一个 5 人群、一个 15 人群 + Long groupSmall = insertGroupAndMembers(5); + Long groupMid = insertGroupAndMembers(15); + assertNotNull(groupSmall); + assertNotNull(groupMid); + + // 调用 + List> dist = mapper.selectGroupSizeDistribution(); + + // 断言:分桶包含「1-9 人」和「10-49 人」 + Map byRange = dist.stream().collect(java.util.stream.Collectors.toMap( + m -> m.get("range"), m -> ((Number) m.get("count")).longValue())); + assertEquals(1L, byRange.get("1-9 人")); + assertEquals(1L, byRange.get("10-49 人")); + } + + // ========== 消息 ========== + + @Test + public void testSelectPrivateMessageCount_windowFilter() { + // 准备:1 在窗口内 + 1 在窗口外 + privateMessageMapper.insert(buildPrivate(1L, 2L, WINDOW_BEGIN.plusDays(1))); + privateMessageMapper.insert(buildPrivate(1L, 2L, WINDOW_BEGIN.minusDays(1))); + + // 调用 + 断言 + assertEquals(1L, mapper.selectPrivateMessageCount(WINDOW_BEGIN, WINDOW_END)); + } + + @Test + public void testSelectGroupMessageCount_windowFilter() { + // 准备:1 在窗口内 + 1 在窗口外 + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1))); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.minusDays(1))); + + // 调用 + 断言 + assertEquals(1L, mapper.selectGroupMessageCount(WINDOW_BEGIN, WINDOW_END)); + } + + @Test + public void testSelectMessageTypeDistribution_mergePrivateAndGroup() { + // 准备:私聊 type=0 ×1,群聊 type=0 ×2、type=1 ×1 + privateMessageMapper.insert(buildPrivate(1L, 2L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.TEXT.getType())); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.TEXT.getType())); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.TEXT.getType())); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.IMAGE.getType())); + + // 调用 + List> dist = mapper.selectMessageTypeDistribution(WINDOW_BEGIN, WINDOW_END); + + // 断言:type=TEXT(0) → 3;type=IMAGE(1) → 1 + Map byType = dist.stream().collect(java.util.stream.Collectors.toMap( + m -> ((Number) m.get("type")).intValue(), m -> ((Number) m.get("count")).longValue())); + assertEquals(3L, byType.get(ImContentTypeEnum.TEXT.getType())); + assertEquals(1L, byType.get(ImContentTypeEnum.IMAGE.getType())); + } + + @Test + public void testSelectTopSenders_orderByCountDescAndLimit() { + // 准备:user 1 共 3 条;user 2 共 2 条;user 3 共 1 条 + privateMessageMapper.insert(buildPrivate(1L, 9L, WINDOW_BEGIN.plusDays(1))); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1))); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1))); + privateMessageMapper.insert(buildPrivate(2L, 9L, WINDOW_BEGIN.plusDays(1))); + groupMessageMapper.insert(buildGroupMessage(2L, 100L, WINDOW_BEGIN.plusDays(1))); + privateMessageMapper.insert(buildPrivate(3L, 9L, WINDOW_BEGIN.plusDays(1))); + + // 调用:取 TOP 2 + List> tops = mapper.selectTopSenders(WINDOW_BEGIN, WINDOW_END, 2); + + // 断言:返回 2 条,按消息数倒序,user1 > user2 + assertEquals(2, tops.size()); + assertEquals(1L, ((Number) tops.get(0).get("userId")).longValue()); + assertEquals(3L, ((Number) tops.get(0).get("messageCount")).longValue()); + assertEquals(2L, ((Number) tops.get(1).get("userId")).longValue()); + assertEquals(2L, ((Number) tops.get(1).get("messageCount")).longValue()); + } + + // ========== 工具方法 ========== + + private void insertUser(Long id, LocalDateTime createTime) { + jdbcTemplate.update( + "INSERT INTO system_users (id, username, password, nickname, status, create_time, update_time, deleted, tenant_id) " + + "VALUES (?, ?, '', ?, 0, ?, ?, FALSE, 0)", + id, "u" + id, "n" + id, createTime, createTime); + } + + private static ImPrivateMessageDO buildPrivate(Long senderId, Long receiverId, LocalDateTime sendTime) { + return ImPrivateMessageDO.builder() + .clientMessageId("uuid-" + System.nanoTime()) + .senderId(senderId).receiverId(receiverId) + .type(ImContentTypeEnum.TEXT.getType()) + .content("{}") + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .sendTime(sendTime).build(); + } + + private static ImGroupMessageDO buildGroupMessage(Long senderId, Long groupId, LocalDateTime sendTime) { + return new ImGroupMessageDO() + .setClientMessageId("uuid-" + System.nanoTime()) + .setSenderId(senderId).setGroupId(groupId) + .setType(ImContentTypeEnum.TEXT.getType()) + .setContent("{}") + .setStatus(ImMessageStatusEnum.NORMAL.getStatus()) + .setSendTime(sendTime); + } + + private static ImGroupDO buildGroup(CommonStatusEnum status) { + return ImGroupDO.builder().name("g" + System.nanoTime()).ownerUserId(1L) + .status(status.getStatus()).build(); + } + + private Long insertGroupAndMembers(int memberCount) { + ImGroupDO group = buildGroup(CommonStatusEnum.ENABLE); + groupMapper.insert(group); + for (int i = 0; i < memberCount; i++) { + groupMemberMapper.insert(new ImGroupMemberDO() + .setGroupId(group.getId()).setUserId((long) (1000 + i)) + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setRole(3)); + } + return group.getId(); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImplTest.java new file mode 100644 index 0000000000..b14f4eacd5 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImplTest.java @@ -0,0 +1,177 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.item.ImFacePackItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackItemDO; +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFacePackItemMapper; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_ITEM_NOT_EXISTS; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link ImFacePackItemServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImFacePackItemServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImFacePackItemServiceImpl service; + + @Mock + private ImFacePackItemMapper facePackItemMapper; + @Mock + private ImFacePackService facePackService; + + // ========== getEnabledItemListByPackIds ========== + + @Test + public void testGetEnabledItemListByPackIds_emptyShortReturn() { + // 调用 + 断言:空入参直接返回空列表,不查 DB + assertTrue(service.getEnabledItemListByPackIds(Collections.emptyList()).isEmpty()); + verify(facePackItemMapper, never()).selectListByPackIdsAndStatus(any(), any()); + } + + @Test + public void testGetEnabledItemListByPackIds_passesEnableStatus() { + // 准备 + List packIds = Arrays.asList(1L, 2L); + ImFacePackItemDO item = new ImFacePackItemDO(); + when(facePackItemMapper.selectListByPackIdsAndStatus(packIds, CommonStatusEnum.ENABLE.getStatus())) + .thenReturn(Collections.singletonList(item)); + + // 调用 + 断言 + assertEquals(1, service.getEnabledItemListByPackIds(packIds).size()); + } + + // ========== createFacePackItem ========== + + @Test + public void testCreateFacePackItem_success() { + // 准备 + ImFacePackItemSaveReqVO reqVO = new ImFacePackItemSaveReqVO(); + reqVO.setPackId(10L).setUrl("a.png").setName("dog").setWidth(100).setHeight(100) + .setSort(0).setStatus(CommonStatusEnum.ENABLE.getStatus()); + when(facePackService.validateFacePackExists(10L)).thenReturn(new ImFacePackDO()); + + // 调用 + service.createFacePackItem(reqVO); + + // 断言 + ArgumentCaptor captor = ArgumentCaptor.forClass(ImFacePackItemDO.class); + verify(facePackItemMapper).insert(captor.capture()); + assertEquals(10L, captor.getValue().getPackId()); + assertEquals("a.png", captor.getValue().getUrl()); + } + + @Test + public void testCreateFacePackItem_packNotExists() { + // 准备:所属表情包不存在 + ImFacePackItemSaveReqVO reqVO = new ImFacePackItemSaveReqVO(); + reqVO.setPackId(99L).setUrl("a.png").setWidth(100).setHeight(100).setSort(0) + .setStatus(CommonStatusEnum.ENABLE.getStatus()); + when(facePackService.validateFacePackExists(99L)) + .thenThrow(new ServiceException(FACE_PACK_NOT_EXISTS.getCode(), FACE_PACK_NOT_EXISTS.getMsg())); + + // 调用 + 断言:不落库 + assertThrows(ServiceException.class, () -> service.createFacePackItem(reqVO)); + verify(facePackItemMapper, never()).insert(any(ImFacePackItemDO.class)); + } + + // ========== updateFacePackItem ========== + + @Test + public void testUpdateFacePackItem_success() { + // 准备 + ImFacePackItemSaveReqVO reqVO = new ImFacePackItemSaveReqVO(); + reqVO.setId(1L).setPackId(10L).setUrl("a.png").setWidth(100).setHeight(100) + .setSort(0).setStatus(CommonStatusEnum.ENABLE.getStatus()); + when(facePackItemMapper.selectById(1L)).thenReturn(new ImFacePackItemDO()); + when(facePackService.validateFacePackExists(10L)).thenReturn(new ImFacePackDO()); + + // 调用 + service.updateFacePackItem(reqVO); + + // 断言 + verify(facePackItemMapper).updateById(any(ImFacePackItemDO.class)); + } + + @Test + public void testUpdateFacePackItem_itemNotExists() { + ImFacePackItemSaveReqVO reqVO = new ImFacePackItemSaveReqVO(); + reqVO.setId(99L).setPackId(10L).setUrl("a.png").setWidth(100).setHeight(100).setSort(0) + .setStatus(CommonStatusEnum.ENABLE.getStatus()); + when(facePackItemMapper.selectById(99L)).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.updateFacePackItem(reqVO)); + assertEquals(FACE_PACK_ITEM_NOT_EXISTS.getCode(), exception.getCode()); + verify(facePackItemMapper, never()).updateById(any(ImFacePackItemDO.class)); + } + + // ========== deleteFacePackItem ========== + + @Test + public void testDeleteFacePackItem_success() { + when(facePackItemMapper.selectById(1L)).thenReturn(new ImFacePackItemDO()); + + service.deleteFacePackItem(1L); + + verify(facePackItemMapper).deleteById(1L); + } + + @Test + public void testDeleteFacePackItem_notExists() { + when(facePackItemMapper.selectById(99L)).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.deleteFacePackItem(99L)); + assertEquals(FACE_PACK_ITEM_NOT_EXISTS.getCode(), exception.getCode()); + verify(facePackItemMapper, never()).deleteById(anyLong()); + } + + @Test + public void testDeleteFacePackItemList_emptySkip() { + service.deleteFacePackItemList(Collections.emptyList()); + + verify(facePackItemMapper, never()).deleteByIds(any()); + } + + @Test + public void testDeleteFacePackItemList_success() { + List ids = Arrays.asList(1L, 2L); + when(facePackItemMapper.deleteByIds(ids)).thenReturn(2); + + service.deleteFacePackItemList(ids); + + verify(facePackItemMapper).deleteByIds(ids); + } + + @Test + public void testDeleteFacePackItemList_ignoreMissingIds() { + List ids = Arrays.asList(1L, 2L); + when(facePackItemMapper.deleteByIds(ids)).thenReturn(1); + + service.deleteFacePackItemList(ids); + + verify(facePackItemMapper).deleteByIds(ids); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImplTest.java new file mode 100644 index 0000000000..1b7c8ab7e3 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImplTest.java @@ -0,0 +1,177 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.manager.face.vo.pack.ImFacePackSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFacePackDO; +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFacePackMapper; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.Arrays; +import java.util.Collections; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_HAS_ITEMS; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_PACK_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link ImFacePackServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImFacePackServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImFacePackServiceImpl service; + + @Mock + private ImFacePackMapper facePackMapper; + @Mock + private ImFacePackItemService facePackItemService; + + // ========== validateFacePackExists ========== + + @Test + public void testValidateFacePackExists_notFound() { + when(facePackMapper.selectById(1L)).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.validateFacePackExists(1L)); + assertEquals(FACE_PACK_NOT_EXISTS.getCode(), exception.getCode()); + } + + @Test + public void testValidateFacePackExists_returnsDo() { + ImFacePackDO pack = ImFacePackDO.builder().id(1L).name("猫主子").build(); + when(facePackMapper.selectById(1L)).thenReturn(pack); + + assertEquals(pack, service.validateFacePackExists(1L)); + } + + // ========== createFacePack ========== + + @Test + public void testCreateFacePack_insert() { + // 准备 + ImFacePackSaveReqVO reqVO = new ImFacePackSaveReqVO(); + reqVO.setName("猫主子").setIcon("icon.png").setSort(0).setStatus(CommonStatusEnum.ENABLE.getStatus()); + + // 调用 + service.createFacePack(reqVO); + + // 断言:mapper.insert 被调用一次 + ArgumentCaptor captor = ArgumentCaptor.forClass(ImFacePackDO.class); + verify(facePackMapper).insert(captor.capture()); + assertEquals("猫主子", captor.getValue().getName()); + assertEquals(CommonStatusEnum.ENABLE.getStatus(), captor.getValue().getStatus()); + } + + // ========== updateFacePack ========== + + @Test + public void testUpdateFacePack_success() { + // 准备:存在 + ImFacePackSaveReqVO reqVO = new ImFacePackSaveReqVO(); + reqVO.setId(1L).setName("新名").setSort(1).setStatus(CommonStatusEnum.ENABLE.getStatus()); + when(facePackMapper.selectById(1L)).thenReturn(ImFacePackDO.builder().id(1L).build()); + + // 调用 + service.updateFacePack(reqVO); + + // 断言 + ArgumentCaptor captor = ArgumentCaptor.forClass(ImFacePackDO.class); + verify(facePackMapper).updateById(captor.capture()); + assertEquals(1L, captor.getValue().getId()); + assertEquals("新名", captor.getValue().getName()); + } + + @Test + public void testUpdateFacePack_notExists() { + // 准备 + ImFacePackSaveReqVO reqVO = new ImFacePackSaveReqVO(); + reqVO.setId(99L).setName("x").setSort(0).setStatus(CommonStatusEnum.ENABLE.getStatus()); + when(facePackMapper.selectById(99L)).thenReturn(null); + + // 调用 + 断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> service.updateFacePack(reqVO)); + assertEquals(FACE_PACK_NOT_EXISTS.getCode(), exception.getCode()); + verify(facePackMapper, never()).updateById(any(ImFacePackDO.class)); + } + + // ========== deleteFacePack ========== + + @Test + public void testDeleteFacePack_success() { + when(facePackMapper.selectById(1L)).thenReturn(ImFacePackDO.builder().id(1L).build()); + when(facePackItemService.getFacePackItemCount(1L)).thenReturn(0L); + + service.deleteFacePack(1L); + + verify(facePackMapper).deleteById(1L); + } + + @Test + public void testDeleteFacePack_hasItems() { + // 准备:包下仍有表情 + when(facePackMapper.selectById(1L)).thenReturn(ImFacePackDO.builder().id(1L).build()); + when(facePackItemService.getFacePackItemCount(1L)).thenReturn(3L); + + ServiceException exception = assertThrows(ServiceException.class, () -> service.deleteFacePack(1L)); + assertEquals(FACE_PACK_HAS_ITEMS.getCode(), exception.getCode()); + verify(facePackMapper, never()).deleteById(anyLong()); + } + + @Test + public void testDeleteFacePack_notExists() { + when(facePackMapper.selectById(99L)).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, () -> service.deleteFacePack(99L)); + assertEquals(FACE_PACK_NOT_EXISTS.getCode(), exception.getCode()); + } + + // ========== deleteFacePackList ========== + + @Test + public void testDeleteFacePackList_emptySkip() { + // 调用:空 ids 列表直接返回,不查询、不删除 + service.deleteFacePackList(Collections.emptyList()); + + verify(facePackItemService, never()).getFacePackItemCount(any(java.util.Collection.class)); + verify(facePackMapper, never()).deleteByIds(any()); + } + + @Test + public void testDeleteFacePackList_anyHasItemsRejectAll() { + // 准备:批量中存在表情 + when(facePackItemService.getFacePackItemCount(Arrays.asList(1L, 2L))).thenReturn(1L); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.deleteFacePackList(Arrays.asList(1L, 2L))); + assertEquals(FACE_PACK_HAS_ITEMS.getCode(), exception.getCode()); + verify(facePackMapper, never()).deleteByIds(any()); + } + + @Test + public void testDeleteFacePackList_success() { + // 准备 + when(facePackItemService.getFacePackItemCount(Arrays.asList(1L, 2L))).thenReturn(0L); + + // 调用 + service.deleteFacePackList(Arrays.asList(1L, 2L)); + + // 断言 + verify(facePackMapper).deleteByIds(Arrays.asList(1L, 2L)); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImplTest.java new file mode 100644 index 0000000000..94ca14258e --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImplTest.java @@ -0,0 +1,164 @@ +package cn.iocoder.yudao.module.im.service.face; + +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.face.vo.useritem.ImFaceUserItemSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.face.ImFaceUserItemDO; +import cn.iocoder.yudao.module.im.dal.mysql.face.ImFaceUserItemMapper; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.dao.DuplicateKeyException; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_DUPLICATED; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_MAX_LIMIT; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_NOT_EXISTS; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FACE_USER_ITEM_NOT_OWN; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link ImFaceUserItemServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImFaceUserItemServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImFaceUserItemServiceImpl service; + + @Mock + private ImFaceUserItemMapper faceUserItemMapper; + @Spy + private ImProperties imProperties = new ImProperties(); + + // ========== createFaceUserItem ========== + + @Test + public void testCreateFaceUserItem_success() { + // 准备 + ImFaceUserItemSaveReqVO reqVO = new ImFaceUserItemSaveReqVO(); + reqVO.setUrl("a.png").setName("doge").setWidth(100).setHeight(100); + when(faceUserItemMapper.selectByUserIdAndUrl(1L, "a.png")).thenReturn(null); + when(faceUserItemMapper.selectCountByUserId(1L)).thenReturn(10L); + + // 调用 + service.createFaceUserItem(1L, reqVO); + + // 断言:写入用户编号 + ArgumentCaptor captor = ArgumentCaptor.forClass(ImFaceUserItemDO.class); + verify(faceUserItemMapper).insert(captor.capture()); + assertEquals(1L, captor.getValue().getUserId()); + assertEquals("a.png", captor.getValue().getUrl()); + } + + @Test + public void testCreateFaceUserItem_duplicateUrl() { + // 准备:相同 (userId, url) 已存在 + ImFaceUserItemSaveReqVO reqVO = new ImFaceUserItemSaveReqVO(); + reqVO.setUrl("a.png").setWidth(100).setHeight(100); + when(faceUserItemMapper.selectByUserIdAndUrl(1L, "a.png")) + .thenReturn(new ImFaceUserItemDO().setUserId(1L)); + + // 调用 + 断言:抛重复异常,不落库 + ServiceException exception = assertThrows(ServiceException.class, + () -> service.createFaceUserItem(1L, reqVO)); + assertEquals(FACE_USER_ITEM_DUPLICATED.getCode(), exception.getCode()); + verify(faceUserItemMapper, never()).insert(any(ImFaceUserItemDO.class)); + } + + @Test + public void testCreateFaceUserItem_maxLimit() { + // 准备:个人表情已达上限 + ImFaceUserItemSaveReqVO reqVO = new ImFaceUserItemSaveReqVO(); + reqVO.setUrl("a.png").setWidth(100).setHeight(100); + imProperties.getFace().setUserItemMaxCount(20); + when(faceUserItemMapper.selectByUserIdAndUrl(1L, "a.png")).thenReturn(null); + when(faceUserItemMapper.selectCountByUserId(1L)).thenReturn(20L); + + // 调用 + 断言:抛上限异常,不落库 + ServiceException exception = assertThrows(ServiceException.class, + () -> service.createFaceUserItem(1L, reqVO)); + assertEquals(FACE_USER_ITEM_MAX_LIMIT.getCode(), exception.getCode()); + verify(faceUserItemMapper, never()).insert(any(ImFaceUserItemDO.class)); + } + + @Test + public void testCreateFaceUserItem_duplicateKey() { + // 准备:并发插入触发唯一约束 + ImFaceUserItemSaveReqVO reqVO = new ImFaceUserItemSaveReqVO(); + reqVO.setUrl("a.png").setWidth(100).setHeight(100); + when(faceUserItemMapper.selectByUserIdAndUrl(1L, "a.png")).thenReturn(null); + when(faceUserItemMapper.selectCountByUserId(1L)).thenReturn(10L); + when(faceUserItemMapper.insert(any(ImFaceUserItemDO.class))).thenThrow(new DuplicateKeyException("duplicate")); + + // 调用 + 断言:数据库唯一约束冲突转业务重复异常 + ServiceException exception = assertThrows(ServiceException.class, + () -> service.createFaceUserItem(1L, reqVO)); + assertEquals(FACE_USER_ITEM_DUPLICATED.getCode(), exception.getCode()); + } + + // ========== deleteFaceUserItem(用户端) ========== + + @Test + public void testDeleteFaceUserItem_userOwn_success() { + // 准备:归属当前用户 + when(faceUserItemMapper.selectById(10L)).thenReturn(new ImFaceUserItemDO().setUserId(1L)); + + // 调用 + service.deleteFaceUserItem(1L, 10L); + + // 断言 + verify(faceUserItemMapper).deleteById(10L); + } + + @Test + public void testDeleteFaceUserItem_notExists() { + when(faceUserItemMapper.selectById(99L)).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.deleteFaceUserItem(1L, 99L)); + assertEquals(FACE_USER_ITEM_NOT_EXISTS.getCode(), exception.getCode()); + verify(faceUserItemMapper, never()).deleteById(anyLong()); + } + + @Test + public void testDeleteFaceUserItem_notOwn() { + // 准备:表情归属 user 2,但 user 1 来删 + when(faceUserItemMapper.selectById(10L)).thenReturn(new ImFaceUserItemDO().setUserId(2L)); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.deleteFaceUserItem(1L, 10L)); + assertEquals(FACE_USER_ITEM_NOT_OWN.getCode(), exception.getCode()); + verify(faceUserItemMapper, never()).deleteById(anyLong()); + } + + // ========== deleteFaceUserItem(管理后台) ========== + + @Test + public void testDeleteFaceUserItemAdmin_success() { + when(faceUserItemMapper.selectById(10L)).thenReturn(new ImFaceUserItemDO().setUserId(2L)); + + service.deleteFaceUserItem(10L); + + verify(faceUserItemMapper).deleteById(10L); + } + + @Test + public void testDeleteFaceUserItemAdmin_notExists() { + when(faceUserItemMapper.selectById(99L)).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.deleteFaceUserItem(99L)); + assertEquals(FACE_USER_ITEM_NOT_EXISTS.getCode(), exception.getCode()); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java new file mode 100644 index 0000000000..2697e58bf7 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java @@ -0,0 +1,355 @@ +package cn.iocoder.yudao.module.im.service.friend; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.request.ImFriendRequestApplyReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; +import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendRequestMapper; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; +import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.dao.DuplicateKeyException; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link ImFriendRequestServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImFriendRequestServiceImpl friendRequestService; + + @Mock + private ImFriendRequestMapper friendRequestMapper; + @Mock + private ImFriendService friendService; + @Mock + private ImWebSocketService websocketService; + @Mock + private ImProperties imProperties; + @Mock + private AdminUserApi adminUserApi; + + // ========== applyFriend ========== + + @Test + public void testApplyFriend_addSelf() { + // 准备:发起人 = 接收人 + ImFriendRequestApplyReqVO reqVO = new ImFriendRequestApplyReqVO(); + reqVO.setToUserId(1L); + + // 调用 + 断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> friendRequestService.applyFriend(1L, reqVO)); + assertEquals(FRIEND_ADD_SELF.getCode(), exception.getCode()); + } + + @Test + public void testApplyFriend_alreadyFriend() { + // 准备 + ImFriendRequestApplyReqVO reqVO = new ImFriendRequestApplyReqVO(); + reqVO.setToUserId(2L); + when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.FRIEND.getState()); + + // 调用 + 断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> friendRequestService.applyFriend(1L, reqVO)); + assertEquals(FRIEND_REQUEST_ALREADY_FRIEND.getCode(), exception.getCode()); + } + + @Test + public void testApplyFriend_blockedByPeer() { + // 准备 + ImFriendRequestApplyReqVO reqVO = new ImFriendRequestApplyReqVO(); + reqVO.setToUserId(2L); + when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.BLOCKED.getState()); + + // 调用 + 断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> friendRequestService.applyFriend(1L, reqVO)); + assertEquals(FRIEND_REQUEST_BLOCKED_BY_PEER.getCode(), exception.getCode()); + } + + @Test + public void testApplyFriend_silentReAdd() { + // 准备:单向好友 — 我已删除,但对方仍把我当好友 + ImFriendRequestApplyReqVO reqVO = new ImFriendRequestApplyReqVO(); + reqVO.setToUserId(2L).setDisplayName("老张").setAddSource(1); + when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.NONE.getState()); + ImFriendDO peerFriend = ImFriendDO.builder().userId(2L).friendUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(friendService.getFriend(2L, 1L)).thenReturn(peerFriend); + + // 调用 + ImFriendRequestDO result = friendRequestService.applyFriend(1L, reqVO); + + // 断言:走静默重新加好友,不落申请记录 + assertNull(result); + verify(friendService).silentReAddFriend(eq(1L), eq(2L), eq("老张"), eq(1)); + verify(friendRequestMapper, never()).insert(any(ImFriendRequestDO.class)); + verify(websocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + @Test + public void testApplyFriend_blockedByPeerWhenMyselfDeleted() { + // 准备:我侧已删除(getFriendState=NONE),对方仍把我当好友但已拉黑 + ImFriendRequestApplyReqVO reqVO = new ImFriendRequestApplyReqVO(); + reqVO.setToUserId(2L).setDisplayName("老张").setAddSource(1); + when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.NONE.getState()); + ImFriendDO peerFriend = ImFriendDO.builder().userId(2L).friendUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).blocked(true).build(); + when(friendService.getFriend(2L, 1L)).thenReturn(peerFriend); + + // 调用 + 断言:必须拒掉,不能走 silentReAddFriend 绕过拉黑 + ServiceException exception = assertThrows(ServiceException.class, + () -> friendRequestService.applyFriend(1L, reqVO)); + assertEquals(FRIEND_REQUEST_BLOCKED_BY_PEER.getCode(), exception.getCode()); + verify(friendService, never()).silentReAddFriend(anyLong(), anyLong(), anyString(), anyInt()); + verify(friendRequestMapper, never()).insert(any(ImFriendRequestDO.class)); + } + + @Test + public void testApplyFriend_insertNew() { + // 准备:双方都无关系,且无历史申请 + ImFriendRequestApplyReqVO reqVO = new ImFriendRequestApplyReqVO(); + reqVO.setToUserId(2L).setApplyContent("加个好友").setDisplayName("老张").setAddSource(1); + when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.NONE.getState()); + when(friendService.getFriend(2L, 1L)).thenReturn(null); + when(friendRequestMapper.selectByFromUserIdAndToUserId(1L, 2L)).thenReturn(null); + when(adminUserApi.getUser(1L)).thenReturn(new AdminUserRespDTO().setNickname("张三").setAvatar("a.png")); + when(imProperties.getFriend()).thenReturn(new ImProperties.Friend()); + + // 调用 + ImFriendRequestDO result = friendRequestService.applyFriend(1L, reqVO); + + // 断言:落新申请记录 + ArgumentCaptor captor = ArgumentCaptor.forClass(ImFriendRequestDO.class); + verify(friendRequestMapper).insert(captor.capture()); + ImFriendRequestDO saved = captor.getValue(); + assertEquals(1L, saved.getFromUserId()); + assertEquals(2L, saved.getToUserId()); + assertEquals("加个好友", saved.getApplyContent()); + assertEquals(ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), saved.getHandleResult()); + assertSame(saved, result); + // 断言:推送给接收方 + verify(websocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any()); + } + + @Test + public void testApplyFriend_reuseOldRequest() { + // 准备:双方都无关系,但存在历史申请 — 走 reset 复用 + ImFriendRequestApplyReqVO reqVO = new ImFriendRequestApplyReqVO(); + reqVO.setToUserId(2L).setApplyContent("再来一次").setDisplayName("老张").setAddSource(2); + when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.NONE.getState()); + when(friendService.getFriend(2L, 1L)).thenReturn(null); + ImFriendRequestDO old = new ImFriendRequestDO().setId(100L).setFromUserId(1L).setToUserId(2L) + .setHandleResult(ImFriendRequestHandleResultEnum.REFUSED.getResult()) + .setHandleContent("旧拒绝").setApplyContent("旧内容"); + when(friendRequestMapper.selectByFromUserIdAndToUserId(1L, 2L)).thenReturn(old); + when(adminUserApi.getUser(1L)).thenReturn(null); + when(imProperties.getFriend()).thenReturn(new ImProperties.Friend()); + + // 调用 + ImFriendRequestDO result = friendRequestService.applyFriend(1L, reqVO); + + // 断言:复用旧记录,未触发 insert + verify(friendRequestMapper).updateByIdReset(eq(100L), eq("再来一次"), eq("老张"), eq(2), + any(java.time.LocalDateTime.class)); + verify(friendRequestMapper, never()).insert(any(ImFriendRequestDO.class)); + assertEquals(100L, result.getId()); + assertEquals("再来一次", result.getApplyContent()); + assertEquals(ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), result.getHandleResult()); + assertNull(result.getHandleContent()); + assertNull(result.getHandleTime()); + } + + @Test + public void testApplyFriend_insertDuplicateKey_reuseOldRequest() { + // 准备:首次查询不存在,插入时命中唯一键,回查到并发写入的旧申请 + ImFriendRequestApplyReqVO reqVO = new ImFriendRequestApplyReqVO(); + reqVO.setToUserId(2L).setApplyContent("并发申请").setDisplayName("老张").setAddSource(2); + when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.NONE.getState()); + when(friendService.getFriend(2L, 1L)).thenReturn(null); + ImFriendRequestDO old = new ImFriendRequestDO().setId(100L).setFromUserId(1L).setToUserId(2L) + .setHandleResult(ImFriendRequestHandleResultEnum.REFUSED.getResult()); + when(friendRequestMapper.selectByFromUserIdAndToUserId(1L, 2L)).thenReturn(null, old); + when(friendRequestMapper.insert(any(ImFriendRequestDO.class))).thenThrow(new DuplicateKeyException("dup")); + when(adminUserApi.getUser(1L)).thenReturn(null); + when(imProperties.getFriend()).thenReturn(new ImProperties.Friend()); + + // 调用 + ImFriendRequestDO result = friendRequestService.applyFriend(1L, reqVO); + + // 断言:复用并重置旧申请,不向上抛数据库异常 + verify(friendRequestMapper).updateByIdReset(eq(100L), eq("并发申请"), eq("老张"), eq(2), + any(LocalDateTime.class)); + assertEquals(100L, result.getId()); + assertEquals(ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), result.getHandleResult()); + verify(websocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any()); + } + + // ========== agreeFriendRequest ========== + + @Test + public void testAgreeFriendRequest_success() { + // 准备 + ImFriendRequestDO request = new ImFriendRequestDO().setId(100L).setFromUserId(1L).setToUserId(2L) + .setHandleResult(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()); + when(friendRequestMapper.selectById(100L)).thenReturn(request); + when(friendRequestMapper.updateByIdAndHandleResult(eq(100L), + eq(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()), any(ImFriendRequestDO.class))).thenReturn(1); + + // 调用 + friendRequestService.agreeFriendRequest(2L, 100L); + + // 断言:双向建立好友 + 推 APPROVED 给发起方 + verify(adminUserApi, never()).validateUserList(any()); + verify(friendService).becomeFriends(request); + verify(websocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + } + + @Test + public void testAgreeFriendRequest_notExists() { + // 准备:申请不存在 + when(friendRequestMapper.selectById(100L)).thenReturn(null); + + // 调用 + 断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> friendRequestService.agreeFriendRequest(2L, 100L)); + assertEquals(FRIEND_REQUEST_NOT_EXISTS.getCode(), exception.getCode()); + } + + @Test + public void testAgreeFriendRequest_notToMe() { + // 准备:操作人不是接收方 + ImFriendRequestDO request = new ImFriendRequestDO().setId(100L).setFromUserId(1L).setToUserId(2L) + .setHandleResult(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()); + when(friendRequestMapper.selectById(100L)).thenReturn(request); + + // 调用 + 断言:3L 不是接收方 2L + ServiceException exception = assertThrows(ServiceException.class, + () -> friendRequestService.agreeFriendRequest(3L, 100L)); + assertEquals(FRIEND_REQUEST_NOT_TO_ME.getCode(), exception.getCode()); + } + + @Test + public void testAgreeFriendRequest_alreadyHandled() { + // 准备:申请已被处理 + ImFriendRequestDO request = new ImFriendRequestDO().setId(100L).setFromUserId(1L).setToUserId(2L) + .setHandleResult(ImFriendRequestHandleResultEnum.AGREED.getResult()); + when(friendRequestMapper.selectById(100L)).thenReturn(request); + + // 调用 + 断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> friendRequestService.agreeFriendRequest(2L, 100L)); + assertEquals(FRIEND_REQUEST_HANDLED.getCode(), exception.getCode()); + } + + @Test + public void testAgreeFriendRequest_concurrentCasFail() { + // 准备:fail-fast 校验通过,但乐观锁 CAS 被并发抢先 + ImFriendRequestDO request = new ImFriendRequestDO().setId(100L).setFromUserId(1L).setToUserId(2L) + .setHandleResult(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()); + when(friendRequestMapper.selectById(100L)).thenReturn(request); + when(friendRequestMapper.updateByIdAndHandleResult(anyLong(), anyInt(), any(ImFriendRequestDO.class))).thenReturn(0); + + // 调用 + 断言:返回 FRIEND_REQUEST_HANDLED + ServiceException exception = assertThrows(ServiceException.class, + () -> friendRequestService.agreeFriendRequest(2L, 100L)); + assertEquals(FRIEND_REQUEST_HANDLED.getCode(), exception.getCode()); + verify(friendService, never()).becomeFriends(any(ImFriendRequestDO.class)); + } + + // ========== refuseFriendRequest ========== + + @Test + public void testRefuseFriendRequest_success() { + // 准备 + ImFriendRequestDO request = new ImFriendRequestDO().setId(100L).setFromUserId(1L).setToUserId(2L) + .setHandleResult(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()); + when(friendRequestMapper.selectById(100L)).thenReturn(request); + when(friendRequestMapper.updateByIdAndHandleResult(eq(100L), + eq(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()), any(ImFriendRequestDO.class))).thenReturn(1); + + // 调用 + friendRequestService.refuseFriendRequest(2L, 100L, "不认识"); + + // 断言:handleResult / handleContent 写入;推 REJECTED 给发起方 + ArgumentCaptor captor = ArgumentCaptor.forClass(ImFriendRequestDO.class); + verify(friendRequestMapper).updateByIdAndHandleResult(eq(100L), + eq(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()), captor.capture()); + assertEquals(ImFriendRequestHandleResultEnum.REFUSED.getResult(), captor.getValue().getHandleResult()); + assertEquals("不认识", captor.getValue().getHandleContent()); + verify(websocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + } + + @Test + public void testRefuseFriendRequest_concurrentCasFail() { + // 准备:CAS 失败 + ImFriendRequestDO request = new ImFriendRequestDO().setId(100L).setFromUserId(1L).setToUserId(2L) + .setHandleResult(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()); + when(friendRequestMapper.selectById(100L)).thenReturn(request); + when(friendRequestMapper.updateByIdAndHandleResult(anyLong(), anyInt(), any(ImFriendRequestDO.class))).thenReturn(0); + + // 调用 + 断言:抛 HANDLED,且不推 REJECTED + ServiceException exception = assertThrows(ServiceException.class, + () -> friendRequestService.refuseFriendRequest(2L, 100L, "x")); + assertEquals(FRIEND_REQUEST_HANDLED.getCode(), exception.getCode()); + verify(websocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + // ========== getMyFriendRequestList ========== + + @Test + public void testGetMyFriendRequestList_delegate() { + // 准备:mapper 返回 mock 数据 + LocalDateTime updateTime = LocalDateTime.now(); + ImFriendRequestDO cursor = new ImFriendRequestDO(); + cursor.setId(99L); + cursor.setUpdateTime(updateTime); + ImFriendRequestDO one = new ImFriendRequestDO(); + one.setId(1L); + when(friendRequestMapper.selectById(99L)).thenReturn(cursor); + when(friendRequestMapper.selectMyList(1L, updateTime, 99L, 20)) + .thenReturn(java.util.Collections.singletonList(one)); + + // 调用 + 断言:cursor 由 Service 查询后传给 Mapper + assertEquals(1, friendRequestService.getMyFriendRequestList(1L, 99L, 20).size()); + verify(friendRequestMapper).selectMyList(1L, updateTime, 99L, 20); + } + + @Test + public void testGetMyFriendRequestList_cursorNotExists() { + // 准备:cursor 不存在 + when(friendRequestMapper.selectById(99L)).thenReturn(null); + + // 调用 + 断言:返回空列表 + assertTrue(friendRequestService.getMyFriendRequestList(1L, 99L, 20).isEmpty()); + verify(friendRequestMapper, never()).selectMyList(anyLong(), any(), anyLong(), anyInt()); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java new file mode 100644 index 0000000000..b0d4d18348 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java @@ -0,0 +1,291 @@ +package cn.iocoder.yudao.module.im.service.friend; + +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; +import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; +import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.dao.DuplicateKeyException; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link ImFriendServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImFriendServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImFriendServiceImpl friendService; + + @Mock + private ImFriendMapper imFriendMapper; + @Mock + private AdminUserApi adminUserApi; + @Mock + private ImWebSocketService imWebSocketService; + @Mock + private ImPrivateMessageService privateMessageService; + + // ========== updateFriend ========== + + @Test + public void testUpdateFriend_success() { + // 准备 + ImFriendUpdateReqVO reqVO = new ImFriendUpdateReqVO(); + reqVO.setFriendUserId(2L); + reqVO.setSilent(true); + ImFriendDO friend = ImFriendDO.builder().id(100L).userId(1L).friendUserId(2L) + .silent(false).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(friend); + + // 调用 + friendService.updateFriend(1L, reqVO); + + // 断言:更新了 silent 字段 + ArgumentCaptor captor = ArgumentCaptor.forClass(ImFriendDO.class); + verify(imFriendMapper).updateById(captor.capture()); + assertEquals(100L, captor.getValue().getId()); + assertTrue(captor.getValue().getSilent()); + // 断言:推送了好友更新通知 + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + } + + @Test + public void testUpdateFriend_notFriend() { + // 准备 + ImFriendUpdateReqVO reqVO = new ImFriendUpdateReqVO(); + reqVO.setFriendUserId(2L); + reqVO.setSilent(true); + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(null); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> friendService.updateFriend(1L, reqVO)); + assertEquals(FRIEND_NOT_FRIEND.getCode(), exception.getCode()); + } + + @Test + public void testUpdateFriend_disabledFriend() { + // 准备 + ImFriendUpdateReqVO reqVO = new ImFriendUpdateReqVO(); + reqVO.setFriendUserId(2L); + reqVO.setSilent(true); + ImFriendDO friend = ImFriendDO.builder().id(100L).userId(1L).friendUserId(2L) + .status(CommonStatusEnum.DISABLE.getStatus()).build(); + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(friend); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> friendService.updateFriend(1L, reqVO)); + assertEquals(FRIEND_NOT_FRIEND.getCode(), exception.getCode()); + verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); + verify(imWebSocketService, never()).sendNotificationAsync(any(Long.class), anyInt(), anyInt(), any()); + } + + @Test + public void testUpdateFriend_displayNameOnly() { + // 准备:只传备注、不传 silent —— 走差量更新 + ImFriendUpdateReqVO reqVO = new ImFriendUpdateReqVO(); + reqVO.setFriendUserId(2L); + reqVO.setDisplayName("老张"); + ImFriendDO friend = ImFriendDO.builder().id(100L).userId(1L).friendUserId(2L) + .silent(false).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(friend); + + // 调用 + friendService.updateFriend(1L, reqVO); + + // 断言:updateById 收到 displayName 但没有 silent(MyBatis-Plus 靠 NOT_NULL 跳过) + ArgumentCaptor captor = ArgumentCaptor.forClass(ImFriendDO.class); + verify(imFriendMapper).updateById(captor.capture()); + assertEquals(100L, captor.getValue().getId()); + assertEquals("老张", captor.getValue().getDisplayName()); + assertNull(captor.getValue().getSilent()); + // 断言:推送好友更新通知 + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + } + + @Test + public void testUpdateFriend_emptyRequest() { + // 准备:silent / displayName / pinned 都不传 —— 进入方法立刻返回,不查 mapper 也不发推送 + ImFriendUpdateReqVO reqVO = new ImFriendUpdateReqVO(); + reqVO.setFriendUserId(2L); + + // 调用 + friendService.updateFriend(1L, reqVO); + + // 断言:没查记录、没触发 SQL 更新 / 没发 WebSocket 推送 + verify(imFriendMapper, never()).selectByUserIdAndFriendUserId(anyLong(), anyLong()); + verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); + verify(imWebSocketService, never()).sendNotificationAsync(any(Long.class), anyInt(), anyInt(), any()); + } + + // ========== 建立好友 ========== + + @Test + public void testAddFriend0_existingEnabledSkip() { + // 准备:已存在且启用 + ImFriendDO exists = ImFriendDO.builder().id(10L).userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).silent(true).pinned(true).blocked(true).build(); + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(exists); + + // 调用 + friendService.addFriend0(1L, 2L, null, null); + + // 断言:不插入也不更新 + verify(imFriendMapper, never()).insert(any(ImFriendDO.class)); + verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); + verify(imFriendMapper, never()).updateReAddFields(anyLong(), anyInt(), any(LocalDateTime.class), + any(LocalDateTime.class), anyBoolean(), anyBoolean(), anyBoolean(), any(), any()); + } + + @Test + public void testAddFriend0_existingDisabledRecovers() { + // 准备:已存在且 DISABLE,应当恢复状态 + ImFriendDO exists = ImFriendDO.builder().id(10L).userId(1L).friendUserId(2L) + .status(CommonStatusEnum.DISABLE.getStatus()).build(); + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(exists); + + // 调用 + friendService.addFriend0(1L, 2L, null, null); + + // 断言:恢复 ENABLE,并清空 deleteTime + verify(imFriendMapper).updateReAddFields(eq(10L), eq(CommonStatusEnum.ENABLE.getStatus()), + any(LocalDateTime.class), any(LocalDateTime.class), eq(false), eq(false), eq(false), isNull(), isNull()); + verify(imFriendMapper, never()).insert(any(ImFriendDO.class)); + } + + @Test + public void testAddFriend0_duplicateKeyPropagates() { + // 准备:mapper 抛并发冲突;极端并发下让异常向外抛,由外层事务回滚 + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(null); + when(imFriendMapper.insert(any(ImFriendDO.class))) + .thenThrow(new DuplicateKeyException("concurrent insert")); + + // 调用 + 断言:异常向外抛 + assertThrows(DuplicateKeyException.class, () -> friendService.addFriend0(1L, 2L, null, null)); + verify(imFriendMapper).insert(any(ImFriendDO.class)); + } + + // ========== deleteFriend ========== + + @Test + public void testDeleteFriend0_alreadyDisabled() { + // 准备:已经是 DISABLE,不再更新 + ImFriendDO exists = ImFriendDO.builder().id(10L).userId(1L).friendUserId(2L) + .status(CommonStatusEnum.DISABLE.getStatus()).build(); + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(exists); + + boolean result = friendService.deleteFriend0(1L, 2L); + + assertFalse(result); + verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); + } + + @Test + public void testDeleteFriend0_enabledGetsDisabled() { + ImFriendDO exists = ImFriendDO.builder().id(10L).userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(exists); + + boolean result = friendService.deleteFriend0(1L, 2L); + + assertTrue(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(ImFriendDO.class); + verify(imFriendMapper).updateById(captor.capture()); + assertEquals(CommonStatusEnum.DISABLE.getStatus(), captor.getValue().getStatus()); + assertNotNull(captor.getValue().getDeleteTime()); + } + + @Test + public void testDeleteFriend_alreadyDisabledSkipNotification() { + // 准备:已经是 DISABLE,不再推本端删除通知 + ImFriendDO exists = ImFriendDO.builder().id(10L).userId(1L).friendUserId(2L) + .status(CommonStatusEnum.DISABLE.getStatus()).build(); + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(exists); + + friendService.deleteFriend(1L, 2L, true); + + verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); + verify(privateMessageService, never()).sendPrivateMessage(anyLong(), any(ImPrivateMessageSendDTO.class)); + } + + @Test + public void testDeleteFriend_enabledSendNotification() { + // 准备 + ImFriendDO exists = ImFriendDO.builder().id(10L).userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(imFriendMapper.selectByUserIdAndFriendUserId(1L, 2L)).thenReturn(exists); + + friendService.deleteFriend(1L, 2L, true); + + verify(imFriendMapper).updateById(any(ImFriendDO.class)); + verify(privateMessageService).sendPrivateMessage(eq(1L), any(ImPrivateMessageSendDTO.class)); + } + + // ========== 其它读方法 ========== + + @Test + public void testGetFriendList() { + List list = ListUtil.of( + ImFriendDO.builder().id(1L).userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImFriendDO.builder().id(2L).userId(1L).friendUserId(3L) + .status(CommonStatusEnum.DISABLE.getStatus()) + .deleteTime(LocalDateTime.now()).build() + ); + when(imFriendMapper.selectListByUserId(1L)).thenReturn(list); + + List result = friendService.getFriendList(1L); + assertEquals(2, result.size()); + } + + @Test + public void testGetActiveFriendList_emptySkip() { + List result = friendService.getActiveFriendList(1L, Collections.emptyList()); + + assertTrue(result.isEmpty()); + verify(imFriendMapper, never()).selectListByUserIdAndFriendUserIdsAndStatus(anyLong(), anyCollection(), anyInt()); + } + + @Test + public void testGetMutualEnableFriendList_filterSingleSideDeleted() { + ImFriendDO friend2 = ImFriendDO.builder().id(1L).userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + ImFriendDO friend3 = ImFriendDO.builder().id(2L).userId(1L).friendUserId(3L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(imFriendMapper.selectListByUserIdAndStatus(1L, CommonStatusEnum.ENABLE.getStatus())) + .thenReturn(ListUtil.of(friend2, friend3)); + when(imFriendMapper.selectListByUserIdsAndFriendUserIdAndStatus(anyCollection(), eq(1L), + eq(CommonStatusEnum.ENABLE.getStatus()))).thenReturn(ListUtil.of( + ImFriendDO.builder().userId(2L).friendUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + List result = friendService.getMutualEnableFriendList(1L); + + assertEquals(1, result.size()); + assertEquals(2L, result.get(0).getFriendUserId()); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImplTest.java new file mode 100644 index 0000000000..ba4e0a8481 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImplTest.java @@ -0,0 +1,349 @@ +package cn.iocoder.yudao.module.im.service.group; + +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.dao.DuplicateKeyException; + +import java.util.List; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link ImGroupMemberServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImGroupMemberServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImGroupMemberServiceImpl groupMemberService; + + @Mock + private ImGroupMemberMapper groupMemberMapper; + @Mock + private ImWebSocketService webSocketService; + @Mock + private cn.iocoder.yudao.module.im.service.message.ImGroupMessageService groupMessageService; + + // ========== addGroupMember ========== + + @Test + public void testAddGroupMember_newInsert() { + // 准备:成员记录不存在 + when(groupMemberMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(null); + + // 调用 + ImGroupMemberDO result = groupMemberService.addGroupMember(10L, 1L); + + // 断言:执行了 insert,返回记录的 status 为 ENABLE + assertNotNull(result); + assertEquals(10L, result.getGroupId()); + assertEquals(1L, result.getUserId()); + assertEquals(CommonStatusEnum.ENABLE.getStatus(), result.getStatus()); + assertNotNull(result.getJoinTime()); + verify(groupMemberMapper).insert(any(ImGroupMemberDO.class)); + } + + @Test + public void testAddGroupMember_existingEnabledReturns() { + // 准备:已存在且 ENABLE,只返回已有记录,不做其它操作 + ImGroupMemberDO exists = ImGroupMemberDO.builder().id(50L).groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(exists); + + ImGroupMemberDO result = groupMemberService.addGroupMember(10L, 1L); + + assertEquals(50L, result.getId()); + verify(groupMemberMapper, never()).insert(any(ImGroupMemberDO.class)); + verify(groupMemberMapper, never()).updateById(any(ImGroupMemberDO.class)); + } + + @Test + public void testAddGroupMember_existingDisabledRecovers() { + // 准备:已存在且 DISABLE,应重置为 ENABLE 并重置 role 为 MEMBER + ImGroupMemberDO exists = ImGroupMemberDO.builder().id(50L).groupId(10L).userId(1L) + .status(CommonStatusEnum.DISABLE.getStatus()) + .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build(); + when(groupMemberMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(exists); + + ImGroupMemberDO result = groupMemberService.addGroupMember(10L, 1L); + + verify(groupMemberMapper).updateRejoinFields(eq(50L), eq(CommonStatusEnum.ENABLE.getStatus()), + any(), eq(ImGroupMemberRoleEnum.NORMAL.getRole()), isNull(), isNull()); + assertEquals(CommonStatusEnum.ENABLE.getStatus(), result.getStatus()); + assertEquals(ImGroupMemberRoleEnum.NORMAL.getRole(), result.getRole()); + assertNull(result.getQuitTime()); + assertNull(result.getMuteEndTime()); + } + + @Test + public void testAddGroupMember_duplicateKeyFallsBackToSelect() { + // 准备:第一次 select 返回 null,insert 抛 DuplicateKey,再次 select 返回已插入记录 + ImGroupMemberDO inserted = ImGroupMemberDO.builder().id(80L).groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(null).thenReturn(inserted); + when(groupMemberMapper.insert(any(ImGroupMemberDO.class))) + .thenThrow(new DuplicateKeyException("concurrent insert")); + + // 调用:冲突后降级 select + ImGroupMemberDO result = groupMemberService.addGroupMember(10L, 1L); + + assertNotNull(result); + assertEquals(80L, result.getId()); + } + + // ========== addGroupMembers ========== + + @Test + public void testAddGroupMembers_mixedInsertAndUpdate() { + // 准备:用户 2 不存在(新增),用户 3 已存在且 DISABLE(恢复),用户 4 已存在且 ENABLE(跳过) + ImGroupMemberDO exist3 = ImGroupMemberDO.builder().id(30L).groupId(10L).userId(3L) + .status(CommonStatusEnum.DISABLE.getStatus()).build(); + ImGroupMemberDO exist4 = ImGroupMemberDO.builder().id(40L).groupId(10L).userId(4L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberMapper.selectListByGroupIdAndUserIds(eq(10L), anyCollection())) + .thenReturn(ListUtil.of(exist3, exist4)); + + // 调用 + groupMemberService.addGroupMembers(10L, ListUtil.of(2L, 3L, 4L)); + + // 断言:updates 只有用户 3;inserts 只有用户 2 + verify(groupMemberMapper).updateRejoinFields(eq(30L), eq(CommonStatusEnum.ENABLE.getStatus()), + any(), eq(ImGroupMemberRoleEnum.NORMAL.getRole()), isNull(), isNull()); + verify(groupMemberMapper).insertBatch(argThat((List list) -> + list.size() == 1 && list.get(0).getUserId().equals(2L))); + } + + @Test + public void testAddGroupMembers_allExisting_onlyUpdates() { + // 准备:传入的 3 个用户都已有记录(全部 DISABLE) → 只做 update,不做 insert + List existing = ListUtil.of( + ImGroupMemberDO.builder().id(1L).groupId(10L).userId(2L) + .status(CommonStatusEnum.DISABLE.getStatus()).build(), + ImGroupMemberDO.builder().id(2L).groupId(10L).userId(3L) + .status(CommonStatusEnum.DISABLE.getStatus()).build() + ); + when(groupMemberMapper.selectListByGroupIdAndUserIds(eq(10L), anyCollection())) + .thenReturn(existing); + + groupMemberService.addGroupMembers(10L, ListUtil.of(2L, 3L)); + + verify(groupMemberMapper, times(2)).updateRejoinFields(anyLong(), eq(CommonStatusEnum.ENABLE.getStatus()), + any(), eq(ImGroupMemberRoleEnum.NORMAL.getRole()), isNull(), isNull()); + verify(groupMemberMapper, never()).insertBatch(anyList()); + } + + @Test + public void testAddGroupMembers_allNew_onlyInserts() { + // 准备:都不存在 → 只做 insert + when(groupMemberMapper.selectListByGroupIdAndUserIds(eq(10L), anyCollection())) + .thenReturn(ListUtil.of()); + + groupMemberService.addGroupMembers(10L, ListUtil.of(2L, 3L)); + + verify(groupMemberMapper, never()).updateRejoinFields(anyLong(), anyInt(), any(), anyInt(), any(), any()); + verify(groupMemberMapper).insertBatch(anyList()); + } + + @Test + public void testAddGroupMembers_allExistingEnabled_nothingHappens() { + // 准备:都已存在且 ENABLE → 既不 update 也不 insert + List existing = ListUtil.of( + ImGroupMemberDO.builder().id(1L).groupId(10L).userId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build() + ); + when(groupMemberMapper.selectListByGroupIdAndUserIds(eq(10L), anyCollection())) + .thenReturn(existing); + + groupMemberService.addGroupMembers(10L, ListUtil.of(2L)); + + verify(groupMemberMapper, never()).updateRejoinFields(anyLong(), anyInt(), any(), anyInt(), any(), any()); + verify(groupMemberMapper, never()).insertBatch(anyList()); + } + + @Test + public void testAddGroupMembers_batchInsertDuplicateFallback() { + // 准备:两个新增成员,批量插入失败时降级为逐个 addGroupMember + when(groupMemberMapper.selectListByGroupIdAndUserIds(eq(10L), anyCollection())) + .thenReturn(ListUtil.of()); + doThrow(new DuplicateKeyException("concurrent batch insert")) + .when(groupMemberMapper).insertBatch(anyList()); + // addGroupMember 单条兜底逻辑 + when(groupMemberMapper.selectByGroupIdAndUserId(eq(10L), anyLong())).thenReturn(null); + + // 调用 + groupMemberService.addGroupMembers(10L, ListUtil.of(2L, 3L)); + + // 断言:降级为逐条调用 insert + verify(groupMemberMapper, times(2)).insert(any(ImGroupMemberDO.class)); + } + + // ========== validateMemberInGroup ========== + + @Test + public void testValidateMemberInGroup_notInGroup() { + when(groupMemberMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMemberService.validateMemberInGroup(10L, 1L)); + assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); + } + + @Test + public void testValidateMemberInGroup_disabledEqualsNotInGroup() { + ImGroupMemberDO member = ImGroupMemberDO.builder().id(50L).groupId(10L).userId(1L) + .status(CommonStatusEnum.DISABLE.getStatus()).build(); + when(groupMemberMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(member); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMemberService.validateMemberInGroup(10L, 1L)); + assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); + } + + @Test + public void testValidateMemberInGroup_success() { + ImGroupMemberDO member = ImGroupMemberDO.builder().id(50L).groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(member); + + ImGroupMemberDO result = groupMemberService.validateMemberInGroup(10L, 1L); + assertEquals(50L, result.getId()); + } + + // ========== updateGroupMember ========== + + @Test + public void testUpdateGroupMember_success() { + ImGroupMemberDO member = ImGroupMemberDO.builder().id(50L).groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(member); + + ImGroupMemberUpdateReqVO reqVO = new ImGroupMemberUpdateReqVO() + .setGroupId(10L).setSilent(true).setDisplayUserName("昵称"); + + groupMemberService.updateGroupMember(1L, reqVO); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupMemberDO.class); + verify(groupMemberMapper).updateById(captor.capture()); + assertEquals(50L, captor.getValue().getId()); + assertTrue(captor.getValue().getSilent()); + // 公开字段昵称变化 → 全员在线同步 GROUP_MEMBER_NICKNAME_UPDATE + verify(groupMessageService).sendGroupMessage(eq(1L), any(cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO.class)); + // 个人字段 silent 变化 → 仅自己多端同步 GROUP_MEMBER_SETTING_UPDATE + verify(groupMessageService).sendGroupMessage(eq(1L), eq(ListUtil.of(1L)), + any(cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO.class)); + } + + // ========== removeGroupMember ========== + + @Test + public void testRemoveGroupMember_success() { + ImGroupMemberDO member = ImGroupMemberDO.builder().id(50L).groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(member); + + groupMemberService.removeGroupMember(10L, 1L); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupMemberDO.class); + verify(groupMemberMapper).updateById(captor.capture()); + assertEquals(50L, captor.getValue().getId()); + assertEquals(CommonStatusEnum.DISABLE.getStatus(), captor.getValue().getStatus()); + assertNotNull(captor.getValue().getQuitTime()); + } + + @Test + public void testRemoveGroupMember_notInGroup() { + when(groupMemberMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMemberService.removeGroupMember(10L, 1L)); + assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); + } + + // ========== removeGroupMembers ========== + + @Test + public void testRemoveGroupMembers_batch() { + groupMemberService.removeGroupMembers(10L, ListUtil.of(2L, 3L)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupMemberDO.class); + verify(groupMemberMapper).updateByGroupIdAndUserIdsAndStatus(eq(10L), anyCollection(), + eq(CommonStatusEnum.ENABLE.getStatus()), captor.capture()); + assertEquals(CommonStatusEnum.DISABLE.getStatus(), captor.getValue().getStatus()); + assertNotNull(captor.getValue().getQuitTime()); + } + + @Test + public void testRemoveGroupMembersByGroupId() { + groupMemberService.removeGroupMembersByGroupId(10L); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupMemberDO.class); + verify(groupMemberMapper).updateByGroupIdAndStatus(eq(10L), + eq(CommonStatusEnum.ENABLE.getStatus()), captor.capture()); + assertEquals(CommonStatusEnum.DISABLE.getStatus(), captor.getValue().getStatus()); + assertNotNull(captor.getValue().getQuitTime()); + } + + // ========== getActiveGroupMemberUserIdsByGroupId ========== + + @Test + public void testGetActiveGroupMemberUserIdsByGroupId_extractsUserIds() { + // 准备:3 个 ENABLE 成员 + List members = ListUtil.of( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupMemberDO.builder().groupId(10L).userId(3L) + .status(CommonStatusEnum.ENABLE.getStatus()).build() + ); + when(groupMemberMapper.selectListByGroupIdAndStatus( + 10L, CommonStatusEnum.ENABLE.getStatus())).thenReturn(members); + + // 调用 + List userIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L); + + // 断言:只返回 userId、顺序保留 + assertEquals(ListUtil.of(1L, 2L, 3L), userIds); + } + + @Test + public void testGetActiveGroupMemberUserIdsByGroupId_emptyList() { + when(groupMemberMapper.selectListByGroupIdAndStatus( + 10L, CommonStatusEnum.ENABLE.getStatus())).thenReturn(ListUtil.of()); + + List userIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L); + + assertTrue(userIds.isEmpty()); + } + + @Test + public void testGetGroupMemberListByOwnerAndAdmin_passesRoles() { + List roles = ListUtil.of(ImGroupMemberRoleEnum.OWNER.getRole(), ImGroupMemberRoleEnum.ADMIN.getRole()); + List members = ListUtil.of(ImGroupMemberDO.builder().groupId(10L).userId(1L).build()); + when(groupMemberMapper.selectListByGroupIdAndStatusAndRoles(10L, CommonStatusEnum.ENABLE.getStatus(), roles)) + .thenReturn(members); + + List result = groupMemberService.getGroupMemberListByOwnerAndAdmin(10L); + + assertEquals(members, result); + verify(groupMemberMapper).selectListByGroupIdAndStatusAndRoles( + 10L, CommonStatusEnum.ENABLE.getStatus(), roles); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java new file mode 100644 index 0000000000..0d05e10d8a --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java @@ -0,0 +1,350 @@ +package cn.iocoder.yudao.module.im.service.group; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.request.ImGroupRequestApplyReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupRequestDO; +import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupRequestMapper; +import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; +import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.dao.DuplicateKeyException; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link ImGroupRequestServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImGroupRequestServiceImpl groupRequestService; + + @Mock + private ImGroupRequestMapper groupRequestMapper; + @Mock + private ImGroupService groupService; + @Mock + private ImGroupMemberService groupMemberService; + @Mock + private ImGroupMessageService groupMessageService; + @Mock + private ImWebSocketService websocketService; + @Mock + private AdminUserApi adminUserApi; + + // ==================== applyJoinGroup ==================== + + @Test + public void testApplyJoinGroup_freeMode_directJoin() { + // 准备:群是 FREE 模式 + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .joinApproval(false) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupService.validateGroupExists(10L)).thenReturn(group); + + ImGroupRequestApplyReqVO reqVO = new ImGroupRequestApplyReqVO(); + reqVO.setGroupId(10L); + reqVO.setAddSource(ImGroupAddSourceEnum.SEARCH.getSource()); + + // 调用 + ImGroupRequestDO result = groupRequestService.applyJoinGroup(1L, reqVO); + + // 断言:FREE 路径直接入群,不落申请记录 + assertNull(result); + verify(groupService).validateMemberCountLimit(10L, 1); + verify(groupMemberService).addGroupMember(eq(10L), eq(1L), + eq(ImGroupMemberRoleEnum.NORMAL.getRole()), + eq(ImGroupAddSourceEnum.SEARCH.getSource()), isNull()); + verify(groupRequestMapper, never()).insert(any(ImGroupRequestDO.class)); + // 推 1510 自由进群 + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_ENTER.getType(), dtoCaptor.getValue().getType()); + } + + @Test + public void testApplyJoinGroup_approvalMode_createsRequest() { + // 准备:群是 APPLY 模式 + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .joinApproval(true) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupService.validateGroupExists(10L)).thenReturn(group); + // 群里有 owner + 一个 admin,作为 1503 推送目标 + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( + ImGroupMemberDO.builder().groupId(10L).userId(99L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupMemberDO.builder().groupId(10L).userId(98L) + .role(ImGroupMemberRoleEnum.ADMIN.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + when(adminUserApi.getUser(1L)).thenReturn(buildUser(1L, "申请人")); + + ImGroupRequestApplyReqVO reqVO = new ImGroupRequestApplyReqVO(); + reqVO.setGroupId(10L); + reqVO.setApplyContent("我想进群"); + reqVO.setAddSource(ImGroupAddSourceEnum.SEARCH.getSource()); + + // 调用 + ImGroupRequestDO result = groupRequestService.applyJoinGroup(1L, reqVO); + + // 断言:申请记录已落库 + 不直进群 + assertNotNull(result); + verify(groupMemberService, never()).addGroupMember(anyLong(), anyLong(), anyInt(), anyInt(), anyLong()); + verify(groupRequestMapper).insert(any(ImGroupRequestDO.class)); + // 1503 推送给 owner(99) + admin(98),去重后两条 + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + @Test + public void testApplyJoinGroup_insertDuplicateKey_reuseOldRequest() { + // 准备:群是 APPLY 模式,首次查询不存在,插入时命中唯一键 + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .joinApproval(true) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupService.validateGroupExists(10L)).thenReturn(group); + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( + ImGroupMemberDO.builder().groupId(10L).userId(99L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + when(adminUserApi.getUser(1L)).thenReturn(buildUser(1L, "申请人")); + ImGroupRequestDO old = new ImGroupRequestDO().setId(50L).setGroupId(10L).setUserId(1L) + .setHandleResult(ImGroupRequestHandleResultEnum.REFUSED.getResult()); + when(groupRequestMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(null, old); + when(groupRequestMapper.insert(any(ImGroupRequestDO.class))).thenThrow(new DuplicateKeyException("dup")); + + ImGroupRequestApplyReqVO reqVO = new ImGroupRequestApplyReqVO(); + reqVO.setGroupId(10L); + reqVO.setApplyContent("我想进群"); + reqVO.setAddSource(ImGroupAddSourceEnum.SEARCH.getSource()); + + // 调用 + ImGroupRequestDO result = groupRequestService.applyJoinGroup(1L, reqVO); + + // 断言:复用并重置旧申请 + verify(groupRequestMapper).updateApplyByIdReset(eq(50L), eq("我想进群"), + eq(ImGroupAddSourceEnum.SEARCH.getSource()), any()); + assertEquals(50L, result.getId()); + assertEquals(ImGroupRequestHandleResultEnum.UNHANDLED.getResult(), result.getHandleResult()); + verify(websocketService).sendNotificationAsync(eq(99L), anyInt(), anyInt(), any()); + } + + @Test + public void testApplyJoinGroup_alreadyMember_throws() { + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .joinApproval(true) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupService.validateGroupExists(10L)).thenReturn(group); + when(groupMemberService.getGroupMember(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + + ImGroupRequestApplyReqVO reqVO = new ImGroupRequestApplyReqVO(); + reqVO.setGroupId(10L); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupRequestService.applyJoinGroup(1L, reqVO)); + assertEquals(GROUP_REQUEST_ALREADY_MEMBER.getCode(), exception.getCode()); + } + + // ==================== agreeGroupRequest ==================== + + @Test + public void testAgreeGroupRequest_success_activeApply() { + // 准备:主动申请未处理,操作人是 admin + ImGroupRequestDO request = new ImGroupRequestDO() + .setGroupId(10L).setUserId(2L).setInviterUserId(null) + .setAddSource(ImGroupAddSourceEnum.SEARCH.getSource()) + .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()); + request.setId(50L); + when(groupRequestMapper.selectById(50L)).thenReturn(request); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.ADMIN.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + when(groupRequestMapper.updateByIdAndHandleResult(eq(50L), + eq(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()), any())).thenReturn(1); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupService.getGroup(10L)).thenReturn(group); + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( + ImGroupMemberDO.builder().groupId(10L).userId(99L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + // 调用 + groupRequestService.agreeGroupRequest(1L, 50L); + + // 断言:人数校验 + 写群成员 + 推 1505 + 推 1510(主动申请) + verify(groupService).validateMemberCountLimit(10L, 1); + verify(groupMemberService).addGroupMember(eq(10L), eq(2L), + eq(ImGroupMemberRoleEnum.NORMAL.getRole()), + eq(ImGroupAddSourceEnum.SEARCH.getSource()), isNull()); + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_ENTER.getType(), dtoCaptor.getValue().getType()); + // 1505 推送给申请人 + owner,去重后两条 + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + @Test + public void testAgreeGroupRequest_concurrent_secondCallFails() { + // 准备:申请存在但乐观锁更新返回 0(被并发处理过) + ImGroupRequestDO request = new ImGroupRequestDO() + .setGroupId(10L).setUserId(2L) + .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()); + request.setId(50L); + when(groupRequestMapper.selectById(50L)).thenReturn(request); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + when(groupRequestMapper.updateByIdAndHandleResult(eq(50L), + eq(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()), any())).thenReturn(0); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupRequestService.agreeGroupRequest(1L, 50L)); + assertEquals(GROUP_REQUEST_HANDLED.getCode(), exception.getCode()); + // 不写群成员 + verify(groupMemberService, never()).addGroupMember(anyLong(), anyLong(), anyInt(), any(), any()); + } + + @Test + public void testAgreeGroupRequest_notOwnerOrAdmin_throws() { + ImGroupRequestDO request = new ImGroupRequestDO() + .setGroupId(10L).setUserId(2L) + .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()); + request.setId(50L); + when(groupRequestMapper.selectById(50L)).thenReturn(request); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupRequestService.agreeGroupRequest(1L, 50L)); + assertEquals(GROUP_REQUEST_NOT_TO_ME.getCode(), exception.getCode()); + } + + // ==================== refuseGroupRequest ==================== + + @Test + public void testRefuseGroupRequest_success() { + ImGroupRequestDO request = new ImGroupRequestDO() + .setGroupId(10L).setUserId(2L) + .setHandleResult(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()); + request.setId(50L); + when(groupRequestMapper.selectById(50L)).thenReturn(request); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + when(groupRequestMapper.updateByIdAndHandleResult(eq(50L), + eq(ImGroupRequestHandleResultEnum.UNHANDLED.getResult()), any())).thenReturn(1); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupService.getGroup(10L)).thenReturn(group); + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + groupRequestService.refuseGroupRequest(1L, 50L, "暂不通过"); + + // 不写群成员;推 1506 给申请人 + 群主(同一人 1L 时去重为 1 + 申请人 2L = 2 条) + verify(groupMemberService, never()).addGroupMember(anyLong(), anyLong(), anyInt(), any(), any()); + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + // ==================== createInviteRequestList ==================== + + @Test + public void testCreateInviteRequestList_success() { + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .joinApproval(true) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupService.validateGroupExists(10L)).thenReturn(group); + when(groupRequestMapper.selectByGroupIdAndUserId(eq(10L), anyLong())).thenReturn(null); + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( + ImGroupMemberDO.builder().groupId(10L).userId(99L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + when(adminUserApi.getUserMap(anyCollection())).thenReturn(MapUtil.builder() + .put(2L, buildUser(2L, "用户A")) + .put(3L, buildUser(3L, "用户B")) + .build()); + + // 调用:邀请人 1L 邀请 2L、3L(都没有旧记录) + groupRequestService.createInviteRequestList(10L, 1L, ListUtil.of(2L, 3L)); + + // 断言:插入 2 条 + 推 1503 给 owner(每条 1 帧)共 2 帧 + ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupRequestDO.class); + verify(groupRequestMapper, times(2)).insert(captor.capture()); + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + // 断言:每条记录 inviterUserId=1 + addSource=INVITE,避免审批通过后回写群成员留痕的来源为空 / 脏带旧值 + Collection inserted = captor.getAllValues(); + assertEquals(2, inserted.size()); + inserted.forEach(insert -> { + assertEquals(1L, insert.getInviterUserId()); + assertEquals(ImGroupAddSourceEnum.INVITE.getSource(), insert.getAddSource()); + }); + } + + @Test + public void testCreateInviteRequestList_insertDuplicateKey_reuseOldRequest() { + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .joinApproval(true) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupService.validateGroupExists(10L)).thenReturn(group); + ImGroupRequestDO old = new ImGroupRequestDO().setId(50L).setGroupId(10L).setUserId(2L) + .setHandleResult(ImGroupRequestHandleResultEnum.REFUSED.getResult()); + when(groupRequestMapper.selectByGroupIdAndUserId(10L, 2L)).thenReturn(null, old); + when(groupRequestMapper.insert(any(ImGroupRequestDO.class))).thenThrow(new DuplicateKeyException("dup")); + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( + ImGroupMemberDO.builder().groupId(10L).userId(99L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + when(adminUserApi.getUserMap(anyCollection())).thenReturn(MapUtil.of(2L, buildUser(2L, "用户A"))); + + // 调用 + groupRequestService.createInviteRequestList(10L, 1L, ListUtil.of(2L)); + + // 断言:复用并重置旧邀请申请 + verify(groupRequestMapper).updateInviteByIdReset(eq(50L), eq(1L), + eq(ImGroupAddSourceEnum.INVITE.getSource()), any()); + verify(websocketService).sendNotificationAsync(eq(99L), anyInt(), anyInt(), any()); + } + + private AdminUserRespDTO buildUser(Long id, String nickname) { + AdminUserRespDTO user = new AdminUserRespDTO(); + user.setId(id); + user.setNickname(nickname); + return user; + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java new file mode 100644 index 0000000000..d75e36351a --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java @@ -0,0 +1,1184 @@ +package cn.iocoder.yudao.module.im.service.group; + +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupAdminAddReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupAdminRemoveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupMuteMemberReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupTransferOwnerReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; +import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; +import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link ImGroupServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImGroupServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImGroupServiceImpl groupService; + + @Mock + private ImGroupMapper groupMapper; + @Mock + private ImGroupMemberService groupMemberService; + @Mock + private ImGroupMessageService groupMessageService; + @Mock + private ImWebSocketService webSocketService; + @Mock + private ImFriendService friendService; + @Mock + private ImGroupRequestService groupRequestService; + @Mock + private AdminUserApi adminUserApi; + @Spy + private ImProperties imProperties = new ImProperties(); + + // ========== createGroup ========== + + @Test + public void testCreateGroup_success() { + // 准备:仅创建者,无初始成员 + ImGroupCreateReqVO reqVO = new ImGroupCreateReqVO(); + reqVO.setName("测试群"); + when(groupMapper.insert(any(ImGroupDO.class))).thenAnswer(invocation -> { + ImGroupDO group = invocation.getArgument(0); + group.setId(100L); + return 1; + }); + + // 调用 + ImGroupDO result = groupService.createGroup(reqVO, 1L); + + // 断言:群主 + 状态 + assertEquals(100L, result.getId()); + assertEquals(1L, result.getOwnerUserId()); + assertEquals(CommonStatusEnum.ENABLE.getStatus(), result.getStatus()); + // 验证:群主加入群(带 OWNER role)+ 不调批量加成员(无初始成员) + verify(groupMemberService).addGroupMember(100L, 1L, ImGroupMemberRoleEnum.OWNER.getRole()); + verify(groupMemberService, never()).addGroupMembers(anyLong(), anyCollection()); + // 验证:推送 GROUP_CREATE 通知(payload memberUserIds 含创建者自己) + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_CREATE.getType(), dtoCaptor.getValue().getType()); + } + + @Test + public void testCreateGroup_withInitialMembers() { + // 准备:创建者 + 2 个初始成员,都是好友 + ImGroupCreateReqVO reqVO = new ImGroupCreateReqVO(); + reqVO.setName("测试群"); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); + when(groupMapper.insert(any(ImGroupDO.class))).thenAnswer(invocation -> { + ImGroupDO group = invocation.getArgument(0); + group.setId(100L); + return 1; + }); + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( + ImFriendDO.builder().userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImFriendDO.builder().userId(1L).friendUserId(3L) + .status(CommonStatusEnum.ENABLE.getStatus()).build() + )); + + // 调用 + ImGroupDO result = groupService.createGroup(reqVO, 1L); + + // 断言:群创建成功 + 创建者 + 初始成员都加入 + assertEquals(100L, result.getId()); + verify(groupMemberService).addGroupMember(100L, 1L, ImGroupMemberRoleEnum.OWNER.getRole()); + verify(groupMemberService).addGroupMembers(eq(100L), anyCollection(), + eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L)); + // 验证:推送 GROUP_CREATE 通知,payload memberUserIds 含全员(创建者 + 邀请) + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_CREATE.getType(), dtoCaptor.getValue().getType()); + } + + @Test + public void testCreateGroup_initialMemberNotFriend() { + // 准备:初始成员里有非好友 + ImGroupCreateReqVO reqVO = new ImGroupCreateReqVO(); + reqVO.setName("测试群"); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); + // 只有 2 是好友,3 不是 + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( + ImFriendDO.builder().userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + AdminUserRespDTO u3 = new AdminUserRespDTO(); + u3.setId(3L); + u3.setNickname("李四"); + when(adminUserApi.getUserMap(anyCollection())).thenReturn(Map.of(3L, u3)); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.createGroup(reqVO, 1L)); + assertEquals(GROUP_INVITE_NOT_FRIEND.getCode(), exception.getCode()); + verify(groupMapper, never()).insert(any(ImGroupDO.class)); + } + + // ========== updateGroup ========== + + @Test + public void testUpdateGroup_notOwner() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备:当前用户不是群主 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(99L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ImGroupUpdateReqVO reqVO = new ImGroupUpdateReqVO(); + reqVO.setId(10L); + reqVO.setName("新名字"); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.updateGroup(reqVO, 1L)); + assertEquals(GROUP_NOT_OWNER.getCode(), exception.getCode()); + } + } + + @Test + public void testUpdateGroup_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备 + ImGroupDO group = ImGroupDO.builder().id(10L).name("旧名字").ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ImGroupUpdateReqVO reqVO = new ImGroupUpdateReqVO(); + reqVO.setId(10L); + reqVO.setName("新名字"); + + // 调用 + ImGroupDO result = groupService.updateGroup(reqVO, 1L); + + // 断言:更新了数据库 + verify(groupMapper).updateById(any(ImGroupDO.class)); + assertEquals("新名字", result.getName()); + // 推送 GROUP_NAME_UPDATE 通知给全员 + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_NAME_UPDATE.getType(), dtoCaptor.getValue().getType()); + } + } + + @Test + public void testUpdateGroup_joinApprovalChanged() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L) + .joinApproval(false).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ImGroupUpdateReqVO reqVO = new ImGroupUpdateReqVO(); + reqVO.setId(10L); + reqVO.setJoinApproval(true); + + // 调用 + ImGroupDO result = groupService.updateGroup(reqVO, 1L); + + // 断言 + verify(groupMapper).updateById(any(ImGroupDO.class)); + assertTrue(result.getJoinApproval()); + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_INFO_UPDATE.getType(), dtoCaptor.getValue().getType()); + } + } + + @Test + public void testUpdateGroup_joinApprovalSame() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L) + .joinApproval(true).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ImGroupUpdateReqVO reqVO = new ImGroupUpdateReqVO(); + reqVO.setId(10L); + reqVO.setJoinApproval(true); + + // 调用 + ImGroupDO result = groupService.updateGroup(reqVO, 1L); + + // 断言 + verify(groupMapper).updateById(any(ImGroupDO.class)); + assertTrue(result.getJoinApproval()); + verify(groupMessageService, never()).sendGroupMessage(anyLong(), anyCollection(), any()); + } + } + + // ========== dissolveGroup ========== + + @Test + public void testDissolveGroup_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + // 调用 + groupService.dissolveGroup(10L, 1L); + + // 断言:群状态变为 DISABLE + 群成员全部移除 + 清理已读缓存 + ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupDO.class); + verify(groupMapper).updateById(captor.capture()); + assertEquals(CommonStatusEnum.DISABLE.getStatus(), captor.getValue().getStatus()); + assertNotNull(captor.getValue().getDissolvedTime()); + verify(groupMemberService).removeGroupMembersByGroupId(10L); + // 推送 GROUP_DISSOLVE 通知(send-before-remove,sendGroupMessage 内部查 active 自动覆盖全员,含群主多端同步) + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_DISSOLVE.getType(), dtoCaptor.getValue().getType()); + } + } + + @Test + public void testDissolveGroup_banned_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备:已封禁但未解散的群,群主仍可解散 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L) + .banned(true).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + // 调用 + groupService.dissolveGroup(10L, 1L); + + // 断言:封禁状态不阻止解散 + verify(groupMapper).updateById(argThat((ImGroupDO update) -> + CommonStatusEnum.DISABLE.getStatus().equals(update.getStatus()))); + verify(groupMemberService).removeGroupMembersByGroupId(10L); + } + } + + @Test + public void testDissolveGroupByManager_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备:管理员解散封禁群,不要求管理员是群主 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L) + .banned(true).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + // 调用 + groupService.dissolveGroupByManager(99L, 10L); + + // 断言:使用管理员编号发通知并完成清理 + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(99L), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_DISSOLVE.getType(), dtoCaptor.getValue().getType()); + verify(groupMemberService).removeGroupMembersByGroupId(10L); + } + } + + @Test + public void testDissolveGroup_notOwner() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.dissolveGroup(10L, 1L)); + assertEquals(GROUP_NOT_OWNER.getCode(), exception.getCode()); + } + } + + // ========== inviteGroupMember ========== + + @Test + public void testInviteGroupMember_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备:群存在 + joinApproval=false(自由进群) + 当前用户是群主 + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .joinApproval(false) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + + // 当前成员只有群主 + List activeMembers = new ArrayList<>(); + activeMembers.add(ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(activeMembers); + + // 被邀请人 2 和 3 都是好友 + ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); + List friends = List.of( + ImFriendDO.builder().userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImFriendDO.builder().userId(1L).friendUserId(3L) + .status(CommonStatusEnum.ENABLE.getStatus()).build() + ); + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(friends); + + // 调用 + groupService.inviteGroupMember(1L, reqVO); + + // 断言:校验群成员 + 批量添加成员(带 INVITE 来源 + 邀请人)+ 推送 GROUP_MEMBER_INVITE + verify(groupMemberService).validateMemberInGroup(10L, 1L); + verify(groupMemberService).addGroupMembers(eq(10L), anyCollection(), + eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L)); + verify(groupRequestService, never()).createInviteRequestList(anyLong(), anyLong(), anyCollection()); + verify(webSocketService, never()).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any()); + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType()); + } + } + + @Test + public void testInviteGroupMember_approval_normalRoutesToApproval() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备:joinApproval=true,开启审批;普通成员邀请走审批,落 group_request + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .joinApproval(true) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( + ImGroupMemberDO.builder().groupId(10L).userId(99L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( + ImFriendDO.builder().userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImFriendDO.builder().userId(1L).friendUserId(3L) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + // 调用 + groupService.inviteGroupMember(1L, reqVO); + + // 断言:走审批分支:调 createInviteRequestList;不写群成员、不推 1509 + verify(groupRequestService).createInviteRequestList(eq(10L), eq(1L), anyCollection()); + verify(groupMemberService, never()).addGroupMembers(anyLong(), anyCollection(), any(), any()); + verify(groupMessageService, never()).sendGroupMessage(anyLong(), any(ImGroupMessageSendDTO.class)); + } + } + + @Test + public void testInviteGroupMember_approval_ownerBypassesApproval() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备:joinApproval=true,但邀请人是群主;视同已审批,直进群 + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .joinApproval(true) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( + ImFriendDO.builder().userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImFriendDO.builder().userId(1L).friendUserId(3L) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + // 调用 + groupService.inviteGroupMember(1L, reqVO); + + // 断言:绕过审批,直接 addGroupMembers + 推 1509;不落 group_request + verify(groupRequestService, never()).createInviteRequestList(anyLong(), anyLong(), anyCollection()); + verify(groupMemberService).addGroupMembers(eq(10L), anyCollection(), + eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L)); + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType()); + } + } + + @Test + public void testInviteGroupMember_notFriend() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)) + .thenReturn(List.of(ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); + // 只有 2 是好友,3 不是 + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( + ImFriendDO.builder().userId(1L).friendUserId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + AdminUserRespDTO u3 = new AdminUserRespDTO(); u3.setId(3L); u3.setNickname("李四"); + when(adminUserApi.getUserMap(anyCollection())).thenReturn(Map.of(3L, u3)); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.inviteGroupMember(1L, reqVO)); + assertEquals(GROUP_INVITE_NOT_FRIEND.getCode(), exception.getCode()); + } + } + + @Test + public void testInviteGroupMember_memberExceed() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备:群 10 已有 499 人(逼近 MAX_GROUP_MEMBER=500),再邀请 2 人 → 501 超限 + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + List activeMembers = new ArrayList<>(); + for (long i = 1; i <= 499; i++) { + activeMembers.add(ImGroupMemberDO.builder().groupId(10L).userId(i) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + } + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(activeMembers); + + ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(new ArrayList<>(List.of(600L, 601L))); + // 被邀请人都是好友 + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( + ImFriendDO.builder().userId(1L).friendUserId(600L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImFriendDO.builder().userId(1L).friendUserId(601L) + .status(CommonStatusEnum.ENABLE.getStatus()).build() + )); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.inviteGroupMember(1L, reqVO)); + assertEquals(GROUP_MEMBER_EXCEED.getCode(), exception.getCode()); + // 断言:不加成员、不推送 + verify(groupMemberService, never()).addGroupMembers(anyLong(), anyCollection()); + } + } + + @Test + public void testInviteGroupMember_skipsMembersAlreadyInGroup() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备 + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + // 用户 2 已在群中 + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()).build() + )); + + ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L))); // 只邀请 2,他已在群中 + + // 调用 + groupService.inviteGroupMember(1L, reqVO); + + // 断言:不会触发添加、不会推送 + verify(groupMemberService, never()).addGroupMembers(anyLong(), anyCollection()); + verify(webSocketService, never()).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any()); + } + } + + // ========== quitGroup ========== + + @Test + public void testQuitGroup_ownerCannotQuit() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.quitGroup(10L, 1L)); + assertEquals(GROUP_OWNER_CANNOT_QUIT.getCode(), exception.getCode()); + } + } + + @Test + public void testQuitGroup_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + groupService.quitGroup(10L, 1L); + + verify(groupMemberService).removeGroupMember(10L, 1L); + // 推送 GROUP_MEMBER_QUIT 通知给全员(含 quitter,前端自判清群) + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_QUIT.getType(), dtoCaptor.getValue().getType()); + } + } + + @Test + public void testQuitGroup_bannedGroupAllowed() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .banned(true).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + groupService.quitGroup(10L, 1L); + + verify(groupMemberService).removeGroupMember(10L, 1L); + verify(groupMessageService).sendGroupMessage(eq(1L), any(ImGroupMessageSendDTO.class)); + } + } + + // ========== removeGroupMember ========== + + @Test + public void testRemoveGroupMember_cannotRemoveSelf() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()); + + ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(List.of(1L, 2L)); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.removeGroupMember(1L, reqVO)); + assertEquals(GROUP_CANNOT_REMOVE_SELF.getCode(), exception.getCode()); + } + } + + @Test + public void testRemoveGroupMember_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + // 操作者:群主 + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()); + // 目标:两个普通成员 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupMemberDO.builder().groupId(10L).userId(3L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(List.of(2L, 3L)); + + groupService.removeGroupMember(1L, reqVO); + + verify(groupMemberService).removeGroupMembers(eq(10L), anyCollection()); + // 推送 GROUP_MEMBER_KICK 通知给全员(含被踢者,前端自判清群) + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_KICK.getType(), dtoCaptor.getValue().getType()); + } + } + + @Test + public void testRemoveGroupMember_adminCannotRemoveAdmin() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + // 操作者:管理员 + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build()); + // 目标:另一个管理员 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .role(ImGroupMemberRoleEnum.ADMIN.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(List.of(2L)); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.removeGroupMember(1L, reqVO)); + assertEquals(GROUP_REMOVE_ADMIN_DENIED.getCode(), exception.getCode()); + verify(groupMemberService, never()).removeGroupMembers(anyLong(), anyCollection()); + } + } + + @Test + public void testRemoveGroupMember_ownerCannotBeRemoved() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + // 操作者:群主(不能踢自己;这里换成另一个 userId 的群主语义 — 用 ADMIN 操作群主) + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()); + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( + ImGroupMemberDO.builder().groupId(10L).userId(99L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build())); + + ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(List.of(99L)); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.removeGroupMember(1L, reqVO)); + assertEquals(GROUP_REMOVE_OWNER_DENIED.getCode(), exception.getCode()); + } + } + + @Test + public void testRemoveGroupMember_skipInactiveTargets() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + // 操作者:群主 + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()); + // 目标:2L 有效普通成员;3L 已退群(DISABLE)的历史管理员,应被跳过而非拦截整批 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupMemberDO.builder().groupId(10L).userId(3L) + .role(ImGroupMemberRoleEnum.ADMIN.getRole()) + .status(CommonStatusEnum.DISABLE.getStatus()).build())); + + ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); + reqVO.setGroupId(10L); + reqVO.setMemberUserIds(List.of(2L, 3L)); + + groupService.removeGroupMember(1L, reqVO); + + // 仅有效成员 2L 进入移除,已退群的 3L 被跳过 + ArgumentCaptor removeCaptor = ArgumentCaptor.forClass(Collection.class); + verify(groupMemberService).removeGroupMembers(eq(10L), removeCaptor.capture()); + assertEquals(Set.of(2L), Set.copyOf(removeCaptor.getValue())); + } + } + + // ========== addGroupAdmin ========== + + @Test + public void testAddGroupAdmin_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); + // 目标 3 是普通成员 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( + ImGroupMemberDO.builder().userId(3L).status(CommonStatusEnum.ENABLE.getStatus()) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build())); + // 群里已有 1 个 ADMIN,1 + 1 ≤ 3 不超上限 + when(groupMemberService.getGroupMemberCountByRole(10L, ImGroupMemberRoleEnum.ADMIN.getRole())) + .thenReturn(1L); + when(groupMemberService.updateGroupMemberRole(eq(10L), anyCollection(), + eq(ImGroupMemberRoleEnum.ADMIN.getRole()))).thenReturn(1); + + ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); + reqVO.setId(10L); + reqVO.setUserIds(List.of(3L)); + + groupService.addGroupAdmin(1L, reqVO); + + verify(groupMemberService).updateGroupMemberRole(eq(10L), argThat((Set ids) -> + ids.size() == 1 && ids.contains(3L)), + eq(ImGroupMemberRoleEnum.ADMIN.getRole())); + } + } + + @Test + public void testAddGroupAdmin_exceedsLimit() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( + ImGroupMemberDO.builder().userId(5L).status(CommonStatusEnum.ENABLE.getStatus()) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build())); + // 群里已有 3 个 ADMIN(达到上限),再加 1 会超 + when(groupMemberService.getGroupMemberCountByRole(10L, ImGroupMemberRoleEnum.ADMIN.getRole())) + .thenReturn(3L); + + ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); + reqVO.setId(10L); + reqVO.setUserIds(List.of(5L)); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.addGroupAdmin(1L, reqVO)); + assertEquals(GROUP_ADMIN_MAX_LIMIT.getCode(), exception.getCode()); + verify(groupMemberService, never()).updateGroupMemberRole(anyLong(), anyCollection(), anyInt()); + } + } + + @Test + public void testAddGroupAdmin_targetIsOwner() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( + ImGroupMemberDO.builder().userId(1L).status(CommonStatusEnum.ENABLE.getStatus()) + .role(ImGroupMemberRoleEnum.OWNER.getRole()).build())); + + ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); + reqVO.setId(10L); + reqVO.setUserIds(List.of(1L)); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.addGroupAdmin(1L, reqVO)); + assertEquals(GROUP_ADMIN_TARGET_IS_OWNER.getCode(), exception.getCode()); + } + } + + @Test + public void testAddGroupAdmin_idempotentSkipWhenAlreadyAdmin() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); + // 目标已是 ADMIN:再加无需操作 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( + ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus()) + .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build())); + + ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); + reqVO.setId(10L); + reqVO.setUserIds(List.of(2L)); + + groupService.addGroupAdmin(1L, reqVO); + + verify(groupMemberService, never()).updateGroupMemberRole(anyLong(), anyCollection(), anyInt()); + verify(groupMemberService, never()).getGroupMemberCountByRole(anyLong(), anyInt()); + } + } + + // ========== removeGroupAdmin ========== + + @Test + public void testRemoveGroupAdmin_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( + ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus()) + .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build())); + when(groupMemberService.updateGroupMemberRole(eq(10L), anyCollection(), + eq(ImGroupMemberRoleEnum.NORMAL.getRole()))).thenReturn(1); + + ImGroupAdminRemoveReqVO reqVO = new ImGroupAdminRemoveReqVO(); + reqVO.setId(10L); + reqVO.setUserIds(List.of(2L)); + + groupService.removeGroupAdmin(1L, reqVO); + + verify(groupMemberService).updateGroupMemberRole(eq(10L), argThat((Set ids) -> + ids.size() == 1 && ids.contains(2L)), + eq(ImGroupMemberRoleEnum.NORMAL.getRole())); + } + } + + @Test + public void testRemoveGroupAdmin_idempotentSkipWhenAlreadyMember() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); + // 目标已是 MEMBER:撤销无需操作 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( + ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus()) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build())); + + ImGroupAdminRemoveReqVO reqVO = new ImGroupAdminRemoveReqVO(); + reqVO.setId(10L); + reqVO.setUserIds(List.of(2L)); + + groupService.removeGroupAdmin(1L, reqVO); + + verify(groupMemberService, never()).updateGroupMemberRole(anyLong(), anyCollection(), anyInt()); + } + } + + // ========== transferGroupOwner ========== + + @Test + public void testTransferGroupOwner_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); + when(groupMemberService.validateMemberInGroup(10L, 2L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()); + when(groupMemberService.updateGroupMemberRole(eq(10L), eq(Set.of(2L)), + eq(ImGroupMemberRoleEnum.OWNER.getRole()))).thenReturn(1); + when(groupMemberService.updateGroupMemberRole(eq(10L), eq(Set.of(1L)), + eq(ImGroupMemberRoleEnum.NORMAL.getRole()))).thenReturn(1); + + ImGroupTransferOwnerReqVO reqVO = new ImGroupTransferOwnerReqVO(); + reqVO.setId(10L); + reqVO.setNewOwnerUserId(2L); + + groupService.transferGroupOwner(1L, reqVO); + + // 群表 owner 切换 + ArgumentCaptor groupCaptor = ArgumentCaptor.forClass(ImGroupDO.class); + verify(groupMapper).updateById(groupCaptor.capture()); + assertEquals(2L, groupCaptor.getValue().getOwnerUserId()); + // 旧群主 → MEMBER;新群主 → OWNER + verify(groupMemberService).updateGroupMemberRole(eq(10L), eq(Set.of(1L)), + eq(ImGroupMemberRoleEnum.NORMAL.getRole())); + verify(groupMemberService).updateGroupMemberRole(eq(10L), eq(Set.of(2L)), + eq(ImGroupMemberRoleEnum.OWNER.getRole())); + } + } + + @Test + public void testTransferGroupOwner_toSelf() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); + + ImGroupTransferOwnerReqVO reqVO = new ImGroupTransferOwnerReqVO(); + reqVO.setId(10L); + reqVO.setNewOwnerUserId(1L); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.transferGroupOwner(1L, reqVO)); + assertEquals(GROUP_TRANSFER_OWNER_TO_SELF.getCode(), exception.getCode()); + verify(groupMapper, never()).updateById(any(ImGroupDO.class)); + } + } + + @Test + public void testTransferGroupOwner_notOwner() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); + + ImGroupTransferOwnerReqVO reqVO = new ImGroupTransferOwnerReqVO(); + reqVO.setId(10L); + reqVO.setNewOwnerUserId(2L); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.transferGroupOwner(1L, reqVO)); + assertEquals(GROUP_NOT_OWNER.getCode(), exception.getCode()); + } + } + + // ========== banGroup ========== + + @Test + public void testBanGroup_dissolved() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).status(CommonStatusEnum.DISABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ImGroupManagerBanReqVO reqVO = new ImGroupManagerBanReqVO(); + reqVO.setId(10L).setReason("违规"); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.banGroup(1L, reqVO)); + assertEquals(GROUP_DISSOLVED.getCode(), exception.getCode()); + verify(groupMapper, never()).updateById(any(ImGroupDO.class)); + verify(groupMessageService, never()).sendGroupMessage(anyLong(), any(ImGroupMessageSendDTO.class)); + } + } + + @Test + public void testBanGroup_alreadyBannedSkip() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).banned(true) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ImGroupManagerBanReqVO reqVO = new ImGroupManagerBanReqVO(); + reqVO.setId(10L).setReason("违规"); + + groupService.banGroup(1L, reqVO); + + verify(groupMapper, never()).updateById(any(ImGroupDO.class)); + verify(groupMessageService, never()).sendGroupMessage(anyLong(), any(ImGroupMessageSendDTO.class)); + } + } + + // ========== muteMember ========== + + @Test + public void testMuteMember_normalCannotMuteAdmin() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()); + when(groupMemberService.validateMemberInGroup(10L, 2L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build()); + + ImGroupMuteMemberReqVO reqVO = new ImGroupMuteMemberReqVO(); + reqVO.setId(10L).setUserId(2L).setMutedSeconds(60); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.muteMember(1L, reqVO)); + assertEquals(GROUP_NOT_OWNER_OR_ADMIN.getCode(), exception.getCode()); + verify(groupMemberService, never()).updateGroupMemberMuteEndTime(anyLong(), anyLong(), any()); + verify(groupMessageService, never()).sendGroupMessage(anyLong(), any(ImGroupMessageSendDTO.class)); + } + } + + // ========== validateGroupExists ========== + + @Test + public void testValidateGroupExists_notExists() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + when(groupMapper.selectById(10L)).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.validateGroupExists(10L)); + assertEquals(GROUP_NOT_EXISTS.getCode(), exception.getCode()); + } + } + + @Test + public void testValidateGroupExists_banned() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L).banned(true) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.validateGroupExists(10L)); + assertEquals(GROUP_BANNED.getCode(), exception.getCode()); + } + } + + @Test + public void testValidateGroupExists_dissolved() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + ImGroupDO group = ImGroupDO.builder().id(10L) + .status(CommonStatusEnum.DISABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupService.validateGroupExists(10L)); + assertEquals(GROUP_DISSOLVED.getCode(), exception.getCode()); + } + } + + // ========== getMyGroupList ========== + + @Test + public void testGetMyGroupList_noMembers() { + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(new ArrayList<>()); + + List result = groupService.getMyGroupList(1L); + assertTrue(result.isEmpty()); + verify(groupMapper, never()).selectByIds(anyCollection()); + } + + @Test + public void testGetMyGroupList_success() { + // 曾经加入的所有群(含退群) + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(new ArrayList<>(List.of( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupMemberDO.builder().groupId(20L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupMemberDO.builder().groupId(30L).userId(1L) + .status(CommonStatusEnum.DISABLE.getStatus()).build() + ))); + List groups = List.of( + ImGroupDO.builder().id(10L).status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupDO.builder().id(20L).status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupDO.builder().id(30L).status(CommonStatusEnum.ENABLE.getStatus()).build() + ); + when(groupMapper.selectByIds(anyCollection())).thenReturn(groups); + + List result = groupService.getMyGroupList(1L); + assertEquals(3, result.size()); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImplTest.java new file mode 100644 index 0000000000..bbe516a81e --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImplTest.java @@ -0,0 +1,94 @@ +package cn.iocoder.yudao.module.im.service.message; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import cn.iocoder.yudao.module.im.dal.mysql.message.ImChannelMessageMapper; +import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link ImChannelMessageServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImChannelMessageServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImChannelMessageServiceImpl channelMessageService; + + @Mock + private ImChannelMessageMapper channelMessageMapper; + @Mock + private ImChannelMaterialService channelMaterialService; + @Mock + private ImWebSocketService webSocketService; + @Mock + private ImConversationReadService conversationReadService; + + @Test + public void testReadChannelMessages_messageNotExists() { + // 准备:messageId 不存在(伪造 / 未来 id) + when(channelMessageMapper.selectById(999L)).thenReturn(null); + + // 调用 + channelMessageService.readChannelMessages(1L, 10L, 999L); + + // 断言:不推进读位置 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + } + + @Test + public void testReadChannelMessages_wrongChannel() { + // 准备:消息属于别的频道 + ImChannelMessageDO message = ImChannelMessageDO.builder() + .id(100L).channelId(20L).sendTime(LocalDateTime.now()).build(); + when(channelMessageMapper.selectById(100L)).thenReturn(message); + + // 调用:声称读的是频道 10 + channelMessageService.readChannelMessages(1L, 10L, 100L); + + // 断言:不推进读位置 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + } + + @Test + public void testReadChannelMessages_notVisible() { + // 准备:定向消息,接收人不含当前用户 + ImChannelMessageDO message = ImChannelMessageDO.builder() + .id(100L).channelId(10L).receiverUserIds(List.of(2L, 3L)).sendTime(LocalDateTime.now()).build(); + when(channelMessageMapper.selectById(100L)).thenReturn(message); + + // 调用 + channelMessageService.readChannelMessages(1L, 10L, 100L); + + // 断言:不推进读位置 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + } + + @Test + public void testReadChannelMessages_notAdvanced() { + // 准备:消息真实可见,但读位置未前进(updateConversationReadPosition 返回 false) + ImChannelMessageDO message = ImChannelMessageDO.builder() + .id(100L).channelId(10L).sendTime(LocalDateTime.now()).build(); + when(channelMessageMapper.selectById(100L)).thenReturn(message); + when(conversationReadService.updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong())) + .thenReturn(false); + + // 调用 + channelMessageService.readChannelMessages(1L, 10L, 100L); + + // 断言:读位置未前进 → 不推 READ 事件 + verify(webSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java new file mode 100644 index 0000000000..ffa1bb5d31 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java @@ -0,0 +1,1052 @@ +package cn.iocoder.yudao.module.im.service.message; + +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; +import cn.iocoder.yudao.module.im.service.group.ImGroupService; +import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; +import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; + +import java.time.LocalDateTime; +import java.util.*; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link ImGroupMessageServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImGroupMessageServiceImpl groupMessageService; + + @Mock + private ImGroupMessageMapper groupMessageMapper; + @Mock + private ImGroupService groupService; + @Mock + private ImGroupMemberService groupMemberService; + @Mock + private ImSensitiveWordService sensitiveWordService; + @Mock + private ImConversationReadService conversationReadService; + @Mock + private ImWebSocketService imWebSocketService; + + @Spy + private ImProperties imProperties = new ImProperties(); + + private ImGroupMessageSendReqVO buildSendReqVO() { + ImGroupMessageSendReqVO reqVO = new ImGroupMessageSendReqVO(); + reqVO.setClientMessageId("test-uuid-group-001"); + reqVO.setGroupId(10L); + reqVO.setType(ImContentTypeEnum.TEXT.getType()); + reqVO.setContent("{\"content\":\"群聊你好\"}"); + return reqVO; + } + + // ========== 发送测试 ========== + + @Test + public void testSendMessage_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) + .thenReturn(groupMessageService); + + // 准备 + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + reqVO.setReceipt(true); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")) + .thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + group.setName("测试群"); + when(groupService.validateGroupExists(10L)).thenReturn(group); + + ImGroupMemberDO member = ImGroupMemberDO.builder() + .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) + .thenReturn(List.of(1L, 2L, 3L)); + when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenAnswer(invocation -> { + ImGroupMessageDO msg = invocation.getArgument(0); + msg.setId(99L); + return 1; + }); + + // 调用 + ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); + + // 断言 + assertNotNull(result); + assertEquals(1L, result.getSenderId()); + assertEquals(10L, result.getGroupId()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.getStatus()); + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus()); + + // 验证推送给 3 个群成员(含发送者自己,用于多端同步) + ArgumentCaptor payloadCaptor = + ArgumentCaptor.forClass(ImGroupMessageNotification.class); + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> + ids.size() == 3 && ids.contains(1L) && ids.contains(2L) && ids.contains(3L)), + eq(ImConversationTypeEnum.GROUP.getType()), eq(ImContentTypeEnum.TEXT.getType()), + payloadCaptor.capture()); + ImGroupMessageNotification payload = payloadCaptor.getValue(); + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), payload.getReceiptStatus()); + assertEquals(0, payload.getReadCount()); + assertEquals(List.of(1L, 2L, 3L), payload.getReceiverUserIds()); + } + } + + @Test + public void testSendMessage_senderAlwaysVisible() { + // 边界:成员缓存漏掉发送者(理论上 validateMemberInGroup 已挡,这里纯防御);发送者仍必须固化进 receiver_user_ids + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) + .thenReturn(groupMessageService); + + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")) + .thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + group.setName("测试群"); + when(groupService.validateGroupExists(10L)).thenReturn(group); + ImGroupMemberDO member = ImGroupMemberDO.builder() + .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + // 成员缓存只返回 {2,3},漏掉发送者 1 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) + .thenReturn(List.of(2L, 3L)); + when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenAnswer(invocation -> { + ImGroupMessageDO msg = invocation.getArgument(0); + msg.setId(99L); + return 1; + }); + + ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); + + // 断言:固化快照必含发送者,推送目标补回发送者 = {1,2,3} + assertTrue(result.getReceiverUserIds().contains(1L), "发送者必须在 receiver_user_ids 快照内"); + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> + ids.size() == 3 && ids.contains(1L) && ids.contains(2L) && ids.contains(3L)), + anyInt(), anyInt(), any()); + } + } + + @Test + public void testSendMessage_clientMessageIdIdempotent() { + // 准备 + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + ImGroupMessageDO existing = ImGroupMessageDO.builder() + .id(100L).clientMessageId("test-uuid-group-001").senderId(1L).groupId(10L) + .type(0).content("{\"content\":\"群聊你好\"}").status(0) + .sendTime(LocalDateTime.now()).build(); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")) + .thenReturn(existing); + + // 调用 + ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); + + // 断言 + assertEquals(100L, result.getId()); + verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); + } + + @Test + public void testSendMessage_notInGroup() { + // 准备 + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")) + .thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + when(groupService.validateGroupExists(10L)).thenReturn(group); + when(groupMemberService.validateMemberInGroup(10L, 1L)) + .thenThrow(new ServiceException(GROUP_MEMBER_NOT_IN_GROUP.getCode(), GROUP_MEMBER_NOT_IN_GROUP.getMsg())); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.sendGroupMessage(1L, reqVO)); + assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); + } + + @Test + public void testSendMessage_quoteDirectedMessageRejected() { + // 准备:发送人 1 在群 10,当前有效成员(广播受众)= {1,2,3} + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + reqVO.setContent("{\"content\":\"引用\",\"quote\":{\"messageId\":500}}"); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")).thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + when(groupService.validateGroupExists(10L)).thenReturn(group); + ImGroupMemberDO member = ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L, 3L)); + // 被引用原消息:定向给 {1,2}(对发送人 1 可见,但不覆盖广播受众 {1,2,3}) + ImGroupMessageDO original = ImGroupMessageDO.builder().id(500L).groupId(10L).senderId(2L) + .status(ImMessageStatusEnum.NORMAL.getStatus()).receiverUserIds(List.of(1L, 2L)) + .type(ImContentTypeEnum.TEXT.getType()).content("{\"content\":\"密\"}") + .sendTime(LocalDateTime.now()).build(); + when(groupMessageMapper.selectById(500L)).thenReturn(original); + + // 调用并断言:引用定向消息被拒绝,避免原文随广播泄漏给全群 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.sendGroupMessage(1L, reqVO)); + assertEquals(MESSAGE_QUOTE_INVALID.getCode(), exception.getCode()); + verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); + } + + @Test + public void testSendMessage_quoteBroadcastAccepted() { + // 准备:广播受众 {1,2,3},被引用原消息快照覆盖全部受众 → 允许引用 + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + reqVO.setContent("{\"content\":\"引用\",\"quote\":{\"messageId\":500}}"); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")).thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + when(groupService.validateGroupExists(10L)).thenReturn(group); + ImGroupMemberDO member = ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L, 3L)); + ImGroupMessageDO original = ImGroupMessageDO.builder().id(500L).groupId(10L).senderId(2L) + .status(ImMessageStatusEnum.NORMAL.getStatus()).receiverUserIds(List.of(1L, 2L, 3L)) + .type(ImContentTypeEnum.TEXT.getType()).content("{\"content\":\"公开\"}") + .sendTime(LocalDateTime.now()).build(); + when(groupMessageMapper.selectById(500L)).thenReturn(original); + when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenAnswer(invocation -> { + invocation.getArgument(0).setId(99L); + return 1; + }); + + // 调用并断言:正常入库 + ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); + assertEquals(99L, result.getId()); + verify(groupMessageMapper).insert(any(ImGroupMessageDO.class)); + } + + // ========== pull 测试 ========== + + @Test + public void testPullMessages_doesNotOverwriteStatus() { + // 准备:拉到两条消息;Phase 2 起 pull 不再按读位置覆盖 status,已读由前端按读位置判断 + LocalDateTime now = LocalDateTime.now(); + ImGroupMemberDO member = ImGroupMemberDO.builder() + .groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(now.minusDays(10)).build(); + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(List.of(member)); + + ImGroupMessageDO low = ImGroupMessageDO.builder() + .id(5L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .sendTime(now.minusHours(2)).build(); + ImGroupMessageDO high = ImGroupMessageDO.builder() + .id(10L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .sendTime(now.minusHours(1)).build(); + when(groupMessageMapper.selectListByMinId(eq(1L), anyCollection(), eq(0L), + any(LocalDateTime.class), eq(100))) + .thenReturn(List.of(low, high)); + + List result = groupMessageService.pullGroupMessageList(1L, 0L, 100); + + // 断言:status 保持 DB 原值(都 NORMAL),pull 不覆盖 + assertEquals(2, result.size()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.get(0).getStatus()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.get(1).getStatus()); + } + + @Test + public void testPullMessages_receiptReadCountForSender() { + // 准备:本人发送的回执消息,可见成员 {1,2,3},分母排除发送者后为 {2,3} + LocalDateTime now = LocalDateTime.now(); + ImGroupMemberDO member = ImGroupMemberDO.builder() + .groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(now.minusDays(10)).build(); + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(List.of(member)); + + ImGroupMessageDO receiptMsg = ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(1L) + .receiverUserIds(List.of(1L, 2L, 3L)) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .receiptStatus(ImMessageReceiptStatusEnum.PENDING.getStatus()) + .sendTime(now.minusHours(1)).build(); + when(groupMessageMapper.selectListByMinId(eq(1L), anyCollection(), eq(0L), + any(LocalDateTime.class), eq(100))) + .thenReturn(List.of(receiptMsg)); + + Map positions = new HashMap<>(); + positions.put(1L, 100L); + positions.put(2L, 100L); + positions.put(3L, 50L); + when(conversationReadService.getUserReadMessageIdMap(eq(ImConversationTypeEnum.GROUP.getType()), eq(10L))) + .thenReturn(positions); + + List allMembers = List.of( + member, + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(now.minusDays(20)).build(), + ImGroupMemberDO.builder().groupId(10L).userId(3L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(now.minusDays(20)).build() + ); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(allMembers); + + List result = groupMessageService.pullGroupMessageList(1L, 0L, 100); + + // 断言:分母 {2,3},仅用户 2 读到 >=100 → readCount==1 + assertEquals(1, result.size()); + assertEquals(1, result.get(0).getReadCount()); + } + + @Test + public void testPullMessages_noReceiptMessageSkipsReadCount() { + // 准备:本人发送但不需要回执(NO_RECEIPT)的消息——不补 readCount,status 也不被覆盖 + LocalDateTime now = LocalDateTime.now(); + ImGroupMemberDO member = ImGroupMemberDO.builder() + .groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(now.minusDays(10)).build(); + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(List.of(member)); + + ImGroupMessageDO noReceiptMsg = ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(1L) + .receiverUserIds(List.of(1L, 2L, 3L)) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .receiptStatus(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus()) + .sendTime(now.minusHours(1)).build(); + when(groupMessageMapper.selectListByMinId(eq(1L), anyCollection(), eq(0L), + any(LocalDateTime.class), eq(100))) + .thenReturn(List.of(noReceiptMsg)); + + List result = groupMessageService.pullGroupMessageList(1L, 0L, 100); + + // 断言:status 保持 NORMAL(pull 不覆盖),NO_RECEIPT 不补 readCount,也不查 readCount 相关的读位置映射 + assertEquals(1, result.size()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.get(0).getStatus()); + assertNull(result.get(0).getReadCount()); + verify(conversationReadService, never()).getUserReadMessageIdMap(anyInt(), anyLong()); + } + + // ========== 撤回测试 ========== + + @Test + public void testRecallMessage_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) + .thenReturn(groupMessageService); + + // 准备:消息由用户 1 发送,刚发送 5 分钟内 + ImGroupMessageDO message = ImGroupMessageDO.builder() + .id(50L).senderId(1L).groupId(10L) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .sendTime(LocalDateTime.now()).build(); + when(groupMessageMapper.selectById(50L)).thenReturn(message); + // 撤回前需要校验用户仍在群中 + ImGroupMemberDO selfMember = ImGroupMemberDO.builder() + .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(selfMember); + when(groupMessageMapper.updateById(any(ImGroupMessageDO.class))).thenReturn(1); + when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenReturn(1); + + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) + .thenReturn(List.of(1L, 2L)); + + // 调用 + ImGroupMessageDO result = groupMessageService.recallGroupMessage(1L, 50L); + + // 断言:返回撤回消息 + assertNotNull(result); + // 验证:更新原消息状态 + 插入 RecallMessage + verify(groupMessageMapper).updateById(any(ImGroupMessageDO.class)); + verify(groupMessageMapper).insert(any(ImGroupMessageDO.class)); + // 验证:给 2 个活跃成员推送撤回提示 + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> + ids.size() == 2 && ids.contains(1L) && ids.contains(2L)), + anyInt(), anyInt(), any()); + } + } + + @Test + public void testRecallMessage_notOwn() { + // 准备 + ImGroupMessageDO message = ImGroupMessageDO.builder() + .id(50L).senderId(2L).groupId(10L) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .sendTime(LocalDateTime.now()).build(); + when(groupMessageMapper.selectById(50L)).thenReturn(message); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.recallGroupMessage(1L, 50L)); + assertEquals(MESSAGE_RECALL_DENIED.getCode(), exception.getCode()); + } + + @Test + public void testRecallMessage_alreadyRecalled() { + // 准备 + ImGroupMessageDO message = ImGroupMessageDO.builder() + .id(50L).senderId(1L).groupId(10L) + .status(ImMessageStatusEnum.RECALL.getStatus()) + .sendTime(LocalDateTime.now()).build(); + when(groupMessageMapper.selectById(50L)).thenReturn(message); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.recallGroupMessage(1L, 50L)); + assertEquals(MESSAGE_ALREADY_RECALLED.getCode(), exception.getCode()); + } + + @Test + public void testRecallMessage_timeout() { + // 准备:消息发送于 10 分钟前(超过 5 分钟窗口) + ImGroupMessageDO message = ImGroupMessageDO.builder() + .id(50L).senderId(1L).groupId(10L) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .sendTime(LocalDateTime.now().minusMinutes(10)).build(); + when(groupMessageMapper.selectById(50L)).thenReturn(message); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.recallGroupMessage(1L, 50L)); + assertEquals(MESSAGE_RECALL_TIMEOUT.getCode(), exception.getCode()); + } + + // ========== 群已读测试 ========== + + @Test + public void testReadMessages_messageInvisible() { + // 准备:消息不在用户接收快照内 + when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(2L)) + .sendTime(LocalDateTime.now()).build()); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); + assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + } + + @Test + public void testGetReadUserIds_withVisibleScope() { + // 准备:发送者用户 5 是群成员 + ImGroupMemberDO currentMember = ImGroupMemberDO.builder() + .groupId(10L).userId(5L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(); + when(groupMemberService.validateMemberInGroup(10L, 5L)).thenReturn(currentMember); + + // 准备:消息由用户 5 发,快照可见范围为 {1,2,5}(用户 3 不在快照内 → 不可见) + ImGroupMessageDO message = ImGroupMessageDO.builder() + .id(80L).groupId(10L).senderId(5L) + .receiverUserIds(List.of(1L, 2L, 5L)) + .sendTime(LocalDateTime.of(2026, 4, 12, 10, 0, 0)).build(); + when(groupMessageMapper.selectById(80L)).thenReturn(message); + + // 准备:群成员一览 + // 用户 1: 在快照内 + // 用户 2: 在快照内 + // 用户 3: 不在快照内 → 不可见 + // 用户 5: 发送者,不计入回执 + List allMembers = List.of( + currentMember, + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(), + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(), + ImGroupMemberDO.builder().groupId(10L).userId(3L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(), + ImGroupMemberDO.builder().groupId(10L).userId(5L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build() // 发送者 + ); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(allMembers); + + // 准备:已读位置 — 用户 1 读到 100, 用户 2 读到 50, 用户 3 读到 200 + Map positions = new HashMap<>(); + positions.put(1L, 100L); + positions.put(2L, 50L); + positions.put(3L, 200L); + when(conversationReadService.getUserReadMessageIdMap(eq(ImConversationTypeEnum.GROUP.getType()), eq(10L))) + .thenReturn(positions); + + // 调用:查询 messageId=80 的已读用户 + List readUsers = groupMessageService.getGroupReadUserIds(5L, 10L, 80L); + + // 断言: + // 用户 1: 可见 + readMaxId=100>=80 → 已读 ✓ + // 用户 2: 可见 + readMaxId=50<80 → 未读 ✗ + // 用户 3: 不在快照内 → 不可见 → 不算 + // 用户 5: 发送者 → 排除 + assertEquals(1, readUsers.size()); + assertTrue(readUsers.contains(1L)); + assertFalse(readUsers.contains(2L)); + assertFalse(readUsers.contains(3L)); + assertFalse(readUsers.contains(5L)); + } + + @Test + public void testGetReadUserIds_notInGroup() { + // 准备:当前用户不在群中 + when(groupMemberService.validateMemberInGroup(10L, 99L)) + .thenThrow(new ServiceException(GROUP_MEMBER_NOT_IN_GROUP.getCode(), GROUP_MEMBER_NOT_IN_GROUP.getMsg())); + + // 调用并断言:越权校验 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.getGroupReadUserIds(99L, 10L, 80L)); + assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); + } + + // ========== 发送边界 ========== + + @Test + public void testSendMessage_receiptPending() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) + .thenReturn(groupMessageService); + + // 准备:reqVO.receipt=true + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + reqVO.setReceipt(true); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")) + .thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + when(groupService.validateGroupExists(10L)).thenReturn(group); + ImGroupMemberDO member = ImGroupMemberDO.builder() + .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L)); + when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenReturn(1); + + // 调用 + ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); + + // 断言:receipt=true → 回执状态为 PENDING + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus()); + } + } + + @Test + public void testSendMessage_sensitiveWordBlocked() { + // 准备:文本消息命中敏感词 + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")) + .thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + when(groupService.validateGroupExists(10L)).thenReturn(group); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + doThrow(new ServiceException(MESSAGE_SENSITIVE_WORD_BLOCKED)) + .when(sensitiveWordService).validateText(reqVO.getContent()); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.sendGroupMessage(1L, reqVO)); + assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode()); + // 断言:不入库、不推送 + verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); + verify(imWebSocketService, never()).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any()); + } + + // ========== 撤回边界 ========== + + @Test + public void testRecallMessage_notExists() { + when(groupMessageMapper.selectById(50L)).thenReturn(null); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.recallGroupMessage(1L, 50L)); + assertEquals(MESSAGE_NOT_EXISTS.getCode(), exception.getCode()); + } + + @Test + public void testRecallMessage_senderNotInGroup() { + // 准备:消息合法、可撤回时间内,但发送人已退群 + ImGroupMessageDO message = ImGroupMessageDO.builder() + .id(50L).senderId(1L).groupId(10L) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .sendTime(LocalDateTime.now()).build(); + when(groupMessageMapper.selectById(50L)).thenReturn(message); + when(groupMemberService.validateMemberInGroup(10L, 1L)) + .thenThrow(new ServiceException(GROUP_MEMBER_NOT_IN_GROUP.getCode(), GROUP_MEMBER_NOT_IN_GROUP.getMsg())); + + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.recallGroupMessage(1L, 50L)); + assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); + // 断言:不执行更新、不插 tip + verify(groupMessageMapper, never()).updateById(any(ImGroupMessageDO.class)); + verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); + } + + // ========== 已读事件 ========== + + @Test + public void testReadGroupMessages_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) + .thenReturn(groupMessageService); + + // 准备:消息对用户可见;已读位置从 5 前进到 100 + when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) + .sendTime(LocalDateTime.now()).build()); + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(5L); + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L)) + .thenReturn(true); + // readGroupMessageEvent 内部会调 selectListByGroupIdAndPendingReceipt → 返回空简化流程 + when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 5L, 100L)).thenReturn(List.of()); + + // 调用 + groupMessageService.readGroupMessages(1L, 10L, 100L); + + // 断言:已读位置更新 + READ 事件 + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + verify(groupMemberService, never()).validateMemberInGroup(10L, 1L); + } + } + + @Test + public void testReadGroupMessages_disabled() { + // 准备:关闭群聊已读 + imProperties.getMessage().setGroupReadEnabled(false); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); + assertEquals(MESSAGE_GROUP_READ_DISABLED.getCode(), exception.getCode()); + // 断言:已读位置不写、不推送 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + @Test + public void testGetGroupReadUserIds_disabled() { + // 准备:关闭群聊已读 + imProperties.getMessage().setGroupReadEnabled(false); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.getGroupReadUserIds(1L, 10L, 100L)); + assertEquals(MESSAGE_GROUP_READ_DISABLED.getCode(), exception.getCode()); + } + + @Test + public void testSendGroupMessage_groupReadDisabled_forcesNoReceipt() { + // 关闭群已读:发送方传 receipt=true 也强制落 NO_RECEIPT + imProperties.getMessage().setGroupReadEnabled(false); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) + .thenReturn(groupMessageService); + + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + reqVO.setReceipt(true); // 发送方明确要求回执 + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")) + .thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + when(groupService.validateGroupExists(10L)).thenReturn(group); + ImGroupMemberDO member = ImGroupMemberDO.builder() + .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L)); + + ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); + + assertEquals(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), result.getReceiptStatus(), + "群已读关闭时即使发送方传 receipt=true 也强制落 NO_RECEIPT"); + } + } + + @Test + public void testReadGroupMessages_cursorAlreadyAhead() { + // 准备:已读游标已 >= 目标,直接返回 + when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) + .sendTime(LocalDateTime.now()).build()); + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(200L); + // CAS 命中旧位置 200 > 100,单调更新不前进,返回 false + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L)) + .thenReturn(false); + + // 调用 + groupMessageService.readGroupMessages(1L, 10L, 100L); + + // 断言:调用了 CAS 更新但读位置未前进 → 不推送 + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + @Test + public void testReadGroupMessages_messageNotInGroup() { + // 准备:messageId 属于其它群 + when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() + .id(100L).groupId(20L).senderId(2L).sendTime(LocalDateTime.now()).build()); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); + assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + } + + @Test + public void testReadGroupMessages_quitMemberVisibleMessage_success() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) + .thenReturn(groupMessageService); + + // 准备:历史消息的接收快照包含用户 + when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L, 2L)) + .sendTime(LocalDateTime.now().minusMinutes(1)).build()); + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(0L); + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L)) + .thenReturn(true); + when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)).thenReturn(List.of()); + + // 调用 + groupMessageService.readGroupMessages(1L, 10L, 100L); + + // 断言:不校验当前在群,直接推进已读位置 + verify(groupMemberService, never()).validateMemberInGroup(10L, 1L); + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); + } + } + + @Test + public void testReadGroupMessages_quitMemberInvisibleMessage() { + // 准备:消息不在历史接收快照内 + when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(2L)) + .sendTime(LocalDateTime.now()).build()); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); + assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + } + + // ========== 回执状态迁移 ========== + + @Test + public void testReadGroupMessageEvent_receiptDoneTransition() { + // 准备:消息 100 发送者 5,群 10 活跃成员 {5,1,2},可见用户中除发送者外 {1,2} + // 已读位置 1→100、2→100 → 全部已读 → 迁移为 DONE + ImGroupMessageDO pending = ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(5L) + .receiverUserIds(List.of(5L, 1L, 2L)) + .sendTime(LocalDateTime.now().minusMinutes(1)) + .receiptStatus(ImMessageReceiptStatusEnum.PENDING.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()).build(); + when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)) + .thenReturn(List.of(pending)); + List activeMembers = List.of( + ImGroupMemberDO.builder().groupId(10L).userId(5L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.now().minusDays(10)).build(), + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.now().minusDays(10)).build(), + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.now().minusDays(10)).build() + ); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(activeMembers); + Map positions = new HashMap<>(); + positions.put(1L, 100L); + positions.put(2L, 100L); + when(conversationReadService.getUserReadMessageIdMap(ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(positions); + + // 调用 + groupMessageService.readGroupMessageEvent(1L, 10L, 0L, 100L); + + // 断言:消息回执状态更新为 DONE + ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupMessageDO.class); + verify(groupMessageMapper).updateById(captor.capture()); + assertEquals(100L, captor.getValue().getId()); + assertEquals(ImMessageReceiptStatusEnum.DONE.getStatus(), captor.getValue().getReceiptStatus()); + // 断言:给消息发送方(5)推送 RECEIPT 事件 + verify(imWebSocketService).sendNotificationAsync(eq(5L), anyInt(), anyInt(), any()); + } + + @Test + public void testReadGroupMessageEvent_receiptStaysPendingWhenPartialRead() { + // 准备:用户 2 还没读到 100 → 仍为 PENDING,不更新 DB + ImGroupMessageDO pending = ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(5L) + .receiverUserIds(List.of(5L, 1L, 2L)) + .sendTime(LocalDateTime.now().minusMinutes(1)) + .receiptStatus(ImMessageReceiptStatusEnum.PENDING.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()).build(); + when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)) + .thenReturn(List.of(pending)); + List activeMembers = List.of( + ImGroupMemberDO.builder().groupId(10L).userId(5L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.now().minusDays(10)).build(), + ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.now().minusDays(10)).build(), + ImGroupMemberDO.builder().groupId(10L).userId(2L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.now().minusDays(10)).build() + ); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(activeMembers); + Map positions = new HashMap<>(); + positions.put(1L, 100L); + positions.put(2L, 50L); // 还没读到 100 + when(conversationReadService.getUserReadMessageIdMap(ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(positions); + + groupMessageService.readGroupMessageEvent(1L, 10L, 0L, 100L); + + // 断言:回执状态保持 PENDING,不更新 DB + verify(groupMessageMapper, never()).updateById(any(ImGroupMessageDO.class)); + // 仍然推送 RECEIPT 事件给发送方(携带已读人数) + verify(imWebSocketService).sendNotificationAsync(eq(5L), anyInt(), anyInt(), any()); + } + + // ========== 定向群消息 ========== + + @Test + public void testSendGroupMessage_noReceiverUserIds_broadcastsToAllMembers() { + // 准备:无定向 → 应发给所有 ENABLE 成员(含发送者自己,多端同步) + ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType()) + .setContent("{\"messageId\":1}"); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) + .thenReturn(List.of(1L, 2L, 3L, 4L)); + + groupMessageService.sendGroupMessage(1L, dto); + + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> + ids.size() == 4 && ids.contains(1L) && ids.contains(2L) + && ids.contains(3L) && ids.contains(4L)), + anyInt(), anyInt(), any()); + } + + @Test + public void testSendGroupMessage_withReceiverUserIds_onlyTargetedPlusSender() { + // 准备:定向给 {2,3},发送者 1;群成员 {1,2,3,4} + // 预期推送目标:{1,2,3}(发送者自己多端同步 + 定向列表);4 被过滤 + ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType()) + .setContent("{\"messageId\":1}") + .setReceiverUserIds(List.of(2L, 3L)); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) + .thenReturn(List.of(1L, 2L, 3L, 4L)); + + groupMessageService.sendGroupMessage(1L, dto); + + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> + ids.size() == 3 && ids.contains(1L) && ids.contains(2L) + && ids.contains(3L) && !ids.contains(4L)), + anyInt(), anyInt(), any()); + } + + @Test + public void testSendGroupMessage_withReceiverUserIds_senderAlwaysIncluded() { + // 边界:发送者不在群成员缓存里(理论上应先被 validateMemberInGroup 挡住,这里纯防御) + // 预期:定向用户 + 发送者自己(始终纳入快照保证多端同步);非定向成员 4 被过滤 + ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType()) + .setContent("{\"messageId\":1}") + .setReceiverUserIds(List.of(2L, 3L)); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) + .thenReturn(List.of(1L, 2L, 3L, 4L)); + + groupMessageService.sendGroupMessage(99L, dto); + + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> + ids.size() == 3 && ids.contains(2L) && ids.contains(3L) + && ids.contains(99L) && !ids.contains(4L)), + anyInt(), anyInt(), any()); + } + + // ========== DTO 群消息 ========== + + @Test + public void testSendGroupMessage_dto_persistsAndSerializesPojoContent() { + // 准备:persistent=true 类型 + POJO content + ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType()) + .setContent(new RecallMessage().setMessageId(50L)); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L)); + + groupMessageService.sendGroupMessage(1L, dto); + + // 断言:入库 + 系统字段兜底 + content 序列化为 JSON + receiptStatus=NO_RECEIPT + ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupMessageDO.class); + verify(groupMessageMapper).insert(captor.capture()); + ImGroupMessageDO message = captor.getValue(); + assertEquals(1L, message.getSenderId()); + assertEquals(10L, message.getGroupId()); + assertEquals(ImContentTypeEnum.RECALL.getType(), message.getType()); + assertEquals("{\"messageId\":50}", message.getContent()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), message.getStatus()); + assertEquals(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), message.getReceiptStatus()); + assertNotNull(message.getClientMessageId()); + assertNotNull(message.getSendTime()); + } + + @Test + public void testSendGroupMessage_dto_receiptPending() { + // 准备:dto.receipt=true → receiptStatus=PENDING + ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType()) + .setContent("{\"messageId\":50}").setReceipt(true); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L)); + + groupMessageService.sendGroupMessage(1L, dto); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupMessageDO.class); + verify(groupMessageMapper).insert(captor.capture()); + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), captor.getValue().getReceiptStatus()); + } + + @Test + public void testSendGroupMessage_dto_nonPersistentTypeNotInserted() { + // 准备:persistent=false 类型(RECEIPT 回执)→ 不入库,仅推送 + ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() + .setGroupId(10L).setType(ImContentTypeEnum.RECEIPT.getType()); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L)); + + groupMessageService.sendGroupMessage(1L, dto); + + verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); + verify(imWebSocketService).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any()); + } + + @Test + public void testSendGroupMessage_dto_groupMemberNicknameUpdateNotInserted() { + // 准备:成员昵称变更只做在线同步,不入库 + ImGroupMessageSendDTO dto = ImGroupMessageSendDTO.ofGroupMemberNicknameUpdate(10L, 1L, "群昵称"); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L)); + + groupMessageService.sendGroupMessage(1L, dto); + + verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> + ids.size() == 2 && ids.contains(1L) && ids.contains(2L)), + anyInt(), eq(ImContentTypeEnum.GROUP_MEMBER_NICKNAME_UPDATE.getType()), any()); + } + + @Test + public void testSendGroupMessage_threeArg_explicitTargetsBypassActiveMembers() { + // 准备:调用方传入显式 targets(解散场景成员已被批量 DISABLE,必须按移除前快照推送) + Set targets = Set.of(1L, 2L, 3L); + ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() + .setGroupId(10L).setType(ImContentTypeEnum.GROUP_DISSOLVE.getType()).setContent("{}"); + + groupMessageService.sendGroupMessage(1L, targets, dto); + + // 断言:不读取 active members,按调用方 targets 推送 + verify(groupMemberService, never()).getActiveGroupMemberUserIdsByGroupId(anyLong()); + verify(imWebSocketService).sendNotificationAsync(eq(targets), anyInt(), anyInt(), any()); + } + + // ========== getGroupMessageList ========== + + @Test + public void testGetGroupMessageList_delegatesToMapper() { + // 准备:可见性由 SQL 按 receiver_user_ids 快照过滤,Service 直接透传 mapper 结果 + ImGroupMemberDO member = ImGroupMemberDO.builder() + .id(50L).groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(LocalDateTime.now().minusDays(5)).build(); + when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); + + ImGroupMessageListReqVO reqVO = new ImGroupMessageListReqVO(); + reqVO.setGroupId(10L); + reqVO.setMaxId(100L); + reqVO.setLimit(20); + + List mockList = List.of( + ImGroupMessageDO.builder().id(99L).groupId(10L).senderId(2L) + .receiverUserIds(List.of(1L)).sendTime(LocalDateTime.now()).build() + ); + when(groupMessageMapper.selectHistoryListByUser(1L, 10L, 100L, 20)).thenReturn(mockList); + + // 调用 + List result = groupMessageService.getGroupMessageList(1L, reqVO); + + // 断言:透传到 mapper,参数一致 + assertEquals(1, result.size()); + assertEquals(99L, result.get(0).getId()); + verify(groupMessageMapper).selectHistoryListByUser(1L, 10L, 100L, 20); + } + + @Test + public void testGetGroupMessageList_notInGroup() { + // 准备:用户从未在群中(getGroupMember 返回 null) + ImGroupMessageListReqVO reqVO = new ImGroupMessageListReqVO(); + reqVO.setGroupId(10L); + reqVO.setLimit(20); + when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(null); + + // 调用并断言:鉴权失败 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.getGroupMessageList(1L, reqVO)); + assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); + // 断言:不查询 DB + verify(groupMessageMapper, never()).selectHistoryListByUser(anyLong(), anyLong(), anyLong(), anyInt()); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java new file mode 100644 index 0000000000..91436cf6d0 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java @@ -0,0 +1,445 @@ +package cn.iocoder.yudao.module.im.service.message; + +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; +import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; +import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; +import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReceiptNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImPrivateMessageNotification; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link ImPrivateMessageServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImPrivateMessageServiceImpl privateMessageService; + + @Mock + private ImPrivateMessageMapper privateMessageMapper; + @Mock + private ImFriendService friendService; + @Mock + private ImSensitiveWordService sensitiveWordService; + @Mock + private ImConversationReadService conversationReadService; + @Mock + private ImWebSocketService imWebSocketService; + + @Spy + private ImProperties imProperties = new ImProperties(); + + private ImPrivateMessageSendReqVO buildSendReqVO() { + ImPrivateMessageSendReqVO reqVO = new ImPrivateMessageSendReqVO(); + reqVO.setClientMessageId("test-uuid-001"); + reqVO.setReceiverId(2L); + reqVO.setType(ImContentTypeEnum.TEXT.getType()); + reqVO.setContent("{\"content\":\"你好\"}"); + return reqVO; + } + + // ========== 发送测试 ========== + + @Test + public void testSendMessage_success() { + // 准备 + ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); + when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) + .thenReturn(null); + when(privateMessageMapper.insert(any(ImPrivateMessageDO.class))).thenAnswer(invocation -> { + ImPrivateMessageDO msg = invocation.getArgument(0); + msg.setId(99L); + return 1; + }); + + // 调用 + ImPrivateMessageDO result = privateMessageService.sendPrivateMessage(1L, reqVO); + + // 断言 + assertNotNull(result); + assertEquals(1L, result.getSenderId()); + assertEquals(2L, result.getReceiverId()); + assertEquals(ImContentTypeEnum.TEXT.getType(), result.getType()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.getStatus()); + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus(), + "用户私聊消息默认需要回执(PENDING)"); + assertNotNull(result.getSendTime()); + + // 验证调用 + verify(friendService).validateFriend(1L, 2L); + verify(sensitiveWordService).validateText(reqVO.getContent()); + verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class)); + // 验证推送给接收方和发送方 + verify(imWebSocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any()); + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + } + + @Test + public void testSendMessage_clientMessageIdIdempotent() { + // 准备:模拟已存在消息 + ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); + ImPrivateMessageDO existingMessage = ImPrivateMessageDO.builder() + .id(100L).clientMessageId("test-uuid-001").senderId(1L).receiverId(2L) + .type(0).content("{\"content\":\"你好\"}").status(0) + .sendTime(LocalDateTime.now()).build(); + when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) + .thenReturn(existingMessage); + + // 调用 + ImPrivateMessageDO result = privateMessageService.sendPrivateMessage(1L, reqVO); + + // 断言:返回已存在的消息 + assertEquals(100L, result.getId()); + // 验证不会重复插入 + verify(privateMessageMapper, never()).insert(any(ImPrivateMessageDO.class)); + } + + @Test + public void testSendMessage_notFriend() { + // 准备 + ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); + when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) + .thenReturn(null); + doThrow(new ServiceException(FRIEND_NOT_FRIEND)) + .when(friendService).validateFriend(1L, 2L); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> privateMessageService.sendPrivateMessage(1L, reqVO)); + assertEquals(FRIEND_NOT_FRIEND.getCode(), exception.getCode()); + } + + // ========== pull 测试 ========== + + @Test + public void testPullMessages_success() { + // 准备 + List mockMessages = ListUtil.of( + ImPrivateMessageDO.builder().id(1L).senderId(1L).receiverId(2L).build(), + ImPrivateMessageDO.builder().id(2L).senderId(2L).receiverId(1L).build() + ); + when(privateMessageMapper.selectListByMinId(eq(1L), eq(0L), any(LocalDateTime.class), eq(100))) + .thenReturn(mockMessages); + + // 调用 + List result = privateMessageService.pullPrivateMessageList(1L, 0L, 100); + + // 断言 + assertEquals(2, result.size()); + } + + @Test + public void testPullMessages_sizeExceeded() { + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> privateMessageService.pullPrivateMessageList(1L, 0L, 1001)); + assertEquals(MESSAGE_PULL_SIZE_EXCEEDED.getCode(), exception.getCode()); + } + + // ========== 已读测试 ========== + + @Test + public void testReadMessages_success() { + // 准备:前端上报已读到 messageId=5;mapper 返回更新行数 2 表示有待回执消息被标记完成 + when(privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus( + eq(2L), eq(1L), eq(5L), + eq(ImMessageReceiptStatusEnum.PENDING.getStatus()), any(ImPrivateMessageDO.class))) + .thenReturn(2); + // 读位置前进 → 才下发事件 + when(conversationReadService.updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong())) + .thenReturn(true); + + // 调用 + privateMessageService.readPrivateMessages(1L, 2L, 5L); + + // 断言:把待回执(PENDING)消息标记为已完成(DONE);status 不再写 READ + verify(privateMessageMapper).updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus( + eq(2L), eq(1L), eq(5L), + eq(ImMessageReceiptStatusEnum.PENDING.getStatus()), any(ImPrivateMessageDO.class)); + + // 断言:发送了 READ + RECEIPT 事件,payload 字段正确 + ArgumentCaptor userCaptor = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor contentTypeCaptor = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor payloadCaptor = ArgumentCaptor.forClass(Object.class); + verify(imWebSocketService, times(2)).sendNotificationAsync( + userCaptor.capture(), eq(ImConversationTypeEnum.PRIVATE.getType()), + contentTypeCaptor.capture(), payloadCaptor.capture()); + + // 第一次:发给自己的 READ 事件 + assertEquals(1L, userCaptor.getAllValues().get(0)); + assertEquals(ImContentTypeEnum.READ.getType(), contentTypeCaptor.getAllValues().get(0)); + ImMessageReadNotification readPayload = (ImMessageReadNotification) payloadCaptor.getAllValues().get(0); + assertEquals(1L, readPayload.getSenderId()); + assertEquals(2L, readPayload.getReceiverId()); + assertEquals(5L, readPayload.getId(), "READ id 应为前端上报的 messageId"); + + // 第二次:发给对方的 RECEIPT 事件 + assertEquals(2L, userCaptor.getAllValues().get(1)); + assertEquals(ImContentTypeEnum.RECEIPT.getType(), contentTypeCaptor.getAllValues().get(1)); + ImMessageReceiptNotification receiptPayload = + (ImMessageReceiptNotification) payloadCaptor.getAllValues().get(1); + assertEquals(5L, receiptPayload.getId(), "RECEIPT id 应为前端上报的 messageId"); + } + + // ========== 撤回测试 ========== + + @Test + public void testRecallMessage_success() { + // 准备 + ImPrivateMessageDO message = ImPrivateMessageDO.builder() + .id(10L).senderId(1L).receiverId(2L) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .sendTime(LocalDateTime.now()).build(); // 刚发送,5 分钟内 + when(privateMessageMapper.selectById(10L)).thenReturn(message); + when(privateMessageMapper.updateById(any(ImPrivateMessageDO.class))).thenReturn(1); + when(privateMessageMapper.insert(any(ImPrivateMessageDO.class))).thenReturn(1); + + // 调用 + ImPrivateMessageDO result = privateMessageService.recallPrivateMessage(1L, 10L); + + // 断言:返回撤回消息 + assertNotNull(result); + // 验证:更新原消息状态 + 插入 RecallMessage + verify(privateMessageMapper).updateById(any(ImPrivateMessageDO.class)); + verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class)); + // 验证推送了消息(给接收方和发送方) + verify(imWebSocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + @Test + public void testRecallMessage_notOwn() { + // 准备 + ImPrivateMessageDO message = ImPrivateMessageDO.builder() + .id(10L).senderId(2L).receiverId(1L) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .sendTime(LocalDateTime.now()).build(); + when(privateMessageMapper.selectById(10L)).thenReturn(message); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> privateMessageService.recallPrivateMessage(1L, 10L)); + assertEquals(MESSAGE_RECALL_DENIED.getCode(), exception.getCode()); + } + + @Test + public void testRecallMessage_alreadyRecalled() { + // 准备 + ImPrivateMessageDO message = ImPrivateMessageDO.builder() + .id(10L).senderId(1L).receiverId(2L) + .status(ImMessageStatusEnum.RECALL.getStatus()) + .sendTime(LocalDateTime.now()).build(); + when(privateMessageMapper.selectById(10L)).thenReturn(message); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> privateMessageService.recallPrivateMessage(1L, 10L)); + assertEquals(MESSAGE_ALREADY_RECALLED.getCode(), exception.getCode()); + } + + @Test + public void testRecallMessage_notExists() { + // 准备 + when(privateMessageMapper.selectById(10L)).thenReturn(null); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> privateMessageService.recallPrivateMessage(1L, 10L)); + assertEquals(MESSAGE_NOT_EXISTS.getCode(), exception.getCode()); + } + + @Test + public void testRecallMessage_timeout() { + // 准备:消息发送于 10 分钟前(超过 5 分钟窗口) + ImPrivateMessageDO message = ImPrivateMessageDO.builder() + .id(10L).senderId(1L).receiverId(2L) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .sendTime(LocalDateTime.now().minusMinutes(10)).build(); + when(privateMessageMapper.selectById(10L)).thenReturn(message); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> privateMessageService.recallPrivateMessage(1L, 10L)); + assertEquals(MESSAGE_RECALL_TIMEOUT.getCode(), exception.getCode()); + // 断言:不推送、不插 tipMessage + verify(privateMessageMapper, never()).insert(any(ImPrivateMessageDO.class)); + } + + @Test + public void testSendMessage_sensitiveWordBlocked() { + // 准备:文本消息命中敏感词 + ImPrivateMessageSendReqVO reqVO = buildSendReqVO(); + when(privateMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-001")) + .thenReturn(null); + doThrow(new ServiceException(MESSAGE_SENSITIVE_WORD_BLOCKED)) + .when(sensitiveWordService).validateText(reqVO.getContent()); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> privateMessageService.sendPrivateMessage(1L, reqVO)); + assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode()); + // 断言:不入库、不推送 + verify(privateMessageMapper, never()).insert(any(ImPrivateMessageDO.class)); + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + @Test + public void testReadMessages_disabled() { + // 准备:关闭私聊已读 + imProperties.getMessage().setPrivateReadEnabled(false); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> privateMessageService.readPrivateMessages(1L, 2L, 5L)); + assertEquals(MESSAGE_PRIVATE_READ_DISABLED.getCode(), exception.getCode()); + // 断言:不更新消息状态、不推送 + verify(privateMessageMapper, never()).updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus( + anyLong(), anyLong(), anyLong(), anyInt(), any(ImPrivateMessageDO.class)); + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + @Test + public void testReadMessages_notAdvanced() { + // 准备:读位置未前进(已读过 / CAS 失败),不下发事件 + when(conversationReadService.updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong())) + .thenReturn(false); + + // 调用 + privateMessageService.readPrivateMessages(1L, 2L, 5L); + + // 断言:读位置没前进,不推送 READ / RECEIPT + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + + // ========== getMaxReadMessageId 测试 ========== + + @Test + public void testGetMaxReadMessageId_hit() { + // 准备:对方(2) 在与我(1) 的会话里读位置=10 + when(conversationReadService.getConversationReadMessageId(eq(2L), anyInt(), eq(1L))) + .thenReturn(10L); + + // 调用 + Long result = privateMessageService.getMaxReadMessageId(1L, 2L); + + // 断言 + assertEquals(10L, result); + } + + @Test + public void testGetMaxReadMessageId_miss() { + // 准备:对方一条都没读过(读位置为 null) + when(conversationReadService.getConversationReadMessageId(eq(2L), anyInt(), eq(1L))) + .thenReturn(null); + + // 调用 + Long result = privateMessageService.getMaxReadMessageId(1L, 2L); + + // 断言:原样返回 null,前端按 falsy 跳过 + assertNull(result); + } + + @Test + public void testGetMaxReadMessageId_disabled() { + // 准备:关闭私聊已读 + imProperties.getMessage().setPrivateReadEnabled(false); + + // 调用并断言 + ServiceException exception = assertThrows(ServiceException.class, + () -> privateMessageService.getMaxReadMessageId(1L, 2L)); + assertEquals(MESSAGE_PRIVATE_READ_DISABLED.getCode(), exception.getCode()); + } + + // ========== DTO 私聊消息 ========== + + @Test + public void testSendPrivateMessage_dto_persistsAndSerializesPojoContent() { + // 准备:persistent=true 类型 + POJO content + ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO() + .setReceiverId(2L).setType(ImContentTypeEnum.RECALL.getType()) + .setContent(new RecallMessage().setMessageId(50L)); + + privateMessageService.sendPrivateMessage(1L, dto); + + // 断言:入库 + 系统字段兜底 + content 序列化为 JSON + ArgumentCaptor captor = ArgumentCaptor.forClass(ImPrivateMessageDO.class); + verify(privateMessageMapper).insert(captor.capture()); + ImPrivateMessageDO message = captor.getValue(); + assertEquals(1L, message.getSenderId()); + assertEquals(2L, message.getReceiverId()); + assertEquals(ImContentTypeEnum.RECALL.getType(), message.getType()); + assertEquals("{\"messageId\":50}", message.getContent()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), message.getStatus()); + assertNotNull(message.getClientMessageId()); + assertNotNull(message.getSendTime()); + // 断言:sender + receiver 双端推送 + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + verify(imWebSocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any()); + } + + @Test + public void testSendPrivateMessage_dto_nonPersistentTypeNotInserted() { + // 准备:persistent=false 类型(FRIEND_DELETE 通知)→ 不入库;仅推 sender 多端,receiver 不感知 + ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO() + .setReceiverId(2L).setType(ImContentTypeEnum.FRIEND_DELETE.getType()); + + privateMessageService.sendPrivateMessage(1L, dto); + + verify(privateMessageMapper, never()).insert(any(ImPrivateMessageDO.class)); + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + verify(imWebSocketService, never()).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any()); + } + + // ========== getPrivateMessageList ========== + + @Test + public void testGetPrivateMessageList_delegatesToMapper() { + // 准备 + ImPrivateMessageListReqVO reqVO = new ImPrivateMessageListReqVO(); + reqVO.setReceiverId(2L); + reqVO.setMaxId(100L); + reqVO.setLimit(20); + List mockList = ListUtil.of( + ImPrivateMessageDO.builder().id(99L).senderId(1L).receiverId(2L).build(), + ImPrivateMessageDO.builder().id(98L).senderId(2L).receiverId(1L).build() + ); + when(privateMessageMapper.selectHistoryList(1L, 2L, 100L, 20)).thenReturn(mockList); + + // 调用 + List result = privateMessageService.getPrivateMessageList(1L, reqVO); + + // 断言:透传到 mapper,参数一致 + assertEquals(2, result.size()); + verify(privateMessageMapper).selectHistoryList(1L, 2L, 100L, 20); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImplTest.java new file mode 100644 index 0000000000..b178022d3e --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImplTest.java @@ -0,0 +1,391 @@ +package cn.iocoder.yudao.module.im.service.rtc; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.rtc.vo.ImRtcCallCreateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.rtc.vo.ImRtcCallInviteReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; +import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcParticipantDO; +import cn.iocoder.yudao.module.im.dal.mysql.rtc.ImRtcCallMapper; +import cn.iocoder.yudao.module.im.dal.mysql.rtc.ImRtcParticipantMapper; +import cn.iocoder.yudao.module.im.dal.redis.rtc.ImRtcCallLockRedisDAO; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallStatusEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantRoleEnum; +import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantStatusEnum; +import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.dao.DuplicateKeyException; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.RTC_GROUP_INVITEE_OVER_LIMIT; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.RTC_GROUP_INVITEE_REQUIRED; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.RTC_SELF_BUSY; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link ImRtcCallServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImRtcCallServiceImpl rtcCallService; + + @Mock + private ImRtcParticipantMapper rtcParticipantMapper; + @Mock + private ImRtcCallMapper rtcCallMapper; + @Mock + private ImRtcCallLockRedisDAO rtcCallLockRedisDAO; + @Mock + private AdminUserApi adminUserApi; + @Mock + private ImWebSocketService webSocketService; + @Mock + private ImProperties imProperties; + @Mock + private ImGroupMemberService groupMemberService; + + // ========== timeoutInvitingParticipants ========== + + @Test + public void testTimeoutInvitingParticipants_emptyCandidates_returnsZeroAndNoDownstream() { + // 准备:无超时候选 + when(rtcParticipantMapper.selectListByStatusAndInviteTimeBefore( + eq(ImRtcParticipantStatusEnum.INVITING.getStatus()), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // 调用 + int result = rtcCallService.timeoutInvitingParticipants(1); + + // 断言:返回 0;无候选时不应触发 user 预查 / call 查询 / 推送 + assertEquals(0, result); + verifyNoInteractions(adminUserApi, rtcCallMapper, webSocketService); + } + + @Test + public void testTimeoutInvitingParticipants_thresholdConvertedToCutoff() { + // 准备:阈值 5 分钟;mock 空候选避免触发后续逻辑 + when(rtcParticipantMapper.selectListByStatusAndInviteTimeBefore(any(), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // 调用 + LocalDateTime before = LocalDateTime.now(); + rtcCallService.timeoutInvitingParticipants(5); + + // 断言:cutoff = now - 5 分钟(允许 5 秒漂移) + ArgumentCaptor cutoffCaptor = ArgumentCaptor.forClass(LocalDateTime.class); + verify(rtcParticipantMapper).selectListByStatusAndInviteTimeBefore( + eq(ImRtcParticipantStatusEnum.INVITING.getStatus()), cutoffCaptor.capture()); + LocalDateTime cutoff = cutoffCaptor.getValue(); + LocalDateTime expected = before.minusMinutes(5); + assertTrue(Duration.between(cutoff, expected).abs().getSeconds() < 5, + "cutoff 应当约等于 now - 5 min;实际:" + cutoff); + } + + @Test + public void testTimeoutInvitingParticipants_casAllFails_noPushNoEndSession() { + // 准备:候选非空但每个 CAS 都失败(并发已变状态) + ImRtcParticipantDO p = buildParticipant(10L, "r1", 100L, ImRtcParticipantStatusEnum.INVITING); + when(rtcParticipantMapper.selectListByStatusAndInviteTimeBefore(any(), any())) + .thenReturn(ListUtil.of(p)); + when(adminUserApi.getUserMap(anySet())).thenReturn(MapUtil.of(100L, buildUser(100L))); + when(rtcParticipantMapper.updateByIdAndStatus(eq(10L), eq(ImRtcParticipantStatusEnum.INVITING.getStatus()), any())) + .thenReturn(0); + + // 调用 + int result = rtcCallService.timeoutInvitingParticipants(1); + + // 断言:CAS 全失败时返回 0;不查 call、不推送 + assertEquals(0, result); + verify(rtcCallMapper, never()).selectByRoom(any()); + verifyNoInteractions(webSocketService); + } + + @Test + public void testTimeoutInvitingParticipants_groupCall_pushesNoAnswerSkipsEndSession() { + // 准备:群通话单候选 CAS 成功;shouldCloseGroupRoom 通过 selectListByRoom 多 JOINED 让其返 false,跳过 endSession + ImRtcParticipantDO p = buildParticipant(10L, "r1", 100L, ImRtcParticipantStatusEnum.INVITING); + when(rtcParticipantMapper.selectListByStatusAndInviteTimeBefore(any(), any())) + .thenReturn(ListUtil.of(p)); + when(adminUserApi.getUserMap(anySet())).thenReturn(MapUtil.of(100L, buildUser(100L))); + when(rtcParticipantMapper.updateByIdAndStatus(eq(10L), eq(ImRtcParticipantStatusEnum.INVITING.getStatus()), any())) + .thenReturn(1); + ImRtcCallDO call = buildCall("r1", 200L, ImConversationTypeEnum.GROUP, 999L); + when(rtcCallMapper.selectByRoom("r1")).thenReturn(call); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(999L)).thenReturn(ListUtil.of(200L, 201L)); + // 房内 2 个 JOINED + 1 个 INVITING → shouldCloseGroupRoom 返 false + when(rtcParticipantMapper.selectListByRoom("r1")).thenReturn(ListUtil.of( + buildParticipant(20L, "r1", 200L, ImRtcParticipantStatusEnum.JOINED), + buildParticipant(21L, "r1", 201L, ImRtcParticipantStatusEnum.JOINED), + buildParticipant(22L, "r1", 202L, ImRtcParticipantStatusEnum.INVITING) + )); + + // 调用 + int result = rtcCallService.timeoutInvitingParticipants(1); + + // 断言:成功 1 个;NO_ANSWER 信令推到主叫;不触发 endSession + assertEquals(1, result); + verify(webSocketService).sendNotificationAsync(eq(200L), anyInt(), anyInt(), any()); + verify(rtcCallMapper, never()).updateByIdAndStatusIn(any(), anyCollection(), any()); + } + + @Test + public void testTimeoutInvitingParticipants_callMissing_silentSkip() { + // 准备:CAS 成功后通话主表缺失(异常兜底场景) + ImRtcParticipantDO p = buildParticipant(10L, "r1", 100L, ImRtcParticipantStatusEnum.INVITING); + when(rtcParticipantMapper.selectListByStatusAndInviteTimeBefore(any(), any())) + .thenReturn(ListUtil.of(p)); + when(adminUserApi.getUserMap(anySet())).thenReturn(MapUtil.of(100L, buildUser(100L))); + when(rtcParticipantMapper.updateByIdAndStatus(eq(10L), eq(ImRtcParticipantStatusEnum.INVITING.getStatus()), any())) + .thenReturn(1); + when(rtcCallMapper.selectByRoom("r1")).thenReturn(null); + + // 调用 + int result = rtcCallService.timeoutInvitingParticipants(1); + + // 断言:CAS 已成功但 call 缺失视为部分失败返 0;不应推送 + assertEquals(0, result); + verifyNoInteractions(webSocketService); + } + + // ========== noAnswerCallCheck ========== + + @Test + public void testNoAnswerCallCheck_authFails_silentNoOp() { + // 准备:selectByRoomAndUserId 返 null 覆盖三种鉴权失败场景(非参与者 / 非法 room / null room) + when(rtcParticipantMapper.selectByRoomAndUserId(any(), eq(100L))).thenReturn(null); + + // 调用 + rtcCallService.noAnswerCallCheck(100L, "r1"); + rtcCallService.noAnswerCallCheck(100L, ""); + rtcCallService.noAnswerCallCheck(100L, null); + + // 断言:仅鉴权查询了 3 次;不应进入后续超时扫描 / 推送 + verify(rtcParticipantMapper, times(3)).selectByRoomAndUserId(any(), eq(100L)); + verify(rtcParticipantMapper, never()).selectListByRoomAndStatusAndInviteTimeBefore(any(), any(), any()); + verifyNoInteractions(adminUserApi, webSocketService); + } + + @Test + public void testNoAnswerCallCheck_usesBackendThreshold_notFrontend() { + // 准备:鉴权通过 + 后端配置阈值 2 分钟 + 无候选(避免触发推送) + when(rtcParticipantMapper.selectByRoomAndUserId("r1", 100L)) + .thenReturn(buildParticipant(10L, "r1", 100L, ImRtcParticipantStatusEnum.INVITING)); + ImProperties.Rtc rtcConfig = new ImProperties.Rtc(); + rtcConfig.setInviteTimeoutMinutes(2); + when(imProperties.getRtc()).thenReturn(rtcConfig); + when(rtcParticipantMapper.selectListByRoomAndStatusAndInviteTimeBefore( + eq("r1"), eq(ImRtcParticipantStatusEnum.INVITING.getStatus()), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // 调用 + LocalDateTime before = LocalDateTime.now(); + rtcCallService.noAnswerCallCheck(100L, "r1"); + + // 断言:扫描时使用 cutoff = now - 2 分钟(后端配置),而非前端 60s + ArgumentCaptor cutoffCaptor = ArgumentCaptor.forClass(LocalDateTime.class); + verify(rtcParticipantMapper).selectListByRoomAndStatusAndInviteTimeBefore( + eq("r1"), eq(ImRtcParticipantStatusEnum.INVITING.getStatus()), cutoffCaptor.capture()); + LocalDateTime cutoff = cutoffCaptor.getValue(); + LocalDateTime expected = before.minusMinutes(2); + assertTrue(Duration.between(cutoff, expected).abs().getSeconds() < 5, + "cutoff 应当约等于 now - 2 min(后端配置);实际:" + cutoff); + } + + @Test + public void testNoAnswerCallCheck_groupCall_pushesNoAnswer() { + // 准备:鉴权通过 + 单候选 CAS 成功 + 群通话不关房 + when(rtcParticipantMapper.selectByRoomAndUserId("r1", 100L)) + .thenReturn(buildParticipant(10L, "r1", 100L, ImRtcParticipantStatusEnum.INVITING)); + ImProperties.Rtc rtcConfig = new ImProperties.Rtc(); + rtcConfig.setInviteTimeoutMinutes(1); + when(imProperties.getRtc()).thenReturn(rtcConfig); + ImRtcParticipantDO timeoutTarget = buildParticipant(11L, "r1", 101L, ImRtcParticipantStatusEnum.INVITING); + when(rtcParticipantMapper.selectListByRoomAndStatusAndInviteTimeBefore( + eq("r1"), eq(ImRtcParticipantStatusEnum.INVITING.getStatus()), any())) + .thenReturn(ListUtil.of(timeoutTarget)); + when(adminUserApi.getUserMap(anySet())).thenReturn(MapUtil.of(101L, buildUser(101L))); + when(rtcParticipantMapper.updateByIdAndStatus(eq(11L), eq(ImRtcParticipantStatusEnum.INVITING.getStatus()), any())) + .thenReturn(1); + ImRtcCallDO call = buildCall("r1", 200L, ImConversationTypeEnum.GROUP, 999L); + when(rtcCallMapper.selectByRoom("r1")).thenReturn(call); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(999L)).thenReturn(ListUtil.of(200L, 201L)); + // 让 shouldCloseGroupRoom 返 false + when(rtcParticipantMapper.selectListByRoom("r1")).thenReturn(ListUtil.of( + buildParticipant(20L, "r1", 200L, ImRtcParticipantStatusEnum.JOINED), + buildParticipant(21L, "r1", 201L, ImRtcParticipantStatusEnum.JOINED) + )); + + // 调用 + rtcCallService.noAnswerCallCheck(100L, "r1"); + + // 断言:NO_ANSWER 信令推到主叫 200L;不触发 endSession + verify(webSocketService).sendNotificationAsync(eq(200L), anyInt(), anyInt(), any()); + verify(rtcCallMapper, never()).updateByIdAndStatusIn(any(), anyCollection(), any()); + } + + // ========== createCall ========== + + @Test + public void testCreateCall_groupOnlyInviteSelf_throwInviteeRequired() throws Exception { + when(imProperties.getRtc()).thenReturn(new ImProperties.Rtc()); + when(rtcCallLockRedisDAO.lockGroup(eq(10L), any())).thenAnswer(invocation -> { + @SuppressWarnings("unchecked") + Callable callable = invocation.getArgument(1); + return callable.call(); + }); + ImRtcCallCreateReqVO reqVO = new ImRtcCallCreateReqVO(); + reqVO.setConversationType(ImConversationTypeEnum.GROUP.getType()); + reqVO.setGroupId(10L); + reqVO.setInviteeIds(CollUtil.newHashSet(100L)); + + ServiceException exception = assertThrows(ServiceException.class, + () -> rtcCallService.createCall(100L, reqVO)); + + assertEquals(RTC_GROUP_INVITEE_REQUIRED.getCode(), exception.getCode()); + verify(rtcParticipantMapper, never()).insertBatch(anyList()); + } + + // ========== acceptCall / joinCall 忙线校验 ========== + + @Test + public void testAcceptCall_joinedOtherRoom_throwSelfBusy() { + // 准备:当前通话仍在邀请中,但用户已加入另一个房间 + when(imProperties.getRtc()).thenReturn(new ImProperties.Rtc()); + ImRtcCallDO call = buildCall("r1", 200L, ImConversationTypeEnum.PRIVATE, null); + when(rtcCallMapper.selectByRoom("r1")).thenReturn(call); + when(rtcParticipantMapper.selectByRoomAndUserId("r1", 100L)) + .thenReturn(buildParticipant(10L, "r1", 100L, ImRtcParticipantStatusEnum.INVITING)); + when(rtcParticipantMapper.selectLastOneByUserIdAndStatusInAndRoomNot(eq(100L), anyCollection(), eq("r1"))) + .thenReturn(buildParticipant(11L, "r2", 100L, ImRtcParticipantStatusEnum.JOINED)); + + // 调用 + 断言:拒绝接听,不覆盖其它房间状态 + ServiceException exception = assertThrows(ServiceException.class, + () -> rtcCallService.acceptCall(100L, "r1")); + assertEquals(RTC_SELF_BUSY.getCode(), exception.getCode()); + verify(rtcParticipantMapper, never()).updateByIdAndStatus(eq(10L), any(), any()); + } + + @Test + public void testJoinCall_joinedOtherRoom_throwSelfBusy() { + // 准备:群通话活跃,用户已加入另一个房间 + when(imProperties.getRtc()).thenReturn(new ImProperties.Rtc()); + ImRtcCallDO call = buildCall("r1", 200L, ImConversationTypeEnum.GROUP, 999L); + when(rtcCallMapper.selectByRoom("r1")).thenReturn(call); + when(groupMemberService.validateMemberInGroup(999L, 100L)).thenReturn(new ImGroupMemberDO()); + when(rtcParticipantMapper.selectLastOneByUserIdAndStatusInAndRoomNot(eq(100L), anyCollection(), eq("r1"))) + .thenReturn(buildParticipant(11L, "r2", 100L, ImRtcParticipantStatusEnum.JOINED)); + + // 调用 + 断言:拒绝加入,不写参与者状态 + ServiceException exception = assertThrows(ServiceException.class, + () -> rtcCallService.joinCall(100L, "r1")); + assertEquals(RTC_SELF_BUSY.getCode(), exception.getCode()); + verify(rtcParticipantMapper, never()).insert(any(ImRtcParticipantDO.class)); + verify(rtcParticipantMapper, never()).updateById(any(ImRtcParticipantDO.class)); + } + + @Test + public void testJoinCall_insertDuplicateKey_reuseExistingParticipant() { + // 准备:群通话活跃,首次查询无参与者,插入时命中唯一键 + when(imProperties.getRtc()).thenReturn(new ImProperties.Rtc()); + ImRtcCallDO call = buildCall("r1", 200L, ImConversationTypeEnum.GROUP, 999L); + when(rtcCallMapper.selectByRoom("r1")).thenReturn(call); + when(groupMemberService.validateMemberInGroup(999L, 100L)).thenReturn(new ImGroupMemberDO()); + when(rtcParticipantMapper.selectLastOneByUserIdAndStatusInAndRoomNot(eq(100L), anyCollection(), eq("r1"))) + .thenReturn(null); + when(rtcParticipantMapper.selectByRoomAndUserId("r1", 100L)) + .thenReturn(null, buildParticipant(10L, "r1", 100L, ImRtcParticipantStatusEnum.JOINED)); + when(rtcParticipantMapper.insert(any(ImRtcParticipantDO.class))).thenThrow(new DuplicateKeyException("dup")); + + // 调用 + ImRtcCallDO result = rtcCallService.joinCall(100L, "r1"); + + // 断言:不向上抛数据库异常 + assertSame(call, result); + verify(rtcParticipantMapper, never()).updateById(any(ImRtcParticipantDO.class)); + } + + @Test + public void testInviteCall_overLimit_throws() throws Exception { + ImProperties.Rtc rtcConfig = new ImProperties.Rtc(); + rtcConfig.setGroupMaxParticipants(3); + when(imProperties.getRtc()).thenReturn(rtcConfig); + ImRtcCallDO call = buildCall("r1", 200L, ImConversationTypeEnum.GROUP, 999L); + when(rtcCallMapper.selectByRoom("r1")).thenReturn(call); + when(rtcParticipantMapper.selectByRoomAndUserId("r1", 200L)) + .thenReturn(buildParticipant(10L, "r1", 200L, ImRtcParticipantStatusEnum.JOINED)); + when(rtcCallLockRedisDAO.lockGroup(eq(999L), any())).thenAnswer(invocation -> { + @SuppressWarnings("unchecked") + Callable callable = invocation.getArgument(1); + return callable.call(); + }); + when(rtcParticipantMapper.selectListByRoom("r1")).thenReturn(ListUtil.of( + buildParticipant(10L, "r1", 200L, ImRtcParticipantStatusEnum.JOINED), + buildParticipant(11L, "r1", 201L, ImRtcParticipantStatusEnum.JOINED), + buildParticipant(12L, "r1", 202L, ImRtcParticipantStatusEnum.INVITING) + )); + ImRtcCallInviteReqVO reqVO = new ImRtcCallInviteReqVO(); + reqVO.setRoom("r1"); + reqVO.setInviteeIds(CollUtil.newHashSet(203L)); + + ServiceException exception = assertThrows(ServiceException.class, + () -> rtcCallService.inviteCall(200L, reqVO)); + + assertEquals(RTC_GROUP_INVITEE_OVER_LIMIT.getCode(), exception.getCode()); + verify(rtcParticipantMapper, never()).insertBatch(anyList()); + } + + // ========== 测试数据构造 ========== + + private ImRtcParticipantDO buildParticipant(Long id, String room, Long userId, ImRtcParticipantStatusEnum status) { + return new ImRtcParticipantDO() + .setId(id) + .setRoom(room) + .setUserId(userId) + .setRole(ImRtcParticipantRoleEnum.INVITEE.getRole()) + .setStatus(status.getStatus()) + .setInviteTime(LocalDateTime.now()); + } + + private ImRtcCallDO buildCall(String room, Long inviterUserId, ImConversationTypeEnum conversationType, Long groupId) { + return new ImRtcCallDO() + .setRoom(room) + .setConversationType(conversationType.getType()) + .setMediaType(1) + .setInviterUserId(inviterUserId) + .setGroupId(groupId) + .setStatus(ImRtcCallStatusEnum.RUNNING.getStatus()) + .setStartTime(LocalDateTime.now()); + } + + private AdminUserRespDTO buildUser(Long id) { + AdminUserRespDTO user = new AdminUserRespDTO(); + user.setId(id); + user.setNickname("user-" + id); + return user; + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImplTest.java new file mode 100644 index 0000000000..5e7711d7c0 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImplTest.java @@ -0,0 +1,278 @@ +package cn.iocoder.yudao.module.im.service.sensitiveword; + +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; +import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_DUPLICATED; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * {@link ImSensitiveWordServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImSensitiveWordServiceImplTest extends BaseMockitoUnitTest { + + private static final Long TENANT_ID = 1L; + + @InjectMocks + private ImSensitiveWordServiceImpl sensitiveWordService; + + @Mock + private ImSensitiveWordMapper imSensitiveWordMapper; + + @BeforeEach + public void setUp() { + // 设置租户上下文;validateText 与 loadFresh 都依赖 TenantContextHolder + TenantContextHolder.setTenantId(TENANT_ID); + // mock 启用敏感词列表;LoadingCache 在首次 validateText 时懒加载,无需主动 init + // 用 lenient 避免 null / empty 等不触发 cache load 的用例报 UnnecessaryStubbing + lenient().when(imSensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus())) + .thenReturn(ListUtil.of( + ImSensitiveWordDO.builder().id(1L).word("badword") + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImSensitiveWordDO.builder().id(2L).word("违禁词") + .status(CommonStatusEnum.ENABLE.getStatus()).build() + )); + } + + @AfterEach + public void tearDown() { + // 清理租户上下文,避免污染其它测试 + TenantContextHolder.clear(); + } + + @Test + public void testValidateText_null() { + // null 直接返回,不抛异常 + assertDoesNotThrow(() -> sensitiveWordService.validateText(null)); + } + + @Test + public void testValidateText_empty() { + assertDoesNotThrow(() -> sensitiveWordService.validateText("")); + } + + @Test + public void testValidateText_clean() { + // 正常文本不应命中 + assertDoesNotThrow(() -> sensitiveWordService.validateText("hello world")); + } + + @Test + public void testValidateText_hitEnglish() { + ServiceException exception = assertThrows(ServiceException.class, + () -> sensitiveWordService.validateText("this contains badword here")); + assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode()); + } + + @Test + public void testValidateText_hitChinese() { + ServiceException exception = assertThrows(ServiceException.class, + () -> sensitiveWordService.validateText("这条消息里有违禁词哦")); + assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode()); + } + + @Test + public void testValidateText_lazyLoadsCacheOnFirstCall() { + // 调用:首次 validateText 应触发 cache load + sensitiveWordService.validateText("hello world"); + + // 断言:mapper 各调用 1 次(loadFresh 里先取 maxUpdateTime 再读词库) + verify(imSensitiveWordMapper, times(1)).selectMaxUpdateTime(TENANT_ID); + verify(imSensitiveWordMapper, times(1)).selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + } + + @Test + public void testValidateText_reusesCachedBsAcrossCalls() { + // 调用:连续两次 validateText + sensitiveWordService.validateText("hello world"); + sensitiveWordService.validateText("another text"); + + // 断言:第二次复用 cache,mapper 仍只被调用 1 次 + verify(imSensitiveWordMapper, times(1)).selectMaxUpdateTime(TENANT_ID); + verify(imSensitiveWordMapper, times(1)).selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + } + + @Test + public void testCreateSensitiveWord_invalidatesCacheAndReloadsOnNextValidate() { + // 准备:首次 validateText 触发 cache load(旧词库) + sensitiveWordService.validateText("hello world"); + verify(imSensitiveWordMapper, times(1)).selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + + // 准备:mapper 返回新词库(额外多一个 newbad),并让 createSensitiveWord 走通 + when(imSensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus())) + .thenReturn(ListUtil.of( + ImSensitiveWordDO.builder().id(1L).word("badword") + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImSensitiveWordDO.builder().id(2L).word("违禁词") + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImSensitiveWordDO.builder().id(3L).word("newbad") + .status(CommonStatusEnum.ENABLE.getStatus()).build() + )); + when(imSensitiveWordMapper.selectByWord("newbad")).thenReturn(null); + + // 调用:新增敏感词,触发 invalidate + ImSensitiveWordSaveReqVO reqVO = new ImSensitiveWordSaveReqVO(); + reqVO.setWord("newbad"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + sensitiveWordService.createSensitiveWord(reqVO); + + // 调用:再次 validateText,应触发重新 load + ServiceException exception = assertThrows(ServiceException.class, + () -> sensitiveWordService.validateText("contains newbad here")); + assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode()); + // 旧词依然命中 + assertThrows(ServiceException.class, + () -> sensitiveWordService.validateText("contains badword here")); + + // 断言:selectListByStatus 共被调用 2 次(首次 load + invalidate 后 reload) + verify(imSensitiveWordMapper, times(2)).selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + } + + @Test + public void testCreateSensitiveWord_duplicateWord_throws() { + // 准备:mock 已存在同名敏感词 + when(imSensitiveWordMapper.selectByWord("dup")).thenReturn( + ImSensitiveWordDO.builder().id(99L).word("dup") + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + + // 调用 + 断言:重复敏感词抛 SENSITIVE_WORD_DUPLICATED + ImSensitiveWordSaveReqVO reqVO = new ImSensitiveWordSaveReqVO(); + reqVO.setWord("dup"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + ServiceException exception = assertThrows(ServiceException.class, + () -> sensitiveWordService.createSensitiveWord(reqVO)); + assertEquals(SENSITIVE_WORD_DUPLICATED.getCode(), exception.getCode()); + + // 断言:未走到 insert + verify(imSensitiveWordMapper, never()).insert(any(ImSensitiveWordDO.class)); + } + + @Test + public void testUpdateSensitiveWord_invalidatesCache() { + // 准备:首次 validateText 触发 cache load + sensitiveWordService.validateText("hello world"); + verify(imSensitiveWordMapper, times(1)).selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + + // 准备:让 update 校验通过 + when(imSensitiveWordMapper.selectById(1L)).thenReturn( + ImSensitiveWordDO.builder().id(1L).word("badword") + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + when(imSensitiveWordMapper.selectByWord("updatedbad")).thenReturn(null); + // 准备:reload 时返回新词库 + when(imSensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus())) + .thenReturn(ListUtil.of( + ImSensitiveWordDO.builder().id(1L).word("updatedbad") + .status(CommonStatusEnum.ENABLE.getStatus()).build() + )); + + // 调用:更新敏感词,触发 invalidate + ImSensitiveWordSaveReqVO reqVO = new ImSensitiveWordSaveReqVO(); + reqVO.setId(1L); + reqVO.setWord("updatedbad"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + sensitiveWordService.updateSensitiveWord(reqVO); + + // 调用 + 断言:再次 validateText 应使用新词库 + ServiceException exception = assertThrows(ServiceException.class, + () -> sensitiveWordService.validateText("contains updatedbad here")); + assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode()); + + // 断言:selectListByStatus 共被调用 2 次 + verify(imSensitiveWordMapper, times(2)).selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + } + + @Test + public void testDeleteSensitiveWord_invalidatesCache() { + // 准备:首次 validateText 触发 cache load + sensitiveWordService.validateText("hello world"); + verify(imSensitiveWordMapper, times(1)).selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + + // 准备:让 delete 校验通过 + when(imSensitiveWordMapper.selectById(1L)).thenReturn( + ImSensitiveWordDO.builder().id(1L).word("badword") + .status(CommonStatusEnum.ENABLE.getStatus()).build()); + // 准备:reload 时返回剩余词库(只剩中文那条) + when(imSensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus())) + .thenReturn(ListUtil.of( + ImSensitiveWordDO.builder().id(2L).word("违禁词") + .status(CommonStatusEnum.ENABLE.getStatus()).build() + )); + + // 调用:删除 badword,触发 invalidate + sensitiveWordService.deleteSensitiveWord(1L); + + // 调用 + 断言:badword 已被删,不再命中 + assertDoesNotThrow(() -> sensitiveWordService.validateText("contains badword here")); + // 中文词依然在 + assertThrows(ServiceException.class, + () -> sensitiveWordService.validateText("这条消息里有违禁词哦")); + + // 断言:selectListByStatus 共被调用 2 次 + verify(imSensitiveWordMapper, times(2)).selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + } + + @Test + public void testDeleteSensitiveWord_notExists_throws() { + // 准备:selectById 返回 null + when(imSensitiveWordMapper.selectById(999L)).thenReturn(null); + + // 调用 + 断言:抛 SENSITIVE_WORD_NOT_EXISTS + ServiceException exception = assertThrows(ServiceException.class, + () -> sensitiveWordService.deleteSensitiveWord(999L)); + assertEquals(SENSITIVE_WORD_NOT_EXISTS.getCode(), exception.getCode()); + + // 断言:未走到 deleteById + verify(imSensitiveWordMapper, never()).deleteById(anyLong()); + } + + @Test + public void testDeleteSensitiveWordList_invalidatesCache() { + // 准备:首次 validateText 触发 cache load + sensitiveWordService.validateText("hello world"); + verify(imSensitiveWordMapper, times(1)).selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + + // 准备:reload 时返回空词库 + when(imSensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus())) + .thenReturn(ListUtil.of()); + + // 调用:批量删除,触发 invalidate + sensitiveWordService.deleteSensitiveWordList(ListUtil.of(1L, 2L)); + + // 调用 + 断言:所有词都不再命中 + assertDoesNotThrow(() -> sensitiveWordService.validateText("contains badword here")); + assertDoesNotThrow(() -> sensitiveWordService.validateText("这条消息里有违禁词哦")); + + // 断言:deleteByIds 被调用 1 次;selectListByStatus 共 2 次 + verify(imSensitiveWordMapper, times(1)).deleteByIds(ListUtil.of(1L, 2L)); + verify(imSensitiveWordMapper, times(2)).selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + } + + @Test + public void testDeleteSensitiveWordList_emptyIds_skip() { + // 调用:空列表直接返回 + sensitiveWordService.deleteSensitiveWordList(ListUtil.of()); + + // 断言:mapper 不被调用 + verify(imSensitiveWordMapper, never()).deleteByIds(anyList()); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImplTest.java new file mode 100644 index 0000000000..a55037d6fd --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImplTest.java @@ -0,0 +1,162 @@ +package cn.iocoder.yudao.module.im.service.statistics; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.dal.mysql.statistics.ImStatisticsManagerMapper; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +/** + * {@link ImStatisticsManagerServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { + + private static final LocalDateTime BEGIN = LocalDateTime.of(2026, 1, 1, 0, 0); + private static final LocalDateTime END = LocalDateTime.of(2026, 2, 1, 0, 0); + + @InjectMocks + private ImStatisticsManagerServiceImpl service; + + @Mock + private ImStatisticsManagerMapper statisticsMapper; + + // ========== 单值委托 ========== + + @Test + public void testGetTotalUserCount_delegate() { + when(statisticsMapper.selectTotalUserCount()).thenReturn(99L); + assertEquals(99L, service.getTotalUserCount()); + } + + @Test + public void testGetNewUserCount_delegate() { + when(statisticsMapper.selectNewUserCount(BEGIN, END)).thenReturn(10L); + assertEquals(10L, service.getNewUserCount(BEGIN, END)); + } + + @Test + public void testGetActiveUserCount_delegate() { + when(statisticsMapper.selectActiveUserCount(BEGIN, END)).thenReturn(5L); + assertEquals(5L, service.getActiveUserCount(BEGIN, END)); + } + + @Test + public void testGetTotalGroupCount_delegate() { + when(statisticsMapper.selectTotalGroupCount()).thenReturn(33L); + assertEquals(33L, service.getTotalGroupCount()); + } + + @Test + public void testGetPrivateMessageCount_delegate() { + when(statisticsMapper.selectPrivateMessageCount(BEGIN, END)).thenReturn(123L); + assertEquals(123L, service.getPrivateMessageCount(BEGIN, END)); + } + + @Test + public void testGetGroupMessageCount_delegate() { + when(statisticsMapper.selectGroupMessageCount(BEGIN, END)).thenReturn(456L); + assertEquals(456L, service.getGroupMessageCount(BEGIN, END)); + } + + // ========== 每日序列 ========== + + @Test + public void testGetNewUserDailyCountMap_dateConvert() { + // 准备:date 字段以 SQL Date / String 混入,count 混入多种 Number + when(statisticsMapper.selectNewUserDailyCount(eq(BEGIN), eq(END))).thenReturn(Arrays.asList( + row("date", java.sql.Date.valueOf("2026-01-10"), "count", BigInteger.valueOf(5)), + row("date", "2026-01-11", "count", 8))); + + // 调用 + Map result = service.getNewUserDailyCountMap(BEGIN, END); + + // 断言:date → LocalDateTime 起始零点 + assertEquals(2, result.size()); + assertEquals(5L, result.get(LocalDate.of(2026, 1, 10).atStartOfDay())); + assertEquals(8L, result.get(LocalDate.of(2026, 1, 11).atStartOfDay())); + } + + @Test + public void testGetActiveUserDailyCountMap_dateConvert() { + when(statisticsMapper.selectActiveUserDailyCount(any(), any())).thenReturn(Arrays.asList( + row("date", "2026-01-10", "count", 3L))); + + Map result = service.getActiveUserDailyCountMap(BEGIN, END); + assertEquals(3L, result.get(LocalDate.of(2026, 1, 10).atStartOfDay())); + } + + @Test + public void testGetPrivateMessageDailyCountMap_dateConvert() { + when(statisticsMapper.selectPrivateMessageDailyCount(any(), any())).thenReturn(Arrays.asList( + row("date", "2026-01-10", "count", 7L))); + assertEquals(7L, service.getPrivateMessageDailyCountMap(BEGIN, END) + .get(LocalDate.of(2026, 1, 10).atStartOfDay())); + } + + @Test + public void testGetGroupMessageDailyCountMap_dateConvert() { + when(statisticsMapper.selectGroupMessageDailyCount(any(), any())).thenReturn(Arrays.asList( + row("date", "2026-01-10", "count", 9L))); + assertEquals(9L, service.getGroupMessageDailyCountMap(BEGIN, END) + .get(LocalDate.of(2026, 1, 10).atStartOfDay())); + } + + // ========== 分桶 / 分布 ========== + + @Test + public void testGetGroupSizeCountMap() { + when(statisticsMapper.selectGroupSizeDistribution()).thenReturn(Arrays.asList( + row("range", "1-9 人", "count", BigInteger.valueOf(3)), + row("range", "10-49 人", "count", 2))); + + Map result = service.getGroupSizeCountMap(); + assertEquals(3L, result.get("1-9 人")); + assertEquals(2L, result.get("10-49 人")); + } + + @Test + public void testGetMessageTypeCountMap() { + when(statisticsMapper.selectMessageTypeDistribution(BEGIN, END)).thenReturn(Arrays.asList( + row("type", 101L, "count", BigInteger.valueOf(8)), + row("type", 102, "count", 1))); + + Map result = service.getMessageTypeCountMap(BEGIN, END); + assertEquals(8L, result.get(101)); + assertEquals(1L, result.get(102)); + } + + @Test + public void testGetTopSenderCountMap_passesLimit() { + when(statisticsMapper.selectTopSenders(BEGIN, END, 3)).thenReturn(Arrays.asList( + row("userId", 1, "messageCount", BigInteger.valueOf(10)), + row("userId", 2L, "messageCount", 5))); + + Map result = service.getTopSenderCountMap(BEGIN, END, 3); + assertEquals(10L, result.get(1L)); + assertEquals(5L, result.get(2L)); + } + + // ========== 工具 ========== + + private static Map row(String k1, Object v1, String k2, Object v2) { + Map map = new HashMap<>(); + map.put(k1, v1); + map.put(k2, v2); + return map; + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImplTest.java new file mode 100644 index 0000000000..898aa1e28d --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImplTest.java @@ -0,0 +1,256 @@ +package cn.iocoder.yudao.module.im.service.websocket; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.service.websocket.notification.ImNotificationWebSocketDTO; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImPrivateMessageNotification; +import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.*; + +/** + * {@link ImWebSocketServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImWebSocketServiceImpl imWebSocketService; + + @Mock + private WebSocketSenderApi webSocketSenderApi; + + @AfterEach + public void tearDown() { + // 清理事务同步上下文,避免串扰其它用例 + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clearSynchronization(); + } + } + + // ========== 私聊推送 ========== + + @Test + public void testSendNotificationAsync_private_noTransactionSendsImmediately() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) + .thenReturn(imWebSocketService); + + // 准备 + ImPrivateMessageNotification dto = new ImPrivateMessageNotification().setSenderId(1L).setReceiverId(2L) + .setType(ImContentTypeEnum.TEXT.getType()); + + // 调用:无事务,应立即发送 + imWebSocketService.sendNotificationAsync(2L, ImConversationTypeEnum.PRIVATE.getType(), + ImContentTypeEnum.TEXT.getType(), dto); + + // 断言 + verify(webSocketSenderApi).sendObject( + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.PRIVATE, + ImContentTypeEnum.TEXT, dto))); + } + } + + @Test + public void testSendNotificationAsync_private_inTransactionDeferredUntilCommit() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) + .thenReturn(imWebSocketService); + + // 准备:开启事务同步 + TransactionSynchronizationManager.initSynchronization(); + try { + ImPrivateMessageNotification dto = new ImPrivateMessageNotification().setSenderId(1L).setReceiverId(2L) + .setType(ImContentTypeEnum.TEXT.getType()); + + // 调用 + imWebSocketService.sendNotificationAsync(2L, ImConversationTypeEnum.PRIVATE.getType(), + ImContentTypeEnum.TEXT.getType(), dto); + + // 断言:事务未提交,未推送 + verify(webSocketSenderApi, never()).sendObject(anyInt(), anyLong(), anyString(), any()); + + // 模拟事务提交 + List syncs = + TransactionSynchronizationManager.getSynchronizations(); + assertEquals(1, syncs.size()); + syncs.forEach(TransactionSynchronization::afterCommit); + + // 断言:提交后推送 + verify(webSocketSenderApi).sendObject( + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.PRIVATE, + ImContentTypeEnum.TEXT, dto))); + } finally { + TransactionSynchronizationManager.clear(); + } + } + } + + // ========== 群聊推送 ========== + + @Test + public void testSendNotificationAsync_group_fanOutToAllUsers() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) + .thenReturn(imWebSocketService); + + ImGroupMessageNotification dto = new ImGroupMessageNotification(); + dto.setGroupId(10L); + dto.setSenderId(1L); + dto.setType(ImContentTypeEnum.TEXT.getType()); + + imWebSocketService.sendNotificationAsync(ListUtil.of(1L, 2L, 3L), + ImConversationTypeEnum.GROUP.getType(), ImContentTypeEnum.TEXT.getType(), dto); + + verify(webSocketSenderApi).sendObject( + eq(UserTypeEnum.ADMIN.getValue()), eq(1L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); + verify(webSocketSenderApi).sendObject( + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); + verify(webSocketSenderApi).sendObject( + eq(UserTypeEnum.ADMIN.getValue()), eq(3L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); + } + } + + @Test + public void testSendNotificationAsync_group_senderExceptionDoesNotBreakOthers() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) + .thenReturn(imWebSocketService); + + ImGroupMessageNotification dto = new ImGroupMessageNotification(); + dto.setGroupId(10L); + // 给 1 号用户推送时抛异常,不能影响 2/3 号 + doThrow(new RuntimeException("user offline")) + .when(webSocketSenderApi).sendObject(anyInt(), eq(1L), anyString(), any()); + + imWebSocketService.sendNotificationAsync(ListUtil.of(1L, 2L, 3L), + ImConversationTypeEnum.GROUP.getType(), ImContentTypeEnum.TEXT.getType(), dto); + + // 2L 和 3L 也都被推送 + verify(webSocketSenderApi).sendObject(anyInt(), eq(2L), anyString(), any()); + verify(webSocketSenderApi).sendObject(anyInt(), eq(3L), anyString(), any()); + } + } + + @Test + public void testDoSendNotification_emptyUserIds_noSend() { + ImGroupMessageNotification dto = new ImGroupMessageNotification(); + dto.setGroupId(10L); + dto.setType(ImContentTypeEnum.TEXT.getType()); + ImNotificationWebSocketDTO notification = buildNotification(ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto); + + imWebSocketService.doSendNotification(Collections.emptyList(), notification); + imWebSocketService.doSendNotification(null, notification); + + verifyNoInteractions(webSocketSenderApi); + } + + @Test + public void testDoSendNotification_distinctUserIds() { + ImGroupMessageNotification dto = new ImGroupMessageNotification(); + dto.setGroupId(10L); + dto.setType(ImContentTypeEnum.TEXT.getType()); + ImNotificationWebSocketDTO notification = buildNotification(ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto); + + imWebSocketService.doSendNotification(Arrays.asList(1L, 2L, 1L, null), notification); + + verify(webSocketSenderApi).sendObject( + eq(UserTypeEnum.ADMIN.getValue()), eq(1L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); + verify(webSocketSenderApi).sendObject( + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); + verifyNoMoreInteractions(webSocketSenderApi); + } + + @Test + public void testSendNotificationAsync_private_exceptionSwallowed() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) + .thenReturn(imWebSocketService); + + // 准备:sender 抛异常 + ImPrivateMessageNotification dto = new ImPrivateMessageNotification().setSenderId(1L).setReceiverId(2L) + .setType(ImContentTypeEnum.TEXT.getType()); + doThrow(new RuntimeException("user offline")) + .when(webSocketSenderApi).sendObject(anyInt(), anyLong(), anyString(), any()); + + // 调用:异常应被吞掉,不向上抛 + imWebSocketService.sendNotificationAsync(2L, ImConversationTypeEnum.PRIVATE.getType(), + ImContentTypeEnum.TEXT.getType(), dto); + + verify(webSocketSenderApi).sendObject(anyInt(), eq(2L), anyString(), any()); + } + } + + @Test + public void testSendNotificationAsync_group_singleUserDefaultOverload() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) + .thenReturn(imWebSocketService); + + ImGroupMessageNotification dto = new ImGroupMessageNotification(); + dto.setGroupId(10L); + dto.setType(ImContentTypeEnum.TEXT.getType()); + + imWebSocketService.sendNotificationAsync(42L, ImConversationTypeEnum.GROUP.getType(), + ImContentTypeEnum.TEXT.getType(), dto); + + verify(webSocketSenderApi).sendObject( + eq(UserTypeEnum.ADMIN.getValue()), eq(42L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); + } + } + + private static boolean isNotification(Object object, ImConversationTypeEnum conversationType, + ImContentTypeEnum contentType, Object payload) { + if (!(object instanceof ImNotificationWebSocketDTO notification)) { + return false; + } + return conversationType.getType().equals(notification.getConversationType()) + && contentType.getType().equals(notification.getContentType()) + && payload == notification.getPayload(); + } + + private static ImNotificationWebSocketDTO buildNotification(ImConversationTypeEnum conversationType, + ImContentTypeEnum contentType, Object payload) { + return new ImNotificationWebSocketDTO() + .setConversationType(conversationType.getType()) + .setContentType(contentType.getType()) + .setPayload(payload); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/util/ImMessageUtilsTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/util/ImMessageUtilsTest.java new file mode 100644 index 0000000000..a821bd00b2 --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/util/ImMessageUtilsTest.java @@ -0,0 +1,184 @@ +package cn.iocoder.yudao.module.im.util; + +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link ImMessageUtils} 的单元测试 + * + * @author 芋道源码 + */ +public class ImMessageUtilsTest { + + // ========== parseQuoteMessageId ========== + + @Test + public void testParseQuoteMessageId_valid() { + // 准备:content 含合法 quote 字段 + String content = "{\"text\":\"你好\",\"quote\":{\"messageId\":1001,\"senderId\":2,\"type\":1}}"; + + // 调用 + 断言 + assertEquals(1001L, ImMessageUtils.parseQuoteMessageId(content)); + } + + @Test + public void testParseQuoteMessageId_invalidJson() { + // 准备:content 非合法 JSON + // 调用 + 断言:解析失败返回 null + assertNull(ImMessageUtils.parseQuoteMessageId("not a json")); + } + + @Test + public void testParseQuoteMessageId_noQuoteField() { + // 准备:content 无 quote 字段 + String content = "{\"text\":\"你好\"}"; + + // 调用 + 断言 + assertNull(ImMessageUtils.parseQuoteMessageId(content)); + } + + @Test + public void testParseQuoteMessageId_quoteIsNotMap() { + // 准备:quote 字段不是对象 + String content = "{\"text\":\"你好\",\"quote\":\"oops\"}"; + + // 调用 + 断言 + assertNull(ImMessageUtils.parseQuoteMessageId(content)); + } + + @Test + public void testParseQuoteMessageId_messageIdMissing() { + // 准备:quote 对象内无 messageId + String content = "{\"quote\":{\"senderId\":2}}"; + + // 调用 + 断言 + assertNull(ImMessageUtils.parseQuoteMessageId(content)); + } + + // ========== appendQuote ========== + + @Test + public void testAppendQuote_existingContent() { + // 准备:已有 text 字段的 content + String content = "{\"text\":\"你好\"}"; + QuoteMessage quote = new QuoteMessage().setMessageId(1001L).setSenderId(2L).setType(1).setContent("{}"); + + // 调用 + String result = ImMessageUtils.appendQuote(content, quote); + + // 断言:保留原字段,注入 quote + Map map = JsonUtils.parseMap(result); + assertEquals("你好", map.get("text")); + assertTrue(map.get("quote") instanceof Map); + assertEquals(1001, ((Map) map.get("quote")).get("messageId")); + } + + @Test + public void testAppendQuote_blankContent() { + // 准备:content 为空字符串,无法解析为 map + QuoteMessage quote = new QuoteMessage().setMessageId(1001L); + + // 调用 + String result = ImMessageUtils.appendQuote("", quote); + + // 断言:内部新建 map,仅含 quote + Map map = JsonUtils.parseMap(result); + assertEquals(1, map.size()); + assertTrue(map.containsKey("quote")); + } + + @Test + public void testAppendQuote_overwriteExistingQuote() { + // 准备:content 已经有一个旧 quote,期望被新 quote 覆盖 + String content = "{\"text\":\"hi\",\"quote\":{\"messageId\":1}}"; + QuoteMessage quote = new QuoteMessage().setMessageId(9999L).setSenderId(2L).setType(1); + + // 调用 + String result = ImMessageUtils.appendQuote(content, quote); + + // 断言:quote.messageId 被覆盖为新值 + Map map = JsonUtils.parseMap(result); + assertEquals(9999, ((Map) map.get("quote")).get("messageId")); + } + + // ========== removeQuote ========== + + @Test + public void testRemoveQuote_blankContent() { + // 调用 + 断言:空内容原样返回 + assertEquals("", ImMessageUtils.removeQuote("")); + assertNull(ImMessageUtils.removeQuote(null)); + } + + @Test + public void testRemoveQuote_noQuoteField() { + // 准备:content 字符串里没有 quote 关键字,提前返回原值 + String content = "{\"text\":\"你好\"}"; + + // 调用 + 断言:同一引用直接返回 + assertEquals(content, ImMessageUtils.removeQuote(content)); + } + + @Test + public void testRemoveQuote_withQuote() { + // 准备:含 quote 的 content + String content = "{\"text\":\"你好\",\"quote\":{\"messageId\":1}}"; + + // 调用 + String result = ImMessageUtils.removeQuote(content); + + // 断言:quote 被移除,保留其它字段 + Map map = JsonUtils.parseMap(result); + assertFalse(map.containsKey("quote")); + assertEquals("你好", map.get("text")); + } + + @Test + public void testRemoveQuote_invalidJsonContainingQuoteToken() { + // 准备:字符串包含 quote 字面量但解析失败 + String content = "not-a-json-but-has-\"quote\"-token"; + + // 调用 + 断言:解析失败返回原值 + assertEquals(content, ImMessageUtils.removeQuote(content)); + } + + // ========== buildQuote ========== + + @Test + public void testBuildQuote_stripsNestedQuote() { + // 准备:被引用消息本身也含有 quote 字段;防止嵌套 + String originalContent = "{\"text\":\"被引用\",\"quote\":{\"messageId\":777}}"; + + // 调用 + QuoteMessage quote = ImMessageUtils.buildQuote(1001L, 2L, 1, originalContent); + + // 断言:基础字段透传,content 已被剥离 quote + assertEquals(1001L, quote.getMessageId()); + assertEquals(2L, quote.getSenderId()); + assertEquals(1, quote.getType()); + Map contentMap = JsonUtils.parseMap(quote.getContent()); + assertFalse(contentMap.containsKey("quote")); + assertEquals("被引用", contentMap.get("text")); + } + + @Test + public void testBuildQuote_originalWithoutQuote() { + // 准备:被引用消息原本就没 quote 字段 + String originalContent = "{\"text\":\"hi\"}"; + + // 调用 + QuoteMessage quote = ImMessageUtils.buildQuote(1001L, 2L, 1, originalContent); + + // 断言:content 原样保留 + assertEquals(originalContent, quote.getContent()); + } + +} diff --git a/yudao-module-im/src/test/resources/application-unit-test.yaml b/yudao-module-im/src/test/resources/application-unit-test.yaml new file mode 100644 index 0000000000..44be16f850 --- /dev/null +++ b/yudao-module-im/src/test/resources/application-unit-test.yaml @@ -0,0 +1,49 @@ +spring: + main: + lazy-initialization: true # 开启懒加载,加快速度 + banner-mode: off # 单元测试,禁用 Banner + +--- #################### 数据库相关配置 #################### + +spring: + # 数据源配置项 + datasource: + name: ruoyi-vue-pro + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value,day; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 + driver-class-name: org.h2.Driver + username: sa + password: + druid: + async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 + initial-size: 1 # 单元测试,配置为 1,提升启动速度 + sql: + init: + schema-locations: classpath:/sql/create_tables.sql + encoding: UTF-8 + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 127.0.0.1 # 地址 + port: 16379 # 端口(单元测试,使用 16379 端口) + database: 0 # 数据库索引 + +mybatis: + lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 + +--- #################### 定时任务相关配置 #################### + +--- #################### 配置中心相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项(单元测试,禁用 Lock4j) + +--- #################### 监控相关配置 #################### + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + info: + base-package: cn.iocoder.yudao.module diff --git a/yudao-module-im/src/test/resources/sql/clean.sql b/yudao-module-im/src/test/resources/sql/clean.sql new file mode 100644 index 0000000000..c25a453d8c --- /dev/null +++ b/yudao-module-im/src/test/resources/sql/clean.sql @@ -0,0 +1,15 @@ +DELETE FROM "im_private_message"; +DELETE FROM "im_group_message"; +DELETE FROM "im_conversation_read"; +DELETE FROM "im_group"; +DELETE FROM "im_group_member"; +DELETE FROM "im_friend"; +DELETE FROM "im_friend_request"; +DELETE FROM "im_group_request"; +DELETE FROM "im_face_pack"; +DELETE FROM "im_face_pack_item"; +DELETE FROM "im_face_user_item"; +DELETE FROM "im_rtc_call"; +DELETE FROM "im_rtc_participant"; +DELETE FROM "im_sensitive_word"; +DELETE FROM "system_users"; diff --git a/yudao-module-im/src/test/resources/sql/create_tables.sql b/yudao-module-im/src/test/resources/sql/create_tables.sql new file mode 100644 index 0000000000..86579c413d --- /dev/null +++ b/yudao-module-im/src/test/resources/sql/create_tables.sql @@ -0,0 +1,349 @@ +CREATE TABLE IF NOT EXISTS "im_private_message" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "client_message_id" varchar(64) DEFAULT NULL COMMENT '客户端消息编号', + "sender_id" bigint NOT NULL COMMENT '发送人编号', + "receiver_id" bigint NOT NULL COMMENT '接收人编号', + "type" smallint NOT NULL COMMENT '消息类型', + "content" varchar(8192) DEFAULT NULL COMMENT '消息内容', + "status" tinyint NOT NULL COMMENT '消息状态', + "receipt_status" tinyint NOT NULL DEFAULT 0 COMMENT '回执状态', + "send_time" timestamp NOT NULL COMMENT '发送时间', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_private_message_sender_client" UNIQUE ("sender_id", "client_message_id", "tenant_id") +) COMMENT 'IM 私聊消息表'; + +CREATE TABLE IF NOT EXISTS "im_group_message" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "client_message_id" varchar(64) DEFAULT NULL COMMENT '客户端消息编号', + "sender_id" bigint NOT NULL COMMENT '发送人编号', + "group_id" bigint NOT NULL COMMENT '群编号', + "type" smallint NOT NULL COMMENT '消息类型', + "content" varchar(8192) DEFAULT NULL COMMENT '消息内容', + "status" tinyint NOT NULL COMMENT '消息状态', + "send_time" timestamp NOT NULL COMMENT '发送时间', + "receiver_user_ids" text DEFAULT NULL COMMENT '定向接收用户编号列表', + "at_user_ids" varchar(1024) DEFAULT NULL COMMENT '@ 目标用户编号列表', + "receipt_status" tinyint NOT NULL DEFAULT 0 COMMENT '回执状态', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_group_message_sender_client" UNIQUE ("sender_id", "client_message_id", "tenant_id") +) COMMENT 'IM 群聊消息表'; + +CREATE TABLE IF NOT EXISTS "im_group" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "name" varchar(64) NOT NULL COMMENT '群名称', + "owner_user_id" bigint NOT NULL COMMENT '群主用户编号', + "avatar" varchar(512) DEFAULT NULL COMMENT '群头像', + "notice" varchar(2048) DEFAULT NULL COMMENT '群公告', + "banned" bit DEFAULT FALSE COMMENT '是否封禁', + "banned_reason" varchar(512) DEFAULT NULL COMMENT '封禁原因', + "banned_time" timestamp DEFAULT NULL COMMENT '封禁时间', + "status" tinyint NOT NULL COMMENT '群状态', + "dissolved_time" timestamp DEFAULT NULL COMMENT '解散时间', + "muted_all" bit DEFAULT FALSE COMMENT '是否全群禁言', + "join_approval" bit NOT NULL DEFAULT FALSE COMMENT '进群是否需群主 / 管理员审批;false 自由进群,true 需审批', + "pinned_message_ids" varchar(128) DEFAULT NULL COMMENT '群置顶消息编号列表,逗号分隔', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +) COMMENT 'IM 群信息表'; + +CREATE TABLE IF NOT EXISTS "im_group_member" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "group_id" bigint NOT NULL COMMENT '群编号', + "user_id" bigint NOT NULL COMMENT '用户编号', + "display_user_name" varchar(64) DEFAULT NULL COMMENT '组内显示名', + "group_remark" varchar(64) DEFAULT NULL COMMENT '群备注', + "silent" bit DEFAULT FALSE COMMENT '是否免打扰', + "status" tinyint NOT NULL COMMENT '成员状态', + "role" tinyint NOT NULL DEFAULT 3 COMMENT '成员角色:1=群主 2=管理员 3=普通成员', + "join_time" timestamp DEFAULT NULL COMMENT '入群时间', + "add_source" tinyint DEFAULT NULL COMMENT '加入来源', + "inviter_user_id" bigint DEFAULT NULL COMMENT '邀请人用户编号;用户主动申请进群时为 NULL', + "quit_time" timestamp DEFAULT NULL COMMENT '退群时间', + "mute_end_time" timestamp DEFAULT NULL COMMENT '禁言到期时间', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_group_member" UNIQUE ("group_id", "user_id", "tenant_id") +) COMMENT 'IM 群成员表'; + +CREATE TABLE IF NOT EXISTS "im_friend" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "user_id" bigint NOT NULL COMMENT '用户编号', + "friend_user_id" bigint NOT NULL COMMENT '好友用户编号', + "silent" bit DEFAULT FALSE COMMENT '是否免打扰', + "display_name" varchar(64) NOT NULL DEFAULT '' COMMENT '好友展示备注(仅自己可见)', + "add_source" tinyint DEFAULT NULL COMMENT '添加来源', + "pinned" bit DEFAULT FALSE COMMENT '是否置顶联系人', + "blocked" bit DEFAULT FALSE COMMENT '是否拉黑', + "status" tinyint NOT NULL COMMENT '好友状态', + "add_time" timestamp DEFAULT NULL COMMENT '添加好友时间', + "delete_time" timestamp DEFAULT NULL COMMENT '删除好友时间', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_user_friend" UNIQUE ("user_id", "friend_user_id", "tenant_id") +) COMMENT 'IM 好友关系表'; + +CREATE TABLE IF NOT EXISTS "im_friend_request" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "from_user_id" bigint NOT NULL COMMENT '发起方用户编号', + "to_user_id" bigint NOT NULL COMMENT '接收方用户编号', + "handle_result" tinyint NOT NULL DEFAULT 0 COMMENT '处理结果;0未处理;1同意;2拒绝', + "apply_content" varchar(255) DEFAULT NULL COMMENT '申请理由', + "handle_content" varchar(255) DEFAULT NULL COMMENT '处理理由', + "display_name" varchar(64) DEFAULT NULL COMMENT '发起方对接收方的备注', + "add_source" tinyint DEFAULT NULL COMMENT '添加来源', + "handle_time" timestamp DEFAULT NULL COMMENT '处理时间', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_friend_request" UNIQUE ("from_user_id", "to_user_id", "tenant_id") +) COMMENT 'IM 好友申请记录表'; + +CREATE TABLE IF NOT EXISTS "im_group_request" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "group_id" bigint NOT NULL COMMENT '群编号', + "user_id" bigint NOT NULL COMMENT '申请人 / 被邀请人用户编号', + "inviter_user_id" bigint DEFAULT NULL COMMENT '邀请人用户编号;NULL=主动申请;非NULL=被邀请待审批', + "apply_content" varchar(255) DEFAULT NULL COMMENT '申请理由', + "add_source" tinyint DEFAULT NULL COMMENT '加入来源', + "handle_result" tinyint NOT NULL DEFAULT 0 COMMENT '处理结果;0未处理;1同意;2拒绝', + "handle_user_id" bigint DEFAULT NULL COMMENT '处理人用户编号', + "handle_content" varchar(255) DEFAULT NULL COMMENT '处理理由', + "handle_time" timestamp DEFAULT NULL COMMENT '处理时间', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_group_request" UNIQUE ("group_id", "user_id", "tenant_id") +) COMMENT 'IM 加群申请记录表'; + +CREATE TABLE IF NOT EXISTS "im_face_pack" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "name" varchar(64) NOT NULL COMMENT '表情包名称', + "icon" varchar(512) DEFAULT NULL COMMENT '表情包图标(面板底部 tab 显示)', + "sort" int NOT NULL DEFAULT 0 COMMENT '排序', + "status" tinyint NOT NULL COMMENT '状态', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +) COMMENT 'IM 表情包表'; + +CREATE TABLE IF NOT EXISTS "im_face_pack_item" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "pack_id" bigint NOT NULL COMMENT '所属表情包编号', + "url" varchar(512) NOT NULL COMMENT '表情图 URL', + "name" varchar(64) DEFAULT NULL COMMENT '表情名(可选;如「狗头」「捂脸」)', + "width" int NOT NULL DEFAULT 0 COMMENT '渲染宽度(像素)', + "height" int NOT NULL DEFAULT 0 COMMENT '渲染高度(像素)', + "sort" int NOT NULL DEFAULT 0 COMMENT '排序', + "status" tinyint NOT NULL COMMENT '状态', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +) COMMENT 'IM 表情包项表'; + +CREATE TABLE IF NOT EXISTS "im_rtc_call" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "room" varchar(64) NOT NULL COMMENT '业务通话编号', + "conversation_type" tinyint NOT NULL COMMENT '会话类型', + "media_type" tinyint NOT NULL COMMENT '媒体类型', + "inviter_user_id" bigint NOT NULL COMMENT '发起人用户编号', + "group_id" bigint DEFAULT NULL COMMENT '群编号', + "status" tinyint NOT NULL COMMENT '通话状态', + "end_reason" tinyint DEFAULT NULL COMMENT '结束原因', + "start_time" timestamp NOT NULL COMMENT '发起时间', + "accept_time" timestamp DEFAULT NULL COMMENT '接通时间', + "end_time" timestamp DEFAULT NULL COMMENT '结束时间', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +) COMMENT 'IM 通话记录表'; + +CREATE TABLE IF NOT EXISTS "im_rtc_participant" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "call_id" bigint NOT NULL COMMENT '通话编号', + "room" varchar(64) NOT NULL COMMENT '业务通话编号', + "user_id" bigint NOT NULL COMMENT '参与者用户编号', + "role" tinyint NOT NULL COMMENT '参与角色', + "status" tinyint NOT NULL COMMENT '参与状态', + "invite_time" timestamp NOT NULL COMMENT '被邀请时间', + "accept_time" timestamp DEFAULT NULL COMMENT '接听时间', + "leave_time" timestamp DEFAULT NULL COMMENT '离开时间', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_rtc_participant_room_user" UNIQUE ("room", "user_id", "tenant_id") +) COMMENT 'IM 通话参与者表'; + +CREATE TABLE IF NOT EXISTS "im_face_user_item" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "user_id" bigint NOT NULL COMMENT '所属用户编号', + "url" varchar(512) NOT NULL COMMENT '表情图 URL', + "name" varchar(64) DEFAULT NULL COMMENT '表情名(可选)', + "width" int NOT NULL DEFAULT 0 COMMENT '渲染宽度(像素)', + "height" int NOT NULL DEFAULT 0 COMMENT '渲染高度(像素)', + "sort" int NOT NULL DEFAULT 0 COMMENT '排序', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_face_user_item_user_url_deleted" UNIQUE ("user_id", "url", "deleted") +) COMMENT 'IM 用户私有表情表'; + +CREATE TABLE IF NOT EXISTS "im_channel" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "code" varchar(64) NOT NULL COMMENT '频道业务码;唯一', + "name" varchar(64) NOT NULL COMMENT '频道名称', + "avatar" varchar(512) DEFAULT NULL COMMENT '频道头像', + "sort" int NOT NULL DEFAULT 0 COMMENT '排序', + "status" tinyint NOT NULL DEFAULT 0 COMMENT '状态;0 启用 1 停用', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +) COMMENT 'IM 频道表'; + +CREATE TABLE IF NOT EXISTS "im_channel_material" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "channel_id" bigint NOT NULL COMMENT '频道编号', + "type" tinyint NOT NULL COMMENT '内容类型;1 站内富文本 2 外链', + "title" varchar(128) NOT NULL COMMENT '标题', + "cover_url" varchar(512) DEFAULT NULL COMMENT '封面图', + "summary" varchar(255) DEFAULT NULL COMMENT '摘要', + "content" clob DEFAULT NULL COMMENT '正文;富文本 HTML', + "url" varchar(512) DEFAULT NULL COMMENT '跳转链接;为空时点击在客户端内置详情页拉 content;非空则跳 url', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +) COMMENT 'IM 频道素材表'; + +CREATE TABLE IF NOT EXISTS "im_channel_message" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "channel_id" bigint NOT NULL COMMENT '频道编号;冗余 im_channel_material.channel_id 便于检索', + "material_id" bigint NOT NULL COMMENT '关联素材编号', + "type" smallint NOT NULL COMMENT '消息类型', + "content" varchar(8192) DEFAULT NULL COMMENT '消息内容;推送时 payload JSON 快照;不含富文本正文', + "receiver_user_ids" text DEFAULT NULL COMMENT '接收人编号列表;逗号分隔;为空表示全员', + "send_time" timestamp NOT NULL COMMENT '发送时间', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +) COMMENT 'IM 频道消息表'; + +CREATE TABLE IF NOT EXISTS "im_conversation_read" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "user_id" bigint NOT NULL COMMENT '用户编号', + "conversation_type" tinyint NOT NULL COMMENT '会话类型', + "target_id" bigint NOT NULL COMMENT '目标编号', + "message_id" bigint NOT NULL COMMENT '最大已读消息编号', + "read_time" timestamp NOT NULL COMMENT '最近已读时间', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_conversation_read_user_target" UNIQUE ("user_id", "conversation_type", "target_id", "tenant_id") +) COMMENT 'IM 会话读位置表'; + +CREATE TABLE IF NOT EXISTS "im_sensitive_word" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "word" varchar(128) NOT NULL COMMENT '敏感词', + "status" tinyint NOT NULL DEFAULT 0 COMMENT '状态;0 启用 1 停用', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_sensitive_word" UNIQUE ("word", "tenant_id") +) COMMENT 'IM 敏感词表'; + +CREATE TABLE IF NOT EXISTS "system_users" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "username" varchar(30) NOT NULL DEFAULT '', + "password" varchar(100) NOT NULL DEFAULT '', + "nickname" varchar(30) NOT NULL DEFAULT '', + "remark" varchar(500) DEFAULT NULL, + "dept_id" bigint DEFAULT NULL, + "post_ids" varchar(255) DEFAULT NULL, + "email" varchar(50) DEFAULT '', + "mobile" varchar(11) DEFAULT '', + "sex" tinyint DEFAULT 0, + "avatar" varchar(100) DEFAULT '', + "status" tinyint NOT NULL DEFAULT 0, + "login_ip" varchar(50) DEFAULT '', + "login_date" timestamp DEFAULT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +) COMMENT '用户信息表'; diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index ca7db64073..12bab8ae50 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -1,10 +1,11 @@ package cn.iocoder.yudao.module.infra.controller.admin.file; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*; @@ -27,7 +28,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.nio.charset.StandardCharsets; import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -111,10 +111,10 @@ public class FileController { if (StrUtil.isEmpty(path)) { throw new IllegalArgumentException("结尾的 path 路径必须传递"); } - // 解码,解决中文路径的问题 + // 解码,解决中文、%、+ 等特殊字符路径的问题 // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/ // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/ - path = URLUtil.decode(path, StandardCharsets.UTF_8, false); + path = HttpUtils.decodeUrlPath(path); // 读取内容 byte[] content = fileService.getFileContent(configId, path); @@ -123,7 +123,9 @@ public class FileController { response.setStatus(HttpStatus.NOT_FOUND.value()); return; } - writeAttachment(response, path, content); + FileDO file = fileService.getFileByConfigIdAndPath(configId, path); + String filename = file != null && StrUtil.isNotEmpty(file.getName()) ? file.getName() : FileUtil.getName(path); + writeAttachment(response, filename, content); } @GetMapping("/page") diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java index 06dff7c087..dbdbd9cd60 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file; -import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.infra.framework.file.core.utils.FilePathUtils; import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.AssertTrue; @@ -26,10 +26,7 @@ public class FileUploadReqVO { } public static boolean isDirectoryValid(String directory) { - // 1. 不能包含 .. 防止目录穿越 - // 2. 不能以 / 或 \ 开头,防止上传到根目录 - return !StrUtil.contains(directory, "..") - && !StrUtil.startWithAny(directory, "/", "\\"); + return FilePathUtils.isDirectoryValid(directory); } } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java index c3e14a803e..d3d60b382f 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java @@ -36,7 +36,7 @@ public class AppFileController { @Parameter(name = "file", description = "文件附件", required = true, schema = @Schema(type = "string", format = "binary")) @PermitAll - public CommonResult uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception { + public CommonResult uploadFile(@Valid AppFileUploadReqVO uploadReqVO) throws Exception { MultipartFile file = uploadReqVO.getFile(); byte[] content = IoUtil.readBytes(file.getInputStream()); return success(fileService.createFile(content, file.getOriginalFilename(), diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java index 11233a457b..a5b17c6eb3 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java @@ -23,4 +23,11 @@ public interface FileMapper extends BaseMapperX { .orderByDesc(FileDO::getId)); } + default FileDO selectLatestByConfigIdAndPath(Long configId, String path) { + return selectLastOne(new LambdaQueryWrapperX() + .eq(FileDO::getConfigId, configId) + .eq(FileDO::getPath, path) + .orderByAsc(FileDO::getId)); + } + } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java index 2233f353e7..7cf23c827d 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java @@ -33,6 +33,7 @@ public interface ErrorCodeConstants { ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, "文件路径已存在"); ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, "文件不存在"); ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, "文件为空"); + ErrorCode FILE_PATH_INVALID = new ErrorCode(1_001_003_003, "文件路径不正确"); // ========== 代码生成器 1-001-004-000 ========== ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1_001_004_002, "表定义已经存在"); diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java index b3c8116cd6..d04b85c7e4 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java @@ -21,6 +21,9 @@ public enum CodegenFrontTypeEnum { VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + ANTD + schema 模版 VUE3_VBEN5_ANTD_GENERAL(41), // Vue3 VBEN5 + ANTD 标准模版 + VUE3_VBEN5_ANTDV_NEXT_SCHEMA(42), // Vue3 VBEN5 + Antdv Next + schema 模版 + VUE3_VBEN5_ANTDV_NEXT_GENERAL(43), // Vue3 VBEN5 + Antdv Next 标准模版 + VUE3_VBEN5_EP_SCHEMA(50), // Vue3 VBEN5 + EP + schema 模版 VUE3_VBEN5_EP_GENERAL(51), // Vue3 VBEN5 + EP 标准模版 diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java index 7ddade3eac..d03caca7a0 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.infra.framework.file.core.client; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import lombok.extern.slf4j.Slf4j; /** @@ -72,7 +73,7 @@ public abstract class AbstractFileClient implem * @return URL 访问地址 */ protected String formatFileUrl(String domain, String path) { - return StrUtil.format("{}/admin-api/infra/file/{}/get/{}", domain, getId(), path); + return StrUtil.format("{}/admin-api/infra/file/{}/get/{}", domain, getId(), HttpUtils.encodeUrlPath(path)); } } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/local/LocalFileClient.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/local/LocalFileClient.java index 6e5c0229ba..9c3af16cc5 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/local/LocalFileClient.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/local/LocalFileClient.java @@ -3,8 +3,13 @@ package cn.iocoder.yudao.module.infra.framework.file.core.client.local; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; +import cn.iocoder.yudao.module.infra.framework.file.core.utils.FilePathUtils; -import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_PATH_INVALID; /** * 本地文件客户端 @@ -50,7 +55,13 @@ public class LocalFileClient extends AbstractFileClient { } private String getFilePath(String path) { - return config.getBasePath() + File.separator + path; + FilePathUtils.validatePath(path); + Path basePath = Paths.get(config.getBasePath()).toAbsolutePath().normalize(); + Path filePath = basePath.resolve(path).normalize(); + if (!filePath.startsWith(basePath)) { + throw exception(FILE_PATH_INVALID); + } + return filePath.toString(); } } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java index aad9007d7f..98d37d805c 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java @@ -116,13 +116,18 @@ public class S3FileClient extends AbstractFileClient { @Override public String presignGetUrl(String url, Integer expirationSeconds) { // 1. 将 url 转换为 path - String path = StrUtil.removePrefix(url, config.getDomain() + "/"); - path = HttpUtils.decodeUrlPath(HttpUtils.removeUrlQuery(path)); + // 完整 Domain URL 会解码还原为原始对象名;裸 path 约定已经是未编码的对象名 + boolean domainUrl = StrUtil.startWith(url, config.getDomain() + "/"); + String path = domainUrl ? StrUtil.removePrefix(url, config.getDomain() + "/") : url; + if (domainUrl) { + path = HttpUtils.removeUrlPathQueryAndFragment(path); + path = HttpUtils.decodeUrlPath(path); + } // 2.1 情况一:公开访问:无需签名 // 考虑到老版本的兼容,所以必须是 config.getEnablePublicAccess() 为 false 时,才进行签名 if (!BooleanUtil.isFalse(config.getEnablePublicAccess())) { - return config.getDomain() + "/" + path; + return config.getDomain() + "/" + HttpUtils.encodeUrlPath(path); } // 2.2 情况二:私有访问:生成 GET 预签名 URL diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FilePathUtils.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FilePathUtils.java new file mode 100644 index 0000000000..e61bfe78fd --- /dev/null +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FilePathUtils.java @@ -0,0 +1,106 @@ +package cn.iocoder.yudao.module.infra.framework.file.core.utils; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; + +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_PATH_INVALID; + +/** + * 文件路径工具类 + * + * @author 芋道源码 + */ +public class FilePathUtils { + + private FilePathUtils() { + } + + /** + * 校验文件名是否合法,禁止携带目录路径。 + * + * @param name 文件名 + * @return 文件名 + */ + public static String validateFileName(String name) { + if (StrUtil.isEmpty(name)) { + return name; + } + if (!isPathValid(name) || StrUtil.contains(name, StrUtil.SLASH) || !StrUtil.equals(name, FileUtil.getName(name))) { + throw exception(FILE_PATH_INVALID); + } + return name; + } + + /** + * 校验文件目录是否合法。 + * + * @param directory 文件目录,允许为空 + * @return 是否合法 + */ + public static boolean isDirectoryValid(String directory) { + return StrUtil.isEmpty(directory) || isPathValid(directory); + } + + /** + * 校验文件目录是否合法,不合法时抛出业务异常。 + * + * @param directory 文件目录,允许为空 + */ + public static void validateDirectory(String directory) { + if (!isDirectoryValid(directory)) { + throw exception(FILE_PATH_INVALID); + } + } + + /** + * 校验文件相对路径是否合法,不合法时抛出业务异常。 + * + * @param path 文件相对路径 + */ + public static void validatePath(String path) { + if (StrUtil.isEmpty(path) || !isPathValid(path)) { + throw exception(FILE_PATH_INVALID); + } + } + + /** + * 校验路径是否为安全的相对路径,禁止绝对路径、Windows 盘符、反斜杠、空路径段和目录穿越。 + * + * @param path 路径 + * @return 是否合法 + */ + private static boolean isPathValid(String path) { + // 不能以 / 或 \ 开头,避免传入绝对路径 + if (StrUtil.startWithAny(path, StrUtil.SLASH, "\\")) { + return false; + } + // 不能包含反斜杠或空字符,避免绕过不同系统的路径解析 + if (StrUtil.contains(path, "\\") || path.indexOf('\0') >= 0) { + return false; + } + // 不能是 Windows 盘符路径,例如 C:/test.jpg + if (path.length() >= 2 && Character.isLetter(path.charAt(0)) && path.charAt(1) == ':') { + return false; + } + try { + // 使用 JDK Path 再兜底判断一次绝对路径 + if (Paths.get(path).isAbsolute()) { + return false; + } + } catch (InvalidPathException ex) { + return false; + } + // 不能包含空路径段、当前目录或上级目录,避免目录穿越 + for (String segment : path.split(StrUtil.SLASH, -1)) { + if (StrUtil.isEmpty(segment) || ".".equals(segment) || "..".equals(segment)) { + return false; + } + } + return true; + } + +} diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java index f8cd1c45cd..3b0b53327a 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java @@ -85,9 +85,9 @@ public class FileTypeUtils { // 设置内容显示、下载文件名:https://www.cnblogs.com/wq-9/articles/12165056.html if (isImage(mineType)) { // 参见 https://github.com/YunaiV/ruoyi-vue-pro/issues/692 讨论 - response.setHeader("Content-Disposition", "inline;filename=" + HttpUtils.encodeUtf8(filename)); + response.setHeader("Content-Disposition", buildContentDisposition("inline", filename)); } else { - response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename)); + response.setHeader("Content-Disposition", buildContentDisposition("attachment", filename)); } // 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题 if (StrUtil.containsIgnoreCase(mineType, "video")) { @@ -98,6 +98,29 @@ public class FileTypeUtils { IoUtil.write(response.getOutputStream(), false, content); } + private static String buildContentDisposition(String dispositionType, String filename) { + return StrUtil.format("{};filename=\"{}\";filename*=UTF-8''{}", + dispositionType, buildFallbackFilename(filename), HttpUtils.encodeUrlPathSegment(filename)); + } + + private static String buildFallbackFilename(String filename) { + if (StrUtil.isEmpty(filename)) { + return "download"; + } + StringBuilder result = new StringBuilder(filename.length()); + for (int i = 0; i < filename.length(); i++) { + char ch = filename.charAt(i); + if (ch == '"' || ch == '\\') { + result.append('\\').append(ch); + } else if (ch >= 0x20 && ch <= 0x7E) { + result.append(ch); + } else { + result.append('_'); + } + } + return result.toString(); + } + /** * 判断是否是图片 * diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java index 1ba0cd7ede..d675558a55 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java @@ -241,6 +241,46 @@ public class CodegenEngine { vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue")) .put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/modules/list_sub_erp.vue"), // 特殊:主子表专属逻辑 vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue")) + // VUE3_VBEN5_ANTDV_NEXT_SCHEMA + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), vue3Vben5AntdvNextSchemaTemplatePath("views/data.ts"), + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/data.ts")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), vue3Vben5AntdvNextSchemaTemplatePath("views/index.vue"), + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/index.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), vue3Vben5AntdvNextSchemaTemplatePath("views/form.vue"), + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), vue3Vben5AntdvNextSchemaTemplatePath("views/import.vue"), + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/import-form.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), vue3Vben5AntdvNextSchemaTemplatePath("api/api.ts"), + vue3VbenFilePath("api/${table.moduleName}/${table.businessName}/index.ts")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), vue3Vben5AntdvNextSchemaTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑 + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), vue3Vben5AntdvNextSchemaTemplatePath("views/modules/form_sub_inner.vue"), // 特殊:主子表专属逻辑 + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), vue3Vben5AntdvNextSchemaTemplatePath("views/modules/form_sub_erp.vue"), // 特殊:主子表专属逻辑 + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), vue3Vben5AntdvNextSchemaTemplatePath("views/modules/list_sub_inner.vue"), // 特殊:主子表专属逻辑 + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(), vue3Vben5AntdvNextSchemaTemplatePath("views/modules/list_sub_erp.vue"), // 特殊:主子表专属逻辑 + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue")) + // VUE3_VBEN5_ANTDV_NEXT_GENERAL + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), vue3Vben5AntdvNextGeneralTemplatePath("views/index.vue"), + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/index.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), vue3Vben5AntdvNextGeneralTemplatePath("views/form.vue"), + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), vue3Vben5AntdvNextGeneralTemplatePath("views/import.vue"), + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/import-form.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), vue3Vben5AntdvNextGeneralTemplatePath("api/api.ts"), + vue3VbenFilePath("api/${table.moduleName}/${table.businessName}/index.ts")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), vue3Vben5AntdvNextGeneralTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑 + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), vue3Vben5AntdvNextGeneralTemplatePath("views/modules/form_sub_inner.vue"), // 特殊:主子表专属逻辑 + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), vue3Vben5AntdvNextGeneralTemplatePath("views/modules/form_sub_erp.vue"), // 特殊:主子表专属逻辑 + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), vue3Vben5AntdvNextGeneralTemplatePath("views/modules/list_sub_inner.vue"), // 特殊:主子表专属逻辑 + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue")) + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(), vue3Vben5AntdvNextGeneralTemplatePath("views/modules/list_sub_erp.vue"), // 特殊:主子表专属逻辑 + vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue")) .build(); @Resource @@ -419,7 +459,7 @@ public class CodegenEngine { private String prettyCode(String content, String vmPath) { // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(需要排除 vben5、vue3_admin_uniapp) if (!StrUtil.containsAny(vmPath, "vben5", "vue3_admin_uniapp")) { - content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }"); + content = content.replaceAll(",\\r?\\n}", "\n}").replaceAll(",\\r?\\n }", "\n }"); } // Vue 界面:去除多的 dateFormatter,只有一个的情况下,说明没使用到 if (StrUtil.count(content, "dateFormatter") == 1) { @@ -682,6 +722,14 @@ public class CodegenEngine { return "codegen/vue3_vben5_ele/general/" + path + ".vm"; } + private static String vue3Vben5AntdvNextSchemaTemplatePath(String path) { + return "codegen/vue3_vben5_antdv_next/schema/" + path + ".vm"; + } + + private static String vue3Vben5AntdvNextGeneralTemplatePath(String path) { + return "codegen/vue3_vben5_antdv_next/general/" + path + ".vm"; + } + private static boolean isSubTemplate(String path) { return path.contains("_sub"); } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java index 421bbe8d40..a561b4eba8 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java @@ -86,4 +86,13 @@ public interface FileService { */ byte[] getFileContent(Long configId, String path) throws Exception; + /** + * 获得文件 + * + * @param configId 配置编号 + * @param path 文件路径 + * @return 文件 + */ + FileDO getFileByConfigIdAndPath(Long configId, String path); + } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java index 639c689104..7fe36cd763 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java @@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresigned import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient; +import cn.iocoder.yudao.module.infra.framework.file.core.utils.FilePathUtils; import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils; import com.google.common.annotations.VisibleForTesting; import jakarta.annotation.Resource; @@ -70,11 +71,14 @@ public class FileServiceImpl implements FileService { @Override @SneakyThrows public String createFile(byte[] content, String name, String directory, String type) { - // 1.1 处理 type 为空的情况 + // 1.1 处理 name 的合法性,禁止携带目录路径 + name = FilePathUtils.validateFileName(name); + + // 1.2.1 处理 type 为空的情况 if (StrUtil.isEmpty(type)) { type = FileTypeUtils.getMineType(content, name); } - // 1.2 处理 name 为空的情况 + // 1.2.2 处理 name 为空的情况 if (StrUtil.isEmpty(name)) { name = DigestUtil.sha256Hex(content); } @@ -102,7 +106,11 @@ public class FileServiceImpl implements FileService { @VisibleForTesting String generateUploadPath(String name, String directory) { - // 1. 生成前缀、后缀 + // 1.1 处理 name 和 directory 的合法性 + name = FilePathUtils.validateFileName(name); + FilePathUtils.validatePath(name); + FilePathUtils.validateDirectory(directory); + // 1.2 生成前缀、后缀 String prefix = null; if (PATH_PREFIX_DATE_ENABLE) { prefix = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), PURE_DATE_PATTERN); @@ -159,7 +167,13 @@ public class FileServiceImpl implements FileService { @Override public Long createFile(FileCreateReqVO createReqVO) { + // 1.1 校验参数的合法性 + FilePathUtils.validatePath(createReqVO.getPath()); + createReqVO.setName(FilePathUtils.validateFileName(createReqVO.getName())); + // 1.2 处理 URL 的合法性,移除 URL 中的查询参数(例如签名参数),保证 URL 的唯一性 createReqVO.setUrl(HttpUtils.removeUrlQuery(createReqVO.getUrl())); // 目的:移除私有桶情况下,URL 的签名参数 + + // 2. 保存到数据库 FileDO file = BeanUtils.toBean(createReqVO, FileDO.class); fileMapper.insert(file); return file.getId(); @@ -172,15 +186,17 @@ public class FileServiceImpl implements FileService { @Override public void deleteFile(Long id) throws Exception { - // 校验存在 + // 1.1 校验存在 FileDO file = validateFileExists(id); + // 1.2 校验路径合法性,避免误删文件存储器中的其他文件 + FilePathUtils.validatePath(file.getPath()); - // 从文件存储器中删除 + // 2.1 从文件存储器中删除 FileClient client = fileConfigService.getFileClient(file.getConfigId()); Assert.notNull(client, "客户端({}) 不能为空", file.getConfigId()); client.delete(file.getPath()); - // 删除记录 + // 2.2 删除记录 fileMapper.deleteById(id); } @@ -190,6 +206,7 @@ public class FileServiceImpl implements FileService { // 删除文件 List files = fileMapper.selectByIds(ids); for (FileDO file : files) { + FilePathUtils.validatePath(file.getPath()); // 获取客户端 FileClient client = fileConfigService.getFileClient(file.getConfigId()); Assert.notNull(client, "客户端({}) 不能为空", file.getPath()); @@ -211,9 +228,19 @@ public class FileServiceImpl implements FileService { @Override public byte[] getFileContent(Long configId, String path) throws Exception { + // 1. 校验路径合法性 + FilePathUtils.validatePath(path); + + // 2.1 获取客户端 FileClient client = fileConfigService.getFileClient(configId); Assert.notNull(client, "客户端({}) 不能为空", configId); + // 2.2 获取文件内容 return client.getContent(path); } + @Override + public FileDO getFileByConfigIdAndPath(Long configId, String path) { + return fileMapper.selectLatestByConfigIdAndPath(configId, path); + } + } diff --git a/yudao-module-infra/src/main/resources/codegen/java/service/serviceImpl.vm b/yudao-module-infra/src/main/resources/codegen/java/service/serviceImpl.vm index c73bd7c7f3..98d8358f37 100644 --- a/yudao-module-infra/src/main/resources/codegen/java/service/serviceImpl.vm +++ b/yudao-module-infra/src/main/resources/codegen/java/service/serviceImpl.vm @@ -1,6 +1,15 @@ package ${basePackage}.module.${table.moduleName}.service.${table.businessName}; import cn.hutool.core.collection.CollUtil; +#set ($hasSubJoinMany = false) +#foreach ($subTable in $subTables) +#if ( $subTable.subJoinMany ) +#set ($hasSubJoinMany = true) +#end +#end +#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 && $hasSubJoinMany ) +import cn.hutool.core.util.ObjectUtil; +#end import org.springframework.stereotype.Service; import ${jakartaPackage}.annotation.Resource; import org.springframework.validation.annotation.Validated; diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm index 40087063cb..3a009db87e 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm @@ -40,49 +40,13 @@ /> #elseif ($column.htmlType == "datetime" && $listOperationCondition == "BETWEEN") - #set ($AttrName = $javaField.substring(0,1).toUpperCase() + ${javaField.substring(1)}) - - - ${comment} - - - - - {{ formatDate(formData.${javaField}?.[0]) || '开始日期' }} - - - - - - - {{ formatDate(formData.${javaField}?.[1]) || '结束日期' }} - - - - - - - 取消 - - - 确定 - - - - - - 取消 - - - 确定 - - - + #elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $dictType && "" != $dictType) ${comment} - + 全部 @@ -177,7 +141,7 @@ const emit = defineEmits<{ reset: [] }>() -const visible = ref(false) +const visible = ref(false) // 搜索弹窗显示状态 const formData = reactive({ #foreach($column in $columns) #if ($column.listOperation) @@ -201,7 +165,7 @@ const formData = reactive({ #end #end #end -}) +}) // 搜索表单数据 /** 搜索条件 placeholder 拼接 */ const placeholder = computed(() => { @@ -236,31 +200,7 @@ const placeholder = computed(() => { return conditions.length > 0 ? conditions.join(' | ') : '搜索${table.classComment}' }) -#if ($hasDateTimeBetween == 1) -// 时间范围选择器状态 -#foreach($column in $columns) - #if ($column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN") - #set ($javaField = $column.javaField) - #set ($AttrName = $javaField.substring(0,1).toUpperCase() + ${javaField.substring(1)}) -const visible${AttrName} = ref<[boolean, boolean]>([false, false]) -const temp${AttrName} = ref<[number, number]>([Date.now(), Date.now()]) - -/** ${column.columnComment}[0]确认 */ -function handle${AttrName}0Confirm() { - formData.${javaField} = [temp${AttrName}.value[0], formData.${javaField}?.[1]] - visible${AttrName}.value[0] = false -} - -/** ${column.columnComment}[1]确认 */ -function handle${AttrName}1Confirm() { - formData.${javaField} = [formData.${javaField}?.[0], temp${AttrName}.value[1]] - visible${AttrName}.value[1] = false -} - #end -#end -#end - -/** 搜索 */ +/** 搜索按钮操作 */ function handleSearch() { visible.value = false emit('search', { @@ -277,7 +217,7 @@ function handleSearch() { }) } -/** 重置 */ +/** 重置按钮操作 */ function handleReset() { #foreach($column in $columns) #if ($column.listOperation) diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm index b3302e521d..bde41c2ca7 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm @@ -62,11 +62,12 @@ #end #end import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' +import { onUnload } from '@dcloudio/uni-app' import { onMounted, ref } from 'vue' -import { useToast } from 'wot-design-uni' +import { useToast } from '@wot-ui/ui/components/wd-toast' import { delete${simpleClassName}, get${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' import { useAccess } from '@/hooks/useAccess' -import { navigateBackPlus } from '@/utils' +import { delay, navigateBackPlus } from '@/utils' #if ($hasDict == 1) import { DICT_TYPE } from '@/utils/constants' #end @@ -87,8 +88,8 @@ definePage({ const { hasAccessByCodes } = useAccess() const toast = useToast() -const formData = ref<${simpleClassName}>() -const deleting = ref(false) +const formData = ref<${simpleClassName}>() // 详情数据 +const deleting = ref(false) // 删除状态 /** 返回上一页 */ function handleBack() { @@ -97,7 +98,7 @@ function handleBack() { /** 加载${table.classComment}详情 */ async function getDetail() { - if (!props.id) { + if (!props.id || deleting.value) { return } try { @@ -131,9 +132,8 @@ function handleDelete() { try { await delete${simpleClassName}(props.id) toast.success('删除成功') - setTimeout(() => { - handleBack() - }, 500) + uni.$emit('${table.moduleName}:${table.businessName}:reload') + delay(handleBack) } finally { deleting.value = false } @@ -143,8 +143,14 @@ function handleDelete() { /** 初始化 */ onMounted(() => { + uni.$on('${table.moduleName}:${table.businessName}:reload', getDetail) getDetail() }) + +/** 卸载 */ +onUnload(() => { + uni.$off('${table.moduleName}:${table.businessName}:reload', getDetail) +}) diff --git a/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/form/index b/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/form/index new file mode 100644 index 0000000000..06e1a54fa9 --- /dev/null +++ b/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/form/index @@ -0,0 +1,187 @@ + + + + + diff --git a/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/index b/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/index new file mode 100644 index 0000000000..acb1b87095 --- /dev/null +++ b/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/index @@ -0,0 +1,169 @@ + + + + + diff --git a/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/search-form b/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/search-form new file mode 100644 index 0000000000..9571c6e00c --- /dev/null +++ b/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/search-form @@ -0,0 +1,140 @@ + + + diff --git a/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/xml/InfraStudentMapper b/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/xml/InfraStudentMapper new file mode 100644 index 0000000000..155aa5c27a --- /dev/null +++ b/yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/xml/InfraStudentMapper @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-infra/src/test/resources/codegen/vue3_master_inner/java/InfraStudentServiceImpl b/yudao-module-infra/src/test/resources/codegen/vue3_master_inner/java/InfraStudentServiceImpl index 2b485a4387..31d689e127 100644 --- a/yudao-module-infra/src/test/resources/codegen/vue3_master_inner/java/InfraStudentServiceImpl +++ b/yudao-module-infra/src/test/resources/codegen/vue3_master_inner/java/InfraStudentServiceImpl @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.infra.service.demo; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; import org.springframework.stereotype.Service; import jakarta.annotation.Resource; import org.springframework.validation.annotation.Validated; diff --git a/yudao-module-infra/src/test/resources/codegen/vue3_master_normal/java/InfraStudentServiceImpl b/yudao-module-infra/src/test/resources/codegen/vue3_master_normal/java/InfraStudentServiceImpl index 2b485a4387..31d689e127 100644 --- a/yudao-module-infra/src/test/resources/codegen/vue3_master_normal/java/InfraStudentServiceImpl +++ b/yudao-module-infra/src/test/resources/codegen/vue3_master_normal/java/InfraStudentServiceImpl @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.infra.service.demo; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; import org.springframework.stereotype.Service; import jakarta.annotation.Resource; import org.springframework.validation.annotation.Validated; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java index 7922eb1b24..08b8cb39bb 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java @@ -94,6 +94,9 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { // 2. 组装返回结果 Set deviceIds = convertSet(configList, IotDeviceModbusConfigDO::getDeviceId); Map deviceMap = deviceService.getDeviceMap(deviceIds); + if (CollUtil.isEmpty(deviceMap)) { + return success(new ArrayList<>()); + } Map> pointMap = modbusPointService.getEnabledDeviceModbusPointMapByDeviceIds(deviceIds); Map productMap = productService.getProductMap(convertSet(deviceMap.values(), IotDeviceDO::getProductId)); List result = new ArrayList<>(configList.size()); @@ -139,4 +142,4 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { return success(deviceService.registerSubDevices(reqDTO)); } -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigRespVO.java index e68a7b7851..6b11c617d2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigRespVO.java @@ -37,6 +37,15 @@ public class IotAlertConfigRespVO { @Schema(description = "接收的类型数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") private List receiveTypes; + @Schema(description = "短信模板编号", example = "iot_alert_sms") + private String smsTemplateCode; + + @Schema(description = "邮件模板编号", example = "iot_alert_mail") + private String mailTemplateCode; + + @Schema(description = "站内信模板编号", example = "iot_alert_notify") + private String notifyTemplateCode; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigSaveReqVO.java index 694e8bfdf7..e83e255044 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigSaveReqVO.java @@ -44,4 +44,12 @@ public class IotAlertConfigSaveReqVO { @NotEmpty(message = "接收的类型数组不能为空") private List receiveTypes; + @Schema(description = "短信模板编号", example = "iot_alert_sms") + private String smsTemplateCode; + + @Schema(description = "邮件模板编号", example = "iot_alert_mail") + private String mailTemplateCode; + + @Schema(description = "站内信模板编号", example = "iot_alert_notify") + private String notifyTemplateCode; } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java index 835ef62933..e3f732b982 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java @@ -29,6 +29,9 @@ public class IotSceneRuleRespVO { @Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED) private List actions; + @Schema(description = "最后触发时间") + private LocalDateTime lastTriggerTime; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alert/IotAlertConfigDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alert/IotAlertConfigDO.java index 69f466bf47..792bb817c0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alert/IotAlertConfigDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alert/IotAlertConfigDO.java @@ -81,4 +81,22 @@ public class IotAlertConfigDO extends BaseDO { @TableField(typeHandler = IntegerListTypeHandler.class) private List receiveTypes; + /** + * 短信模板编号 + * + * 关联 SmsTemplateDO 的 code 属性 + */ + private String smsTemplateCode; + /** + * 邮件模板编号 + * + * 关联 MailTemplateDO 的 code 属性 + */ + private String mailTemplateCode; + /** + * 站内信模板编号 + * + * 关联 NotifyTemplateDO 的 code 属性 + */ + private String notifyTemplateCode; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java index 52b0d360dd..330e1055a6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java @@ -33,7 +33,7 @@ public interface IotAlertConfigMapper extends BaseMapperX { default List selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) { return selectList(new LambdaQueryWrapperX() .eq(IotAlertConfigDO::getStatus, status) - .apply(MyBatisUtils.findInSet("scene_rule_ids", sceneRuleId))); + .apply(MyBatisUtils.findInSet("scene_rule_ids"), sceneRuleId)); } -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java index 1e3fb2e576..84c2d99377 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil; 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.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; @@ -34,7 +35,7 @@ public interface IotDeviceMapper extends BaseMapperX { .likeIfPresent(IotDeviceDO::getNickname, reqVO.getNickname()) .eqIfPresent(IotDeviceDO::getState, reqVO.getStatus()) .eqIfPresent(IotDeviceDO::getGatewayId, reqVO.getGatewayId()) - .apply(ObjectUtil.isNotNull(reqVO.getGroupId()), "FIND_IN_SET(" + reqVO.getGroupId() + ",group_ids) > 0") + .apply(ObjectUtil.isNotNull(reqVO.getGroupId()), MyBatisUtils.findInSet("group_ids"), reqVO.getGroupId()) .orderByDesc(IotDeviceDO::getId)); } @@ -72,7 +73,7 @@ public interface IotDeviceMapper extends BaseMapperX { default Long selectCountByGroupId(Long groupId) { return selectCount(new LambdaQueryWrapperX() - .apply("FIND_IN_SET(" + groupId + ",group_ids) > 0")); + .apply(MyBatisUtils.findInSet("group_ids"), groupId)); } default Long selectCountByCreateTime(@Nullable LocalDateTime createTime) { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java index ce2eeb04bc..579b62e9da 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java @@ -28,7 +28,7 @@ public interface IotDataRuleMapper extends BaseMapperX { default List selectListBySinkId(Long sinkId) { return selectList(new LambdaQueryWrapperX() - .apply(MyBatisUtils.findInSet("sink_ids", sinkId))); + .apply(MyBatisUtils.findInSet("sink_ids"), sinkId)); } default List selectListByStatus(Integer status) { @@ -39,4 +39,4 @@ public interface IotDataRuleMapper extends BaseMapperX { return selectOne(IotDataRuleDO::getName, name); } -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java index 065eb2d229..2829bdfd7d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java @@ -98,6 +98,9 @@ public interface ErrorCodeConstants { // ========== IoT 告警配置 1-050-013-000 ========== ErrorCode ALERT_CONFIG_NOT_EXISTS = new ErrorCode(1_050_013_000, "IoT 告警配置不存在"); + ErrorCode ALERT_CONFIG_SMS_TEMPLATE_REQUIRED = new ErrorCode(1_050_013_001, "已选择短信接收方式,短信模板不能为空"); + ErrorCode ALERT_CONFIG_MAIL_TEMPLATE_REQUIRED = new ErrorCode(1_050_013_002, "已选择邮件接收方式,邮件模板不能为空"); + ErrorCode ALERT_CONFIG_NOTIFY_TEMPLATE_REQUIRED = new ErrorCode(1_050_013_003, "已选择站内信接收方式,站内信模板不能为空"); // ========== IoT 告警记录 1-050-014-000 ========== ErrorCode ALERT_RECORD_NOT_EXISTS = new ErrorCode(1_050_014_000, "IoT 告警记录不存在"); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java index aa9378767a..bd422b71ab 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java @@ -1,11 +1,14 @@ package cn.iocoder.yudao.module.iot.service.alert; +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.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO; import cn.iocoder.yudao.module.iot.dal.mysql.alert.IotAlertConfigMapper; +import cn.iocoder.yudao.module.iot.enums.alert.IotAlertReceiveTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import jakarta.annotation.Resource; @@ -16,7 +19,10 @@ import org.springframework.validation.annotation.Validated; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.ALERT_CONFIG_MAIL_TEMPLATE_REQUIRED; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.ALERT_CONFIG_NOT_EXISTS; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.ALERT_CONFIG_NOTIFY_TEMPLATE_REQUIRED; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.ALERT_CONFIG_SMS_TEMPLATE_REQUIRED; /** * IoT 告警配置 Service 实现类 @@ -42,6 +48,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService { // 校验关联数据是否存在 sceneRuleService.validateSceneRuleList(createReqVO.getSceneRuleIds()); adminUserApi.validateUserList(createReqVO.getReceiveUserIds()); + validateReceiveTemplates(createReqVO); // 插入 IotAlertConfigDO alertConfig = BeanUtils.toBean(createReqVO, IotAlertConfigDO.class); @@ -56,6 +63,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService { // 校验关联数据是否存在 sceneRuleService.validateSceneRuleList(updateReqVO.getSceneRuleIds()); adminUserApi.validateUserList(updateReqVO.getReceiveUserIds()); + validateReceiveTemplates(updateReqVO); // 更新 IotAlertConfigDO updateObj = BeanUtils.toBean(updateReqVO, IotAlertConfigDO.class); @@ -76,6 +84,24 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService { } } + private void validateReceiveTemplates(IotAlertConfigSaveReqVO reqVO) { + if (CollUtil.isEmpty(reqVO.getReceiveTypes())) { + return; + } + if (reqVO.getReceiveTypes().contains(IotAlertReceiveTypeEnum.SMS.getType()) + && StrUtil.isBlank(reqVO.getSmsTemplateCode())) { + throw exception(ALERT_CONFIG_SMS_TEMPLATE_REQUIRED); + } + if (reqVO.getReceiveTypes().contains(IotAlertReceiveTypeEnum.MAIL.getType()) + && StrUtil.isBlank(reqVO.getMailTemplateCode())) { + throw exception(ALERT_CONFIG_MAIL_TEMPLATE_REQUIRED); + } + if (reqVO.getReceiveTypes().contains(IotAlertReceiveTypeEnum.NOTIFY.getType()) + && StrUtil.isBlank(reqVO.getNotifyTemplateCode())) { + throw exception(ALERT_CONFIG_NOTIFY_TEMPLATE_REQUIRED); + } + } + @Override public IotAlertConfigDO getAlertConfig(Long id) { return alertConfigMapper.selectById(id); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImpl.java index 93c73f5764..6bb5f5f4f6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImpl.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.iot.service.device.property; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjUtil; @@ -148,8 +147,16 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { // 1. 根据物模型,拼接合法的属性 // TODO @芋艿:【待定 004】赋能后,属性到底以 thingModel 为准(ik),还是 db 的表结构为准(tl)? List thingModels = thingModelService.getThingModelListByProductIdFromCache(device.getProductId()); - Map properties = new HashMap<>(); + Map properties = new LinkedHashMap<>(); params.forEach((key, value) -> { + if (!(key instanceof CharSequence)) { + log.error("[saveDeviceProperty][消息({}) 的属性 key({}) 类型不正确]", message, key); + return; + } + if (value == null) { + log.warn("[saveDeviceProperty][消息({}) 的属性({}) 值为空,跳过]", message, key); + return; + } // 忽略大小写匹配物模型,避免设备上报的 key 与 identifier 大小写不一致导致丢失 IotThingModelDO thingModel = CollUtil.findOne(thingModels, o -> StrUtil.equalsIgnoreCase(o.getIdentifier(), (CharSequence) key)); @@ -158,21 +165,9 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { return; } String identifier = thingModel.getIdentifier(); // 统一以物模型 identifier 作为 key,避免大小写问题 - String dataType = thingModel.getProperty().getDataType(); - if (ObjectUtils.equalsAny(dataType, - IotDataSpecsDataTypeEnum.STRUCT.getDataType(), IotDataSpecsDataTypeEnum.ARRAY.getDataType())) { - // 特殊:STRUCT 和 ARRAY 类型,在 TDengine 里,有没对应数据类型,只能通过 JSON 来存储 - properties.put(identifier, JsonUtils.toJsonString(value)); - } else if (IotDataSpecsDataTypeEnum.INT.getDataType().equals(dataType)) { - properties.put(identifier, Convert.toInt(value)); - } else if (IotDataSpecsDataTypeEnum.FLOAT.getDataType().equals(dataType)) { - properties.put(identifier, Convert.toFloat(value)); - } else if (IotDataSpecsDataTypeEnum.DOUBLE.getDataType().equals(dataType)) { - properties.put(identifier, Convert.toDouble(value)); - } else if (IotDataSpecsDataTypeEnum.BOOL.getDataType().equals(dataType)) { - properties.put(identifier, Convert.toBool(value, false) ? (byte) 1 : (byte) 0); - } else { - properties.put(identifier, value); + Object convertedValue = convertPropertyValue(message, thingModel, value); + if (convertedValue != null) { + properties.put(identifier, convertedValue); } }); if (CollUtil.isEmpty(properties)) { @@ -194,6 +189,23 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { extractAndUpdateDeviceLocation(device, (Map) message.getParams()); } + private Object convertPropertyValue(IotDeviceMessage message, IotThingModelDO thingModel, Object value) { + String identifier = thingModel.getIdentifier(); + String dataType = thingModel.getProperty().getDataType(); + try { + Object convertedValue = thingModelService.convertThingModelPropertyValue(thingModel, value); + if (convertedValue == null) { + log.warn("[saveDeviceProperty][消息({}) 的属性({}) 值({}) 无法转换为类型({}),跳过]", + message, identifier, value, dataType); + } + return convertedValue; + } catch (Exception e) { + log.error("[saveDeviceProperty][消息({}) 的属性({}) 值({}) 转换为类型({}) 异常,跳过]", + message, identifier, value, dataType, e); + return null; + } + } + @Override public Map getLatestDeviceProperties(Long deviceId) { return deviceDataRedisDAO.get(deviceId); @@ -310,4 +322,4 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { return new BigDecimal[]{longitude, latitude}; } -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimeHelper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimeHelper.java index 01ddd57757..238e030fea 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimeHelper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimeHelper.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene; import cn.hutool.core.lang.Assert; import cn.hutool.core.text.CharPool; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotCurrentTimeConditionMatcher; import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotTimerConditionEvaluator; @@ -11,7 +12,6 @@ import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.List; @@ -73,7 +73,7 @@ public class IotSceneRuleTimeHelper { LocalDateTime now = LocalDateTime.now(); if (isDateTimeOperator(operatorEnum)) { // 日期时间匹配(时间戳,秒级) - long currentTimestamp = now.atZone(ZoneId.systemDefault()).toEpochSecond(); + long currentTimestamp = LocalDateTimeUtils.toEpochSecond(now); return matchDateTime(currentTimestamp, operatorEnum, param); } else { // 当日时间匹配(HH:mm:ss) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java index bc264366f1..937b8f603c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.action; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.LocalDateTimeUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; @@ -79,30 +80,38 @@ public class IotAlertTriggerSceneRuleAction implements IotSceneRuleAction { } Map templateParams = buildTemplateParams(config, deviceMessage, device); config.getReceiveUserIds().forEach(userId -> - config.getReceiveTypes().forEach(receiveType -> sendAlertMessageToUser(userId, receiveType, templateParams))); + config.getReceiveTypes().forEach(receiveType -> + sendAlertMessageToUser(userId, receiveType, config, templateParams))); } /** * 按指定接收方式,给单个用户发送告警消息 */ - private void sendAlertMessageToUser(Long userId, Integer receiveType, Map templateParams) { + private void sendAlertMessageToUser(Long userId, Integer receiveType, IotAlertConfigDO config, + Map templateParams) { IotAlertReceiveTypeEnum typeEnum = IotAlertReceiveTypeEnum.of(receiveType); if (typeEnum == null) { return; } + String templateCode = resolveTemplateCode(config, typeEnum); + if (StrUtil.isBlank(templateCode)) {//为了兼容老的结构 + templateCode=typeEnum.getTemplateCode(); + log.warn("[sendAlertMessageToUser][配置({}) 用户({}) 接收方式({}) 未配置模板,使用默认模板({})]", + config.getId(), userId, typeEnum,templateCode); + } try { switch (typeEnum) { case SMS: smsSendApi.sendSingleSmsToAdmin(new SmsSendSingleToUserReqDTO().setUserId(userId) - .setTemplateCode(typeEnum.getTemplateCode()).setTemplateParams(templateParams)); + .setTemplateCode(templateCode).setTemplateParams(templateParams)); break; case MAIL: mailSendApi.sendSingleMailToAdmin(new MailSendSingleToUserReqDTO().setUserId(userId) - .setTemplateCode(typeEnum.getTemplateCode()).setTemplateParams(templateParams)); + .setTemplateCode(templateCode).setTemplateParams(templateParams)); break; case NOTIFY: notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO().setUserId(userId) - .setTemplateCode(typeEnum.getTemplateCode()).setTemplateParams(templateParams)); + .setTemplateCode(templateCode).setTemplateParams(templateParams)); break; } } catch (Exception ex) { @@ -111,6 +120,24 @@ public class IotAlertTriggerSceneRuleAction implements IotSceneRuleAction { } } + private String resolveTemplateCode(IotAlertConfigDO config, IotAlertReceiveTypeEnum typeEnum) { + String templateCode = null; + switch (typeEnum) { + case SMS: + templateCode = config.getSmsTemplateCode(); + break; + case MAIL: + templateCode = config.getMailTemplateCode(); + break; + case NOTIFY: + templateCode = config.getNotifyTemplateCode(); + break; + default: + break; + } + return StrUtil.blankToDefault(templateCode, typeEnum.getTemplateCode()); + } + private Map buildTemplateParams(IotAlertConfigDO config, @Nullable IotDeviceMessage deviceMessage, @Nullable IotDeviceDO device) { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java index 5d62bab917..84cbbf91ec 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java @@ -2,13 +2,17 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; import cn.hutool.core.text.CharPool; import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; +import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; import lombok.extern.slf4j.Slf4j; import java.util.HashMap; @@ -248,4 +252,26 @@ public final class IotSceneRuleMatcherHelper { return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier); } + /** + * 校验匹配器中的产品和设备是否一致 + * + * @param message 消息 + * @param productId 产品编号 + * @param deviceId 设备编号 + * @return 校验结果 + */ + public static boolean productAndDeviceNotMatched(IotDeviceMessage message, Long productId, Long deviceId) { + if (message == null || message.getDeviceId() == null) { + return false; + } + if (deviceId != null && !IotDeviceDO.DEVICE_ID_ALL.equals(deviceId)) { + return ObjectUtil.notEqual(message.getDeviceId(), deviceId); + } + if (productId == null) { + return false; + } + IotDeviceDO device = SpringUtils.getBean(IotDeviceService.class).getDeviceFromCache(message.getDeviceId()); + return device == null || ObjectUtil.notEqual(device.getProductId(), productId); + } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcher.java index a54785ad69..3b41f57b06 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcher.java @@ -31,13 +31,19 @@ public class IotCurrentTimeConditionMatcher implements IotSceneRuleConditionMatc return false; } - // 1.2 检查操作符和参数是否有效 + // 1.2 修复条件匹配中忽略了产品和设备的一致性验证,2025.05.25 by panda + if (IotSceneRuleMatcherHelper.productAndDeviceNotMatched(message, condition.getProductId(),condition.getDeviceId())){ + IotSceneRuleMatcherHelper.logConditionMatchFailure(message,condition,"条件匹配器中产品或设备不匹配"); + return false; + } + + // 1.3 检查操作符和参数是否有效 if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); return false; } - // 1.3 验证操作符是否为支持的时间操作符 + // 1.4 验证操作符是否为支持的时间操作符 String operator = condition.getOperator(); IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); if (operatorEnum == null) { @@ -45,6 +51,7 @@ public class IotCurrentTimeConditionMatcher implements IotSceneRuleConditionMatc return false; } + // 1.5 验证操作符是否为时间相关的操作符 if (IotSceneRuleTimeHelper.isTimeOperator(operatorEnum)) { IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "不支持的时间操作符: " + operator); return false; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcher.java index 5741b95a6f..f2c92ae6a0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcher.java @@ -30,14 +30,20 @@ public class IotDevicePropertyConditionMatcher implements IotSceneRuleConditionM return false; } - // 1.2 检查消息中是否包含条件指定的属性标识符 + // 1.2 修复条件匹配中忽略了产品和设备的一致性验证,2025.05.25 by panda + if (IotSceneRuleMatcherHelper.productAndDeviceNotMatched(message, condition.getProductId(),condition.getDeviceId())){ + IotSceneRuleMatcherHelper.logConditionMatchFailure(message,condition,"条件匹配器中产品或设备不匹配"); + return false; + } + + // 1.3 检查消息中是否包含条件指定的属性标识符 // 注意:属性上报可能同时上报多个属性,所以需要判断 condition.getIdentifier() 是否在 message 的 params 中 if (IotDeviceMessageUtils.notContainsIdentifier(message, condition.getIdentifier())) { IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中不包含属性: " + condition.getIdentifier()); return false; } - // 1.3 检查操作符和参数是否有效 + // 1.4 检查操作符和参数是否有效 if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); return false; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcher.java index 232812270f..15dcbbdbe4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcher.java @@ -27,8 +27,13 @@ public class IotDeviceStateConditionMatcher implements IotSceneRuleConditionMatc IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效"); return false; } + // 1.2 修复条件匹配中忽略了产品和设备的一致性验证,2025.05.25 by panda + if (IotSceneRuleMatcherHelper.productAndDeviceNotMatched(message, condition.getProductId(),condition.getDeviceId())){ + IotSceneRuleMatcherHelper.logConditionMatchFailure(message,condition,"条件匹配器中产品或设备不匹配"); + return false; + } - // 1.2 检查操作符和参数是否有效 + // 1.3 检查操作符和参数是否有效 if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); return false; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcher.java index d997e46e15..2d54ff0a23 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcher.java @@ -43,7 +43,13 @@ public class IotDeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatc return false; } - // 1.3 检查标识符是否匹配 + // 1.3 修复触发器中忽略了产品和设备的一致性验证,2025.05.25 by panda + if (IotSceneRuleMatcherHelper.productAndDeviceNotMatched(message, trigger.getProductId(),trigger.getDeviceId())){ + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message,trigger,"触发器中产品或设备不匹配"); + return false; + } + + // 1.4 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcher.java index 1f019b5761..ea89ce3704 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcher.java @@ -36,7 +36,13 @@ public class IotDevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerM return false; } - // 1.3 检查消息中是否包含触发器指定的属性标识符 + // 1.3 修复触发器中忽略了产品和设备的一致性验证,2025.05.25 by panda + if (IotSceneRuleMatcherHelper.productAndDeviceNotMatched(message, trigger.getProductId(),trigger.getDeviceId())){ + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message,trigger,"触发器中产品或设备不匹配"); + return false; + } + + // 1.4 检查消息中是否包含触发器指定的属性标识符 // 注意:属性上报可能同时上报多个属性,所以需要判断 trigger.getIdentifier() 是否在 message 的 params 中 if (IotDeviceMessageUtils.notContainsIdentifier(message, trigger.getIdentifier())) { IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中不包含属性: " + diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java index 842d081253..5dcde6664d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java @@ -7,7 +7,9 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; +import jakarta.annotation.Resource; import org.springframework.stereotype.Component; import java.util.Map; @@ -20,6 +22,9 @@ import java.util.Map; @Component public class IotDeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher { + @Resource + private IotDeviceService iotDeviceService; + @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; @@ -37,7 +42,12 @@ public class IotDeviceServiceInvokeTriggerMatcher implements IotSceneRuleTrigger IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod() + ", 实际: " + message.getMethod()); return false; } - // 1.3 检查标识符是否匹配 + // 1.3 修复触发器中忽略了产品和设备的一致性验证,2025.05.25 by panda + if (IotSceneRuleMatcherHelper.productAndDeviceNotMatched(message, trigger.getProductId(),trigger.getDeviceId())){ + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message,trigger,"触发器中产品或设备不匹配"); + return false; + } + // 1.4 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcher.java index 6b8c73a501..183442eead 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcher.java @@ -5,7 +5,9 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; +import jakarta.annotation.Resource; import org.springframework.stereotype.Component; /** @@ -16,6 +18,9 @@ import org.springframework.stereotype.Component; @Component public class IotDeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher { + @Resource + private IotDeviceService iotDeviceService; + @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; @@ -36,7 +41,13 @@ public class IotDeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMa return false; } - // 1.3 检查操作符和值是否有效 + // 1.3 修复触发器中忽略了产品和设备的一致性验证,2025.05.25 by panda + if (IotSceneRuleMatcherHelper.productAndDeviceNotMatched(message, trigger.getProductId(),trigger.getDeviceId())){ + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message,trigger,"触发器中产品或设备不匹配"); + return false; + } + + // 1.4 检查操作符和值是否有效 if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) { IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效"); return false; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelService.java index b8c951b949..5bdfd7c6f7 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelService.java @@ -108,4 +108,13 @@ public interface IotThingModelService { */ void validateThingModelListExists(Long productId, Set identifiers); -} \ No newline at end of file + /** + * 按物模型属性的数据类型转换设备上报值 + * + * @param thingModel 物模型 + * @param value 设备上报值 + * @return 转换后的值;无法转换时返回 null + */ + Object convertThingModelPropertyValue(IotThingModelDO thingModel, Object value); + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java index 4a8b97475b..9029960127 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java @@ -1,10 +1,16 @@ package cn.iocoder.yudao.module.iot.service.thingmodel; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelListReqVO; import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO; @@ -12,9 +18,14 @@ import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelS import cn.iocoder.yudao.module.iot.convert.thingmodel.IotThingModelConvert; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelBoolOrEnumDataSpecs; +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs; +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs; import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper; import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants; import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum; +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; +import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField; import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService; import cn.iocoder.yudao.module.iot.service.product.IotProductService; import jakarta.annotation.Resource; @@ -26,7 +37,15 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Set; @@ -166,6 +185,239 @@ public class IotThingModelServiceImpl implements IotThingModelService { } } + @Override + public Object convertThingModelPropertyValue(IotThingModelDO thingModel, Object value) { + if (thingModel == null || thingModel.getProperty() == null || value == null) { + return null; + } + String dataType = thingModel.getProperty().getDataType(); + if (ObjectUtils.equalsAny(dataType, + IotDataSpecsDataTypeEnum.STRUCT.getDataType(), IotDataSpecsDataTypeEnum.ARRAY.getDataType())) { + // 特殊:STRUCT 和 ARRAY 类型,在 TDengine 里,没有对应数据类型,只能通过 JSON 来存储 + return convertToVarchar(JsonUtils.toJsonString(value), TDengineTableField.LENGTH_VARCHAR); + } + if (IotDataSpecsDataTypeEnum.INT.getDataType().equals(dataType)) { + return convertToInt(value); + } + if (IotDataSpecsDataTypeEnum.FLOAT.getDataType().equals(dataType)) { + return convertToFloat(value); + } + if (IotDataSpecsDataTypeEnum.DOUBLE.getDataType().equals(dataType)) { + return convertToDouble(value); + } + if (IotDataSpecsDataTypeEnum.ENUM.getDataType().equals(dataType)) { + return convertEnumToTinyInt(thingModel, value); + } + if (IotDataSpecsDataTypeEnum.BOOL.getDataType().equals(dataType)) { + return convertBoolToTinyInt(value); + } + if (IotDataSpecsDataTypeEnum.TEXT.getDataType().equals(dataType)) { + return convertToVarchar(Convert.toStr(value), getTextLength(thingModel)); + } + if (IotDataSpecsDataTypeEnum.DATE.getDataType().equals(dataType)) { + return convertToTimestamp(value); + } + return null; + } + + private Integer getTextLength(IotThingModelDO thingModel) { + ThingModelDataSpecs dataSpecs = thingModel.getProperty().getDataSpecs(); + if (!(dataSpecs instanceof ThingModelDateOrTextDataSpecs)) { + return null; + } + return ((ThingModelDateOrTextDataSpecs) dataSpecs).getLength(); + } + + private String convertToVarchar(String value, Integer length) { + if (value == null) { + return null; + } + if (length != null && value.getBytes(StandardCharsets.UTF_8).length > length) { + return null; + } + return value; + } + + private Integer convertToInt(Object value) { + BigDecimal decimal = convertToBigDecimal(value); + if (decimal == null) { + return null; + } + try { + return decimal.intValueExact(); + } catch (ArithmeticException e) { + return null; + } + } + + private Float convertToFloat(Object value) { + BigDecimal decimal = convertToBigDecimal(value); + if (decimal == null) { + return null; + } + float result = decimal.floatValue(); + return Float.isFinite(result) ? result : null; + } + + private Double convertToDouble(Object value) { + BigDecimal decimal = convertToBigDecimal(value); + if (decimal == null) { + return null; + } + double result = decimal.doubleValue(); + return Double.isFinite(result) ? result : null; + } + + private BigDecimal convertToBigDecimal(Object value) { + if (value instanceof Boolean) { + return null; + } + String text = Convert.toStr(value); + if (StrUtil.isBlank(text)) { + return null; + } + try { + return new BigDecimal(text); + } catch (NumberFormatException e) { + return null; + } + } + + private Byte convertEnumToTinyInt(IotThingModelDO thingModel, Object value) { + Integer intValue = convertToInt(value); + if (intValue == null && value instanceof CharSequence) { + intValue = getEnumValueByName(thingModel, value.toString()); + } + if (intValue == null || !isTinyInt(intValue)) { + return null; + } + if (CollUtil.isNotEmpty(thingModel.getProperty().getDataSpecsList()) + && getEnumDataSpecsByValue(thingModel, intValue) == null) { + return null; + } + return intValue.byteValue(); + } + + private Integer getEnumValueByName(IotThingModelDO thingModel, String name) { + ThingModelBoolOrEnumDataSpecs dataSpecs = getEnumDataSpecsByName(thingModel, name); + return dataSpecs != null ? dataSpecs.getValue() : null; + } + + private ThingModelBoolOrEnumDataSpecs getEnumDataSpecsByName(IotThingModelDO thingModel, String name) { + if (CollUtil.isEmpty(thingModel.getProperty().getDataSpecsList())) { + return null; + } + for (ThingModelDataSpecs dataSpecs : thingModel.getProperty().getDataSpecsList()) { + if (!(dataSpecs instanceof ThingModelBoolOrEnumDataSpecs)) { + continue; + } + ThingModelBoolOrEnumDataSpecs enumDataSpecs = (ThingModelBoolOrEnumDataSpecs) dataSpecs; + if (StrUtil.equals(enumDataSpecs.getName(), name)) { + return enumDataSpecs; + } + } + return null; + } + + private ThingModelBoolOrEnumDataSpecs getEnumDataSpecsByValue(IotThingModelDO thingModel, Integer value) { + if (CollUtil.isEmpty(thingModel.getProperty().getDataSpecsList())) { + return null; + } + for (ThingModelDataSpecs dataSpecs : thingModel.getProperty().getDataSpecsList()) { + if (!(dataSpecs instanceof ThingModelBoolOrEnumDataSpecs)) { + continue; + } + ThingModelBoolOrEnumDataSpecs enumDataSpecs = (ThingModelBoolOrEnumDataSpecs) dataSpecs; + if (Objects.equals(enumDataSpecs.getValue(), value)) { + return enumDataSpecs; + } + } + return null; + } + + private Byte convertBoolToTinyInt(Object value) { + if (value instanceof Boolean) { + return (Boolean) value ? (byte) 1 : (byte) 0; + } + if (value instanceof CharSequence) { + String text = StrUtil.trim(value.toString()); + if (StrUtil.equalsIgnoreCase(text, "true")) { + return (byte) 1; + } + if (StrUtil.equalsIgnoreCase(text, "false")) { + return (byte) 0; + } + } + Integer intValue = convertToInt(value); + if (intValue == null || (intValue != 0 && intValue != 1)) { + return null; + } + return intValue.byteValue(); + } + + private boolean isTinyInt(Integer value) { + return value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE; + } + + private Long convertToTimestamp(Object value) { + if (value instanceof LocalDateTime) { + return LocalDateTimeUtil.toEpochMilli((LocalDateTime) value); + } + if (value instanceof LocalDate) { + return LocalDateTimeUtil.toEpochMilli(((LocalDate) value).atStartOfDay()); + } + if (value instanceof Instant) { + return ((Instant) value).toEpochMilli(); + } + if (value instanceof OffsetDateTime) { + return ((OffsetDateTime) value).toInstant().toEpochMilli(); + } + if (value instanceof ZonedDateTime) { + return ((ZonedDateTime) value).toInstant().toEpochMilli(); + } + if (value instanceof Date) { + return ((Date) value).getTime(); + } + Long timestamp = convertToLong(value); + if (timestamp != null) { + return timestamp; + } + if (!(value instanceof CharSequence)) { + return null; + } + String text = StrUtil.trim(value.toString()); + if (StrUtil.isBlank(text)) { + return null; + } + try { + return OffsetDateTime.parse(text).toInstant().toEpochMilli(); + } catch (Exception ignored) { + // 尝试本地时间格式,例如 yyyy-MM-dd HH:mm:ss + } + try { + return LocalDateTimeUtil.toEpochMilli(LocalDateTimeUtil.parse(text, DatePattern.NORM_DATETIME_PATTERN)); + } catch (Exception ignored) { + // 尝试其它 Hutool 支持的本地时间格式 + } + try { + return LocalDateTimeUtil.toEpochMilli(LocalDateTimeUtils.parse(text)); + } catch (Exception ignored) { + return null; + } + } + + private Long convertToLong(Object value) { + BigDecimal decimal = convertToBigDecimal(value); + if (decimal == null) { + return null; + } + try { + return decimal.longValueExact(); + } catch (ArithmeticException e) { + return null; + } + } + private void validateProductThingModelMapperExists(Long id) { if (thingModelMapper.selectById(id) == null) { throw exception(THING_MODEL_NOT_EXISTS); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImplTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImplTest.java index 8f87cb89fd..d2f26aaf57 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImplTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImplTest.java @@ -54,6 +54,7 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest { // mock 行为 when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId())) .thenReturn(singletonList(thingModel)); + when(thingModelService.convertThingModelPropertyValue(thingModel, 100)).thenReturn(100); // 调用 service.saveDeviceProperty(device, message); @@ -91,64 +92,62 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest { } @Test - public void testSaveDeviceProperty_boolFromBooleanTrue() { - // 准备参数:物模型为 BOOL,设备上报原生 boolean true - assertBoolValueConvertedToByte(true, (byte) 1); - } - - @Test - public void testSaveDeviceProperty_boolFromBooleanFalse() { - // 准备参数:物模型为 BOOL,设备上报原生 boolean false - assertBoolValueConvertedToByte(false, (byte) 0); - } - - @Test - public void testSaveDeviceProperty_boolFromStringTrue() { - // 准备参数:物模型为 BOOL,设备上报字符串 "true" - assertBoolValueConvertedToByte("true", (byte) 1); - } - - @Test - public void testSaveDeviceProperty_boolFromStringFalse() { - // 准备参数:物模型为 BOOL,设备上报字符串 "false" - assertBoolValueConvertedToByte("false", (byte) 0); - } - - @Test - public void testSaveDeviceProperty_boolFromNumberOne() { - // 准备参数:物模型为 BOOL,设备上报数字 1 - assertBoolValueConvertedToByte(1, (byte) 1); - } - - @Test - public void testSaveDeviceProperty_boolFromNumberZero() { - // 准备参数:物模型为 BOOL,设备上报数字 0 - assertBoolValueConvertedToByte(0, (byte) 0); - } - - /** - * 校验 BOOL 类型属性上报后,最终落到 properties Map 的值类型与数值 - */ - private void assertBoolValueConvertedToByte(Object reportedValue, byte expected) { - // 准备参数 + public void testSaveDeviceProperty_convertValueFailed() { + // 准备参数:物模型存在,但是属性值无法按物模型转换 IotDeviceDO device = buildDevice(); - IotThingModelDO thingModel = buildThingModel("PowerSwitch", IotDataSpecsDataTypeEnum.BOOL.getDataType()); + IotThingModelDO temperature = buildThingModel("Temperature", IotDataSpecsDataTypeEnum.INT.getDataType()); Map params = new HashMap<>(); - params.put("PowerSwitch", reportedValue); + params.put("Temperature", "abc"); + IotDeviceMessage message = buildMessage(params); + + when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId())) + .thenReturn(singletonList(temperature)); + when(thingModelService.convertThingModelPropertyValue(temperature, "abc")).thenReturn(null); + + assertDoesNotThrow(() -> service.saveDeviceProperty(device, message)); + + verify(devicePropertyMapper, never()).insert(any(), any(), anyLong(), anyLong()); + verify(deviceDataRedisDAO, never()).putAll(anyLong(), any()); + } + + @Test + public void testSaveDeviceProperty_skipNullValue() { + // 准备参数:属性值为空,不能写入 TDengine 与 Redis + IotDeviceDO device = buildDevice(); + IotThingModelDO thingModel = buildThingModel("Temperature", IotDataSpecsDataTypeEnum.INT.getDataType()); + Map params = new HashMap<>(); + params.put("Temperature", null); IotDeviceMessage message = buildMessage(params); - // mock 行为 when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId())) .thenReturn(singletonList(thingModel)); - // 调用:不能抛异常 assertDoesNotThrow(() -> service.saveDeviceProperty(device, message)); - // 断言:写入的 value 是 byte 类型,且值匹配 + verify(thingModelService, never()).convertThingModelPropertyValue(any(), any()); + verify(devicePropertyMapper, never()).insert(any(), any(), anyLong(), anyLong()); + verify(deviceDataRedisDAO, never()).putAll(anyLong(), any()); + } + + @Test + public void testSaveDeviceProperty_skipInvalidKeyType() { + // 准备参数:Map 中包含非字符串 key,不能因为强转失败影响其它合法属性 + IotDeviceDO device = buildDevice(); + IotThingModelDO thingModel = buildThingModel("PowerSwitch", IotDataSpecsDataTypeEnum.BOOL.getDataType()); + Map params = new HashMap<>(); + params.put(123, 1); + params.put("PowerSwitch", true); + IotDeviceMessage message = buildMessage(params); + + when(thingModelService.getThingModelListByProductIdFromCache(device.getProductId())) + .thenReturn(singletonList(thingModel)); + when(thingModelService.convertThingModelPropertyValue(thingModel, true)).thenReturn((byte) 1); + + assertDoesNotThrow(() -> service.saveDeviceProperty(device, message)); + Map dbProperties = captureMapperInsertProperties(); - Object actual = dbProperties.get("PowerSwitch"); - assertTrue(actual instanceof Byte, "BOOL 属性应被转为 Byte 类型,实际为 " + (actual == null ? "null" : actual.getClass())); - assertEquals(expected, actual); + assertEquals(1, dbProperties.size()); + assertEquals((byte) 1, dbProperties.get("PowerSwitch")); } // ========== 辅助方法 ========== @@ -173,7 +172,7 @@ public class IotDevicePropertyServiceImplTest extends BaseMockitoUnitTest { /** * 构造一条属性上报消息 */ - private IotDeviceMessage buildMessage(Map params) { + private IotDeviceMessage buildMessage(Map params) { IotDeviceMessage message = new IotDeviceMessage(); message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); message.setParams(params); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java index 479ba40be6..a760bf74da 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.iot.service.rule.scene; import cn.hutool.core.collection.ListUtil; +import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; @@ -21,6 +23,9 @@ import cn.iocoder.yudao.module.iot.service.product.IotProductService; import org.junit.jupiter.api.*; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import java.lang.reflect.Field; import java.util.*; @@ -43,8 +48,23 @@ import static org.mockito.Mockito.*; * * @author HUIHUI */ +@SpringJUnitConfig(classes = IotSceneRuleTimerConditionIntegrationTest.TestConfig.class) public class IotSceneRuleTimerConditionIntegrationTest extends BaseMockitoUnitTest { + /** + * 注入一下 SpringUtil,解析 EL 表达式时需要 + * {@link SpringExpressionUtils#parseExpression} + */ + @Configuration + static class TestConfig { + + @Bean + public SpringUtil springUtil() { + return new SpringUtil(); + } + + } + @InjectMocks private IotSceneRuleServiceImpl sceneRuleService; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleActionTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleActionTest.java index 6bce06974e..5b2a4cb5c7 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleActionTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleActionTest.java @@ -106,6 +106,9 @@ public class IotAlertTriggerSceneRuleActionTest extends BaseMockitoUnitTest { IotAlertReceiveTypeEnum.SMS.getType(), IotAlertReceiveTypeEnum.MAIL.getType(), IotAlertReceiveTypeEnum.NOTIFY.getType())); + c.setSmsTemplateCode("custom_sms"); + c.setMailTemplateCode("custom_mail"); + c.setNotifyTemplateCode("custom_notify"); }); IotDeviceDO device = randomPojo(IotDeviceDO.class); @@ -130,13 +133,13 @@ public class IotAlertTriggerSceneRuleActionTest extends BaseMockitoUnitTest { ArgumentCaptor smsCaptor = ArgumentCaptor.forClass(SmsSendSingleToUserReqDTO.class); verify(smsSendApi, times(1)).sendSingleSmsToAdmin(smsCaptor.capture()); assertEquals(userId, smsCaptor.getValue().getUserId()); - assertEquals(IotAlertReceiveTypeEnum.SMS.getTemplateCode(), smsCaptor.getValue().getTemplateCode()); + assertEquals("custom_sms", smsCaptor.getValue().getTemplateCode()); ArgumentCaptor mailCaptor = ArgumentCaptor.forClass(MailSendSingleToUserReqDTO.class); verify(mailSendApi, times(1)).sendSingleMailToAdmin(mailCaptor.capture()); - assertEquals(IotAlertReceiveTypeEnum.MAIL.getTemplateCode(), mailCaptor.getValue().getTemplateCode()); + assertEquals("custom_mail", mailCaptor.getValue().getTemplateCode()); ArgumentCaptor notifyCaptor = ArgumentCaptor.forClass(NotifySendSingleToUserReqDTO.class); verify(notifyMessageSendApi, times(1)).sendSingleMessageToAdmin(notifyCaptor.capture()); - assertEquals(IotAlertReceiveTypeEnum.NOTIFY.getTemplateCode(), notifyCaptor.getValue().getTemplateCode()); + assertEquals("custom_notify", notifyCaptor.getValue().getTemplateCode()); } @Test diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImplTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImplTest.java new file mode 100644 index 0000000000..2668c43abf --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImplTest.java @@ -0,0 +1,201 @@ +package cn.iocoder.yudao.module.iot.service.thingmodel; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty; +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelBoolOrEnumDataSpecs; +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs; +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService; +import cn.iocoder.yudao.module.iot.service.product.IotProductService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link IotThingModelServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +@Import(IotThingModelServiceImpl.class) +public class IotThingModelServiceImplTest extends BaseDbUnitTest { + + @Resource + private IotThingModelServiceImpl thingModelService; + + @MockitoBean + private IotProductService productService; + @MockitoBean + private IotDeviceModbusPointService deviceModbusPointService; + + @Test + public void testConvertThingModelPropertyValue_boolFromBooleanTrue() { + assertBoolValueConvertedToByte(true, (byte) 1); + } + + @Test + public void testConvertThingModelPropertyValue_boolFromBooleanFalse() { + assertBoolValueConvertedToByte(false, (byte) 0); + } + + @Test + public void testConvertThingModelPropertyValue_boolFromStringTrue() { + assertBoolValueConvertedToByte("true", (byte) 1); + } + + @Test + public void testConvertThingModelPropertyValue_boolFromStringFalse() { + assertBoolValueConvertedToByte("false", (byte) 0); + } + + @Test + public void testConvertThingModelPropertyValue_boolFromNumberOne() { + assertBoolValueConvertedToByte(1, (byte) 1); + } + + @Test + public void testConvertThingModelPropertyValue_boolFromNumberZero() { + assertBoolValueConvertedToByte(0, (byte) 0); + } + + @Test + public void testConvertThingModelPropertyValue_boolInvalid() { + IotThingModelDO thingModel = buildThingModel("PowerSwitch", IotDataSpecsDataTypeEnum.BOOL.getDataType()); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, "yes"); + + assertNull(result); + } + + @Test + public void testConvertThingModelPropertyValue_enumFromStringNumber() { + IotThingModelDO thingModel = buildEnumThingModel("WorkMode", enumSpec("Auto", 1), enumSpec("Manual", 2)); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, "1"); + + assertEquals((byte) 1, result); + } + + @Test + public void testConvertThingModelPropertyValue_enumFromSpecName() { + IotThingModelDO thingModel = buildEnumThingModel("WorkMode", enumSpec("Auto", 1), enumSpec("Manual", 2)); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, "Manual"); + + assertEquals((byte) 2, result); + } + + @Test + public void testConvertThingModelPropertyValue_enumInvalidSpecValue() { + IotThingModelDO thingModel = buildEnumThingModel("WorkMode", enumSpec("Auto", 1), enumSpec("Manual", 2)); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, 3); + + assertNull(result); + } + + @Test + public void testConvertThingModelPropertyValue_enumOutOfTinyIntRange() { + IotThingModelDO thingModel = buildThingModel("WorkMode", IotDataSpecsDataTypeEnum.ENUM.getDataType()); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, 128); + + assertNull(result); + } + + @Test + public void testConvertThingModelPropertyValue_dateFromLocalDateTime() { + IotThingModelDO thingModel = buildThingModel("CollectTime", IotDataSpecsDataTypeEnum.DATE.getDataType()); + LocalDateTime collectTime = LocalDateTime.of(2025, 1, 2, 3, 4, 5); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, collectTime); + + assertEquals(LocalDateTimeUtil.toEpochMilli(collectTime), result); + } + + @Test + public void testConvertThingModelPropertyValue_dateFromString() { + IotThingModelDO thingModel = buildThingModel("CollectTime", IotDataSpecsDataTypeEnum.DATE.getDataType()); + LocalDateTime collectTime = LocalDateTime.of(2025, 1, 2, 3, 4, 5); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, "2025-01-02 03:04:05"); + + assertEquals(LocalDateTimeUtil.toEpochMilli(collectTime), result); + } + + @Test + public void testConvertThingModelPropertyValue_intInvalid() { + IotThingModelDO thingModel = buildThingModel("Temperature", IotDataSpecsDataTypeEnum.INT.getDataType()); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, "abc"); + + assertNull(result); + } + + @Test + public void testConvertThingModelPropertyValue_textOverLength() { + IotThingModelDO thingModel = buildTextThingModel("Remark", 3); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, "abcd"); + + assertNull(result); + } + + @Test + public void testConvertThingModelPropertyValue_structToJson() { + IotThingModelDO thingModel = buildThingModel("GeoLocation", IotDataSpecsDataTypeEnum.STRUCT.getDataType()); + Map value = new HashMap<>(); + value.put("Longitude", 120.1D); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, value); + + assertEquals(JsonUtils.toJsonString(value), result); + } + + private void assertBoolValueConvertedToByte(Object reportedValue, byte expected) { + IotThingModelDO thingModel = buildThingModel("PowerSwitch", IotDataSpecsDataTypeEnum.BOOL.getDataType()); + + Object result = thingModelService.convertThingModelPropertyValue(thingModel, reportedValue); + + assertEquals(expected, result); + } + + private IotThingModelDO buildThingModel(String identifier, String dataType) { + ThingModelProperty property = new ThingModelProperty(); + property.setIdentifier(identifier); + property.setDataType(dataType); + return IotThingModelDO.builder().identifier(identifier).property(property).build(); + } + + private IotThingModelDO buildEnumThingModel(String identifier, ThingModelBoolOrEnumDataSpecs... dataSpecsList) { + IotThingModelDO thingModel = buildThingModel(identifier, IotDataSpecsDataTypeEnum.ENUM.getDataType()); + thingModel.getProperty().setDataSpecsList(Arrays.asList(dataSpecsList)); + return thingModel; + } + + private ThingModelBoolOrEnumDataSpecs enumSpec(String name, Integer value) { + ThingModelBoolOrEnumDataSpecs dataSpecs = new ThingModelBoolOrEnumDataSpecs(); + dataSpecs.setName(name); + dataSpecs.setValue(value); + return dataSpecs; + } + + private IotThingModelDO buildTextThingModel(String identifier, Integer length) { + IotThingModelDO thingModel = buildThingModel(identifier, IotDataSpecsDataTypeEnum.TEXT.getDataType()); + ThingModelDateOrTextDataSpecs dataSpecs = new ThingModelDateOrTextDataSpecs(); + dataSpecs.setLength(length); + thingModel.getProperty().setDataSpecs(dataSpecs); + return thingModel; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java index 67ae67399c..4f14057eb8 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java @@ -6,7 +6,9 @@ import cn.iocoder.yudao.framework.mq.redis.core.job.RedisStreamMessageCleanupJob import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessage; import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener; import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; +import cn.iocoder.yudao.module.iot.core.messagebus.core.kafka.IotKafkaMessageBus; import cn.iocoder.yudao.module.iot.core.messagebus.core.local.IotLocalMessageBus; +import cn.iocoder.yudao.module.iot.core.messagebus.core.rabbitmq.IotRabbitMQMessageBus; import cn.iocoder.yudao.module.iot.core.messagebus.core.redis.IotRedisMessageBus; import cn.iocoder.yudao.module.iot.core.messagebus.core.rocketmq.IotRocketMQMessageBus; import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; @@ -14,8 +16,12 @@ import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.redisson.api.RedissonClient; +import org.springframework.amqp.rabbit.core.RabbitAdmin; +import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; @@ -23,6 +29,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.kafka.core.KafkaTemplate; import java.util.List; @@ -73,6 +80,21 @@ public class IotMessageBusAutoConfiguration { } + // ==================== Kafka 实现 ==================== + + @Configuration + @ConditionalOnProperty(prefix = "yudao.iot.message-bus", name = "type", havingValue = "kafka") + @ConditionalOnClass(KafkaTemplate.class) + public static class IotKafkaMessageBusConfiguration { + + @Bean + public IotKafkaMessageBus iotKafkaMessageBus(KafkaProperties kafkaProperties) { + log.info("[iotKafkaMessageBus][创建 IoT Kafka 消息总线]"); + return new IotKafkaMessageBus(kafkaProperties); + } + + } + // ==================== Redis 实现 ==================== /** @@ -99,7 +121,8 @@ public class IotMessageBusAutoConfiguration { RedisMQTemplate redisTemplate, RedissonClient redissonClient) { List> listeners = getListeners(messageBus); - return new RedisPendingMessageResendJob(listeners, redisTemplate, redissonClient); + return new RedisPendingMessageResendJob(listeners, redisTemplate, redissonClient, + RedisPendingMessageResendJob.IOT_RESEND_LOCK_KEY); } /** @@ -110,7 +133,8 @@ public class IotMessageBusAutoConfiguration { RedisMQTemplate redisTemplate, RedissonClient redissonClient) { List> listeners = getListeners(messageBus); - return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient); + return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient, + RedisStreamMessageCleanupJob.IOT_CLEANUP_LOCK_KEY); } private List> getListeners(IotRedisMessageBus messageBus) { @@ -126,4 +150,25 @@ public class IotMessageBusAutoConfiguration { } -} \ No newline at end of file + // ==================== RabbitMQ 实现 ==================== + + @Configuration + @ConditionalOnProperty(prefix = "yudao.iot.message-bus", name = "type", havingValue = "rabbitmq") + @ConditionalOnClass(RabbitTemplate.class) + public static class IotRabbitMQMessageBusConfiguration { + + @Bean + @ConditionalOnMissingBean + public RabbitAdmin rabbitAdmin(RabbitTemplate rabbitTemplate) { + return new RabbitAdmin(rabbitTemplate); + } + + @Bean + public IotRabbitMQMessageBus iotRabbitMQMessageBus(RabbitTemplate rabbitTemplate, RabbitAdmin rabbitAdmin) { + log.info("[iotRabbitMQMessageBus][创建 IoT RabbitMQ 消息总线]"); + return new IotRabbitMQMessageBus(rabbitTemplate, rabbitAdmin); + } + + } + +} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java index 501eb2b0d8..607f2b869e 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java @@ -20,9 +20,9 @@ public class IotMessageBusProperties { /** * 消息总线类型 * - * 可选值:local、redis、rocketmq、rabbitmq + * 可选值:local、redis、rocketmq、kafka、rabbitmq */ @NotNull(message = "IoT 消息总线类型不能为空") private String type = "local"; -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/kafka/IotKafkaMessageBus.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/kafka/IotKafkaMessageBus.java new file mode 100644 index 0000000000..d859f67347 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/kafka/IotKafkaMessageBus.java @@ -0,0 +1,127 @@ +package cn.iocoder.yudao.module.iot.core.messagebus.core.kafka; + +import cn.hutool.core.util.TypeUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; +import jakarta.annotation.PreDestroy; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.boot.autoconfigure.kafka.KafkaProperties; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.listener.AcknowledgingMessageListener; +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; +import org.springframework.kafka.listener.ContainerProperties; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +/** + * 基于 Kafka 的 {@link IotMessageBus} 实现类 + * + * @author 芋道源码 + */ +@Slf4j +public class IotKafkaMessageBus implements IotMessageBus { + + private final KafkaTemplate kafkaTemplate; + + private final KafkaProperties kafkaProperties; + + @Getter + private final List> subscribers = new ArrayList<>(); + + private final List> containers = new ArrayList<>(); + + public IotKafkaMessageBus(KafkaProperties kafkaProperties) { + this.kafkaProperties = kafkaProperties; + this.kafkaTemplate = new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(buildProducerProperties(kafkaProperties))); + } + + @Override + public void post(String topic, Object message) { + String messageJson = JsonUtils.toJsonString(message); + try { + kafkaTemplate.send(topic, messageJson).get(); + log.info("[post][topic({}) 发送消息({})]", topic, message); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(String.format("发送 Kafka 消息失败,topic(%s) message(%s)", topic, message), e); + } catch (ExecutionException e) { + throw new IllegalStateException(String.format("发送 Kafka 消息失败,topic(%s) message(%s)", topic, message), e); + } + } + + @Override + public void register(IotMessageSubscriber subscriber) { + Type type = TypeUtil.getTypeArgument(subscriber.getClass(), 0); + if (type == null) { + throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName())); + } + + // 1. 创建消费容器 + ContainerProperties containerProperties = new ContainerProperties(subscriber.getTopic()); + containerProperties.setGroupId(subscriber.getGroup()); + containerProperties.setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); + containerProperties.setMissingTopicsFatal(false); + containerProperties.setMessageListener((AcknowledgingMessageListener) (message, acknowledgment) -> { + try { + subscriber.onMessage(JsonUtils.parseObject(message.value(), type)); + acknowledgment.acknowledge(); + } catch (Exception ex) { + log.error("[onMessage][topic({}/{}) message({}) 消费者({}) 处理异常]", + subscriber.getTopic(), subscriber.getGroup(), message, subscriber.getClass().getName(), ex); + throw ex; + } + }); + ConcurrentMessageListenerContainer container = new ConcurrentMessageListenerContainer<>( + new DefaultKafkaConsumerFactory<>(buildConsumerProperties(kafkaProperties, subscriber.getGroup())), + containerProperties); + container.start(); + + // 2. 保存消费者引用 + containers.add(container); + subscribers.add(subscriber); + } + + @PreDestroy + public void destroy() { + for (ConcurrentMessageListenerContainer container : containers) { + try { + container.stop(); + log.info("[destroy][关闭 Kafka 消费者容器成功]"); + } catch (Exception e) { + log.error("[destroy][关闭 Kafka 消费者容器异常]", e); + } + } + kafkaTemplate.destroy(); + } + + private static Map buildProducerProperties(KafkaProperties kafkaProperties) { + Map properties = new HashMap<>(kafkaProperties.buildProducerProperties()); + properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + return properties; + } + + private static Map buildConsumerProperties(KafkaProperties kafkaProperties, String group) { + Map properties = new HashMap<>(kafkaProperties.buildConsumerProperties()); + properties.put(ConsumerConfig.GROUP_ID_CONFIG, group); + properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); + properties.putIfAbsent(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + return properties; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/rabbitmq/IotRabbitMQMessageBus.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/rabbitmq/IotRabbitMQMessageBus.java new file mode 100644 index 0000000000..9ce1dfaec8 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/rabbitmq/IotRabbitMQMessageBus.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.module.iot.core.messagebus.core.rabbitmq; + +import cn.hutool.core.util.TypeUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; +import jakarta.annotation.PreDestroy; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.AcknowledgeMode; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.MessageBuilder; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.rabbit.core.RabbitAdmin; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * 基于 RabbitMQ 的 {@link IotMessageBus} 实现类 + * + * @author ywc + */ +@RequiredArgsConstructor +@Slf4j +public class IotRabbitMQMessageBus implements IotMessageBus { + + private static final String ROUTING_KEY = "#"; + + private final RabbitTemplate rabbitTemplate; + + private final RabbitAdmin rabbitAdmin; + + @Getter + private final List> subscribers = new ArrayList<>(); + + private final List containers = new ArrayList<>(); + + @Override + public void post(String topic, Object message) { + rabbitTemplate.send(topic, ROUTING_KEY, MessageBuilder.withBody(JsonUtils.toJsonByte(message)).build()); + log.info("[post][topic({}) 发送消息({})]", topic, message); + } + + @Override + @SuppressWarnings("DataFlowIssue") + public void register(IotMessageSubscriber subscriber) { + Type type = TypeUtil.getTypeArgument(subscriber.getClass(), 0); + if (type == null) { + throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName())); + } + + // 1.1 声明交换机、队列和绑定关系 + Queue queue = new Queue(subscriber.getGroup(), true, false, false); + rabbitAdmin.declareQueue(queue); + TopicExchange exchange = new TopicExchange(subscriber.getTopic()); + rabbitAdmin.declareExchange(exchange); + Binding binding = BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY); + rabbitAdmin.declareBinding(binding); + + // 1.2 创建消费容器 + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(rabbitTemplate.getConnectionFactory()); + container.setQueues(queue); + container.setConcurrentConsumers(1); + container.setMaxConcurrentConsumers(10); + container.setAcknowledgeMode(AcknowledgeMode.MANUAL); + container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> { + try { + subscriber.onMessage(JsonUtils.parseObject(message.getBody(), type)); + channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); + } catch (Exception ex) { + log.error("[onMessage][topic({}/{}) message({}) 消费者({}) 处理异常]", + subscriber.getTopic(), subscriber.getGroup(), message, subscriber.getClass().getName(), ex); + channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); + } + }); + container.start(); + + // 2. 保存消费者引用 + containers.add(container); + subscribers.add(subscriber); + } + + @PreDestroy + public void destroy() { + for (SimpleMessageListenerContainer container : containers) { + try { + container.stop(); + container.destroy(); + log.info("[destroy][关闭 RabbitMQ 消费者容器成功]"); + } catch (Exception e) { + log.error("[destroy][关闭 RabbitMQ 消费者容器异常]", e); + } + } + } + +} diff --git a/yudao-module-iot/yudao-module-iot-gateway/pom.xml b/yudao-module-iot/yudao-module-iot-gateway/pom.xml index 51e55c3246..d7eae3cdb8 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/pom.xml +++ b/yudao-module-iot/yudao-module-iot-gateway/pom.xml @@ -29,10 +29,14 @@ + + org.springframework.kafka + spring-kafka + true + org.apache.rocketmq rocketmq-spring-boot-starter - true diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/manager/AbstractIotModbusPollScheduler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/manager/AbstractIotModbusPollScheduler.java index e62f85fcf6..a41634bf9f 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/manager/AbstractIotModbusPollScheduler.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/manager/AbstractIotModbusPollScheduler.java @@ -12,6 +12,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; /** @@ -19,6 +20,8 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. *

* 封装通用的定时器管理、per-device 请求队列限速逻辑。 * 子类只需实现 {@link #pollPoint(Long, Long)} 定义具体的轮询动作。 + * 如需将多个点位合并为一个轮询任务,可覆盖 {@link #buildPollTasks(IotModbusDeviceConfigRespDTO)} + * 和 {@link #pollTask(Long, String)}。 *

* * @author 芋道源码 @@ -38,9 +41,9 @@ public abstract class AbstractIotModbusPollScheduler { private static final int MAX_QUEUE_SIZE = 1000; /** - * 设备点位的定时器映射:deviceId -> (pointId -> PointTimerInfo) + * 设备轮询任务的定时器映射:deviceId -> (taskKey -> PollTimerInfo) */ - private final Map> devicePointTimers = new ConcurrentHashMap<>(); + private final Map> devicePollTimers = new ConcurrentHashMap<>(); /** * per-device 请求队列:deviceId -> 待执行请求队列 @@ -60,11 +63,29 @@ public abstract class AbstractIotModbusPollScheduler { } /** - * 点位定时器信息 + * 轮询任务信息 */ @Data @AllArgsConstructor - private static class PointTimerInfo { + protected static class PollTask { + + /** + * 任务标识 + */ + private String key; + /** + * 轮询间隔(用于判断是否需要更新定时器) + */ + private Integer pollInterval; + + } + + /** + * 轮询定时器信息 + */ + @Data + @AllArgsConstructor + private static class PollTimerInfo { /** * Vert.x 定时器 ID @@ -82,75 +103,84 @@ public abstract class AbstractIotModbusPollScheduler { /** * 更新轮询任务(增量更新) * - * 1. 【删除】点位:停止对应的轮询定时器 - * 2. 【新增】点位:创建对应的轮询定时器 - * 3. 【修改】点位:pollInterval 变化,重建对应的轮询定时器 - * 【修改】其他属性变化:不需要重建定时器(pollPoint 运行时从 configCache 取最新 point) + * 1. 【删除】任务:停止对应的轮询定时器 + * 2. 【新增】任务:创建对应的轮询定时器 + * 3. 【修改】任务:pollInterval 变化,重建对应的轮询定时器 + * 【修改】其他属性变化:不需要重建定时器(pollTask 运行时从 configCache 取最新配置) */ public void updatePolling(IotModbusDeviceConfigRespDTO config) { Long deviceId = config.getDeviceId(); - List newPoints = config.getPoints(); - Map currentTimers = devicePointTimers + List newTasks = buildPollTasks(config); + Map currentTimers = devicePollTimers .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>()); - // 1.1 计算新配置中的点位 ID 集合 - Set newPointIds = convertSet(newPoints, IotModbusPointRespDTO::getId); - // 1.2 计算删除的点位 ID 集合 - Set removedPointIds = new HashSet<>(currentTimers.keySet()); - removedPointIds.removeAll(newPointIds); + // 1.1 计算新配置中的任务 Key 集合 + Set newTaskKeys = convertSet(newTasks, PollTask::getKey); + // 1.2 计算删除的任务 Key 集合 + Set removedTaskKeys = new HashSet<>(currentTimers.keySet()); + removedTaskKeys.removeAll(newTaskKeys); - // 2. 处理删除的点位:停止不再存在的定时器 - for (Long pointId : removedPointIds) { - PointTimerInfo timerInfo = currentTimers.remove(pointId); + // 2. 处理删除的任务:停止不再存在的定时器 + for (String taskKey : removedTaskKeys) { + PollTimerInfo timerInfo = currentTimers.remove(taskKey); if (timerInfo != null) { vertx.cancelTimer(timerInfo.getTimerId()); - log.debug("[updatePolling][设备 {} 点位 {} 定时器已删除]", deviceId, pointId); + log.debug("[updatePolling][设备 {} 轮询任务 {} 定时器已删除]", deviceId, taskKey); } } - // 3. 处理新增和修改的点位 - if (CollUtil.isEmpty(newPoints)) { + // 3. 处理新增和修改的任务 + if (CollUtil.isEmpty(newTasks)) { return; } - for (IotModbusPointRespDTO point : newPoints) { - Long pointId = point.getId(); - Integer newPollInterval = point.getPollInterval(); - PointTimerInfo existingTimer = currentTimers.get(pointId); - // 3.1 新增点位:创建定时器 + for (PollTask task : newTasks) { + String taskKey = task.getKey(); + Integer newPollInterval = task.getPollInterval(); + PollTimerInfo existingTimer = currentTimers.get(taskKey); + // 3.1 新增任务:创建定时器 if (existingTimer == null) { - Long timerId = createPollTimer(deviceId, pointId, newPollInterval); + Long timerId = createPollTimer(deviceId, taskKey, newPollInterval); if (timerId != null) { - currentTimers.put(pointId, new PointTimerInfo(timerId, newPollInterval)); - log.debug("[updatePolling][设备 {} 点位 {} 定时器已创建, interval={}ms]", - deviceId, pointId, newPollInterval); + currentTimers.put(taskKey, new PollTimerInfo(timerId, newPollInterval)); + log.debug("[updatePolling][设备 {} 轮询任务 {} 定时器已创建, interval={}ms]", + deviceId, taskKey, newPollInterval); } } else if (!Objects.equals(existingTimer.getPollInterval(), newPollInterval)) { // 3.2 pollInterval 变化:重建定时器 vertx.cancelTimer(existingTimer.getTimerId()); - Long timerId = createPollTimer(deviceId, pointId, newPollInterval); + Long timerId = createPollTimer(deviceId, taskKey, newPollInterval); if (timerId != null) { - currentTimers.put(pointId, new PointTimerInfo(timerId, newPollInterval)); - log.debug("[updatePolling][设备 {} 点位 {} 定时器已更新, interval={}ms -> {}ms]", - deviceId, pointId, existingTimer.getPollInterval(), newPollInterval); + currentTimers.put(taskKey, new PollTimerInfo(timerId, newPollInterval)); + log.debug("[updatePolling][设备 {} 轮询任务 {} 定时器已更新, interval={}ms -> {}ms]", + deviceId, taskKey, existingTimer.getPollInterval(), newPollInterval); } else { - currentTimers.remove(pointId); + currentTimers.remove(taskKey); } } - // 3.3 其他属性变化:无需重建定时器,因为 pollPoint() 运行时从 configCache 获取最新 point,自动使用新配置 + // 3.3 其他属性变化:无需重建定时器,因为 pollTask() 运行时从 configCache 获取最新配置 } } + /** + * 构建轮询任务列表 + * + * 默认每个点位一个轮询任务。TCP Client 等协议可覆盖该方法,将多个点位合并为一个批量读取任务。 + */ + protected List buildPollTasks(IotModbusDeviceConfigRespDTO config) { + return convertList(config.getPoints(), point -> new PollTask(String.valueOf(point.getId()), point.getPollInterval())); + } + /** * 创建轮询定时器 */ - private Long createPollTimer(Long deviceId, Long pointId, Integer pollInterval) { + private Long createPollTimer(Long deviceId, String taskKey, Integer pollInterval) { if (pollInterval == null || pollInterval <= 0) { return null; } return vertx.setPeriodic(pollInterval, timerId -> { try { - submitPollRequest(deviceId, pointId); + submitPollRequest(deviceId, taskKey); } catch (Exception e) { - log.error("[createPollTimer][轮询点位失败, deviceId={}, pointId={}]", deviceId, pointId, e); + log.error("[createPollTimer][轮询任务失败, deviceId={}, taskKey={}]", deviceId, taskKey, e); } }); } @@ -160,7 +190,7 @@ public abstract class AbstractIotModbusPollScheduler { /** * 提交轮询请求到设备请求队列(保证同设备请求间隔) */ - private void submitPollRequest(Long deviceId, Long pointId) { + private void submitPollRequest(Long deviceId, String taskKey) { // 1. 【重要】将请求添加到设备的请求队列 Queue queue = deviceRequestQueues.computeIfAbsent(deviceId, k -> new ConcurrentLinkedQueue<>()); while (queue.size() >= MAX_QUEUE_SIZE) { @@ -168,7 +198,7 @@ public abstract class AbstractIotModbusPollScheduler { queue.poll(); log.warn("[submitPollRequest][设备 {} 请求队列已满({}), 丢弃最旧请求]", deviceId, MAX_QUEUE_SIZE); } - queue.offer(() -> pollPoint(deviceId, pointId)); + queue.offer(() -> pollTask(deviceId, taskKey)); // 2. 处理设备请求队列(如果没有延迟 timer 在等待) processDeviceQueue(deviceId); @@ -238,6 +268,15 @@ public abstract class AbstractIotModbusPollScheduler { // ========== 轮询执行 ========== + /** + * 轮询任务 + * + * 默认将任务标识作为点位 ID,执行单点轮询。 + */ + protected void pollTask(Long deviceId, String taskKey) { + pollPoint(deviceId, Long.valueOf(taskKey)); + } + /** * 轮询单个点位(子类实现具体的读取逻辑) * @@ -252,25 +291,25 @@ public abstract class AbstractIotModbusPollScheduler { * 停止设备的轮询 */ public void stopPolling(Long deviceId) { - Map timers = devicePointTimers.remove(deviceId); - if (CollUtil.isEmpty(timers)) { - return; - } - for (PointTimerInfo timerInfo : timers.values()) { - vertx.cancelTimer(timerInfo.getTimerId()); + Map timers = devicePollTimers.remove(deviceId); + if (CollUtil.isNotEmpty(timers)) { + for (PollTimerInfo timerInfo : timers.values()) { + vertx.cancelTimer(timerInfo.getTimerId()); + } } // 清理请求队列 deviceRequestQueues.remove(deviceId); deviceLastRequestTime.remove(deviceId); deviceDelayTimerActive.remove(deviceId); - log.debug("[stopPolling][设备 {} 停止了 {} 个轮询定时器]", deviceId, timers.size()); + log.debug("[stopPolling][设备 {} 停止了 {} 个轮询定时器]", deviceId, + CollUtil.isEmpty(timers) ? 0 : timers.size()); } /** * 停止所有轮询 */ public void stopAll() { - for (Long deviceId : new ArrayList<>(devicePointTimers.keySet())) { + for (Long deviceId : new ArrayList<>(devicePollTimers.keySet())) { stopPolling(deviceId); } } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusTcpClientUtils.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusTcpClientUtils.java index 1324f3aa5a..1d52f8d490 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusTcpClientUtils.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusTcpClientUtils.java @@ -39,11 +39,38 @@ public class IotModbusTcpClientUtils { public static Future read(IotModbusTcpClientConnectionManager.ModbusConnection connection, Integer slaveId, IotModbusPointRespDTO point) { + return read(connection, slaveId, point.getFunctionCode(), + point.getRegisterAddress(), point.getRegisterCount(), point.getIdentifier()); + } + + /** + * 读取 Modbus 数据 + * + * @param connection Modbus 连接 + * @param slaveId 从站地址 + * @param functionCode 功能码 + * @param registerAddress 寄存器起始地址 + * @param registerCount 寄存器数量 + * @return 原始值(int 数组) + */ + public static Future read(IotModbusTcpClientConnectionManager.ModbusConnection connection, + Integer slaveId, + Integer functionCode, + Integer registerAddress, + Integer registerCount) { + return read(connection, slaveId, functionCode, registerAddress, registerCount, null); + } + + private static Future read(IotModbusTcpClientConnectionManager.ModbusConnection connection, + Integer slaveId, + Integer functionCode, + Integer registerAddress, + Integer registerCount, + String identifier) { return connection.executeBlocking(tcpConnection -> { try { // 1. 创建请求 - ModbusRequest request = createReadRequest(point.getFunctionCode(), - point.getRegisterAddress(), point.getRegisterCount()); + ModbusRequest request = createReadRequest(functionCode, registerAddress, registerCount); request.setUnitID(slaveId); // 2. 执行事务(请求) @@ -53,10 +80,10 @@ public class IotModbusTcpClientUtils { // 3. 解析响应 ModbusResponse response = transaction.getResponse(); - return extractValues(response, point.getFunctionCode()); + return extractValues(response, functionCode); } catch (Exception e) { - throw new RuntimeException(String.format("Modbus 读取失败 [slaveId=%d, identifier=%s, address=%d]", - slaveId, point.getIdentifier(), point.getRegisterAddress()), e); + throw new RuntimeException(String.format("Modbus 读取失败 [slaveId=%d, identifier=%s, functionCode=%d, address=%d, count=%d]", + slaveId, identifier, functionCode, registerAddress, registerCount), e); } }); } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollScheduler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollScheduler.java index 946937d405..fba2f756bb 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollScheduler.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollScheduler.java @@ -9,8 +9,16 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbu import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusTcpClientUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.handler.upstream.IotModbusTcpClientUpstreamHandler; import io.vertx.core.Vertx; +import lombok.AllArgsConstructor; +import lombok.Data; import lombok.extern.slf4j.Slf4j; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; + /** * IoT Modbus TCP Client 轮询调度器:管理点位的轮询定时器,调度读取任务并上报结果 * @@ -35,6 +43,47 @@ public class IotModbusTcpClientPollScheduler extends AbstractIotModbusPollSchedu // ========== 轮询执行 ========== + @Override + protected List buildPollTasks(IotModbusDeviceConfigRespDTO config) { + return convertList(buildReadSegments(config), segment -> new PollTask(segment.getKey(), segment.getPollInterval())); + } + + /** + * 轮询读取段 + */ + @Override + protected void pollTask(Long deviceId, String taskKey) { + // 1.1 从 configCache 获取最新配置 + IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(deviceId); + if (config == null || CollUtil.isEmpty(config.getPoints())) { + log.warn("[pollTask][设备 {} 没有配置]", deviceId); + return; + } + // 1.2 查找读取段。配置变化后,如果当前 taskKey 已不存在,直接跳过等待下一轮 updatePolling 清理 timer + ReadSegment segment = findReadSegment(config, taskKey); + if (segment == null) { + log.debug("[pollTask][设备 {} 读取段 {} 未找到,跳过陈旧轮询任务]", deviceId, taskKey); + return; + } + + // 2.1 获取连接 + IotModbusTcpClientConnectionManager.ModbusConnection connection = connectionManager.getConnection(deviceId); + if (connection == null) { + log.warn("[pollTask][设备 {} 没有连接]", deviceId); + return; + } + // 2.2 获取 slave ID + Integer slaveId = connectionManager.getSlaveId(deviceId); + Assert.notNull(slaveId, "设备 {} 没有配置 slaveId", deviceId); + + // 3. 执行 Modbus 批量读取 + IotModbusTcpClientUtils.read(connection, slaveId, segment.getFunctionCode(), + segment.getStartAddress(), segment.getRegisterCount()) + .onSuccess(rawValues -> handleSegmentReadResult(config, segment, rawValues)) + .onFailure(e -> log.error("[pollTask][读取点位段失败, deviceId={}, segment={}]", + deviceId, segment.getKey(), e)); + } + /** * 轮询单个点位 */ @@ -70,4 +119,174 @@ public class IotModbusTcpClientPollScheduler extends AbstractIotModbusPollSchedu deviceId, point.getIdentifier(), e)); } + private void handleSegmentReadResult(IotModbusDeviceConfigRespDTO config, + ReadSegment segment, + int[] rawValues) { + for (IotModbusPointRespDTO point : segment.getPoints()) { + // 批量读取返回的是整个连续地址段,需要按点位地址切片后再复用单点上报逻辑 + int[] pointRawValues = extractPointRawValues(rawValues, segment, point); + if (pointRawValues == null) { + log.warn("[handleSegmentReadResult][读取段结果长度不足, deviceId={}, segment={}, identifier={}]", + config.getDeviceId(), segment.getKey(), point.getIdentifier()); + continue; + } + upstreamHandler.handleReadResult(config, point, pointRawValues); + } + } + + private ReadSegment findReadSegment(IotModbusDeviceConfigRespDTO config, String taskKey) { + return CollUtil.findOne(buildReadSegments(config), segment -> segment.getKey().equals(taskKey)); + } + + /** + * 构建批量读取段 + * + *

只合并功能码、轮询间隔相同,且地址连续或重叠的点位;不跨功能码、不跨轮询间隔,避免改变原有轮询语义。 + * 同时按 Modbus 协议限制控制单次读取长度,超过限制时拆成多个读取段。 + */ + static List buildReadSegments(IotModbusDeviceConfigRespDTO config) { + if (config == null) { + return Collections.emptyList(); + } + + // 1. 按功能码和轮询间隔分组:两者任一不同,都不能共用同一次 Modbus 读请求 + List validPoints = filterList(config.getPoints(), IotModbusTcpClientPollScheduler::isValidReadPoint); + if (CollUtil.isEmpty(validPoints)) { + return Collections.emptyList(); + } + Map> pointsByGroup = convertMultiMap(validPoints, + point -> new SegmentGroupKey(point.getFunctionCode(), point.getPollInterval())); + + // 2. 组内按地址排序后,合并连续或重叠区间,生成实际轮询的读取段 + List segments = new ArrayList<>(); + for (Map.Entry> entry : pointsByGroup.entrySet()) { + List points = entry.getValue(); + points.sort(Comparator.comparing(IotModbusPointRespDTO::getRegisterAddress) + .thenComparing(IotModbusPointRespDTO::getRegisterCount) + .thenComparing(IotModbusPointRespDTO::getId)); + buildReadSegments(entry.getKey(), points, segments); + } + // 3. 固定排序,保证生成的 taskKey 稳定,便于 updatePolling 做增量更新 + segments.sort(Comparator.comparing(ReadSegment::getFunctionCode) + .thenComparing(ReadSegment::getPollInterval) + .thenComparing(ReadSegment::getStartAddress)); + return segments; + } + + private static void buildReadSegments(SegmentGroupKey groupKey, + List points, + List segments) { + ReadSegment current = null; + int maxRegisterCount = getMaxRegisterCount(groupKey.getFunctionCode()); + // points 已按 registerAddress 排序,因此可以线性合并连续/重叠地址段 + for (IotModbusPointRespDTO point : points) { + int pointStartAddress = point.getRegisterAddress(); + int pointEndAddress = pointStartAddress + point.getRegisterCount(); + // 1. 当前点位无法合并时,新建一个读取段 + if (current == null || !canMerge(current, pointStartAddress, pointEndAddress, maxRegisterCount)) { + current = new ReadSegment(groupKey.getFunctionCode(), groupKey.getPollInterval(), + pointStartAddress, point.getRegisterCount(), new ArrayList<>()); + segments.add(current); + } else { + // 2. 当前点位可合并时,扩展读取段覆盖范围 + current.setRegisterCount(Math.max(current.getEndAddress(), pointEndAddress) - current.getStartAddress()); + } + // 3. 记录读取段包含的点位,读取成功后按点位逐个切片上报 + current.getPoints().add(point); + } + } + + /** + * 判断点位是否可以合并到当前读取段 + * + *

仅合并连续或重叠区间,不合并存在地址空洞的区间,避免额外读取无关寄存器。 + */ + private static boolean canMerge(ReadSegment segment, int pointStartAddress, int pointEndAddress, int maxRegisterCount) { + if (pointStartAddress > segment.getEndAddress()) { + return false; + } + int mergedRegisterCount = Math.max(segment.getEndAddress(), pointEndAddress) - segment.getStartAddress(); + return mergedRegisterCount <= maxRegisterCount; + } + + /** + * 从批量读取结果中提取单个点位的原始值 + * + *

例如读取段从地址 10 开始,点位地址为 12、数量为 2,则取 rawValues[2..4)。 + */ + static int[] extractPointRawValues(int[] rawValues, ReadSegment segment, IotModbusPointRespDTO point) { + if (rawValues == null) { + return null; + } + // 1. 计算点位在批量读取结果中的相对偏移 + int offset = point.getRegisterAddress() - segment.getStartAddress(); + int end = offset + point.getRegisterCount(); + // 2. 防御异常响应长度,避免越界影响同一读取段内其它点位 + if (offset < 0 || end > rawValues.length) { + return null; + } + // 3. 返回单个点位需要的原始寄存器值 + return Arrays.copyOfRange(rawValues, offset, end); + } + + private static boolean isValidReadPoint(IotModbusPointRespDTO point) { + return point != null + && point.getId() != null + && point.getFunctionCode() != null + && point.getRegisterAddress() != null + && point.getRegisterCount() != null + && point.getRegisterCount() > 0 + && point.getPollInterval() != null + && point.getPollInterval() > 0; + } + + @SuppressWarnings("EnhancedSwitchMigration") + private static int getMaxRegisterCount(Integer functionCode) { + switch (functionCode) { + case IotModbusCommonUtils.FC_READ_COILS: + case IotModbusCommonUtils.FC_READ_DISCRETE_INPUTS: + return 2000; + case IotModbusCommonUtils.FC_READ_HOLDING_REGISTERS: + case IotModbusCommonUtils.FC_READ_INPUT_REGISTERS: + return 125; + default: + return 0; + } + } + + /** + * 读取段分组 Key + */ + @Data + @AllArgsConstructor + static class SegmentGroupKey { + + private Integer functionCode; + private Integer pollInterval; + + } + + /** + * 一次 Modbus 批量读请求对应的连续地址段 + */ + @Data + @AllArgsConstructor + static class ReadSegment { + + private Integer functionCode; + private Integer pollInterval; + private Integer startAddress; + private Integer registerCount; + private List points; + + String getKey() { + return functionCode + ":" + pollInterval + ":" + startAddress + ":" + registerCount; + } + + int getEndAddress() { + return startAddress + registerCount; + } + + } + } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/IotModbusTcpServerProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/IotModbusTcpServerProtocol.java index 80ce9eec08..ea6e90845c 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/IotModbusTcpServerProtocol.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/IotModbusTcpServerProtocol.java @@ -326,9 +326,27 @@ public class IotModbusTcpServerProtocol implements IotProtocol { log.error("[refreshConfig][处理设备配置失败, deviceId={}]", config.getDeviceId(), e); } } + + // 3. 清理本轮不再返回配置的已连接设备,避免继续轮询已删除设备的旧点位 + Set missingDeviceIds = configCacheService.cleanupMissingConfigs(connectedDeviceIds, configs); + for (Long deviceId : missingDeviceIds) { + cleanupMissingDevice(deviceId); + } } catch (Exception e) { log.error("[refreshConfig][刷新配置失败]", e); } } + private void cleanupMissingDevice(Long deviceId) { + try { + pollScheduler.stopPolling(deviceId); + pendingRequestManager.removeDevice(deviceId); + configCacheService.removeConfig(deviceId); + connectionManager.closeConnection(deviceId); + log.info("[cleanupMissingDevice][设备 {} 配置已失效,已停止轮询并清理连接]", deviceId); + } catch (Exception e) { + log.error("[cleanupMissingDevice][清理设备失败, deviceId={}]", deviceId, e); + } + } + } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/manager/IotModbusTcpServerConfigCacheService.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/manager/IotModbusTcpServerConfigCacheService.java index 19ba6e900b..f7df6b52bb 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/manager/IotModbusTcpServerConfigCacheService.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/manager/IotModbusTcpServerConfigCacheService.java @@ -12,11 +12,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + /** * IoT Modbus TCP Server 配置缓存:认证时按需加载,断连时清理,定时刷新已连接设备 * @@ -96,6 +99,27 @@ public class IotModbusTcpServerConfigCacheService { } } + /** + * 清理本轮刷新后不再有效的设备配置 + * + * @param refreshedDeviceIds 本轮参与刷新的设备编号 + * @param currentConfigs 本轮远端返回的有效配置 + * @return 本轮已不再有效的设备编号 + */ + public Set cleanupMissingConfigs(Set refreshedDeviceIds, + List currentConfigs) { + if (CollUtil.isEmpty(refreshedDeviceIds)) { + return Collections.emptySet(); + } + Set currentDeviceIds = convertSet(currentConfigs, IotModbusDeviceConfigRespDTO::getDeviceId); + Set missingDeviceIds = new HashSet<>(refreshedDeviceIds); + missingDeviceIds.removeAll(currentDeviceIds); + for (Long deviceId : missingDeviceIds) { + configCache.remove(deviceId); + } + return missingDeviceIds; + } + /** * 获取设备配置 */ diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/manager/IotModbusTcpServerConnectionManager.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/manager/IotModbusTcpServerConnectionManager.java index 781d8ac549..4c01f6a7f7 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/manager/IotModbusTcpServerConnectionManager.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/manager/IotModbusTcpServerConnectionManager.java @@ -9,6 +9,7 @@ import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -109,7 +110,7 @@ public class IotModbusTcpServerConnectionManager { * 获取所有已连接设备的 ID 集合 */ public Set getConnectedDeviceIds() { - return deviceSocketMap.keySet(); + return new HashSet<>(deviceSocketMap.keySet()); } /** @@ -130,6 +131,24 @@ public class IotModbusTcpServerConnectionManager { return info; } + /** + * 关闭指定设备连接,并先移除映射,避免 closeHandler 再按正常断连发送下线消息 + */ + public void closeConnection(Long deviceId) { + NetSocket socket = deviceSocketMap.remove(deviceId); + if (socket == null) { + return; + } + connectionMap.remove(socket); + try { + socket.close(); + log.info("[closeConnection][设备 {} 连接已关闭]", deviceId); + } catch (Exception e) { + log.warn("[closeConnection][关闭设备连接失败, deviceId={}, remoteAddress={}]", + deviceId, socket.remoteAddress(), e); + } + } + /** * 发送数据到设备 * diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml b/yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml index 71ee6f3361..6ddecc3a87 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml @@ -12,6 +12,9 @@ spring: database: 0 # Redis 数据库索引 # password: # Redis 密码,如果有的话 timeout: 30000ms # 连接超时时间 + # Kafka 配置项,对应 KafkaProperties 配置类 + kafka: + bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔 --- #################### 消息队列相关 #################### diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollSchedulerTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollSchedulerTest.java new file mode 100644 index 0000000000..214db67ed9 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollSchedulerTest.java @@ -0,0 +1,147 @@ +package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.manager; + +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.manager.IotModbusTcpClientPollScheduler.ReadSegment; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils.FC_READ_COILS; +import static cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils.FC_READ_HOLDING_REGISTERS; +import static cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils.FC_READ_INPUT_REGISTERS; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link IotModbusTcpClientPollScheduler} 的单元测试 + * + * @author 芋道源码 + */ +public class IotModbusTcpClientPollSchedulerTest { + + @Test + public void testBuildReadSegments_mergeContinuousPoints() { + // 准备参数 + IotModbusPointRespDTO point01 = randomPoint(1L, FC_READ_HOLDING_REGISTERS, 0, 1, 1000); + IotModbusPointRespDTO point02 = randomPoint(2L, FC_READ_HOLDING_REGISTERS, 1, 2, 1000); + IotModbusPointRespDTO point03 = randomPoint(3L, FC_READ_HOLDING_REGISTERS, 4, 1, 1000); + IotModbusDeviceConfigRespDTO config = randomConfig(point01, point02, point03); + + // 调用 + List segments = IotModbusTcpClientPollScheduler.buildReadSegments(config); + + // 断言 + assertEquals(2, segments.size()); + assertEquals(0, segments.get(0).getStartAddress()); + assertEquals(3, segments.get(0).getRegisterCount()); + assertIterableEquals(Arrays.asList(point01, point02), segments.get(0).getPoints()); + assertEquals(4, segments.get(1).getStartAddress()); + assertEquals(1, segments.get(1).getRegisterCount()); + assertIterableEquals(Collections.singletonList(point03), segments.get(1).getPoints()); + } + + @Test + public void testBuildReadSegments_mergeOverlappingPoints() { + // 准备参数 + IotModbusPointRespDTO point01 = randomPoint(1L, FC_READ_HOLDING_REGISTERS, 0, 2, 1000); + IotModbusPointRespDTO point02 = randomPoint(2L, FC_READ_HOLDING_REGISTERS, 1, 1, 1000); + IotModbusDeviceConfigRespDTO config = randomConfig(point01, point02); + + // 调用 + List segments = IotModbusTcpClientPollScheduler.buildReadSegments(config); + + // 断言 + assertEquals(1, segments.size()); + assertEquals(0, segments.get(0).getStartAddress()); + assertEquals(2, segments.get(0).getRegisterCount()); + assertIterableEquals(Arrays.asList(point01, point02), segments.get(0).getPoints()); + } + + @Test + public void testBuildReadSegments_notMergeDifferentFunctionCodeOrPollInterval() { + // 准备参数 + IotModbusPointRespDTO point01 = randomPoint(1L, FC_READ_HOLDING_REGISTERS, 0, 1, 1000); + IotModbusPointRespDTO point02 = randomPoint(2L, FC_READ_INPUT_REGISTERS, 1, 1, 1000); + IotModbusPointRespDTO point03 = randomPoint(3L, FC_READ_HOLDING_REGISTERS, 1, 1, 2000); + IotModbusDeviceConfigRespDTO config = randomConfig(point01, point02, point03); + + // 调用 + List segments = IotModbusTcpClientPollScheduler.buildReadSegments(config); + + // 断言 + assertEquals(3, segments.size()); + assertEquals(FC_READ_HOLDING_REGISTERS, segments.get(0).getFunctionCode()); + assertEquals(1000, segments.get(0).getPollInterval()); + assertEquals(FC_READ_HOLDING_REGISTERS, segments.get(1).getFunctionCode()); + assertEquals(2000, segments.get(1).getPollInterval()); + assertEquals(FC_READ_INPUT_REGISTERS, segments.get(2).getFunctionCode()); + assertEquals(1000, segments.get(2).getPollInterval()); + } + + @Test + public void testBuildReadSegments_splitWhenExceedsMaxRegisterCount() { + // 准备参数 + IotModbusPointRespDTO point01 = randomPoint(1L, FC_READ_HOLDING_REGISTERS, 0, 100, 1000); + IotModbusPointRespDTO point02 = randomPoint(2L, FC_READ_HOLDING_REGISTERS, 100, 30, 1000); + IotModbusDeviceConfigRespDTO config = randomConfig(point01, point02); + + // 调用 + List segments = IotModbusTcpClientPollScheduler.buildReadSegments(config); + + // 断言 + assertEquals(2, segments.size()); + assertEquals(0, segments.get(0).getStartAddress()); + assertEquals(100, segments.get(0).getRegisterCount()); + assertEquals(100, segments.get(1).getStartAddress()); + assertEquals(30, segments.get(1).getRegisterCount()); + } + + @Test + public void testExtractPointRawValues() { + // 准备参数 + IotModbusPointRespDTO point = randomPoint(1L, FC_READ_HOLDING_REGISTERS, 12, 2, 1000); + ReadSegment segment = new ReadSegment(FC_READ_HOLDING_REGISTERS, 1000, 10, 4, Collections.singletonList(point)); + int[] rawValues = new int[]{100, 200, 300, 400}; + + // 调用 + int[] pointRawValues = IotModbusTcpClientPollScheduler.extractPointRawValues(rawValues, segment, point); + + // 断言 + assertArrayEquals(new int[]{300, 400}, pointRawValues); + } + + @Test + public void testExtractPointRawValuesForCoils() { + // 准备参数 + IotModbusPointRespDTO point = randomPoint(1L, FC_READ_COILS, 3, 2, 1000); + ReadSegment segment = new ReadSegment(FC_READ_COILS, 1000, 0, 5, Collections.singletonList(point)); + int[] rawValues = new int[]{1, 0, 1, 1, 0, 0, 0, 0}; // 线圈响应可能按字节补齐 + + // 调用 + int[] pointRawValues = IotModbusTcpClientPollScheduler.extractPointRawValues(rawValues, segment, point); + + // 断言 + assertArrayEquals(new int[]{1, 0}, pointRawValues); + } + + private static IotModbusDeviceConfigRespDTO randomConfig(IotModbusPointRespDTO... points) { + IotModbusDeviceConfigRespDTO config = new IotModbusDeviceConfigRespDTO(); + config.setDeviceId(1L); + config.setPoints(Arrays.asList(points)); + return config; + } + + private static IotModbusPointRespDTO randomPoint(Long id, Integer functionCode, Integer registerAddress, + Integer registerCount, Integer pollInterval) { + IotModbusPointRespDTO point = new IotModbusPointRespDTO(); + point.setId(id); + point.setFunctionCode(functionCode); + point.setRegisterAddress(registerAddress); + point.setRegisterCount(registerCount); + point.setPollInterval(pollInterval); + return point; + } + +} diff --git a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java index 3cac6d0684..5055bb99c8 100644 --- a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java +++ b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.product.controller.admin.comment.vo.*; import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; import cn.iocoder.yudao.module.product.service.comment.ProductCommentService; 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; @@ -34,6 +35,15 @@ public class ProductCommentController { return success(BeanUtils.toBean(pageResult, ProductCommentRespVO.class)); } + @GetMapping("/get") + @Operation(summary = "获得商品评价") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('product:comment:query')") + public CommonResult getComment(@RequestParam("id") Long id) { + ProductCommentDO comment = productCommentService.getComment(id); + return success(BeanUtils.toBean(comment, ProductCommentRespVO.class)); + } + @PutMapping("/update-visible") @Operation(summary = "显示 / 隐藏评论") @PreAuthorize("@ss.hasPermission('product:comment:update')") diff --git a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java index 8e5ea71efe..bd9a9dccd2 100755 --- a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java +++ b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java @@ -118,10 +118,10 @@ public class ProductSpuController { } @GetMapping("/get-count") - @Operation(summary = "获得商品 SPU 分页 tab count") + @Operation(summary = "获得商品 SPU 分页 tab count(支持按 name/categoryId/createTime 筛选)") @PreAuthorize("@ss.hasPermission('product:spu:query')") - public CommonResult> getSpuCount() { - return success(productSpuService.getTabsCount()); + public CommonResult> getSpuCount(ProductSpuPageReqVO reqVO) { + return success(productSpuService.getTabsCount(reqVO)); } @GetMapping("/export-excel") diff --git a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java index 993889cf0a..e243a3cab1 100755 --- a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java +++ b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java @@ -146,6 +146,22 @@ public interface ProductSpuMapper extends BaseMapperX { } } + /** + * 按筛选条件 + Tab 统计商品 SPU 数量(用于带筛选的 tab count) + * + * @param reqVO 筛选条件(name/categoryId/createTime) + * @param tabType Tab 标签类型 + * @return 数量 + */ + default Long selectCountByTab(ProductSpuPageReqVO reqVO, Integer tabType) { + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX() + .likeIfPresent(ProductSpuDO::getName, reqVO.getName()) + .eqIfPresent(ProductSpuDO::getCategoryId, reqVO.getCategoryId()) + .betweenIfPresent(ProductSpuDO::getCreateTime, reqVO.getCreateTime()); + appendTabQuery(tabType, queryWrapper); + return selectCount(queryWrapper); + } + /** * 更新商品 SPU 浏览量 * diff --git a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java index b54e219182..b9403404d3 100644 --- a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java +++ b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java @@ -52,6 +52,14 @@ public interface ProductCommentService { */ void replyComment(ProductCommentReplyReqVO replyVO, Long userId); + /** + * 【管理员】获得商品评价 + * + * @param id 评价编号 + * @return 商品评价 + */ + ProductCommentDO getComment(Long id); + /** * 【管理员】获得商品评价分页 * diff --git a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java index 34b076681b..e6cc2f6342 100644 --- a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java +++ b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java @@ -139,6 +139,11 @@ public class ProductCommentServiceImpl implements ProductCommentService { return productCommentMapper.selectPage(pageVO, visible); } + @Override + public ProductCommentDO getComment(Long id) { + return validateCommentExists(id); + } + @Override public PageResult getCommentPage(ProductCommentPageReqVO pageReqVO) { return productCommentMapper.selectPage(pageReqVO); diff --git a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java index 6ed94604eb..832977539e 100755 --- a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java +++ b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java @@ -118,11 +118,12 @@ public interface ProductSpuService { void updateSpuStatus(ProductSpuUpdateStatusReqVO updateReqVO); /** - * 获取 SPU 列表标签对应的 Count 数量 + * 获取 SPU 列表标签对应的 Count 数量(支持按 name/categoryId/createTime 筛选) * + * @param reqVO 筛选条件 * @return Count 数量 */ - Map getTabsCount(); + Map getTabsCount(ProductSpuPageReqVO reqVO); /** * 通过分类 categoryId 查询 SPU 个数 diff --git a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java index b7b90578bf..a36a746660 100755 --- a/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java +++ b/yudao-module-mall/yudao-module-product/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java @@ -256,23 +256,19 @@ public class ProductSpuServiceImpl implements ProductSpuService { } @Override - public Map getTabsCount() { + public Map getTabsCount(ProductSpuPageReqVO reqVO) { Map counts = Maps.newLinkedHashMapWithExpectedSize(5); - // 查询销售中的商品数量 + // 每个 tab 的数量 = 筛选条件(name/categoryId/createTime)+ 该 tab 的状态/库存条件 counts.put(ProductSpuPageReqVO.FOR_SALE, - productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus())); - // 查询仓库中的商品数量 + productSpuMapper.selectCountByTab(reqVO, ProductSpuPageReqVO.FOR_SALE)); counts.put(ProductSpuPageReqVO.IN_WAREHOUSE, - productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus())); - // 查询售空的商品数量 + productSpuMapper.selectCountByTab(reqVO, ProductSpuPageReqVO.IN_WAREHOUSE)); counts.put(ProductSpuPageReqVO.SOLD_OUT, - productSpuMapper.selectCount(ProductSpuDO::getStock, 0)); - // 查询触发警戒库存的商品数量 + productSpuMapper.selectCountByTab(reqVO, ProductSpuPageReqVO.SOLD_OUT)); counts.put(ProductSpuPageReqVO.ALERT_STOCK, - productSpuMapper.selectCount()); - // 查询回收站中的商品数量 + productSpuMapper.selectCountByTab(reqVO, ProductSpuPageReqVO.ALERT_STOCK)); counts.put(ProductSpuPageReqVO.RECYCLE_BIN, - productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus())); + productSpuMapper.selectCountByTab(reqVO, ProductSpuPageReqVO.RECYCLE_BIN)); return counts; } diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponController.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponController.java index 8dfc99ad4f..fb124c5c41 100755 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponController.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/coupon/CouponController.java @@ -4,10 +4,12 @@ import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO; +import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponRespVO; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponSendReqVO; import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert; import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; @@ -63,6 +65,15 @@ public class CouponController { return success(pageResulVO); } + @GetMapping("/get") + @Operation(summary = "获得优惠劵") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('promotion:coupon:query')") + public CommonResult getCoupon(@RequestParam("id") Long id) { + CouponDO coupon = couponService.getCoupon(id); + return success(BeanUtils.toBean(coupon, CouponRespVO.class)); + } + @PostMapping("/send") @Operation(summary = "发送优惠劵") @PreAuthorize("@ss.hasPermission('promotion:coupon:send')") diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java index 68693e0a3d..fdadd685a1 100755 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 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.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum; @@ -34,7 +35,7 @@ public interface CouponTemplateMapper extends BaseMapperX { .eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType()) .betweenIfPresent(CouponTemplateDO::getCreateTime, reqVO.getCreateTime()) .eqIfPresent(CouponTemplateDO::getProductScope, reqVO.getProductScope()) - .and(reqVO.getProductScopeValue() != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)", + .and(reqVO.getProductScopeValue() != null, w -> w.apply(MyBatisUtils.findInSet("product_scope_values"), reqVO.getProductScopeValue())) .and(canTakeConsumer != null, canTakeConsumer) .orderByDesc(CouponTemplateDO::getId)); @@ -61,7 +62,7 @@ public interface CouponTemplateMapper extends BaseMapperX { Consumer> canTakeConsumer = buildCanTakeQueryConsumer(canTakeTypes); return selectList(new LambdaQueryWrapperX() .eqIfPresent(CouponTemplateDO::getProductScope, productScope) - .and(productScopeValue != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)", + .and(productScopeValue != null, w -> w.apply(MyBatisUtils.findInSet("product_scope_values"), productScopeValue)) .and(canTakeConsumer != null, canTakeConsumer) .last(" LIMIT " + count) diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java index 6d8e9c6847..831ad0d8b6 100755 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java @@ -1,9 +1,9 @@ package cn.iocoder.yudao.module.promotion.dal.mysql.reward; -import cn.hutool.core.util.StrUtil; 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.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 满减送活动 Mapper @@ -34,18 +35,18 @@ public interface RewardActivityMapper extends BaseMapperX { Collection categoryIds, Integer status) { LocalDateTime now = LocalDateTime.now(); - Function, String> productScopeValuesFindInSetFunc = ids -> ids.stream() - .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id)) + Function, String> productScopeValuesFindInSetFunc = ids -> IntStream.range(0, ids.size()) + .mapToObj(index -> MyBatisUtils.findInSetWithParamIndex("product_scope_values", index)) .collect(Collectors.joining(" OR ")); return selectList(new LambdaQueryWrapperX() .eq(RewardActivityDO::getStatus, status) .lt(RewardActivityDO::getStartTime, now) .gt(RewardActivityDO::getEndTime, now) .and(i -> i.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.SPU.getScope()) - .and(i1 -> i1.apply(productScopeValuesFindInSetFunc.apply(spuIds))) + .and(i1 -> i1.apply(productScopeValuesFindInSetFunc.apply(spuIds), spuIds.toArray())) .or(i1 -> i1.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.ALL.getScope())) .or(i1 -> i1.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope()) - .and(i2 -> i2.apply(productScopeValuesFindInSetFunc.apply(categoryIds))))) + .and(i2 -> i2.apply(productScopeValuesFindInSetFunc.apply(categoryIds), categoryIds.toArray())))) .orderByDesc(RewardActivityDO::getId) ); } diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java index 34e50f1f5d..7f0c07daf3 100644 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil; 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.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; @@ -27,7 +28,7 @@ public interface SeckillActivityMapper extends BaseMapperX { .likeIfPresent(SeckillActivityDO::getName, reqVO.getName()) .eqIfPresent(SeckillActivityDO::getStatus, reqVO.getStatus()) .betweenIfPresent(SeckillActivityDO::getCreateTime, reqVO.getCreateTime()) - .apply(ObjectUtil.isNotNull(reqVO.getConfigId()), "FIND_IN_SET(" + reqVO.getConfigId() + ", config_ids) > 0") + .apply(ObjectUtil.isNotNull(reqVO.getConfigId()), MyBatisUtils.findInSet("config_ids"), reqVO.getConfigId()) .orderByDesc(SeckillActivityDO::getId)); } @@ -70,7 +71,7 @@ public interface SeckillActivityMapper extends BaseMapperX { .eqIfPresent(SeckillActivityDO::getStatus, status) .lt(SeckillActivityDO::getStartTime, dateTime) .gt(SeckillActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动 - .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0")); + .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), MyBatisUtils.findInSet("config_ids"), pageReqVO.getConfigId())); } default SeckillActivityDO selectBySpuIdAndStatusAndNow(Long spuId, Integer status) { diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java index 862e130742..ab215f6032 100644 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java @@ -77,7 +77,6 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @Resource public SocialClientApi socialClientApi; - // TODO @芋艿:在详细预览下; @Override public KeyValue validateCombinationRecord( Long userId, Long activityId, Long headId, Long skuId, Integer count) { @@ -97,7 +96,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { } // 2. 父拼团是否存在,是否已经满了 - if (headId != null) { + if (isJoinCombination(headId)) { // 2.1. 查询进行中的父拼团 CombinationRecordDO record = combinationRecordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); if (record == null) { @@ -129,7 +128,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { throw exception(COMBINATION_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS); } // 4.3 校验库存是否充足 - if (count >= sku.getStock()) { + if (count > sku.getStock()) { throw exception(COMBINATION_ACTIVITY_UPDATE_STOCK_FAIL); } @@ -153,6 +152,16 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { return new KeyValue<>(activity, product); } + /** + * 是否加入已有拼团。 + * + * 前端开团时可能不传 headId,也可能传 {@link CombinationRecordDO#HEAD_ID_GROUP},都应视为新开团; + * 只有传入真实团长记录编号时,才需要按参团校验父拼团。 + */ + private static boolean isJoinCombination(Long headId) { + return headId != null && ObjUtil.notEqual(headId, CombinationRecordDO.HEAD_ID_GROUP); + } + @Override @Transactional(rollbackFor = Exception.class) public CombinationRecordDO createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) { @@ -166,7 +175,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId()); CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO, keyValue.getKey(), user, spu, sku); // 2.1. 如果是团长需要设置 headId 为 CombinationRecordDO#HEAD_ID_GROUP - if (record.getHeadId() == null) { + if (!isJoinCombination(record.getHeadId())) { record.setStartTime(LocalDateTime.now()) .setExpireTime(LocalDateTime.now().plusHours(keyValue.getKey().getLimitDuration())) .setHeadId(CombinationRecordDO.HEAD_ID_GROUP); diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java index c24cf3ac9d..f6b464d1f3 100644 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java @@ -160,7 +160,7 @@ public interface CouponService { Map getUserCanCanTakeMap(Long userId, List templates); /** - * 获得优惠劵 + * 【会员】获得优惠劵 * * @param userId 用户编号 * @param id 编号 @@ -168,4 +168,12 @@ public interface CouponService { */ CouponDO getCoupon(Long userId, Long id); + /** + * 【管理员】获得优惠劵 + * + * @param id 编号 + * @return 优惠劵 + */ + CouponDO getCoupon(Long id); + } diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index c3aa766cd2..dd433f316d 100644 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java @@ -378,6 +378,11 @@ public class CouponServiceImpl implements CouponService { return couponMapper.selectByIdAndUserId(id, userId); } + @Override + public CouponDO getCoupon(Long id) { + return couponMapper.selectById(id); + } + private CouponDO validateCouponExists(Long id) { CouponDO coupon = couponMapper.selectById(id); if (coupon == null) { diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java index 8c25cb8646..d4323c60dd 100755 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java @@ -52,6 +52,9 @@ public class CouponTemplateServiceImpl implements CouponTemplateService { @Override public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) { + // 校验发放数量不能小于每人限领数量(仅在 CouponTakeTypeEnum.USER 用户领取时) + validateTotalCountNotLessThanTakeLimitCount(createReqVO.getTakeType(), createReqVO.getTotalCount(), + createReqVO.getTakeLimitCount()); // 校验商品范围 validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues()); // 插入 @@ -66,8 +69,11 @@ public class CouponTemplateServiceImpl implements CouponTemplateService { public void updateCouponTemplate(CouponTemplateUpdateReqVO updateReqVO) { // 校验存在 CouponTemplateDO couponTemplate = validateCouponTemplateExists(updateReqVO.getId()); + // 校验发放数量不能小于每人限领数量(仅在 CouponTakeTypeEnum.USER 用户领取时) + validateTotalCountNotLessThanTakeLimitCount(updateReqVO.getTakeType(), updateReqVO.getTotalCount(), + updateReqVO.getTakeLimitCount()); // 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时) - if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType()) + if (CouponTakeTypeEnum.isUser(updateReqVO.getTakeType()) && !isTotalCountUnlimited(updateReqVO.getTotalCount()) // 非不限制总发放数量 && updateReqVO.getTotalCount() < couponTemplate.getTakeCount()) { throw exception(COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL, couponTemplate.getTakeCount()); @@ -104,6 +110,16 @@ public class CouponTemplateServiceImpl implements CouponTemplateService { return couponTemplate; } + private void validateTotalCountNotLessThanTakeLimitCount(Integer takeType, Integer totalCount, Integer takeLimitCount) { + // 修复 https://gitee.com/yudaocode/yudao-mall-uniapp/issues/IJLP6Q 反馈 + if (CouponTakeTypeEnum.isUser(takeType) + && !isTakeLimitCountUnlimited(takeLimitCount) + && !isTotalCountUnlimited(totalCount) + && takeLimitCount > totalCount) { + throw exception(COUPON_TEMPLATE_NOT_ENOUGH); + } + } + private void validateProductScope(Integer productScope, List productScopeValues) { if (Objects.equals(PromotionProductScopeEnum.SPU.getScope(), productScope)) { productSpuApi.validateSpuList(productScopeValues); diff --git a/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImplTest.java b/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImplTest.java new file mode 100644 index 0000000000..5a07773799 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImplTest.java @@ -0,0 +1,325 @@ +package cn.iocoder.yudao.module.promotion.service.combination; + +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO; +import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecordMapper; +import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum; +import cn.iocoder.yudao.module.system.api.social.SocialClientApi; +import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderCancelTypeEnum; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.function.Consumer; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link CombinationRecordServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import(CombinationRecordServiceImpl.class) +public class CombinationRecordServiceImplTest extends BaseDbUnitTest { + + private static final Long USER_ID = 100L; + private static final Long ACTIVITY_ID = 200L; + private static final Long SPU_ID = 300L; + private static final Long SKU_ID = 400L; + private static final Long HEAD_ID = 500L; + private static final Long ORDER_ID = 600L; + + @Resource + private CombinationRecordServiceImpl combinationRecordService; + @Resource + private CombinationRecordMapper combinationRecordMapper; + + @MockitoBean + private CombinationActivityService combinationActivityService; + @MockitoBean + private MemberUserApi memberUserApi; + @MockitoBean + private ProductSpuApi productSpuApi; + @MockitoBean + private ProductSkuApi productSkuApi; + @MockitoBean + private TradeOrderApi tradeOrderApi; + @MockitoBean + private SocialClientApi socialClientApi; + + @Test + public void testValidateCombinationRecord_headIdNullAsNewGroup() { + // mock 数据 + CombinationActivityDO activity = mockValidateContext(10); + + // 调用 + KeyValue result = combinationRecordService + .validateCombinationRecord(USER_ID, ACTIVITY_ID, null, SKU_ID, 1); + + // 断言 + assertSame(activity, result.getKey()); + assertEquals(SKU_ID, result.getValue().getSkuId()); + } + + @Test + public void testValidateCombinationRecord_headIdGroupAsNewGroup() { + // mock 数据:headId 为 0 时,也代表新开团 + CombinationActivityDO activity = mockValidateContext(10); + + // 调用 + KeyValue result = combinationRecordService + .validateCombinationRecord(USER_ID, ACTIVITY_ID, CombinationRecordDO.HEAD_ID_GROUP, SKU_ID, 1); + + // 断言 + assertSame(activity, result.getKey()); + assertEquals(SKU_ID, result.getValue().getSkuId()); + } + + @Test + public void testValidateCombinationRecord_realHeadIdAsJoinGroup() { + // mock 数据:真实 headId 时,按参团校验父拼团 + CombinationActivityDO activity = mockValidateContext(10); + combinationRecordMapper.insert(randomRecord(o -> { + o.setId(HEAD_ID); + o.setHeadId(CombinationRecordDO.HEAD_ID_GROUP); + o.setUserId(101L); + o.setOrderId(601L); + })); + + // 调用 + KeyValue result = combinationRecordService + .validateCombinationRecord(USER_ID, ACTIVITY_ID, HEAD_ID, SKU_ID, 1); + + // 断言 + assertSame(activity, result.getKey()); + assertEquals(SKU_ID, result.getValue().getSkuId()); + } + + @Test + public void testValidateCombinationRecord_realHeadIdNotExists() { + // mock 数据:拼团活动正常,但父拼团不存在 + mockActivity(); + + // 调用,并断言 + assertServiceException(() -> combinationRecordService.validateCombinationRecord( + USER_ID, ACTIVITY_ID, HEAD_ID, SKU_ID, 1), COMBINATION_RECORD_HEAD_NOT_EXISTS); + } + + @Test + public void testValidateCombinationRecord_countEqualsStock() { + // mock 数据:库存刚好等于购买数量 + mockValidateContext(10); + + // 调用 + KeyValue result = combinationRecordService + .validateCombinationRecord(USER_ID, ACTIVITY_ID, null, SKU_ID, 10); + + // 断言 + assertNotNull(result); + } + + @Test + public void testValidateCombinationRecord_countGreaterThanStock() { + // mock 数据:库存不足 + mockValidateContext(10); + + // 调用,并断言 + assertServiceException(() -> combinationRecordService.validateCombinationRecord( + USER_ID, ACTIVITY_ID, null, SKU_ID, 11), COMBINATION_ACTIVITY_UPDATE_STOCK_FAIL); + } + + @Test + public void testValidateCombinationRecord_haveJoined() { + // mock 数据:用户存在进行中的拼团记录 + mockValidateContext(10); + combinationRecordMapper.insert(randomRecord(o -> { + o.setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); + o.setCount(1); + })); + + // 调用,并断言 + assertServiceException(() -> combinationRecordService.validateCombinationRecord( + USER_ID, ACTIVITY_ID, null, SKU_ID, 1), COMBINATION_RECORD_FAILED_HAVE_JOINED); + } + + @Test + public void testValidateCombinationRecord_totalLimitCountExceed() { + // mock 数据:用户历史购买数量加本次数量超过总限购 + CombinationActivityDO activity = mockValidateContext(10); + activity.setTotalLimitCount(5); + combinationRecordMapper.insert(randomRecord(o -> { + o.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus()); + o.setCount(3); + })); + + // 调用,并断言 + assertServiceException(() -> combinationRecordService.validateCombinationRecord( + USER_ID, ACTIVITY_ID, null, SKU_ID, 3), COMBINATION_RECORD_FAILED_TOTAL_LIMIT_COUNT_EXCEED); + } + + @Test + public void testCreateCombinationRecord_headIdGroupAsNewGroup() { + // mock 数据:开团参数中 headId 为 0 + mockValidateContext(10); + mockCreateContext(); + CombinationRecordCreateReqDTO reqDTO = randomCreateReqDTO(CombinationRecordDO.HEAD_ID_GROUP); + + // 调用 + CombinationRecordDO record = combinationRecordService.createCombinationRecord(reqDTO); + + // 断言:仍然按开团处理,不更新其它团记录 + assertNotNull(record.getId()); + assertEquals(CombinationRecordDO.HEAD_ID_GROUP, record.getHeadId()); + assertEquals(CombinationRecordStatusEnum.IN_PROGRESS.getStatus(), record.getStatus()); + assertEquals(1, record.getUserCount()); + assertFalse(record.getVirtualGroup()); + assertNotNull(record.getStartTime()); + assertNotNull(record.getExpireTime()); + assertEquals(1L, combinationRecordMapper.selectCount()); + assertEquals(CombinationRecordDO.HEAD_ID_GROUP, + combinationRecordMapper.selectById(record.getId()).getHeadId()); + } + + @Test + public void testHandleExpireRecord_cancelOrders() { + // mock 数据:过期团长和一个团员 + CombinationRecordDO headRecord = randomRecord(o -> { + o.setId(HEAD_ID); + o.setHeadId(CombinationRecordDO.HEAD_ID_GROUP); + o.setUserId(USER_ID); + o.setOrderId(ORDER_ID); + }); + CombinationRecordDO memberRecord = randomRecord(o -> { + o.setId(501L); + o.setHeadId(HEAD_ID); + o.setUserId(101L); + o.setOrderId(601L); + }); + combinationRecordMapper.insert(headRecord); + combinationRecordMapper.insert(memberRecord); + + // 调用 + combinationRecordService.handleExpireRecord(headRecord); + + // 断言:整团记录标记为失败,并取消已支付订单 + List records = combinationRecordMapper.selectList(); + assertEquals(2, records.size()); + assertTrue(records.stream().allMatch(item -> + CombinationRecordStatusEnum.FAILED.getStatus().equals(item.getStatus()))); + assertTrue(records.stream().allMatch(item -> item.getEndTime() != null)); + verify(tradeOrderApi).cancelPaidOrder(USER_ID, ORDER_ID, TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType()); + verify(tradeOrderApi).cancelPaidOrder(101L, 601L, TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType()); + } + + private CombinationActivityDO mockValidateContext(Integer stock) { + CombinationActivityDO activity = mockActivity(); + CombinationProductDO product = randomPojo(CombinationProductDO.class, o -> { + o.setActivityId(ACTIVITY_ID); + o.setSpuId(SPU_ID); + o.setSkuId(SKU_ID); + o.setCombinationPrice(100); + }); + ProductSkuRespDTO sku = randomPojo(ProductSkuRespDTO.class, o -> { + o.setId(SKU_ID); + o.setSpuId(SPU_ID); + o.setStock(stock); + o.setPicUrl("https://www.iocoder.cn/sku.png"); + }); + when(combinationActivityService.selectByActivityIdAndSkuId(ACTIVITY_ID, SKU_ID)).thenReturn(product); + when(productSkuApi.getSku(SKU_ID)).thenReturn(sku); + return activity; + } + + private CombinationActivityDO mockActivity() { + CombinationActivityDO activity = randomPojo(CombinationActivityDO.class, o -> { + o.setId(ACTIVITY_ID); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setStartTime(LocalDateTime.now().minusHours(1)); + o.setEndTime(LocalDateTime.now().plusHours(1)); + o.setSingleLimitCount(100); + o.setTotalLimitCount(100); + o.setUserSize(3); + o.setVirtualGroup(false); + o.setLimitDuration(24); + }); + when(combinationActivityService.validateCombinationActivityExists(ACTIVITY_ID)).thenReturn(activity); + return activity; + } + + private void mockCreateContext() { + MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> { + o.setId(USER_ID); + o.setNickname("芋道源码"); + o.setAvatar("https://www.iocoder.cn/avatar.png"); + }); + when(memberUserApi.getUser(USER_ID)).thenReturn(user); + ProductSpuRespDTO spu = randomPojo(ProductSpuRespDTO.class, o -> { + o.setId(SPU_ID); + o.setName("测试商品"); + o.setPicUrl("https://www.iocoder.cn/spu.png"); + }); + when(productSpuApi.getSpu(SPU_ID)).thenReturn(spu); + } + + private static CombinationRecordCreateReqDTO randomCreateReqDTO(Long headId) { + return randomPojo(CombinationRecordCreateReqDTO.class, o -> { + o.setActivityId(ACTIVITY_ID); + o.setSpuId(SPU_ID); + o.setSkuId(SKU_ID); + o.setCount(1); + o.setOrderId(ORDER_ID); + o.setUserId(USER_ID); + o.setHeadId(headId); + o.setCombinationPrice(100); + }); + } + + @SafeVarargs + private static CombinationRecordDO randomRecord(Consumer... consumers) { + return randomPojo(CombinationRecordDO.class, o -> { + o.setActivityId(ACTIVITY_ID); + o.setCombinationPrice(100); + o.setSpuId(SPU_ID); + o.setSpuName("测试商品"); + o.setPicUrl("https://www.iocoder.cn/spu.png"); + o.setSkuId(SKU_ID); + o.setCount(1); + o.setUserId(USER_ID); + o.setNickname("芋道源码"); + o.setAvatar("https://www.iocoder.cn/avatar.png"); + o.setHeadId(CombinationRecordDO.HEAD_ID_GROUP); + o.setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); + o.setOrderId(ORDER_ID); + o.setUserSize(3); + o.setUserCount(1); + o.setVirtualGroup(false); + o.setStartTime(LocalDateTime.now().minusMinutes(10)); + o.setExpireTime(LocalDateTime.now().plusHours(1)); + o.setEndTime(null); + for (Consumer consumer : consumers) { + consumer.accept(o); + } + }); + } + +} diff --git a/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java b/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java index 2f3862849a..1845be201d 100755 --- a/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java +++ b/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java @@ -2,7 +2,10 @@ package cn.iocoder.yudao.module.promotion.service.coupon; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO; @@ -10,21 +13,23 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper; import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import jakarta.annotation.Resource; import java.time.LocalDateTime; +import java.util.function.Consumer; -import static cn.hutool.core.util.RandomUtil.randomEle; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_ENOUGH; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_EXISTS; import static org.junit.jupiter.api.Assertions.*; @@ -33,7 +38,6 @@ import static org.junit.jupiter.api.Assertions.*; * * @author 芋道源码 */ -@Disabled // TODO 芋艿:后续 fix 补充的单测 @Import(CouponTemplateServiceImpl.class) public class CouponTemplateServiceImplTest extends BaseDbUnitTest { @@ -43,13 +47,26 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest { @Resource private CouponTemplateMapper couponTemplateMapper; + @MockitoBean + private ProductCategoryApi productCategoryApi; + @MockitoBean + private ProductSpuApi productSpuApi; + @Test public void testCreateCouponTemplate_success() { // 准备参数 - CouponTemplateCreateReqVO reqVO = randomPojo(CouponTemplateCreateReqVO.class, - o -> o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope()) - .setValidityType(randomEle(CouponTemplateValidityTypeEnum.values()).getType()) - .setDiscountType(randomEle(PromotionDiscountTypeEnum.values()).getType())); + CouponTemplateCreateReqVO reqVO = randomPojo(CouponTemplateCreateReqVO.class, o -> { + o.setTotalCount(10); + o.setTakeLimitCount(2); + o.setTakeType(CouponTakeTypeEnum.USER.getType()); + o.setProductScope(PromotionProductScopeEnum.ALL.getScope()); + o.setProductScopeValues(null); + o.setValidityType(CouponTemplateValidityTypeEnum.TERM.getType()); + o.setFixedStartTerm(0); + o.setFixedEndTerm(7); + o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()); + o.setDiscountPrice(100); + }); // 调用 Long couponTemplateId = couponTemplateService.createCouponTemplate(reqVO); @@ -63,15 +80,21 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest { @Test public void testUpdateCouponTemplate_success() { // mock 数据 - CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class); + CouponTemplateDO dbCouponTemplate = randomCouponTemplateDO(); couponTemplateMapper.insert(dbCouponTemplate);// @Sql: 先插入出一条存在的数据 // 准备参数 CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class, o -> { o.setId(dbCouponTemplate.getId()); // 设置更新的 ID - // 其它通用字段 - o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope()) - .setValidityType(randomEle(CouponTemplateValidityTypeEnum.values()).getType()) - .setDiscountType(randomEle(PromotionDiscountTypeEnum.values()).getType()); + o.setTotalCount(10); + o.setTakeLimitCount(2); + o.setTakeType(CouponTakeTypeEnum.USER.getType()); + o.setProductScope(PromotionProductScopeEnum.ALL.getScope()); + o.setProductScopeValues(null); + o.setValidityType(CouponTemplateValidityTypeEnum.TERM.getType()); + o.setFixedStartTerm(0); + o.setFixedEndTerm(7); + o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()); + o.setDiscountPrice(100); }); // 调用 @@ -84,16 +107,60 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest { @Test public void testUpdateCouponTemplate_notExists() { // 准备参数 - CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class); + CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class, o -> o.setId(randomLongId())); // 调用, 并断言异常 assertServiceException(() -> couponTemplateService.updateCouponTemplate(reqVO), COUPON_TEMPLATE_NOT_EXISTS); } + @Test + public void testCreateCouponTemplate_takeLimitCountGreaterThanTotalCount() { + // 准备参数 + CouponTemplateCreateReqVO reqVO = randomPojo(CouponTemplateCreateReqVO.class, o -> { + o.setTotalCount(1); + o.setTakeLimitCount(2); + o.setTakeType(CouponTakeTypeEnum.USER.getType()); + o.setProductScope(PromotionProductScopeEnum.ALL.getScope()); + o.setProductScopeValues(null); + o.setValidityType(CouponTemplateValidityTypeEnum.TERM.getType()); + o.setFixedStartTerm(0); + o.setFixedEndTerm(7); + o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()); + o.setDiscountPrice(100); + }); + + // 调用,并断言 + assertServiceException(() -> couponTemplateService.createCouponTemplate(reqVO), COUPON_TEMPLATE_NOT_ENOUGH); + } + + @Test + public void testUpdateCouponTemplate_takeLimitCountGreaterThanTotalCount() { + // mock 数据 + CouponTemplateDO couponTemplate = randomCouponTemplateDO(); + couponTemplateMapper.insert(couponTemplate); + // 准备参数 + CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class, o -> { + o.setId(couponTemplate.getId()); // 设置更新的 ID + o.setTotalCount(1); + o.setTakeLimitCount(2); + o.setTakeType(CouponTakeTypeEnum.USER.getType()); + o.setProductScope(PromotionProductScopeEnum.ALL.getScope()); + o.setProductScopeValues(null); + o.setValidityType(CouponTemplateValidityTypeEnum.TERM.getType()); + o.setFixedStartTerm(0); + o.setFixedEndTerm(7); + o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()); + o.setDiscountPrice(100); + }); + + // 调用,并断言 + assertServiceException(() -> couponTemplateService.updateCouponTemplate(reqVO), COUPON_TEMPLATE_NOT_ENOUGH); + } + @Test public void testDeleteCouponTemplate_success() { // mock 数据 - CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class); + CouponTemplateDO dbCouponTemplate = randomCouponTemplateDO(); couponTemplateMapper.insert(dbCouponTemplate);// @Sql: 先插入出一条存在的数据 // 准备参数 Long id = dbCouponTemplate.getId(); @@ -146,4 +213,25 @@ public class CouponTemplateServiceImplTest extends BaseDbUnitTest { assertPojoEquals(dbCouponTemplate, pageResult.getList().get(0)); } + @SafeVarargs + private static CouponTemplateDO randomCouponTemplateDO(Consumer... consumers) { + Consumer consumer = o -> { + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setTotalCount(10); + o.setTakeLimitCount(1); + o.setTakeType(CouponTakeTypeEnum.USER.getType()); + o.setUsePrice(0); + o.setProductScope(PromotionProductScopeEnum.ALL.getScope()); + o.setProductScopeValues(null); + o.setValidityType(CouponTemplateValidityTypeEnum.TERM.getType()); + o.setFixedStartTerm(0); + o.setFixedEndTerm(7); + o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()); + o.setDiscountPrice(100); + o.setTakeCount(0); + o.setUseCount(0); + }; + return randomPojo(CouponTemplateDO.class, ArrayUtils.append(consumer, consumers)); + } + } diff --git a/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/clean.sql b/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/clean.sql index 6a1a24252e..54ec043c16 100644 --- a/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/clean.sql +++ b/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/clean.sql @@ -5,8 +5,9 @@ DELETE FROM "promotion_reward_activity"; DELETE FROM "promotion_discount_activity"; DELETE FROM "promotion_discount_product"; DELETE FROM "promotion_seckill_config"; +DELETE FROM "promotion_combination_record"; DELETE FROM "promotion_combination_activity"; DELETE FROM "promotion_article_category"; DELETE FROM "promotion_article"; DELETE FROM "promotion_diy_template"; -DELETE FROM "promotion_diy_page"; \ No newline at end of file +DELETE FROM "promotion_diy_page"; diff --git a/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/create_tables.sql b/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/create_tables.sql index 00ac3f93dd..fae2e52255 100644 --- a/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/create_tables.sql @@ -23,13 +23,14 @@ CREATE TABLE IF NOT EXISTS "promotion_coupon_template" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" varchar NOT NULL, + "description" varchar, "status" int NOT NULL, "total_count" int NOT NULL, "take_limit_count" int NOT NULL, "take_type" int NOT NULL, "use_price" int NOT NULL, "product_scope" int NOT NULL, - "product_spu_ids" varchar, + "product_scope_values" varchar, "validity_type" int NOT NULL, "valid_start_time" datetime, "valid_end_time" datetime, @@ -57,11 +58,11 @@ CREATE TABLE IF NOT EXISTS "promotion_coupon" "status" int NOT NULL, "user_id" bigint NOT NULL, "take_type" int NOT NULL, - "useprice" int NOT NULL, + "use_price" int NOT NULL, "valid_start_time" datetime NOT NULL, "valid_end_time" datetime NOT NULL, "product_scope" int NOT NULL, - "product_spu_ids" varchar, + "product_scope_values" varchar, "discount_type" int NOT NULL, "discount_percent" int, "discount_price" int, @@ -112,6 +113,27 @@ CREATE TABLE IF NOT EXISTS "promotion_discount_activity" PRIMARY KEY ("id") ) COMMENT '限时折扣活动'; +CREATE TABLE IF NOT EXISTS "promotion_discount_product" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "activity_id" bigint NOT NULL, + "spu_id" bigint NOT NULL, + "sku_id" bigint NOT NULL, + "discount_type" int NOT NULL, + "discount_percent" int, + "discount_price" int, + "activity_name" varchar NOT NULL, + "activity_status" int NOT NULL, + "activity_start_time" datetime NOT NULL, + "activity_end_time" datetime NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '限时折扣商品'; + CREATE TABLE IF NOT EXISTS "promotion_seckill_activity" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, @@ -181,6 +203,36 @@ CREATE TABLE IF NOT EXISTS "promotion_combination_activity" PRIMARY KEY ("id") ) COMMENT '拼团活动'; +CREATE TABLE IF NOT EXISTS "promotion_combination_record" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "activity_id" bigint NOT NULL, + "combination_price" int NOT NULL, + "spu_id" bigint NOT NULL, + "spu_name" varchar NOT NULL, + "pic_url" varchar, + "sku_id" bigint NOT NULL, + "count" int NOT NULL, + "user_id" bigint NOT NULL, + "nickname" varchar, + "avatar" varchar, + "head_id" bigint NOT NULL, + "status" int NOT NULL, + "order_id" bigint NOT NULL, + "user_size" int NOT NULL, + "user_count" int NOT NULL, + "virtual_group" bit NOT NULL, + "expire_time" datetime, + "start_time" datetime, + "end_time" datetime, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '拼团记录'; + CREATE TABLE IF NOT EXISTS "promotion_article_category" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, @@ -253,4 +305,4 @@ CREATE TABLE IF NOT EXISTS "promotion_diy_page" "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL, PRIMARY KEY ("id") -) COMMENT '装修页面'; \ No newline at end of file +) COMMENT '装修页面'; diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryPickUpStoreController.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryPickUpStoreController.java index 904c246f26..c81047b73c 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryPickUpStoreController.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryPickUpStoreController.java @@ -73,7 +73,7 @@ public class DeliveryPickUpStoreController { } List verifyUsers = CollUtil.isNotEmpty(deliveryPickUpStore.getVerifyUserIds()) ? adminUserApi.getUserList(deliveryPickUpStore.getVerifyUserIds()) : null; - return success(BeanUtils.toBean(deliveryPickUpStore, DeliveryPickUpStoreRespVO.class) + return success(DeliveryPickUpStoreConvert.INSTANCE.convert01(deliveryPickUpStore) .setVerifyUsers(BeanUtils.toBean(verifyUsers, UserSimpleBaseVO.class))); } diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreRespVO.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreRespVO.java index fdf39a46d7..3c1233a17e 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreRespVO.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreRespVO.java @@ -21,6 +21,9 @@ public class DeliveryPickUpStoreRespVO extends DeliveryPickUpStoreBaseVO { @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; + @Schema(description = "地区名称", example = "上海上海市普陀区") + private String areaName; + @Schema(description = "核销用户数组", requiredMode = Schema.RequiredMode.REQUIRED) private List verifyUsers; diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java index 46495c003e..310028d801 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java @@ -13,6 +13,7 @@ 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 lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -47,13 +48,13 @@ public class AppAfterSaleController { @PostMapping(value = "/create") @Operation(summary = "申请售后") - public CommonResult createAfterSale(@RequestBody AppAfterSaleCreateReqVO createReqVO) { + public CommonResult createAfterSale(@Valid @RequestBody AppAfterSaleCreateReqVO createReqVO) { return success(afterSaleService.createAfterSale(getLoginUserId(), createReqVO)); } @PutMapping(value = "/delivery") @Operation(summary = "退回货物") - public CommonResult deliveryAfterSale(@RequestBody AppAfterSaleDeliveryReqVO deliveryReqVO) { + public CommonResult deliveryAfterSale(@Valid @RequestBody AppAfterSaleDeliveryReqVO deliveryReqVO) { afterSaleService.deliveryAfterSale(getLoginUserId(), deliveryReqVO); return success(true); } diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryPickUpStoreConvert.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryPickUpStoreConvert.java index 05d8a8f013..560e7fea45 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryPickUpStoreConvert.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryPickUpStoreConvert.java @@ -30,6 +30,9 @@ public interface DeliveryPickUpStoreConvert { PageResult convertPage(PageResult page); + @Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName") + DeliveryPickUpStoreRespVO convert01(DeliveryPickUpStoreDO bean); + List convertList1(List list); @Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName") DeliveryPickUpStoreSimpleRespVO convert02(DeliveryPickUpStoreDO bean); diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java index 0e2380a463..3842bb424b 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.trade.convert.order; import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -268,8 +269,8 @@ public interface TradeOrderConvert { .setTitle(StrUtil.format("{}成功购买{}", user.getNickname(), item.getSpuName())); if (BooleanUtil.isTrue(spu.getSubCommissionType())) { // 特殊:单独设置的佣金需要乘以购买数量。关联 https://gitee.com/yudaocode/yudao-mall-uniapp/issues/ICY7SJ - bo.setFirstFixedPrice(sku.getFirstBrokeragePrice() * item.getCount()) - .setSecondFixedPrice(sku.getSecondBrokeragePrice() * item.getCount()); + bo.setFirstFixedPrice(ObjectUtil.defaultIfNull(sku.getFirstBrokeragePrice(), 0) * item.getCount()) + .setSecondFixedPrice(ObjectUtil.defaultIfNull(sku.getSecondBrokeragePrice(), 0) * item.getCount()); } return bo; } diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java index ed314c3ac0..845fea78cd 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java @@ -38,7 +38,6 @@ import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMinValue; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH; /** @@ -143,7 +142,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService { int calculatePrice(Integer basePrice, Integer percent, Integer fixedPrice) { // 1. 优先使用固定佣金 if (fixedPrice != null && fixedPrice >= 0) { - return ObjectUtil.defaultIfNull(fixedPrice, 0); + return fixedPrice; } // 2. 根据比例计算佣金 if (basePrice != null && basePrice > 0 && percent != null && percent > 0) { @@ -329,7 +328,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService { return respVO; } // 2.2 校验用户是否有分销资格 - respVO.setEnabled(brokerageUserService.getUserBrokerageEnabled(getLoginUserId())); + respVO.setEnabled(brokerageUserService.getUserBrokerageEnabled(userId)); if (BooleanUtil.isFalse(respVO.getEnabled())) { return respVO; } @@ -339,22 +338,24 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService { return respVO; } - // 3.1 商品单独分佣模式 - Integer fixedMinPrice = 0; - Integer fixedMaxPrice = 0; - Integer spuMinPrice = 0; - Integer spuMaxPrice = 0; + // 3.1 获取商品 SKU 列表 List skuList = productSkuApi.getSkuListBySpuId(ListUtil.of(spuId)); if (BooleanUtil.isTrue(spu.getSubCommissionType())) { - fixedMinPrice = getMinValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice); - fixedMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getFirstBrokeragePrice); - // 3.2 全局分佣模式(根据商品价格比例计算) + // 3.2.1 商品独立分销模式:直接取 SKU 固定佣金 + // 注意:固定佣金允许为 0,表示商家主动设为零佣金;为空时,也按 0 处理 + Integer fixedMinPrice = getMinValue(skuList, + sku -> ObjectUtil.defaultIfNull(sku.getFirstBrokeragePrice(), 0)); + Integer fixedMaxPrice = getMaxValue(skuList, + sku -> ObjectUtil.defaultIfNull(sku.getFirstBrokeragePrice(), 0)); + respVO.setBrokerageMinPrice(calculatePrice(null, tradeConfig.getBrokerageFirstPercent(), fixedMinPrice)) + .setBrokerageMaxPrice(calculatePrice(null, tradeConfig.getBrokerageFirstPercent(), fixedMaxPrice)); } else { - spuMinPrice = getMinValue(skuList, ProductSkuRespDTO::getPrice); - spuMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getPrice); + // 3.2.2 全局比例模式:固定佣金传 null,避免被默认值 0 提前拦截 + Integer spuMinPrice = getMinValue(skuList, ProductSkuRespDTO::getPrice); + Integer spuMaxPrice = getMaxValue(skuList, ProductSkuRespDTO::getPrice); + respVO.setBrokerageMinPrice(calculatePrice(spuMinPrice, tradeConfig.getBrokerageFirstPercent(), null)) + .setBrokerageMaxPrice(calculatePrice(spuMaxPrice, tradeConfig.getBrokerageFirstPercent(), null)); } - respVO.setBrokerageMinPrice(calculatePrice(spuMinPrice, tradeConfig.getBrokerageFirstPercent(), fixedMinPrice)); - respVO.setBrokerageMaxPrice(calculatePrice(spuMaxPrice, tradeConfig.getBrokerageFirstPercent(), fixedMaxPrice)); return respVO; } diff --git a/yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml b/yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml index 066f75d4ea..69ee4ede52 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml +++ b/yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml @@ -2,9 +2,19 @@ + + + + ASC + + + DESC + + + +