mirror of
https://gitee.com/zhijiantianya/ruoyi-vue-pro.git
synced 2026-07-05 13:22:18 +08:00
Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/jdk25
# Conflicts: # yudao-dependencies/pom.xml
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -51,4 +51,5 @@ rebel.xml
|
||||
application-my.yaml
|
||||
|
||||
/yudao-ui-app/unpackage/
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
|
||||
BIN
.image/common/im-feature.png
Normal file
BIN
.image/common/im-feature.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 169 KiB |
BIN
.image/common/im-preview-home.png
Normal file
BIN
.image/common/im-preview-home.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
.image/common/im-preview-manager.png
Normal file
BIN
.image/common/im-preview-manager.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
20
README.md
20
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 @@
|
||||
|
||||

|
||||
|
||||
### IM 即时通讯
|
||||
|
||||
演示地址(Boot):<https://doc.iocoder.cn/im-preview/>
|
||||
|
||||
演示地址(Vue3 + Element Plus):<http://dashboard-vue3.yudao.iocoder.cn>
|
||||
|
||||
|
||||

|
||||
|
||||
| 聊天界面 | 聊天管理 |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
## 🐨 技术栈
|
||||
|
||||
### 模块
|
||||
@ -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 模块 |
|
||||
|
||||
3
pom.xml
3
pom.xml
@ -26,6 +26,7 @@
|
||||
<!-- <module>yudao-module-iot</module>-->
|
||||
<!-- <module>yudao-module-mes</module>-->
|
||||
<!-- <module>yudao-module-wms</module>-->
|
||||
<!-- <module>yudao-module-im</module>-->
|
||||
<!-- 请参考 https://doc.iocoder.cn/ai/build/ 文档,完成 AI 模块的启动!!! -->
|
||||
<!-- <module>yudao-module-ai</module>-->
|
||||
</modules>
|
||||
@ -35,7 +36,7 @@
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2026.04-SNAPSHOT</revision>
|
||||
<revision>2026.05-SNAPSHOT</revision>
|
||||
<!-- Maven 相关 -->
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
|
||||
32
script/livekit-poc/README.md
Normal file
32
script/livekit-poc/README.md
Normal file
@ -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` 链接,用两个浏览器(或两台机器)打开同一链接即可看到对方画面。
|
||||
20
script/livekit-poc/docker-compose.yml
Normal file
20
script/livekit-poc/docker-compose.yml
Normal file
@ -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
|
||||
30
script/livekit-poc/livekit.yaml
Normal file
30
script/livekit-poc/livekit.yaml
Normal file
@ -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
|
||||
105
script/livekit-poc/verify.sh
Executable file
105
script/livekit-poc/verify.sh
Executable file
@ -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 "============================================================"
|
||||
@ -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
|
||||
|
||||
|
||||
208
sql/highgo/quartz.sql
Normal file
208
sql/highgo/quartz.sql
Normal file
@ -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;
|
||||
6198
sql/highgo/ruoyi-vue-pro.sql
Normal file
6198
sql/highgo/ruoyi-vue-pro.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
-- ----------------------------
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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 <highgo-image>.tar
|
||||
docker tag <image>:<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`。
|
||||
|
||||
@ -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}")
|
||||
|
||||
|
||||
@ -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'
|
||||
# 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 <image>:<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
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2026.04-SNAPSHOT</revision>
|
||||
<revision>2026.05-SNAPSHOT</revision>
|
||||
<flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.boot.version>4.1.0</spring.boot.version>
|
||||
@ -50,6 +50,8 @@
|
||||
<!-- 工具类相关 -->
|
||||
<anji-plus-captcha.version>1.4.0</anji-plus-captcha.version>
|
||||
<jsoup.version>1.22.2</jsoup.version>
|
||||
<sensitive-word.version>0.29.5</sensitive-word.version>
|
||||
<pinyin4j.version>2.5.1</pinyin4j.version>
|
||||
<lombok.version>1.18.46</lombok.version>
|
||||
<mapstruct.version>1.6.3</mapstruct.version>
|
||||
<hutool-5.version>5.8.44</hutool-5.version>
|
||||
@ -57,6 +59,7 @@
|
||||
<fastexcel.version>1.3.0</fastexcel.version>
|
||||
<velocity.version>2.4.1</velocity.version>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<fastjson2.version>2.0.61</fastjson2.version>
|
||||
<guava.version>33.6.0-jre</guava.version>
|
||||
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
||||
<commons-net.version>3.13.0</commons-net.version>
|
||||
@ -65,7 +68,7 @@
|
||||
<tika-core.version>3.3.0</tika-core.version>
|
||||
<ip2region.version>2.7.0</ip2region.version>
|
||||
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
|
||||
<netty.version>4.2.12.Final</netty.version>
|
||||
<netty.version>4.2.14.Final</netty.version>
|
||||
<mqtt.version>1.2.5</mqtt.version>
|
||||
<vertx.version>4.5.26</vertx.version>
|
||||
<okhttp.version>4.12.0</okhttp.version>
|
||||
@ -75,10 +78,11 @@
|
||||
<awssdk.version>2.44.0</awssdk.version>
|
||||
<justauth.version>1.16.7</justauth.version>
|
||||
<justauth-starter.version>1.4.0</justauth-starter.version>
|
||||
<jimureport.version>2.3.2</jimureport.version>
|
||||
<jimureport.version>2.3.4</jimureport.version>
|
||||
<jimubi.version>2.3.2</jimubi.version>
|
||||
<weixin-java.version>4.8.2-20260501.180637</weixin-java.version>
|
||||
<alipay-sdk-java.version>4.40.771.ALL</alipay-sdk-java.version>
|
||||
<bouncycastle.version>1.80</bouncycastle.version>
|
||||
<alipay-sdk-java.version>4.40.806.ALL</alipay-sdk-java.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@ -521,6 +525,11 @@
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson2.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
@ -569,6 +578,18 @@
|
||||
<version>${jsoup.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.houbb</groupId>
|
||||
<artifactId>sensitive-word</artifactId> <!-- 敏感词检测:trie 树高效匹配 -->
|
||||
<version>${sensitive-word.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.belerweb</groupId>
|
||||
<artifactId>pinyin4j</artifactId> <!-- 汉字转拼音:作为 hutool PinyinUtil 的底层引擎 -->
|
||||
<version>${pinyin4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Vert.x -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
@ -657,6 +678,24 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- 锁定 weixin-java 传递依赖,避免 Maven 版本范围自动升级到 1.80.2 后 Fat Jar 启动失败。
|
||||
反馈:https://t.zsxq.com/pCVBo -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
<version>${bouncycastle.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcutil-jdk18on</artifactId>
|
||||
<version>${bouncycastle.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk18on</artifactId>
|
||||
<version>${bouncycastle.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-pay</artifactId>
|
||||
|
||||
@ -124,6 +124,22 @@ public class CollectionUtils {
|
||||
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertLinkedSet(Collection<T> from, Function<T, U> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new LinkedHashSet<>();
|
||||
}
|
||||
return from.stream().map(func).filter(Objects::nonNull)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertLinkedSet(Collection<T> from, Function<T, U> func, Predicate<T> 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 <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
@ -372,4 +388,14 @@ public class CollectionUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把单元素 head 与集合 tail 合并成新 List(head 在前,tail 顺序保留)
|
||||
*/
|
||||
public static <T> List<T> of(T head, Collection<T> tail) {
|
||||
List<T> list = new ArrayList<>();
|
||||
list.add(head);
|
||||
CollUtil.addAll(list, tail);
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 点时刻序列(升序,含今天)
|
||||
* <p>
|
||||
* 例:getLatestDays(3) 返回 [前天 00:00, 昨天 00:00, 今天 00:00]
|
||||
*
|
||||
* @param days 天数(含今天)
|
||||
* @return 升序的 LocalDateTime 列表
|
||||
*/
|
||||
public static List<LocalDateTime> getLatestDays(int days) {
|
||||
LocalDateTime today = getToday();
|
||||
List<LocalDateTime> dates = new ArrayList<>(days);
|
||||
for (int i = days - 1; i >= 0; i--) {
|
||||
dates.add(today.minusDays(i));
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
public static List<LocalDateTime[]> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<String, Object> parseMap(String text) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, new TypeReference<Map<String, Object>>() {});
|
||||
} 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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接方法的参数
|
||||
*
|
||||
|
||||
@ -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() {
|
||||
// 准备参数
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<AbstractRedisStreamMessageListener<?>> 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<AbstractRedisStreamMessageListener<?>> listeners,
|
||||
RedisMQTemplate redisTemplate,
|
||||
RedissonClient redissonClient) {
|
||||
return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient);
|
||||
return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient,
|
||||
RedisStreamMessageCleanupJob.DEFAULT_CLEANUP_LOCK_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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<AbstractRedisStreamMessageListener<?>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<AbstractRedisStreamMessageListener<?>> 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<String, Object, Object> 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);
|
||||
}
|
||||
|
||||
@ -105,6 +105,13 @@
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,6 +170,17 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
|
||||
return CollUtil.getFirst(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取满足条件的最新一条记录
|
||||
* <p>
|
||||
* 目的:解决并发场景下,插入多条记录后,使用 selectOne 会报错的问题
|
||||
*
|
||||
* @param queryWrapper 查询条件
|
||||
* @return 最新一条;不存在返回 null
|
||||
*/
|
||||
default T selectLastOne(LambdaQueryWrapper<T> queryWrapper) {
|
||||
return CollUtil.getLast(selectList(queryWrapper));
|
||||
}
|
||||
|
||||
default Long selectCount() {
|
||||
return selectCount(new QueryWrapper<>());
|
||||
|
||||
@ -25,6 +25,13 @@ public class LambdaQueryWrapperX<T> extends LambdaQueryWrapper<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> likeRightIfPresent(SFunction<T, ?> column, String val) {
|
||||
if (StringUtils.hasText(val)) {
|
||||
return (LambdaQueryWrapperX<T>) super.likeRight(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
|
||||
if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
|
||||
return (LambdaQueryWrapperX<T>) super.in(column, values);
|
||||
|
||||
@ -27,6 +27,13 @@ public class MPJLambdaWrapperX<T> extends MPJLambdaWrapper<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> likeRightIfPresent(SFunction<S, ?> column, String val) {
|
||||
if (StringUtils.hasText(val)) {
|
||||
return (MPJLambdaWrapperX<T>) super.likeRight(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <S> MPJLambdaWrapperX<T> inIfPresent(SFunction<S, ?> column, Collection<?> values) {
|
||||
if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
|
||||
return (MPJLambdaWrapperX<T>) super.in(column, values);
|
||||
@ -102,7 +109,6 @@ public class MPJLambdaWrapperX<T> extends MPJLambdaWrapper<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// ========== 重写父类方法,方便链式调用 ==========
|
||||
|
||||
@Override
|
||||
|
||||
@ -25,6 +25,13 @@ public class QueryWrapperX<T> extends QueryWrapper<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> likeRightIfPresent(String column, String val) {
|
||||
if (StringUtils.hasText(val)) {
|
||||
return (QueryWrapperX<T>) super.likeRight(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> inIfPresent(String column, Collection<?> values) {
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
return (QueryWrapperX<T>) super.in(column, values);
|
||||
@ -95,13 +102,13 @@ public class QueryWrapperX<T> extends QueryWrapper<T> {
|
||||
}
|
||||
|
||||
public QueryWrapperX<T> 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<T>) 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<T>) ge(column, values[0]);
|
||||
}
|
||||
if (values!= null && values.length != 0 && values[1] != null) {
|
||||
if (values != null && values.length != 0 && values[1] != null) {
|
||||
return (QueryWrapperX<T>) le(column, values[1]);
|
||||
}
|
||||
return this;
|
||||
|
||||
@ -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 <T> Page<T> 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<T>) {
|
||||
QueryWrapper<T> query = (QueryWrapper<T>) 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<T>) {
|
||||
// LambdaQueryWrapper 不直接支持字符串字段排序,使用 last 方法拼接 ORDER BY
|
||||
LambdaQueryWrapper<T> lambdaQuery = (LambdaQueryWrapper<T>) 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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<SortingField> sortingFields = Arrays.asList(
|
||||
new SortingField("userName", SortingField.ORDER_ASC),
|
||||
new SortingField("u.id", SortingField.ORDER_DESC),
|
||||
new SortingField("name desc", SortingField.ORDER_DESC));
|
||||
|
||||
// 调用
|
||||
Page<Object> 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<Object> query = new QueryWrapper<>();
|
||||
List<SortingField> 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<Object> query = new LambdaQueryWrapper<>();
|
||||
List<SortingField> 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<Object> query = new LambdaQueryWrapper<>();
|
||||
List<SortingField> 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<Object> 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<Object> 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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -27,9 +27,10 @@ public class LoginUserHandshakeInterceptor implements HandshakeInterceptor {
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler wsHandler, Map<String, Object> attributes) {
|
||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||
if (loginUser != null) {
|
||||
WebSocketFrameworkUtils.setLoginUser(loginUser, attributes);
|
||||
if (loginUser == null) {
|
||||
return false;
|
||||
}
|
||||
WebSocketFrameworkUtils.setLoginUser(loginUser, attributes);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -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<String> attachments;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<String> attachments;
|
||||
|
||||
@Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Map<String, Object> variables;
|
||||
|
||||
|
||||
@ -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<String> attachments;
|
||||
|
||||
}
|
||||
|
||||
@ -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<Long, AdminUserRespDTO> userMap,
|
||||
Map<Long, DeptRespDTO> deptMap) {
|
||||
Map<Long, AdminUserRespDTO> userMap,
|
||||
Map<Long, DeptRespDTO> 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<Attachment> 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<Long> parseUserIds(HistoricProcessInstance processInstance,
|
||||
|
||||
@ -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", "用户任务附件");
|
||||
|
||||
/**
|
||||
* 操作类型
|
||||
* <p>
|
||||
* 由于 BPM attachment 类型为 String,所以这里就不使用 Integer
|
||||
*/
|
||||
private final String type;
|
||||
/**
|
||||
* 操作名字
|
||||
*/
|
||||
private final String name;
|
||||
}
|
||||
@ -24,7 +24,7 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable<Integer> {
|
||||
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, "发起人部门负责人"),
|
||||
|
||||
@ -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<SequenceFlow> getElementIncomingUserTaskFlows(FlowElement source) {
|
||||
List<SequenceFlow> result = new ArrayList<>();
|
||||
collectElementIncomingUserTaskFlows(source, new HashSet<>(), new HashSet<>(), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void collectElementIncomingUserTaskFlows(FlowElement source, Set<String> visitedSequenceFlowIds,
|
||||
Set<String> resultSequenceFlowIds, List<SequenceFlow> result) {
|
||||
// 如果是开始节点或子流程,则停止该分支向上查找
|
||||
if (source == null || source instanceof StartEvent || source instanceof SubProcess) {
|
||||
return;
|
||||
}
|
||||
// 获取入口连线
|
||||
List<SequenceFlow> 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 节点, 支持网关条件判断
|
||||
*
|
||||
* <ul>
|
||||
* <li>排他网关:走满足条件的唯一一条路径(含默认路径兜底)</li>
|
||||
* <li>包容网关:走所有满足条件的路径</li>
|
||||
* <li>并行网关:走所有出口路径</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param currentElement 当前节点
|
||||
* @param bpmnModel BPMN 模型
|
||||
* @param variables 流程变量(用于网关条件判断)
|
||||
* @return 下一个用户任务节点列表
|
||||
*/
|
||||
public static List<UserTask> getNextUserTasks(FlowElement currentElement, BpmnModel bpmnModel,
|
||||
Map<String, Object> variables) {
|
||||
return getNextUserTasks(currentElement, bpmnModel, variables, new HashSet<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
private static List<UserTask> getNextUserTasks(FlowElement currentElement, BpmnModel bpmnModel,
|
||||
Map<String, Object> variables,
|
||||
Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
|
||||
// 1. 根据节点类型决定要遍历的出口连线
|
||||
// 网关需要根据条件表达式筛选;其它节点直接取所有 outgoing flows
|
||||
List<SequenceFlow> 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<SequenceFlow> getGatewayOutgoingFlows(Gateway gateway, Map<String, Object> 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<UserTask> getNextUserTasks(FlowElement source) {
|
||||
return getNextUserTasks(source, null, null);
|
||||
return getNextUserTasks(source, new HashSet<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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<String, BpmFormFieldVO> formFieldsMap = new HashMap<>();
|
||||
Map<String, BpmFormFieldVO> 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<String, BpmFormFieldVO> formFieldsMap) {
|
||||
if (formField == null) {
|
||||
private static void parseFormField(JsonNode formFieldNode, Map<String, BpmFormFieldVO> 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);
|
||||
}
|
||||
|
||||
@ -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<FlowNode> nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables);
|
||||
// 仅仅获取 UserTask 节点 TODO add from jason:如果网关节点和网关节点相连,获取下个 UserTask. 貌似有点不准。
|
||||
List<FlowNode> nextUserTaskList = CollectionUtils.filterList(nextFlowNodes, node -> node instanceof UserTask);
|
||||
// 3.2 获取 UserTask 节点
|
||||
List<UserTask> nextUserTaskList = BpmnModelUtils.getNextUserTasks(flowElement, bpmnModel, processVariables);
|
||||
List<ActivityNode> 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<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);
|
||||
// 获取已完成节点的附件
|
||||
Set<String> endTaskIds = convertSet(endTasks, HistoricTaskInstance::getId);
|
||||
List<Attachment> attachments = taskService.getAttachments(historicProcessInstance.getId(), endTaskIds, BpmAttachmentTypeEnum.TASK_ATTACHMENT);
|
||||
Map<String, List<Attachment>> taskAttachmentMap = convertMultiMap(attachments, Attachment::getTaskId);
|
||||
List<ActivityNode> approvalNodes = convertList(endTasks, task -> {
|
||||
FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
|
||||
List<Attachment> 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<HistoricTaskInstance> 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
|
||||
|
||||
@ -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<HistoricTaskInstance> getFinishedTaskListByProcessInstanceIdWithoutCancel(String processInstanceId);
|
||||
|
||||
|
||||
/**
|
||||
* 根据条件获取附件
|
||||
*
|
||||
* @param processInstanceId 流程 id
|
||||
* @param taskIds 任务 id 集合
|
||||
* @param attachmentType 附件类型
|
||||
* @return 附件集合
|
||||
*/
|
||||
List<Attachment> getAttachments(String processInstanceId, Set<String> taskIds, BpmAttachmentTypeEnum attachmentType);
|
||||
|
||||
// ========== Update 写入相关方法 ==========
|
||||
|
||||
/**
|
||||
|
||||
@ -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<Attachment> getAttachments(String processInstanceId, Set<String> taskIds, BpmAttachmentTypeEnum attachmentType) {
|
||||
List<Attachment> 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<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
|
||||
List<UserTask> 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<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables());
|
||||
if (approveUserSelectAssignees != null && CollUtil.isNotEmpty(approveUserSelectAssignees.get(nextFlowNode.getId()))) {
|
||||
continue;
|
||||
}
|
||||
// 如果节点存在,但未配置审批人
|
||||
List<Long> 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<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
|
||||
List<String> 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<String> 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<String> 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));
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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<String, Object> 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<String, Object> 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<Long> userIds = strategy.calculateUsersByActivity(null, activityId, null,
|
||||
|
||||
@ -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<String, Object> processVariables = processVariables();
|
||||
|
||||
// 调用
|
||||
List<KeyValue<String, String>> 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<String, Object> processVariables = processVariables();
|
||||
|
||||
// 调用
|
||||
List<KeyValue<String, String>> 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<String, Object> processVariables = processVariables();
|
||||
|
||||
// 调用
|
||||
List<KeyValue<String, String>> 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<KeyValue<String, String>> 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<String> 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<String> 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<String, Object> processVariables() {
|
||||
Map<String, Object> 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\"}";
|
||||
|
||||
}
|
||||
@ -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<List<CrmBusinessRespVO>> getBusinessListByCustomer(@RequestParam("customerId") Long customerId) {
|
||||
List<CrmBusinessDO> 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<List<CrmBusinessRespVO>> getBusinessListByContact(@RequestParam("contactId") Long contactId) {
|
||||
List<CrmBusinessDO> list = businessService.getBusinessListByContact(contactId);
|
||||
return success(buildBusinessDetailList(list));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得商机分页")
|
||||
@PreAuthorize("@ss.hasPermission('crm:business:query')")
|
||||
|
||||
@ -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<List<CrmContactRespVO>> getContactListByCustomer(@RequestParam("customerId") Long customerId) {
|
||||
List<CrmContactDO> 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')")
|
||||
|
||||
@ -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<CrmFollowUpRecordRespVO> 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));
|
||||
}
|
||||
|
||||
|
||||
@ -65,6 +65,10 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
|
||||
.eq(CrmBusinessDO::getOwnerUserId, ownerUserId));
|
||||
}
|
||||
|
||||
default List<CrmBusinessDO> selectListByCustomerId(Long customerId) {
|
||||
return selectList(CrmBusinessDO::getCustomerId, customerId);
|
||||
}
|
||||
|
||||
default PageResult<CrmBusinessDO> selectPage(CrmStatisticsFunnelReqVO pageVO) {
|
||||
return selectPage(pageVO, new LambdaQueryWrapperX<CrmBusinessDO>()
|
||||
.in(CrmBusinessDO::getOwnerUserId, pageVO.getUserIds())
|
||||
|
||||
@ -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<CrmCustomerLim
|
||||
LambdaQueryWrapperX<CrmCustomerLimitConfigDO> query = new LambdaQueryWrapperX<CrmCustomerLimitConfigDO>()
|
||||
.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);
|
||||
|
||||
@ -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, "联系人不存在");
|
||||
|
||||
@ -195,6 +195,22 @@ public interface CrmBusinessService {
|
||||
*/
|
||||
List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
|
||||
|
||||
/**
|
||||
* 获得商机列表,基于指定客户
|
||||
*
|
||||
* @param customerId 客户编号
|
||||
* @return 商机列表
|
||||
*/
|
||||
List<CrmBusinessDO> getBusinessListByCustomerId(Long customerId);
|
||||
|
||||
/**
|
||||
* 获得商机列表,基于指定联系人
|
||||
*
|
||||
* @param contactId 联系人编号
|
||||
* @return 商机列表
|
||||
*/
|
||||
List<CrmBusinessDO> getBusinessListByContact(Long contactId);
|
||||
|
||||
/**
|
||||
* 获得商机分页,目前用于【数据统计】
|
||||
*
|
||||
|
||||
@ -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<CrmBusinessDO> getBusinessListByCustomerId(Long customerId) {
|
||||
return businessMapper.selectListByCustomerId(customerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.READ)
|
||||
public List<CrmBusinessDO> getBusinessListByContact(Long contactId) {
|
||||
// 1. 查询关联的商机编号
|
||||
List<CrmContactBusinessDO> contactBusinessList = contactBusinessService.getContactBusinessListByContactId(contactId);
|
||||
if (CollUtil.isEmpty(contactBusinessList)) {
|
||||
return ListUtil.empty();
|
||||
}
|
||||
// 2. 查询商机列表
|
||||
return businessMapper.selectByIds(convertSet(contactBusinessList, CrmContactBusinessDO::getBusinessId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<CrmBusinessDO> getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO) {
|
||||
return businessMapper.selectPage(pageVO);
|
||||
|
||||
@ -169,4 +169,12 @@ public interface CrmContactService {
|
||||
*/
|
||||
List<CrmContactDO> getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
|
||||
|
||||
/**
|
||||
* 获得联系人列表,基于指定客户
|
||||
*
|
||||
* @param customerId 客户编号
|
||||
* @return 联系人列表
|
||||
*/
|
||||
List<CrmContactDO> getContactListByCustomerId(Long customerId);
|
||||
|
||||
}
|
||||
|
||||
@ -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<CrmContactDO> getContactListByCustomerId(Long customerId) {
|
||||
return contactMapper.selectListByCustomerId(customerId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<Long> 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<Boolean> 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<Boolean> 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<Boolean> deleteSaleOrder(@RequestParam("ids") List<Long> 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<ErpSaleOrderRespVO> 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<PageResult<ErpSaleOrderRespVO>> getSaleOrderPage(@Valid ErpSaleOrderPageReqVO pageReqVO) {
|
||||
PageResult<ErpSaleOrderDO> 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 {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
85
yudao-module-im/pom.xml
Normal file
85
yudao-module-im/pom.xml
Normal file
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-module-im</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
im 模块,我们放即时通讯业务。
|
||||
例如说:单聊、群聊、消息收发、消息撤回、消息已读等等
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-system</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-infra</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 敏感词检测:sensitive-word(trie 树高效匹配,支持忽略大小写 / 全半角 / 数字风格),版本由 yudao-dependencies 统一管理 -->
|
||||
<dependency>
|
||||
<groupId>com.github.houbb</groupId>
|
||||
<artifactId>sensitive-word</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 汉字转拼音:作为 hutool PinyinUtil 的底层引擎,业务侧调用 StrUtils.toPinyin 时按需引入 -->
|
||||
<dependency>
|
||||
<groupId>com.belerweb</groupId>
|
||||
<artifactId>pinyin4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* @author anhaohao
|
||||
* @since 2024/3/9 下午8:59
|
||||
*/
|
||||
package cn.iocoder.yudao.module.im.api;
|
||||
@ -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<ImChannelMaterialRespVO> getMaterial(@RequestParam("id") Long id) {
|
||||
ImChannelMaterialDO material = channelMaterialService.validateMaterialExists(id);
|
||||
return success(BeanUtils.toBean(material, ImChannelMaterialRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<List<ImConversationReadRespVO>> pullMyConversationRead(
|
||||
@RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime,
|
||||
@RequestParam(value = "lastId", required = false) Long lastId,
|
||||
@RequestParam("limit") @Min(1) @Max(200) Integer limit) {
|
||||
List<ImConversationReadDO> list = conversationReadService.pullConversationReadList(
|
||||
getLoginUserId(), lastUpdateTime, lastId, limit);
|
||||
return success(BeanUtils.toBean(list, ImConversationReadRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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<List<ImFacePackUserRespVO>> getFacePackList() {
|
||||
// 1.1 拉所有启用表情包
|
||||
List<ImFacePackDO> packs = facePackService.getEnabledFacePackList();
|
||||
if (packs.isEmpty()) {
|
||||
return success(ListUtil.of());
|
||||
}
|
||||
// 1.2 拉这些包下所有启用表情,按 packId 分组
|
||||
List<ImFacePackItemDO> items = facePackItemService.getEnabledItemListByPackIds(
|
||||
convertList(packs, ImFacePackDO::getId));
|
||||
Map<Long, List<ImFacePackItemDO>> itemsByPackId = convertMultiMap(items, ImFacePackItemDO::getPackId);
|
||||
|
||||
// 2. 拼装:BeanUtils 把 pack 字段映射 + 自己塞 items
|
||||
List<ImFacePackUserRespVO> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<List<ImFaceUserItemRespVO>> getFaceUserItemList() {
|
||||
List<ImFaceUserItemDO> items = faceUserItemService.getFaceUserItemList(getLoginUserId());
|
||||
return success(BeanUtils.toBean(items, ImFaceUserItemRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "添加个人表情")
|
||||
public CommonResult<Long> 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<Boolean> deleteFaceUserItem(@RequestParam("id") Long id) {
|
||||
faceUserItemService.deleteFaceUserItem(getLoginUserId(), id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<Item> 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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<List<ImFriendRespVO>> getMyFriendList() {
|
||||
List<ImFriendDO> 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<List<ImFriendRespVO>> pullMyFriendList(
|
||||
@RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime,
|
||||
@RequestParam(value = "lastId", required = false) Long lastId,
|
||||
@RequestParam("limit") @Min(1) @Max(200) Integer limit) {
|
||||
List<ImFriendDO> 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<ImFriendRespVO> 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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> unblockFriend(
|
||||
@RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId) {
|
||||
friendService.unblockFriend(getLoginUserId(), friendUserId);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// ========== 私有方法:VO 组装 ==========
|
||||
|
||||
private List<ImFriendRespVO> buildFriendRespVOList(Collection<ImFriendDO> friends) {
|
||||
if (CollUtil.isEmpty(friends)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1
|
||||
Map<Long, AdminUserRespDTO> 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)));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<Long> 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<Boolean> agreeFriendRequest(
|
||||
@RequestParam("id") @NotNull(message = "申请编号不能为空") Long id) {
|
||||
friendRequestService.agreeFriendRequest(getLoginUserId(), id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/refuse")
|
||||
@Operation(summary = "拒绝好友申请")
|
||||
public CommonResult<Boolean> 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<List<ImFriendRequestRespVO>> getMyFriendRequestList(
|
||||
@RequestParam(value = "maxId", required = false) Long maxId,
|
||||
@RequestParam("limit") @Min(1) @Max(200) Integer limit) {
|
||||
List<ImFriendRequestDO> 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<List<ImFriendRequestRespVO>> pullMyFriendRequestList(
|
||||
@RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime,
|
||||
@RequestParam(value = "lastId", required = false) Long lastId,
|
||||
@RequestParam("limit") @Min(1) @Max(200) Integer limit) {
|
||||
List<ImFriendRequestDO> 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<ImFriendRequestRespVO> 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<ImFriendRequestRespVO> buildList(List<ImFriendRequestDO> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 双向 OR 列表,userIds 取 from + to 两组并集
|
||||
Set<Long> userIds = convertSetByFlatMap(list,
|
||||
request -> Stream.of(request.getFromUserId(), request.getToUserId()));
|
||||
Map<Long, AdminUserRespDTO> 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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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<ImGroupRespVO> 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<ImGroupRespVO> 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<Boolean> 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<ImGroupRespVO> getGroup(@RequestParam("id") Long id) {
|
||||
ImGroupDO group = groupService.getGroup(id);
|
||||
return success(buildGroupRespVO(group, getLoginUserId()));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获得当前登录用户的群列表")
|
||||
public CommonResult<List<ImGroupRespVO>> getMyGroupList() {
|
||||
Long loginUserId = getLoginUserId();
|
||||
List<ImGroupDO> groups = groupService.getMyGroupList(loginUserId);
|
||||
return success(buildGroupRespVOList(groups, loginUserId));
|
||||
}
|
||||
|
||||
// ==================== 群成员的写操作 ====================
|
||||
|
||||
@PostMapping("/invite")
|
||||
@Operation(summary = "邀请用户加入群")
|
||||
public CommonResult<Boolean> inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) {
|
||||
groupService.inviteGroupMember(getLoginUserId(), inviteReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/quit")
|
||||
@Operation(summary = "退出群")
|
||||
@Parameter(name = "groupId", description = "群编号", required = true)
|
||||
public CommonResult<Boolean> quitGroup(@RequestParam("groupId") Long groupId) {
|
||||
groupService.quitGroup(groupId, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/kicking")
|
||||
@Operation(summary = "移除群成员")
|
||||
public CommonResult<Boolean> removeGroupMember(@Valid @RequestBody ImGroupMemberRemoveReqVO removeReqVO) {
|
||||
groupService.removeGroupMember(getLoginUserId(), removeReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/add-admin")
|
||||
@Operation(summary = "添加群管理员")
|
||||
public CommonResult<Boolean> addGroupAdmin(@Valid @RequestBody ImGroupAdminAddReqVO reqVO) {
|
||||
groupService.addGroupAdmin(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/remove-admin")
|
||||
@Operation(summary = "撤销群管理员")
|
||||
public CommonResult<Boolean> removeGroupAdmin(@Valid @RequestBody ImGroupAdminRemoveReqVO reqVO) {
|
||||
groupService.removeGroupAdmin(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/transfer-owner")
|
||||
@Operation(summary = "转让群主")
|
||||
public CommonResult<Boolean> transferGroupOwner(@Valid @RequestBody ImGroupTransferOwnerReqVO transferReqVO) {
|
||||
groupService.transferGroupOwner(getLoginUserId(), transferReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// ==================== 群消息置顶 ====================
|
||||
|
||||
@PutMapping("/pin-message")
|
||||
@Operation(summary = "置顶群消息(群主 / 管理员)")
|
||||
public CommonResult<Boolean> pinGroupMessage(@Valid @RequestBody ImGroupMessagePinReqVO reqVO) {
|
||||
groupService.pinGroupMessage(getLoginUserId(), reqVO.getId(), reqVO.getMessageId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/unpin-message")
|
||||
@Operation(summary = "取消置顶群消息(群主 / 管理员)")
|
||||
public CommonResult<Boolean> unpinGroupMessage(@Valid @RequestBody ImGroupMessagePinReqVO reqVO) {
|
||||
groupService.unpinGroupMessage(getLoginUserId(), reqVO.getId(), reqVO.getMessageId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// ==================== 群禁言 ====================
|
||||
|
||||
@PutMapping("/mute-all")
|
||||
@Operation(summary = "全群禁言 / 取消(群主 / 管理员)")
|
||||
public CommonResult<Boolean> muteAll(@Valid @RequestBody ImGroupMuteAllReqVO reqVO) {
|
||||
groupService.muteAll(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/mute-member")
|
||||
@Operation(summary = "禁言成员")
|
||||
public CommonResult<Boolean> muteMember(@Valid @RequestBody ImGroupMuteMemberReqVO reqVO) {
|
||||
groupService.muteMember(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/cancel-mute-member")
|
||||
@Operation(summary = "取消成员禁言")
|
||||
public CommonResult<Boolean> 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
|
||||
* <p>
|
||||
* 仅当登录用户是某群的有效成员时才回填该群的 pinnedMessages,避免非成员 / 已退群用户越权拿到置顶消息内容
|
||||
*/
|
||||
private List<ImGroupRespVO> buildGroupRespVOList(List<ImGroupDO> groups, Long loginUserId) {
|
||||
if (CollUtil.isEmpty(groups)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 仅当前用户是有效成员的群才允许回填置顶消息
|
||||
Map<Long, ImGroupMemberDO> memberMap = convertMap(
|
||||
groupMemberService.getGroupMemberListByUserId(loginUserId), ImGroupMemberDO::getGroupId);
|
||||
Set<Long> activeGroupIds = convertSet(memberMap.values(), ImGroupMemberDO::getGroupId,
|
||||
member -> CommonStatusEnum.ENABLE.getStatus().equals(member.getStatus()));
|
||||
Set<Long> allMessageIds = convertSetByFlatMap(groups, group -> activeGroupIds.contains(group.getId())
|
||||
? CollUtil.emptyIfNull(group.getPinnedMessageIds()).stream() : Stream.empty());
|
||||
Map<Long, ImGroupMessageDO> 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<ImGroupMessageDO> pinnedMesages = convertList(group.getPinnedMessageIds(), messageMap::get);
|
||||
return groupVO.setPinnedMessages(BeanUtils.toBean(pinnedMesages, ImGroupMessageRespVO.class));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<Boolean> 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<ImGroupMemberRespVO> 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<List<ImGroupMemberRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) {
|
||||
// 1.1 查询群成员列表(包含 DISABLE 已退群的成员,不按时间过滤)
|
||||
// 说明:保留已退群成员,是为了前端展示历史消息时,仍能通过该接口拿到已退群成员的昵称 / 头像信息,避免显示为空
|
||||
List<ImGroupMemberDO> 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<ImGroupMemberRespVO> buildGroupMemberRespVOList(List<ImGroupMemberDO> members) {
|
||||
if (CollUtil.isEmpty(members)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Long loginUserId = getLoginUserId();
|
||||
// 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1
|
||||
Map<Long, AdminUserRespDTO> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<Long> 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<Boolean> agreeGroupRequest(
|
||||
@RequestParam("id") @NotNull(message = "申请编号不能为空") Long id) {
|
||||
groupRequestService.agreeGroupRequest(getLoginUserId(), id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/refuse")
|
||||
@Operation(summary = "拒绝加群申请(群主或管理员)")
|
||||
public CommonResult<Boolean> 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<List<ImGroupRequestRespVO>> getUnhandledRequestList() {
|
||||
List<ImGroupRequestDO> 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<List<ImGroupRequestRespVO>> pullMyGroupRequestList(
|
||||
@RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime,
|
||||
@RequestParam(value = "lastId", required = false) Long lastId,
|
||||
@RequestParam("limit") @Min(1) @Max(200) Integer limit) {
|
||||
List<ImGroupRequestDO> 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<List<ImGroupRequestRespVO>> getGroupRequestListByGroupId(
|
||||
@RequestParam("groupId") @NotNull(message = "群编号不能为空") Long groupId) {
|
||||
List<ImGroupRequestDO> list = groupRequestService.getGroupRequestListByGroupId(getLoginUserId(), groupId);
|
||||
return success(buildVOList(list));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "按 id 单查申请记录(带越权过滤;WebSocket 通知到达后用)")
|
||||
@Parameter(name = "id", description = "申请记录编号", required = true)
|
||||
public CommonResult<ImGroupRequestRespVO> 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<ImGroupRequestRespVO> buildVOList(List<ImGroupRequestDO> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 1. 聚合 user / inviter 用户信息;convertSetByFlatMap 内部已过滤 null
|
||||
Set<Long> userIds = convertSetByFlatMap(list,
|
||||
request -> Stream.of(request.getUserId(), request.getInviterUserId()));
|
||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
|
||||
// 2. 聚合群信息(封禁 / 解散群也要回填,便于前端展示历史)
|
||||
Set<Long> groupIds = convertSet(list, ImGroupRequestDO::getGroupId);
|
||||
Map<Long, ImGroupDO> 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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<Long> userIds;
|
||||
|
||||
}
|
||||
@ -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<Long> userIds;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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<Long> memberUserIds;
|
||||
|
||||
@Schema(description = "进群是否需群主 / 管理员审批;不传默认 false 自由进群", example = "false")
|
||||
private Boolean joinApproval;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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<ImGroupMessageRespVO> pinnedMessages;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user