feat: add file safety config integration

This commit is contained in:
richjohnrambo
2026-08-23 23:50:21 +08:00
parent 7b942da975
commit ed9413d923
20 changed files with 1407 additions and 2 deletions
@@ -0,0 +1,161 @@
# IM 文件安全第三方接入记录
更新时间:2026-08-23
## 1. 接入范围
本次记录三条文件与安全链路:
1. 阿里云 OSS:用于文件对象存储和签名下载,media handler 标识为 `oss`。
2. 阿里云内容安全增强版:用于文件内容审核,当前 provider 标识为 `aliyun_green`,任务类型为 `content`。
3. 阿里云云安全中心恶意文件检测:用于病毒、木马、WebShell、勒索等恶意文件检测,当前 provider 标识为 `aliyun_sas`,任务类型为 `malware`。
密钥、账号、Bucket 等敏感信息不写入本文档,也不写入源码。运行环境应通过配置中心或部署环境注入。
## 2. 阿里云 OSS 接入
新增媒体 handler:`server/media/oss`。
当前支持:
- `Upload`:上传文件到 OSS Bucket,并把 OSS object key 写入 `FileDef.Location`。
- `Headers`:对 GET/HEAD 生成 OSS 签名 URL,并返回 `308 Permanent Redirect`。
- `Delete`:按 object key 批量删除 OSS 对象。
- `GetIdFromUrl`:从 Tinode 文件 URL 中解析文件 ID。
对象路径规则:
```text
tenants/{tenantID}/{yyyy}/{mm}/{fileID}
```
配置字段:
| 字段 | 说明 |
| --- | --- |
| `access_key_id` | 阿里云 AccessKey ID,部署环境注入 |
| `access_key_secret` | 阿里云 AccessKey Secret,部署环境注入 |
| `endpoint` | OSS 普通 Bucket endpoint |
| `bucket` | OSS Bucket 名称 |
| `region_id` | OSS Bucket 所属地域 |
| `serve_url` | Tinode 对外文件 URL 前缀,默认 `/v0/file/s/` |
| `presign_ttl` | 签名 URL 有效秒数,默认 `120` |
| `cache_control` | 下载缓存头 |
| `cors_origins` | CORS 允许来源 |
| `verify_bucket` | 启动时是否联网校验 Bucket 存在 |
| `use_sys_config` | 为 `true` 时从 `hotgo.hg_sys_config` 读取 OSS 配置 |
当前 `server/tinode.conf` 仍默认使用 `fs`。切换 OSS 时,将 `media.use_handler` 改为 `oss`,并在 `media.handlers.oss` 中设置 `use_sys_config=true` 后,运行时会从后台库 `hotgo.hg_sys_config` 的 `im_third_party` 分组读取 OSS 配置。
代码入口:
- Handler 实现:`server/media/oss/oss.go`
- 后台配置读取:`store.ThirdPartyConfig.Get("im_third_party", ...)`
- Handler 注册:`server/main.go`
- 单元测试:`server/media/oss/oss_tenant_test.go`
注意事项:
- 不使用 AccessPoint endpoint,当前按普通 Bucket endpoint 接入。
- 不在源码或文档中保存真实 AK/SK。
- 下载链路采用签名 URL 重定向,不由 Tinode 代理大文件内容。
## 3. 数据库状态机
新增租户业务库表:`im_file_review_tasks`。
独立租户库方案下,该表不包含 `tenant_id`。每个租户库保存本租户自己的文件审核任务。
核心字段:
| 字段 | 说明 |
| --- | --- |
| `task_id` | 服务端生成的审核任务 ID |
| `file_id` | 对应 `im_fileuploads.id` |
| `user_id` | 上传用户 |
| `task_type` | `content` 或 `malware` |
| `provider` | `aliyun_green` 或 `aliyun_sas` |
| `object_name` | OSS 对象名快照 |
| `mime_type` | 文件 MIME 快照 |
| `file_size` | 文件大小快照 |
| `provider_task_id` | 第三方返回的任务 ID |
| `status` | `pending`、`running`、`clean`、`risky`、`failed` |
| `risk_level` | 第三方风险等级 |
| `risk_labels` | 命中的风险标签 JSON |
| `result_json` | 第三方原始结果 JSON |
| `attempts` / `next_retry_at` | 重试控制 |
| `completed_at` | 审核完成时间 |
唯一约束:
- `task_id` 全局唯一。
- `file_id + task_type + provider` 唯一,避免同一文件重复创建同类任务。
## 4. 当前代码入口
文件上传完成后,在数据库事务内自动创建审核任务:
- 普通/独立库路径:`server/db/mysql/adapter.go` 的 `FileFinishUpload`
- 共享库兼容路径:`server/db/mysql/tenant_adapter.go` 的 `TenantFileFinishUpload`
- 建表逻辑:`server/db/mysql/file_review_schema.go`
- 任务插入:`server/db/mysql/file_review_tasks.go`
- 状态常量和结构:`server/store/types/safety.go`
当前策略是异步审核:上传成功后先生成 `pending` 任务,不阻塞上传响应。
## 5. 阿里云内容安全接入
新增封装包:`server/filesafety/aliyun`。
当前支持:
- `SubmitOSSFile`:提交同账号 OSS 文件审核任务。
- `PollOSSFile`:轮询文件审核结果。
- 默认服务:`document_detection`。
- 支持参数:`ossBucketName`、`ossObjectName`、`ossRegionId`、`dataId`。
文件安全配置通过 `server/filesafety.LoadConfig` 从后台库 `hotgo.hg_sys_config` 的 `im_third_party` 分组读取。内容安全启用后,`server/filesafety.NewGreenClientFromConfig` 可直接构建阿里云内容安全客户端;恶意文件检测当前先落配置和任务状态,具体云安全中心 API worker 后续补充。
结果映射:
| 阿里云风险等级 | 内部状态 |
| --- | --- |
| 空 | `running` |
| `none` / `low` | `clean` |
| 其他风险等级 | `risky` |
后续 worker 需要读取 `provider=aliyun_green,status=pending/running` 的任务,调用提交和轮询接口,并回写 `provider_task_id/status/risk_level/risk_labels/result_json`。
## 6. 阿里云恶意文件检测接入
当前已完成状态机和任务落库,上传成功会创建:
- `task_type=malware`
- `provider=aliyun_sas`
- `status=pending`
后续还需要补云安全中心恶意文件检测 SDK/HTTP client,并实现 worker 提交、轮询和结果映射。
建议映射:
| 三方结果 | 内部状态 |
| --- | --- |
| 未完成 / 排队中 | `running` |
| 无风险 | `clean` |
| 命中恶意类型 | `risky` |
| 调用失败且可重试 | `pending`,更新 `attempts/next_retry_at` |
| 调用失败且不可重试 | `failed` |
## 7. 本地库变更
已在本地 `tinode` 业务库创建并更新 `im_file_review_tasks` 表。
后续新库或已有库启动时,会通过 MySQL adapter 的 ensure 流程自动补表。
## 8. 后续待办
1. 实现文件安全 worker:批量领取 `pending` 任务、提交第三方、轮询结果、回写状态。
2. 接入阿里云云安全中心恶意文件检测 client。
3. 增加审核策略:是否阻断下载、是否只告警、是否对高风险文件自动下架。
4. 在文件下载授权处读取审核结果,按策略限制 `risky/failed/running` 文件访问。
5. 增加后台查询与人工处置接口,用于查看任务、重试、放行、下架。
@@ -0,0 +1,211 @@
-- IM 第三方配置字典与文件安全表
-- 更新时间:2026-08-23
--
-- 执行范围:
-- 1. hotgo 控制库:后台字典项与第三方配置项。
-- 2. tinode 租户业务库:文件安全审核任务表。
--
-- 注意:
-- - 本脚本不写入真实 AccessKey、Secret、密码等敏感值。
-- - 独立租户库方案下,业务表不包含 tenant_id。
-- - 多租户时,im_file_review_tasks 需要在每个租户业务库执行。
USE hotgo;
-- 1. 后台字典类型
INSERT INTO hg_sys_dict_type
(`pid`, `name`, `type`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT 0, 'IM 存储服务商', 'im_storage_provider', 210, 'IM 文件存储 provider 字典', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_type WHERE `type` = 'im_storage_provider');
INSERT INTO hg_sys_dict_type
(`pid`, `name`, `type`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT 0, 'IM 文件审核服务商', 'im_file_review_provider', 220, 'IM 文件审核 provider 字典', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_type WHERE `type` = 'im_file_review_provider');
INSERT INTO hg_sys_dict_type
(`pid`, `name`, `type`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT 0, 'IM 文件审核任务类型', 'im_file_review_task_type', 230, 'IM 文件审核任务类型字典', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_type WHERE `type` = 'im_file_review_task_type');
INSERT INTO hg_sys_dict_type
(`pid`, `name`, `type`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT 0, 'IM 文件审核状态', 'im_file_review_status', 240, 'IM 文件审核任务状态字典', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_type WHERE `type` = 'im_file_review_status');
-- 2. 后台字典数据
INSERT INTO hg_sys_dict_data
(`label`, `value`, `value_type`, `type`, `list_class`, `is_default`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT '阿里云 OSS', 'oss', 'string', 'im_storage_provider', 'success', 1, 10, '阿里云对象存储 OSS', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_data WHERE `type` = 'im_storage_provider' AND `value` = 'oss');
INSERT INTO hg_sys_dict_data
(`label`, `value`, `value_type`, `type`, `list_class`, `is_default`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT '阿里云内容安全增强版', 'aliyun_green', 'string', 'im_file_review_provider', 'info', 1, 10, 'FileModeration + document_detection', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_data WHERE `type` = 'im_file_review_provider' AND `value` = 'aliyun_green');
INSERT INTO hg_sys_dict_data
(`label`, `value`, `value_type`, `type`, `list_class`, `is_default`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT '阿里云云安全中心', 'aliyun_sas', 'string', 'im_file_review_provider', 'warning', 1, 20, '恶意文件检测', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_data WHERE `type` = 'im_file_review_provider' AND `value` = 'aliyun_sas');
INSERT INTO hg_sys_dict_data
(`label`, `value`, `value_type`, `type`, `list_class`, `is_default`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT '内容审核', 'content', 'string', 'im_file_review_task_type', 'info', 1, 10, '文件内容审核任务', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_data WHERE `type` = 'im_file_review_task_type' AND `value` = 'content');
INSERT INTO hg_sys_dict_data
(`label`, `value`, `value_type`, `type`, `list_class`, `is_default`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT '恶意文件检测', 'malware', 'string', 'im_file_review_task_type', 'warning', 1, 20, '病毒、木马、WebShell 等恶意文件检测任务', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_data WHERE `type` = 'im_file_review_task_type' AND `value` = 'malware');
INSERT INTO hg_sys_dict_data
(`label`, `value`, `value_type`, `type`, `list_class`, `is_default`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT '待处理', 'pending', 'string', 'im_file_review_status', 'default', 1, 10, '等待 worker 提交或重试', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_data WHERE `type` = 'im_file_review_status' AND `value` = 'pending');
INSERT INTO hg_sys_dict_data
(`label`, `value`, `value_type`, `type`, `list_class`, `is_default`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT '处理中', 'running', 'string', 'im_file_review_status', 'info', 1, 20, '第三方审核任务处理中', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_data WHERE `type` = 'im_file_review_status' AND `value` = 'running');
INSERT INTO hg_sys_dict_data
(`label`, `value`, `value_type`, `type`, `list_class`, `is_default`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT '无风险', 'clean', 'string', 'im_file_review_status', 'success', 1, 30, '审核通过或无风险', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_data WHERE `type` = 'im_file_review_status' AND `value` = 'clean');
INSERT INTO hg_sys_dict_data
(`label`, `value`, `value_type`, `type`, `list_class`, `is_default`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT '有风险', 'risky', 'string', 'im_file_review_status', 'error', 1, 40, '命中风险标签或恶意文件', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_data WHERE `type` = 'im_file_review_status' AND `value` = 'risky');
INSERT INTO hg_sys_dict_data
(`label`, `value`, `value_type`, `type`, `list_class`, `is_default`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT '失败', 'failed', 'string', 'im_file_review_status', 'warning', 1, 50, '不可重试失败或超过重试次数', 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_dict_data WHERE `type` = 'im_file_review_status' AND `value` = 'failed');
-- 3. 第三方配置项。配置真值存 hg_sys_config;字典只提供 UI 选项。
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', 'OSS 启用状态', 'bool', 'oss_enabled', '0', '0', 10, '是否启用阿里云 OSS 作为 IM 文件存储', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'oss_enabled');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', 'OSS 服务商', 'string', 'oss_provider', 'oss', 'oss', 11, '取值来自字典 im_storage_provider', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'oss_provider');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', 'OSS Endpoint', 'string', 'oss_endpoint', '', '', 12, '例如 oss-cn-shenzhen.aliyuncs.com,禁止写 AccessPoint endpoint', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'oss_endpoint');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', 'OSS Bucket', 'string', 'oss_bucket', '', '', 13, 'IM 文件对象存储 Bucket', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'oss_bucket');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', 'OSS Region', 'string', 'oss_region_id', 'cn-shenzhen', 'cn-shenzhen', 14, 'OSS Bucket 所属地域', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'oss_region_id');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', 'OSS AccessKey ID', 'string', 'oss_access_key_id', '', '', 15, '敏感配置,建议由部署环境或密钥系统注入', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'oss_access_key_id');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', 'OSS AccessKey Secret', 'string', 'oss_access_key_secret', '', '', 16, '敏感配置,后台和日志必须脱敏展示', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'oss_access_key_secret');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', 'OSS 签名 URL TTL', 'int', 'oss_presign_ttl', '120', '120', 17, '签名下载 URL 有效秒数', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'oss_presign_ttl');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', '文件内容安全启用状态', 'bool', 'file_review_content_enabled', '0', '0', 30, '是否启用阿里云内容安全增强版文件审核', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'file_review_content_enabled');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', '内容安全 Provider', 'string', 'file_review_content_provider', 'aliyun_green', 'aliyun_green', 31, '取值来自字典 im_file_review_provider', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'file_review_content_provider');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', '内容安全 Region', 'string', 'aliyun_green_region_id', 'cn-shanghai', 'cn-shanghai', 32, '阿里云内容安全增强版地域', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'aliyun_green_region_id');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', '内容安全 Endpoint', 'string', 'aliyun_green_endpoint', 'green-cip.cn-shanghai.aliyuncs.com', 'green-cip.cn-shanghai.aliyuncs.com', 33, '阿里云内容安全增强版 endpoint', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'aliyun_green_endpoint');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', '内容安全服务', 'string', 'aliyun_green_document_service', 'document_detection', 'document_detection', 34, 'FileModeration 服务名', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'aliyun_green_document_service');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', '恶意文件检测启用状态', 'bool', 'file_review_malware_enabled', '0', '0', 40, '是否启用阿里云云安全中心恶意文件检测', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'file_review_malware_enabled');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', '恶意文件检测 Provider', 'string', 'file_review_malware_provider', 'aliyun_sas', 'aliyun_sas', 41, '取值来自字典 im_file_review_provider', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'file_review_malware_provider');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', '恶意文件检测 Region', 'string', 'aliyun_sas_region_id', 'cn-shanghai', 'cn-shanghai', 42, '阿里云云安全中心恶意文件检测地域', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'aliyun_sas_region_id');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', '阿里云 AccessKey ID', 'string', 'aliyun_access_key_id', '', '', 90, '内容安全和恶意文件检测共用的敏感配置,建议由部署环境或密钥系统注入', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'aliyun_access_key_id');
INSERT INTO hg_sys_config
(`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`)
SELECT 'im_third_party', '阿里云 AccessKey Secret', 'string', 'aliyun_access_key_secret', '', '', 91, '内容安全和恶意文件检测共用的敏感配置,后台和日志必须脱敏展示', 1, 1, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM hg_sys_config WHERE `group` = 'im_third_party' AND `key` = 'aliyun_access_key_secret');
USE tinode;
-- 4. 文件安全审核任务表
CREATE TABLE IF NOT EXISTS im_file_review_tasks (
id BIGINT NOT NULL,
task_id VARCHAR(64) NOT NULL,
file_id BIGINT NOT NULL,
user_id BIGINT NOT NULL DEFAULT 0,
task_type VARCHAR(32) NOT NULL,
provider VARCHAR(64) NOT NULL,
storage_provider VARCHAR(32) NOT NULL DEFAULT '',
object_name VARCHAR(2048) NOT NULL DEFAULT '',
mime_type VARCHAR(255) NOT NULL DEFAULT '',
file_size BIGINT NOT NULL DEFAULT 0,
provider_task_id VARCHAR(128) NOT NULL DEFAULT '',
status VARCHAR(16) NOT NULL DEFAULT 'pending',
risk_level VARCHAR(32) NOT NULL DEFAULT '',
risk_labels JSON,
result_json JSON,
error_code VARCHAR(64) NOT NULL DEFAULT '',
error_message VARCHAR(512) NOT NULL DEFAULT '',
attempts INT NOT NULL DEFAULT 0,
next_retry_at DATETIME(3),
completed_at DATETIME(3),
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY(id),
UNIQUE INDEX uk_im_file_review_tasks_task(task_id),
UNIQUE INDEX uk_im_file_review_tasks_file_provider(file_id, task_type, provider),
INDEX idx_im_file_review_tasks_pending(status, next_retry_at, id),
INDEX idx_im_file_review_tasks_file(file_id),
INDEX idx_im_file_review_tasks_object(storage_provider, object_name(191)),
INDEX idx_im_file_review_tasks_provider_task(provider, provider_task_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;