koishi-plugin-kkk 3.9.0 → 3.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/lib/build-metadata.json +5 -5
- package/lib/karin/module/db/model.js +91 -83
- package/package.json +1 -1
- package/scripts/smoke-db-koishi.cjs +43 -0
- package/src/karin/module/db/model.ts +101 -83
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## 3.9.1
|
|
4
|
+
|
|
5
|
+
> 修 MySQL 上启动直接报 `ER_TOO_LONG_KEY`、插件起不来的问题。
|
|
6
|
+
> 3.9.0 建的索引太宽了:索引字段现在按实际数据长度定长,最宽的索引从 4080 字节降到 1152 字节。
|
|
7
|
+
|
|
8
|
+
### 修复 MySQL 索引过长(ER_TOO_LONG_KEY)
|
|
9
|
+
|
|
10
|
+
- **现象**:MySQL 环境启动就报 `ER_TOO_LONG_KEY`(Specified key was too long),索引超过 3072 字节,表建不出来。
|
|
11
|
+
- **原因**:字符串字段没给长度,MySQL 按 `VARCHAR(255)` 建,`utf8mb4` 下每字符 4 字节,
|
|
12
|
+
单个字段就是 1020 字节。抖音作品缓存表那个由 **四个** 字段组成的唯一键
|
|
13
|
+
(作品 id + sec_uid + 群号 + 推送类型)算下来 4080 字节,直接超了上限。
|
|
14
|
+
另有几张统计表卡在 3060 字节 —— 离上限只差 12 字节,随时会爆。
|
|
15
|
+
- **处理**:凡是进索引(主键 / 唯一键)的字段,都按实际数据长度定长:
|
|
16
|
+
|
|
17
|
+
| 字段 | 长度 | 说明 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| 群号 / QQ 号 / botId / 作品 id / 动态 id | 64 | 最多几十个字符 |
|
|
20
|
+
| 抖音 `sec_uid` | 128 | 实际最长见过 76 |
|
|
21
|
+
| 平台 / 内容形态 / 日期 / 指标名 | 16–32 | 固定枚举、`YYYY-MM-DD` |
|
|
22
|
+
| 过滤词 / 过滤标签 | 128 | 自定义文本 |
|
|
23
|
+
| 备注、昵称等 | 不限制 | 不进索引,不影响 |
|
|
24
|
+
|
|
25
|
+
现在最宽的索引是 1152 字节(原来是 4080),其它都在 1024 以内。
|
|
26
|
+
- **在 MySQL 上已经建出过半截 `kkk` 表的**,需要先删掉再启动 —— 已有索引不一定能被自动改过来:
|
|
27
|
+
|
|
28
|
+
```sql
|
|
29
|
+
SELECT TABLE_NAME FROM information_schema.TABLES
|
|
30
|
+
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE 'kkk%';
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
确认是插件的表之后 `DROP` 掉,重启 Koishi 会按新定义重建。
|
|
34
|
+
- sqlite / postgres 不受影响,也不用做上面这一步(字段长度本来就够)。
|
|
35
|
+
|
|
3
36
|
## 3.9.0
|
|
4
37
|
|
|
5
38
|
> 插件不再自己建数据库了,全部改用 Koishi 自带的数据库服务。
|
package/lib/build-metadata.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.9.
|
|
3
|
-
"buildTime": "2026-09-
|
|
4
|
-
"buildTimestamp":
|
|
2
|
+
"version": "3.9.1",
|
|
3
|
+
"buildTime": "2026-09-29T10:19:42.069Z",
|
|
4
|
+
"buildTimestamp": 1790677182093,
|
|
5
5
|
"name": "koishi-plugin-kkk",
|
|
6
6
|
"description": "抖音 / B站 / 快手 / 小红书 视频解析与动态推送,带 QQ 交互面板与弹幕烧录(karin-plugin-kkk 的 Koishi 移植版)",
|
|
7
7
|
"homepage": "https://github.com/maimai993/koishi-plugin-kkk#readme",
|
|
8
|
-
"commitHash": "
|
|
9
|
-
"shortCommitHash": "
|
|
8
|
+
"commitHash": "0dde6fc426986698fb0e937768eb4aa86d24f2d4",
|
|
9
|
+
"shortCommitHash": "0dde6fc"
|
|
10
10
|
}
|
|
@@ -27,6 +27,14 @@ exports.TABLE = {
|
|
|
27
27
|
statsMetric: 'kkk.statistics.metric',
|
|
28
28
|
};
|
|
29
29
|
/* ------------------------------ 建表 ------------------------------ */
|
|
30
|
+
/**
|
|
31
|
+
* 定长字符串字段。
|
|
32
|
+
*
|
|
33
|
+
* 走索引的字段必须给长度(见文件头「为什么索引字段都写了明确长度」),
|
|
34
|
+
* 这里的数字都是按实际数据留的余量:群号 / QQ 号 / 作品 id 这类最多几十个字符,
|
|
35
|
+
* 抖音 sec_uid 最长见过 76,给 128。
|
|
36
|
+
*/
|
|
37
|
+
const str = (length) => ({ type: 'string', length });
|
|
30
38
|
/** 进程内只建一次(插件热重载时 apply 会再跑一遍,重复 extend 没意义) */
|
|
31
39
|
let modelsReady = false;
|
|
32
40
|
/**
|
|
@@ -39,162 +47,162 @@ const extendModels = (ctx) => {
|
|
|
39
47
|
modelsReady = true;
|
|
40
48
|
// ---------- 抖音 ----------
|
|
41
49
|
ctx.model.extend(exports.TABLE.douyinBot, {
|
|
42
|
-
id:
|
|
43
|
-
createdAt:
|
|
44
|
-
updatedAt:
|
|
50
|
+
id: str(64),
|
|
51
|
+
createdAt: str(32),
|
|
52
|
+
updatedAt: str(32),
|
|
45
53
|
}, { primary: 'id' });
|
|
46
54
|
ctx.model.extend(exports.TABLE.douyinGroup, {
|
|
47
|
-
id:
|
|
48
|
-
botId:
|
|
49
|
-
createdAt:
|
|
50
|
-
updatedAt:
|
|
55
|
+
id: str(64),
|
|
56
|
+
botId: str(64),
|
|
57
|
+
createdAt: str(32),
|
|
58
|
+
updatedAt: str(32),
|
|
51
59
|
}, { primary: ['id', 'botId'] });
|
|
52
60
|
ctx.model.extend(exports.TABLE.douyinUser, {
|
|
53
|
-
sec_uid:
|
|
54
|
-
short_id:
|
|
61
|
+
sec_uid: str(128),
|
|
62
|
+
short_id: str(64),
|
|
55
63
|
remark: 'string',
|
|
56
64
|
living: 'boolean',
|
|
57
|
-
filterMode:
|
|
58
|
-
createdAt:
|
|
59
|
-
updatedAt:
|
|
65
|
+
filterMode: str(16),
|
|
66
|
+
createdAt: str(32),
|
|
67
|
+
updatedAt: str(32),
|
|
60
68
|
}, { primary: 'sec_uid' });
|
|
61
69
|
ctx.model.extend(exports.TABLE.douyinSubscription, {
|
|
62
|
-
groupId:
|
|
63
|
-
sec_uid:
|
|
64
|
-
createdAt:
|
|
65
|
-
updatedAt:
|
|
70
|
+
groupId: str(64),
|
|
71
|
+
sec_uid: str(128),
|
|
72
|
+
createdAt: str(32),
|
|
73
|
+
updatedAt: str(32),
|
|
66
74
|
}, { primary: ['groupId', 'sec_uid'] });
|
|
67
75
|
ctx.model.extend(exports.TABLE.douyinAwemeCache, {
|
|
68
76
|
id: 'unsigned',
|
|
69
|
-
aweme_id:
|
|
70
|
-
sec_uid:
|
|
71
|
-
groupId:
|
|
72
|
-
pushType:
|
|
73
|
-
createdAt:
|
|
74
|
-
updatedAt:
|
|
77
|
+
aweme_id: str(64),
|
|
78
|
+
sec_uid: str(128),
|
|
79
|
+
groupId: str(64),
|
|
80
|
+
pushType: str(32),
|
|
81
|
+
createdAt: str(32),
|
|
82
|
+
updatedAt: str(32),
|
|
75
83
|
}, { autoInc: true, unique: [['aweme_id', 'sec_uid', 'groupId', 'pushType']] });
|
|
76
84
|
ctx.model.extend(exports.TABLE.douyinFilterWord, {
|
|
77
85
|
id: 'unsigned',
|
|
78
|
-
sec_uid:
|
|
79
|
-
word:
|
|
80
|
-
createdAt:
|
|
81
|
-
updatedAt:
|
|
86
|
+
sec_uid: str(128),
|
|
87
|
+
word: str(128),
|
|
88
|
+
createdAt: str(32),
|
|
89
|
+
updatedAt: str(32),
|
|
82
90
|
}, { autoInc: true, unique: [['sec_uid', 'word']] });
|
|
83
91
|
ctx.model.extend(exports.TABLE.douyinFilterTag, {
|
|
84
92
|
id: 'unsigned',
|
|
85
|
-
sec_uid:
|
|
86
|
-
tag:
|
|
87
|
-
createdAt:
|
|
88
|
-
updatedAt:
|
|
93
|
+
sec_uid: str(128),
|
|
94
|
+
tag: str(128),
|
|
95
|
+
createdAt: str(32),
|
|
96
|
+
updatedAt: str(32),
|
|
89
97
|
}, { autoInc: true, unique: [['sec_uid', 'tag']] });
|
|
90
98
|
ctx.model.extend(exports.TABLE.douyinListSnapshot, {
|
|
91
99
|
id: 'unsigned',
|
|
92
|
-
sec_uid:
|
|
93
|
-
pushType:
|
|
94
|
-
aweme_id:
|
|
95
|
-
createdAt:
|
|
96
|
-
updatedAt:
|
|
100
|
+
sec_uid: str(128),
|
|
101
|
+
pushType: str(32),
|
|
102
|
+
aweme_id: str(64),
|
|
103
|
+
createdAt: str(32),
|
|
104
|
+
updatedAt: str(32),
|
|
97
105
|
}, { autoInc: true, unique: [['sec_uid', 'pushType', 'aweme_id']] });
|
|
98
106
|
// ---------- B站 ----------
|
|
99
107
|
ctx.model.extend(exports.TABLE.bilibiliBot, {
|
|
100
|
-
id:
|
|
101
|
-
createdAt:
|
|
102
|
-
updatedAt:
|
|
108
|
+
id: str(64),
|
|
109
|
+
createdAt: str(32),
|
|
110
|
+
updatedAt: str(32),
|
|
103
111
|
}, { primary: 'id' });
|
|
104
112
|
ctx.model.extend(exports.TABLE.bilibiliGroup, {
|
|
105
|
-
id:
|
|
106
|
-
botId:
|
|
107
|
-
createdAt:
|
|
108
|
-
updatedAt:
|
|
113
|
+
id: str(64),
|
|
114
|
+
botId: str(64),
|
|
115
|
+
createdAt: str(32),
|
|
116
|
+
updatedAt: str(32),
|
|
109
117
|
}, { primary: ['id', 'botId'] });
|
|
110
118
|
ctx.model.extend(exports.TABLE.bilibiliUser, {
|
|
111
119
|
host_mid: 'unsigned',
|
|
112
120
|
remark: 'string',
|
|
113
|
-
filterMode:
|
|
114
|
-
createdAt:
|
|
115
|
-
updatedAt:
|
|
121
|
+
filterMode: str(16),
|
|
122
|
+
createdAt: str(32),
|
|
123
|
+
updatedAt: str(32),
|
|
116
124
|
}, { primary: 'host_mid' });
|
|
117
125
|
ctx.model.extend(exports.TABLE.bilibiliSubscription, {
|
|
118
|
-
groupId:
|
|
126
|
+
groupId: str(64),
|
|
119
127
|
host_mid: 'unsigned',
|
|
120
|
-
createdAt:
|
|
121
|
-
updatedAt:
|
|
128
|
+
createdAt: str(32),
|
|
129
|
+
updatedAt: str(32),
|
|
122
130
|
}, { primary: ['groupId', 'host_mid'] });
|
|
123
131
|
ctx.model.extend(exports.TABLE.bilibiliDynamicCache, {
|
|
124
132
|
id: 'unsigned',
|
|
125
|
-
dynamic_id:
|
|
133
|
+
dynamic_id: str(64),
|
|
126
134
|
host_mid: 'unsigned',
|
|
127
|
-
groupId:
|
|
128
|
-
dynamic_type:
|
|
129
|
-
createdAt:
|
|
130
|
-
updatedAt:
|
|
135
|
+
groupId: str(64),
|
|
136
|
+
dynamic_type: str(32),
|
|
137
|
+
createdAt: str(32),
|
|
138
|
+
updatedAt: str(32),
|
|
131
139
|
}, { autoInc: true, unique: [['dynamic_id', 'host_mid', 'groupId']] });
|
|
132
140
|
ctx.model.extend(exports.TABLE.bilibiliFilterWord, {
|
|
133
141
|
id: 'unsigned',
|
|
134
142
|
host_mid: 'unsigned',
|
|
135
|
-
word:
|
|
136
|
-
createdAt:
|
|
137
|
-
updatedAt:
|
|
143
|
+
word: str(128),
|
|
144
|
+
createdAt: str(32),
|
|
145
|
+
updatedAt: str(32),
|
|
138
146
|
}, { autoInc: true, unique: [['host_mid', 'word']] });
|
|
139
147
|
ctx.model.extend(exports.TABLE.bilibiliFilterTag, {
|
|
140
148
|
id: 'unsigned',
|
|
141
149
|
host_mid: 'unsigned',
|
|
142
|
-
tag:
|
|
143
|
-
createdAt:
|
|
144
|
-
updatedAt:
|
|
150
|
+
tag: str(128),
|
|
151
|
+
createdAt: str(32),
|
|
152
|
+
updatedAt: str(32),
|
|
145
153
|
}, { autoInc: true, unique: [['host_mid', 'tag']] });
|
|
146
154
|
// ---------- 统计 ----------
|
|
147
155
|
ctx.model.extend(exports.TABLE.statsParse, {
|
|
148
156
|
id: 'unsigned',
|
|
149
|
-
groupId:
|
|
150
|
-
userId:
|
|
151
|
-
platform:
|
|
157
|
+
groupId: str(64),
|
|
158
|
+
userId: str(64),
|
|
159
|
+
platform: str(32),
|
|
152
160
|
parseCount: 'unsigned',
|
|
153
|
-
createdAt:
|
|
154
|
-
updatedAt:
|
|
161
|
+
createdAt: str(32),
|
|
162
|
+
updatedAt: str(32),
|
|
155
163
|
}, { autoInc: true, unique: [['groupId', 'userId', 'platform']] });
|
|
156
164
|
ctx.model.extend(exports.TABLE.statsHistory, {
|
|
157
165
|
id: 'unsigned',
|
|
158
|
-
date:
|
|
166
|
+
date: str(16),
|
|
159
167
|
totalParses: 'unsigned',
|
|
160
168
|
douyin: 'unsigned',
|
|
161
169
|
bilibili: 'unsigned',
|
|
162
170
|
kuaishou: 'unsigned',
|
|
163
171
|
xiaohongshu: 'unsigned',
|
|
164
|
-
createdAt:
|
|
172
|
+
createdAt: str(32),
|
|
165
173
|
}, { autoInc: true, unique: [['date']] });
|
|
166
174
|
ctx.model.extend(exports.TABLE.statsGlobal, {
|
|
167
|
-
key:
|
|
175
|
+
key: str(64),
|
|
168
176
|
value: 'unsigned',
|
|
169
|
-
updatedAt:
|
|
177
|
+
updatedAt: str(32),
|
|
170
178
|
}, { primary: 'key' });
|
|
171
179
|
ctx.model.extend(exports.TABLE.statsGroupHistory, {
|
|
172
|
-
groupId:
|
|
173
|
-
date:
|
|
174
|
-
platform:
|
|
180
|
+
groupId: str(64),
|
|
181
|
+
date: str(16),
|
|
182
|
+
platform: str(32),
|
|
175
183
|
parseCount: 'unsigned',
|
|
176
|
-
updatedAt:
|
|
184
|
+
updatedAt: str(32),
|
|
177
185
|
}, { primary: ['groupId', 'date', 'platform'] });
|
|
178
186
|
ctx.model.extend(exports.TABLE.statsHour, {
|
|
179
|
-
groupId:
|
|
187
|
+
groupId: str(64),
|
|
180
188
|
hour: 'unsigned',
|
|
181
|
-
platform:
|
|
189
|
+
platform: str(32),
|
|
182
190
|
parseCount: 'unsigned',
|
|
183
|
-
updatedAt:
|
|
191
|
+
updatedAt: str(32),
|
|
184
192
|
}, { primary: ['groupId', 'hour', 'platform'] });
|
|
185
193
|
ctx.model.extend(exports.TABLE.statsWorkType, {
|
|
186
|
-
groupId:
|
|
187
|
-
platform:
|
|
188
|
-
workType:
|
|
194
|
+
groupId: str(64),
|
|
195
|
+
platform: str(32),
|
|
196
|
+
workType: str(32),
|
|
189
197
|
parseCount: 'unsigned',
|
|
190
|
-
updatedAt:
|
|
198
|
+
updatedAt: str(32),
|
|
191
199
|
}, { primary: ['groupId', 'platform', 'workType'] });
|
|
192
200
|
ctx.model.extend(exports.TABLE.statsMetric, {
|
|
193
|
-
groupId:
|
|
194
|
-
metric:
|
|
195
|
-
bucket:
|
|
201
|
+
groupId: str(64),
|
|
202
|
+
metric: str(32),
|
|
203
|
+
bucket: str(32),
|
|
196
204
|
parseCount: 'unsigned',
|
|
197
|
-
updatedAt:
|
|
205
|
+
updatedAt: str(32),
|
|
198
206
|
}, { primary: ['groupId', 'metric', 'bucket'] });
|
|
199
207
|
};
|
|
200
208
|
exports.extendModels = extendModels;
|
package/package.json
CHANGED
|
@@ -157,6 +157,49 @@ const main = async () => {
|
|
|
157
157
|
const removed = await statisticsDB.getRecentHistory(30)
|
|
158
158
|
ok('二次读取稳定', removed.length === 1)
|
|
159
159
|
|
|
160
|
+
// ---------------- MySQL 索引长度自检 ----------------
|
|
161
|
+
// InnoDB 单个索引上限 3072 字节,utf8mb4 下每字符 4 字节(最多 768 字符)。
|
|
162
|
+
// 本地冒烟只能跑 sqlite,MySQL 的 ER_TOO_LONG_KEY 在这儿触发不了,
|
|
163
|
+
// 所以直接从模型定义把每个索引的字节数算出来,超了就失败。
|
|
164
|
+
const LIMIT = 3072
|
|
165
|
+
const CHARSET_BYTES = 4
|
|
166
|
+
// 字符串不给长度时,驱动按 VARCHAR(255) 建
|
|
167
|
+
const DEFAULT_STRING_LENGTH = 255
|
|
168
|
+
const NUMERIC_BYTES = { unsigned: 4, integer: 4, bigint: 8, float: 4, double: 8, decimal: 8, boolean: 1, date: 8, time: 8, timestamp: 8 }
|
|
169
|
+
|
|
170
|
+
// 索引定义分散在三处:primary(主键)、unique(唯一键)、indexes(显式索引)
|
|
171
|
+
const collectIndexes = (model) => {
|
|
172
|
+
const list = []
|
|
173
|
+
if (model.primary) list.push({ kind: 'primary', keys: [].concat(model.primary) })
|
|
174
|
+
for (const item of model.unique ?? []) list.push({ kind: 'unique', keys: [].concat(item) })
|
|
175
|
+
for (const item of model.indexes ?? []) list.push({ kind: 'index', keys: Object.keys(item.keys ?? {}) })
|
|
176
|
+
return list
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let worst = { label: '', bytes: 0 }
|
|
180
|
+
let checked = 0
|
|
181
|
+
for (const [name, model] of Object.entries(ctx.model.tables ?? {})) {
|
|
182
|
+
if (!name.startsWith('kkk.')) continue
|
|
183
|
+
for (const index of collectIndexes(model)) {
|
|
184
|
+
let bytes = 0
|
|
185
|
+
for (const key of index.keys) {
|
|
186
|
+
const field = model.fields?.[key]
|
|
187
|
+
const type = typeof field === 'string' ? field : (field?.deftype ?? field?.type?.type)
|
|
188
|
+
if (type === 'string' || type === 'text') {
|
|
189
|
+
bytes += (typeof field === 'object' && field.length ? field.length : DEFAULT_STRING_LENGTH) * CHARSET_BYTES
|
|
190
|
+
} else {
|
|
191
|
+
bytes += NUMERIC_BYTES[type] ?? 8
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
checked++
|
|
195
|
+
const label = name + ' [' + index.keys.join(', ') + ']'
|
|
196
|
+
if (bytes > worst.bytes) worst = { label, bytes }
|
|
197
|
+
ok('索引长度 ' + index.kind + ' ' + label + ' = ' + bytes + 'B', bytes <= LIMIT)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
ok('索引自检覆盖到表', checked >= 20, checked)
|
|
201
|
+
console.log(' 最宽索引:' + worst.label + ' = ' + worst.bytes + ' 字节(MySQL InnoDB 上限 ' + LIMIT + ')')
|
|
202
|
+
|
|
160
203
|
await ctx.stop()
|
|
161
204
|
fs.rmSync(path.dirname(dbFile), { recursive: true, force: true })
|
|
162
205
|
|
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
* `createdAt` / `updatedAt` **仍然存 ISO 字符串**(而不是 timestamp / Date):
|
|
11
11
|
* 上层几百处读的都是字符串(比如 `createdAt < 截止日期` 这种字典序比较),
|
|
12
12
|
* 换成 Date 要全改一遍,没必要。
|
|
13
|
+
*
|
|
14
|
+
* ## 为什么索引字段都写了明确长度
|
|
15
|
+
*
|
|
16
|
+
* MySQL InnoDB 的单个索引总长上限是 3072 字节,utf8mb4 下每个字符 4 字节,
|
|
17
|
+
* 也就是**一个索引最多 768 个字符**。字符串字段不给长度时,驱动按 VARCHAR(255) 建,
|
|
18
|
+
* 复合索引很容易爆:比如作品缓存表原本 `aweme_id + sec_uid + groupId + pushType`
|
|
19
|
+
* 四个 255 字段就是 1020 字符 = 4080 字节,直接 `ER_TOO_LONG_KEY`。
|
|
20
|
+
* 所以凡是进了 primary / unique 的字段,都按实际数据长度写死(群号、QQ 号这类
|
|
21
|
+
* 给 64 已经很宽裕),不进索引的字段(备注、昵称之类)保持默认。
|
|
13
22
|
*/
|
|
14
23
|
import { Context } from 'koishi'
|
|
15
24
|
|
|
@@ -245,6 +254,15 @@ declare module 'koishi' {
|
|
|
245
254
|
|
|
246
255
|
/* ------------------------------ 建表 ------------------------------ */
|
|
247
256
|
|
|
257
|
+
/**
|
|
258
|
+
* 定长字符串字段。
|
|
259
|
+
*
|
|
260
|
+
* 走索引的字段必须给长度(见文件头「为什么索引字段都写了明确长度」),
|
|
261
|
+
* 这里的数字都是按实际数据留的余量:群号 / QQ 号 / 作品 id 这类最多几十个字符,
|
|
262
|
+
* 抖音 sec_uid 最长见过 76,给 128。
|
|
263
|
+
*/
|
|
264
|
+
const str = (length: number) => ({ type: 'string', length }) as const
|
|
265
|
+
|
|
248
266
|
/** 进程内只建一次(插件热重载时 apply 会再跑一遍,重复 extend 没意义) */
|
|
249
267
|
let modelsReady = false
|
|
250
268
|
|
|
@@ -258,182 +276,182 @@ export const extendModels = (ctx: Context): void => {
|
|
|
258
276
|
|
|
259
277
|
// ---------- 抖音 ----------
|
|
260
278
|
ctx.model.extend(TABLE.douyinBot, {
|
|
261
|
-
id:
|
|
262
|
-
createdAt:
|
|
263
|
-
updatedAt:
|
|
279
|
+
id: str(64),
|
|
280
|
+
createdAt: str(32),
|
|
281
|
+
updatedAt: str(32),
|
|
264
282
|
}, { primary: 'id' })
|
|
265
283
|
|
|
266
284
|
ctx.model.extend(TABLE.douyinGroup, {
|
|
267
|
-
id:
|
|
268
|
-
botId:
|
|
269
|
-
createdAt:
|
|
270
|
-
updatedAt:
|
|
285
|
+
id: str(64),
|
|
286
|
+
botId: str(64),
|
|
287
|
+
createdAt: str(32),
|
|
288
|
+
updatedAt: str(32),
|
|
271
289
|
}, { primary: ['id', 'botId'] })
|
|
272
290
|
|
|
273
291
|
ctx.model.extend(TABLE.douyinUser, {
|
|
274
|
-
sec_uid:
|
|
275
|
-
short_id:
|
|
292
|
+
sec_uid: str(128),
|
|
293
|
+
short_id: str(64),
|
|
276
294
|
remark: 'string',
|
|
277
295
|
living: 'boolean',
|
|
278
|
-
filterMode:
|
|
279
|
-
createdAt:
|
|
280
|
-
updatedAt:
|
|
296
|
+
filterMode: str(16),
|
|
297
|
+
createdAt: str(32),
|
|
298
|
+
updatedAt: str(32),
|
|
281
299
|
}, { primary: 'sec_uid' })
|
|
282
300
|
|
|
283
301
|
ctx.model.extend(TABLE.douyinSubscription, {
|
|
284
|
-
groupId:
|
|
285
|
-
sec_uid:
|
|
286
|
-
createdAt:
|
|
287
|
-
updatedAt:
|
|
302
|
+
groupId: str(64),
|
|
303
|
+
sec_uid: str(128),
|
|
304
|
+
createdAt: str(32),
|
|
305
|
+
updatedAt: str(32),
|
|
288
306
|
}, { primary: ['groupId', 'sec_uid'] })
|
|
289
307
|
|
|
290
308
|
ctx.model.extend(TABLE.douyinAwemeCache, {
|
|
291
309
|
id: 'unsigned',
|
|
292
|
-
aweme_id:
|
|
293
|
-
sec_uid:
|
|
294
|
-
groupId:
|
|
295
|
-
pushType:
|
|
296
|
-
createdAt:
|
|
297
|
-
updatedAt:
|
|
310
|
+
aweme_id: str(64),
|
|
311
|
+
sec_uid: str(128),
|
|
312
|
+
groupId: str(64),
|
|
313
|
+
pushType: str(32),
|
|
314
|
+
createdAt: str(32),
|
|
315
|
+
updatedAt: str(32),
|
|
298
316
|
}, { autoInc: true, unique: [['aweme_id', 'sec_uid', 'groupId', 'pushType']] })
|
|
299
317
|
|
|
300
318
|
ctx.model.extend(TABLE.douyinFilterWord, {
|
|
301
319
|
id: 'unsigned',
|
|
302
|
-
sec_uid:
|
|
303
|
-
word:
|
|
304
|
-
createdAt:
|
|
305
|
-
updatedAt:
|
|
320
|
+
sec_uid: str(128),
|
|
321
|
+
word: str(128),
|
|
322
|
+
createdAt: str(32),
|
|
323
|
+
updatedAt: str(32),
|
|
306
324
|
}, { autoInc: true, unique: [['sec_uid', 'word']] })
|
|
307
325
|
|
|
308
326
|
ctx.model.extend(TABLE.douyinFilterTag, {
|
|
309
327
|
id: 'unsigned',
|
|
310
|
-
sec_uid:
|
|
311
|
-
tag:
|
|
312
|
-
createdAt:
|
|
313
|
-
updatedAt:
|
|
328
|
+
sec_uid: str(128),
|
|
329
|
+
tag: str(128),
|
|
330
|
+
createdAt: str(32),
|
|
331
|
+
updatedAt: str(32),
|
|
314
332
|
}, { autoInc: true, unique: [['sec_uid', 'tag']] })
|
|
315
333
|
|
|
316
334
|
ctx.model.extend(TABLE.douyinListSnapshot, {
|
|
317
335
|
id: 'unsigned',
|
|
318
|
-
sec_uid:
|
|
319
|
-
pushType:
|
|
320
|
-
aweme_id:
|
|
321
|
-
createdAt:
|
|
322
|
-
updatedAt:
|
|
336
|
+
sec_uid: str(128),
|
|
337
|
+
pushType: str(32),
|
|
338
|
+
aweme_id: str(64),
|
|
339
|
+
createdAt: str(32),
|
|
340
|
+
updatedAt: str(32),
|
|
323
341
|
}, { autoInc: true, unique: [['sec_uid', 'pushType', 'aweme_id']] })
|
|
324
342
|
|
|
325
343
|
// ---------- B站 ----------
|
|
326
344
|
ctx.model.extend(TABLE.bilibiliBot, {
|
|
327
|
-
id:
|
|
328
|
-
createdAt:
|
|
329
|
-
updatedAt:
|
|
345
|
+
id: str(64),
|
|
346
|
+
createdAt: str(32),
|
|
347
|
+
updatedAt: str(32),
|
|
330
348
|
}, { primary: 'id' })
|
|
331
349
|
|
|
332
350
|
ctx.model.extend(TABLE.bilibiliGroup, {
|
|
333
|
-
id:
|
|
334
|
-
botId:
|
|
335
|
-
createdAt:
|
|
336
|
-
updatedAt:
|
|
351
|
+
id: str(64),
|
|
352
|
+
botId: str(64),
|
|
353
|
+
createdAt: str(32),
|
|
354
|
+
updatedAt: str(32),
|
|
337
355
|
}, { primary: ['id', 'botId'] })
|
|
338
356
|
|
|
339
357
|
ctx.model.extend(TABLE.bilibiliUser, {
|
|
340
358
|
host_mid: 'unsigned',
|
|
341
359
|
remark: 'string',
|
|
342
|
-
filterMode:
|
|
343
|
-
createdAt:
|
|
344
|
-
updatedAt:
|
|
360
|
+
filterMode: str(16),
|
|
361
|
+
createdAt: str(32),
|
|
362
|
+
updatedAt: str(32),
|
|
345
363
|
}, { primary: 'host_mid' })
|
|
346
364
|
|
|
347
365
|
ctx.model.extend(TABLE.bilibiliSubscription, {
|
|
348
|
-
groupId:
|
|
366
|
+
groupId: str(64),
|
|
349
367
|
host_mid: 'unsigned',
|
|
350
|
-
createdAt:
|
|
351
|
-
updatedAt:
|
|
368
|
+
createdAt: str(32),
|
|
369
|
+
updatedAt: str(32),
|
|
352
370
|
}, { primary: ['groupId', 'host_mid'] })
|
|
353
371
|
|
|
354
372
|
ctx.model.extend(TABLE.bilibiliDynamicCache, {
|
|
355
373
|
id: 'unsigned',
|
|
356
|
-
dynamic_id:
|
|
374
|
+
dynamic_id: str(64),
|
|
357
375
|
host_mid: 'unsigned',
|
|
358
|
-
groupId:
|
|
359
|
-
dynamic_type:
|
|
360
|
-
createdAt:
|
|
361
|
-
updatedAt:
|
|
376
|
+
groupId: str(64),
|
|
377
|
+
dynamic_type: str(32),
|
|
378
|
+
createdAt: str(32),
|
|
379
|
+
updatedAt: str(32),
|
|
362
380
|
}, { autoInc: true, unique: [['dynamic_id', 'host_mid', 'groupId']] })
|
|
363
381
|
|
|
364
382
|
ctx.model.extend(TABLE.bilibiliFilterWord, {
|
|
365
383
|
id: 'unsigned',
|
|
366
384
|
host_mid: 'unsigned',
|
|
367
|
-
word:
|
|
368
|
-
createdAt:
|
|
369
|
-
updatedAt:
|
|
385
|
+
word: str(128),
|
|
386
|
+
createdAt: str(32),
|
|
387
|
+
updatedAt: str(32),
|
|
370
388
|
}, { autoInc: true, unique: [['host_mid', 'word']] })
|
|
371
389
|
|
|
372
390
|
ctx.model.extend(TABLE.bilibiliFilterTag, {
|
|
373
391
|
id: 'unsigned',
|
|
374
392
|
host_mid: 'unsigned',
|
|
375
|
-
tag:
|
|
376
|
-
createdAt:
|
|
377
|
-
updatedAt:
|
|
393
|
+
tag: str(128),
|
|
394
|
+
createdAt: str(32),
|
|
395
|
+
updatedAt: str(32),
|
|
378
396
|
}, { autoInc: true, unique: [['host_mid', 'tag']] })
|
|
379
397
|
|
|
380
398
|
// ---------- 统计 ----------
|
|
381
399
|
ctx.model.extend(TABLE.statsParse, {
|
|
382
400
|
id: 'unsigned',
|
|
383
|
-
groupId:
|
|
384
|
-
userId:
|
|
385
|
-
platform:
|
|
401
|
+
groupId: str(64),
|
|
402
|
+
userId: str(64),
|
|
403
|
+
platform: str(32),
|
|
386
404
|
parseCount: 'unsigned',
|
|
387
|
-
createdAt:
|
|
388
|
-
updatedAt:
|
|
405
|
+
createdAt: str(32),
|
|
406
|
+
updatedAt: str(32),
|
|
389
407
|
}, { autoInc: true, unique: [['groupId', 'userId', 'platform']] })
|
|
390
408
|
|
|
391
409
|
ctx.model.extend(TABLE.statsHistory, {
|
|
392
410
|
id: 'unsigned',
|
|
393
|
-
date:
|
|
411
|
+
date: str(16),
|
|
394
412
|
totalParses: 'unsigned',
|
|
395
413
|
douyin: 'unsigned',
|
|
396
414
|
bilibili: 'unsigned',
|
|
397
415
|
kuaishou: 'unsigned',
|
|
398
416
|
xiaohongshu: 'unsigned',
|
|
399
|
-
createdAt:
|
|
417
|
+
createdAt: str(32),
|
|
400
418
|
}, { autoInc: true, unique: [['date']] })
|
|
401
419
|
|
|
402
420
|
ctx.model.extend(TABLE.statsGlobal, {
|
|
403
|
-
key:
|
|
421
|
+
key: str(64),
|
|
404
422
|
value: 'unsigned',
|
|
405
|
-
updatedAt:
|
|
423
|
+
updatedAt: str(32),
|
|
406
424
|
}, { primary: 'key' })
|
|
407
425
|
|
|
408
426
|
ctx.model.extend(TABLE.statsGroupHistory, {
|
|
409
|
-
groupId:
|
|
410
|
-
date:
|
|
411
|
-
platform:
|
|
427
|
+
groupId: str(64),
|
|
428
|
+
date: str(16),
|
|
429
|
+
platform: str(32),
|
|
412
430
|
parseCount: 'unsigned',
|
|
413
|
-
updatedAt:
|
|
431
|
+
updatedAt: str(32),
|
|
414
432
|
}, { primary: ['groupId', 'date', 'platform'] })
|
|
415
433
|
|
|
416
434
|
ctx.model.extend(TABLE.statsHour, {
|
|
417
|
-
groupId:
|
|
435
|
+
groupId: str(64),
|
|
418
436
|
hour: 'unsigned',
|
|
419
|
-
platform:
|
|
437
|
+
platform: str(32),
|
|
420
438
|
parseCount: 'unsigned',
|
|
421
|
-
updatedAt:
|
|
439
|
+
updatedAt: str(32),
|
|
422
440
|
}, { primary: ['groupId', 'hour', 'platform'] })
|
|
423
441
|
|
|
424
442
|
ctx.model.extend(TABLE.statsWorkType, {
|
|
425
|
-
groupId:
|
|
426
|
-
platform:
|
|
427
|
-
workType:
|
|
443
|
+
groupId: str(64),
|
|
444
|
+
platform: str(32),
|
|
445
|
+
workType: str(32),
|
|
428
446
|
parseCount: 'unsigned',
|
|
429
|
-
updatedAt:
|
|
447
|
+
updatedAt: str(32),
|
|
430
448
|
}, { primary: ['groupId', 'platform', 'workType'] })
|
|
431
449
|
|
|
432
450
|
ctx.model.extend(TABLE.statsMetric, {
|
|
433
|
-
groupId:
|
|
434
|
-
metric:
|
|
435
|
-
bucket:
|
|
451
|
+
groupId: str(64),
|
|
452
|
+
metric: str(32),
|
|
453
|
+
bucket: str(32),
|
|
436
454
|
parseCount: 'unsigned',
|
|
437
|
-
updatedAt:
|
|
455
|
+
updatedAt: str(32),
|
|
438
456
|
}, { primary: ['groupId', 'metric', 'bucket'] })
|
|
439
457
|
}
|