@sfmc-bds/db-server 0.5.3 → 0.7.0
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/dist/domain/activity-log.d.ts +129 -0
- package/dist/domain/activity-log.d.ts.map +1 -0
- package/dist/domain/activity-log.js +186 -0
- package/dist/domain/activity-log.js.map +7 -0
- package/dist/domain/bind-providers.d.ts +59 -0
- package/dist/domain/bind-providers.d.ts.map +1 -0
- package/dist/domain/bind-providers.js +33 -0
- package/dist/domain/bind-providers.js.map +7 -0
- package/dist/domain/metrics.d.ts +5 -0
- package/dist/domain/metrics.d.ts.map +1 -1
- package/dist/domain/metrics.js +28 -2
- package/dist/domain/metrics.js.map +2 -2
- package/dist/domain/player-level.d.ts +29 -0
- package/dist/domain/player-level.d.ts.map +1 -0
- package/dist/domain/player-level.js +30 -0
- package/dist/domain/player-level.js.map +7 -0
- package/dist/domain/process-resources.d.ts +12 -0
- package/dist/domain/process-resources.d.ts.map +1 -1
- package/dist/domain/process-resources.js +72 -1
- package/dist/domain/process-resources.js.map +2 -2
- package/dist/domain/qq-bind.d.ts +21 -0
- package/dist/domain/qq-bind.d.ts.map +1 -0
- package/dist/domain/qq-bind.js +54 -0
- package/dist/domain/qq-bind.js.map +7 -0
- package/dist/domain/schema.d.ts +1 -0
- package/dist/domain/schema.d.ts.map +1 -1
- package/dist/domain/schema.js +26 -0
- package/dist/domain/schema.js.map +2 -2
- package/dist/domain/tracked-processes.d.ts +30 -0
- package/dist/domain/tracked-processes.d.ts.map +1 -0
- package/dist/domain/tracked-processes.js +78 -0
- package/dist/domain/tracked-processes.js.map +7 -0
- package/dist/domain/wechat-bind.d.ts +56 -0
- package/dist/domain/wechat-bind.d.ts.map +1 -0
- package/dist/domain/wechat-bind.js +84 -0
- package/dist/domain/wechat-bind.js.map +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -7
- package/dist/index.js.map +2 -2
- package/dist/lib/sqlite.d.ts +10 -0
- package/dist/lib/sqlite.d.ts.map +1 -1
- package/dist/lib/sqlite.js +17 -2
- package/dist/lib/sqlite.js.map +2 -2
- package/dist/routes/bind-confirm.d.ts +30 -0
- package/dist/routes/bind-confirm.d.ts.map +1 -0
- package/dist/routes/bind-confirm.js +27 -0
- package/dist/routes/bind-confirm.js.map +7 -0
- package/dist/routes/qq-bind.d.ts +3 -1
- package/dist/routes/qq-bind.d.ts.map +1 -1
- package/dist/routes/qq-bind.js +8 -64
- package/dist/routes/qq-bind.js.map +2 -2
- package/dist/routes/status.d.ts.map +1 -1
- package/dist/routes/status.js +10 -2
- package/dist/routes/status.js.map +2 -2
- package/dist/routes/wechat-activity.d.ts +32 -0
- package/dist/routes/wechat-activity.d.ts.map +1 -0
- package/dist/routes/wechat-activity.js +40 -0
- package/dist/routes/wechat-activity.js.map +7 -0
- package/dist/routes/wechat-bind.d.ts +39 -0
- package/dist/routes/wechat-bind.d.ts.map +1 -0
- package/dist/routes/wechat-bind.js +56 -0
- package/dist/routes/wechat-bind.js.map +7 -0
- package/package.json +10 -4
package/dist/lib/sqlite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/lib/sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAiB,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,EAChE,MAAM,CAAC,EAAE,OAAO,EAAE,KACf,OAAO,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,EAAE,aAAa,GAAE,MAAY,GAAG,OAAO,CAwClF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAO3D"}
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/lib/sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAiB,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,EAChE,MAAM,CAAC,EAAE,OAAO,EAAE,KACf,OAAO,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,EAAE,aAAa,GAAE,MAAY,GAAG,OAAO,CAwClF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAO3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAUtG"}
|
package/dist/lib/sqlite.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { DatabaseSync, StatementSync } from "node:sqlite";
|
|
4
4
|
function createQuery(db, maxStatements = 200) {
|
|
@@ -40,8 +40,23 @@ function openDatabase(filePath) {
|
|
|
40
40
|
db.exec("PRAGMA busy_timeout = 5000");
|
|
41
41
|
return db;
|
|
42
42
|
}
|
|
43
|
+
function withReadonlyQuery(filePath, missing, run) {
|
|
44
|
+
if (!existsSync(filePath)) return missing();
|
|
45
|
+
const db = new DatabaseSync(filePath, { readOnly: true });
|
|
46
|
+
try {
|
|
47
|
+
db.exec("PRAGMA query_only = ON");
|
|
48
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
49
|
+
return run(createQuery(db, 16));
|
|
50
|
+
} finally {
|
|
51
|
+
try {
|
|
52
|
+
db.close();
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
43
57
|
export {
|
|
44
58
|
createQuery,
|
|
45
|
-
openDatabase
|
|
59
|
+
openDatabase,
|
|
60
|
+
withReadonlyQuery
|
|
46
61
|
};
|
|
47
62
|
//# sourceMappingURL=sqlite.js.map
|
package/dist/lib/sqlite.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/sqlite.ts"],
|
|
4
|
-
"sourcesContent": ["import { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { DatabaseSync, StatementSync } from \"node:sqlite\";\nimport type { SQLStatement } from \"sql-template-strings\";\n\nexport type QueryFn = (\n sql: string | SQLStatement | { sql: string; values?: unknown[] },\n values?: unknown[]\n) => unknown[] | { changes: number | bigint };\n\n/**\n * \u521B\u5EFA\u7EDF\u4E00\u7684 SQL \u67E5\u8BE2\u63A5\u53E3\uFF08\u5185\u7F6E LRU \u9884\u7F16\u8BD1\u8BED\u53E5\u7F13\u5B58\u673A\u5236\uFF09\u3002\n * \u652F\u6301\u4E09\u79CD\u8C03\u7528\u5F62\u6001\uFF1A\n * 1. `query(\"SELECT * FROM t WHERE id = ?\", [1])`\n * 2. `query({ sql: \"SELECT * FROM t WHERE id = ?\", values: [1] })`\n * 3. `query(SQL`SELECT * FROM t WHERE id = ${1}`)`\uFF08sql-template-strings\uFF09\n *\n * @param db SQLite \u6570\u636E\u5E93\u8FDE\u63A5\u5B9E\u4F8B\u3002\n * @param maxStatements \u9884\u7F16\u8BD1\u8BED\u53E5\u7F13\u5B58\u4E0A\u9650\uFF08\u9ED8\u8BA4\u4E3A 200\uFF09\u3002\n * @returns \u7EDF\u4E00\u67E5\u8BE2\u6267\u884C\u51FD\u6570\u3002\n */\nexport function createQuery(db: DatabaseSync, maxStatements: number = 200): QueryFn {\n\n const stmts = new Map<string, StatementSync>();\n\n function getStmt(sql: string): StatementSync {\n let s = stmts.get(sql);\n if (!s) {\n s = db.prepare(sql);\n if (stmts.size >= maxStatements) {\n const first = stmts.keys().next().value;\n if (first !== undefined) stmts.delete(first);\n }\n stmts.set(sql, s);\n }\n return s;\n }\n\n function runQuery(sql: string, values: unknown[]): any[] | { changes: number | bigint } {\n const s = getStmt(sql);\n const upper = sql.trim().toUpperCase();\n const needsRows =\n upper.startsWith(\"SELECT\") ||\n upper.startsWith(\"WITH\") ||\n upper.startsWith(\"PRAGMA\") ||\n /\\bRETURNING\\b/.test(upper);\n const bind = values as any[];\n if (needsRows) return s.all(...bind);\n const r = s.run(...bind);\n return { changes: r.changes };\n }\n\n return function query(raw: string | { sql: string; values?: unknown[] }, maybeValues?: unknown[]): any[] | { changes: number | bigint } {\n // \u683C\u5F0F 1\uFF1Astring + values\n if (typeof raw === \"string\") {\n return runQuery(raw, maybeValues ?? []);\n }\n // \u683C\u5F0F 2 / 3\uFF1A{ sql, values } \u6216 sql-template-strings \u5BF9\u8C61\n const o = raw as { sql: string; values?: unknown[] };\n return runQuery(o.sql, o.values ?? []);\n };\n}\n\n/**\n * \u6253\u5F00 SQLite \u6570\u636E\u5E93\u6587\u4EF6\uFF08\u57FA\u4E8E Node.js \u539F\u751F DatabaseSync \u540C\u6B65\u6A21\u5757\uFF09\u3002\n * \u81EA\u52A8\u9012\u5F52\u521B\u5EFA\u6240\u5728\u7236\u7EA7\u76EE\u5F55\uFF0C\u5E76\u521D\u59CB\u5316\u914D\u7F6E\u5916\u952E\u7EA6\u675F\uFF08`foreign_keys = ON`\uFF09\u3001\u9884\u5199\u65E5\u5FD7\uFF08`journal_mode = WAL`\uFF09\u4E0E\u5FD9\u7B49\u5F85\u8D85\u65F6\uFF08`busy_timeout = 5000`\uFF09\u3002\n *\n * @param filePath \u6570\u636E\u5E93\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84\u3002\n * @returns SQLite DatabaseSync \u8FDE\u63A5\u5B9E\u4F8B\u3002\n */\nexport function openDatabase(filePath: string): DatabaseSync {\n mkdirSync(dirname(filePath), { recursive: true });\n const db = new DatabaseSync(filePath);\n db.exec(\"PRAGMA foreign_keys = ON\");\n db.exec(\"PRAGMA journal_mode = WAL\");\n db.exec(\"PRAGMA busy_timeout = 5000\");\n return db;\n}\n\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,iBAAiB;
|
|
4
|
+
"sourcesContent": ["import { existsSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { DatabaseSync, StatementSync } from \"node:sqlite\";\nimport type { SQLStatement } from \"sql-template-strings\";\n\nexport type QueryFn = (\n sql: string | SQLStatement | { sql: string; values?: unknown[] },\n values?: unknown[]\n) => unknown[] | { changes: number | bigint };\n\n/**\n * \u521B\u5EFA\u7EDF\u4E00\u7684 SQL \u67E5\u8BE2\u63A5\u53E3\uFF08\u5185\u7F6E LRU \u9884\u7F16\u8BD1\u8BED\u53E5\u7F13\u5B58\u673A\u5236\uFF09\u3002\n * \u652F\u6301\u4E09\u79CD\u8C03\u7528\u5F62\u6001\uFF1A\n * 1. `query(\"SELECT * FROM t WHERE id = ?\", [1])`\n * 2. `query({ sql: \"SELECT * FROM t WHERE id = ?\", values: [1] })`\n * 3. `query(SQL`SELECT * FROM t WHERE id = ${1}`)`\uFF08sql-template-strings\uFF09\n *\n * @param db SQLite \u6570\u636E\u5E93\u8FDE\u63A5\u5B9E\u4F8B\u3002\n * @param maxStatements \u9884\u7F16\u8BD1\u8BED\u53E5\u7F13\u5B58\u4E0A\u9650\uFF08\u9ED8\u8BA4\u4E3A 200\uFF09\u3002\n * @returns \u7EDF\u4E00\u67E5\u8BE2\u6267\u884C\u51FD\u6570\u3002\n */\nexport function createQuery(db: DatabaseSync, maxStatements: number = 200): QueryFn {\n\n const stmts = new Map<string, StatementSync>();\n\n function getStmt(sql: string): StatementSync {\n let s = stmts.get(sql);\n if (!s) {\n s = db.prepare(sql);\n if (stmts.size >= maxStatements) {\n const first = stmts.keys().next().value;\n if (first !== undefined) stmts.delete(first);\n }\n stmts.set(sql, s);\n }\n return s;\n }\n\n function runQuery(sql: string, values: unknown[]): any[] | { changes: number | bigint } {\n const s = getStmt(sql);\n const upper = sql.trim().toUpperCase();\n const needsRows =\n upper.startsWith(\"SELECT\") ||\n upper.startsWith(\"WITH\") ||\n upper.startsWith(\"PRAGMA\") ||\n /\\bRETURNING\\b/.test(upper);\n const bind = values as any[];\n if (needsRows) return s.all(...bind);\n const r = s.run(...bind);\n return { changes: r.changes };\n }\n\n return function query(raw: string | { sql: string; values?: unknown[] }, maybeValues?: unknown[]): any[] | { changes: number | bigint } {\n // \u683C\u5F0F 1\uFF1Astring + values\n if (typeof raw === \"string\") {\n return runQuery(raw, maybeValues ?? []);\n }\n // \u683C\u5F0F 2 / 3\uFF1A{ sql, values } \u6216 sql-template-strings \u5BF9\u8C61\n const o = raw as { sql: string; values?: unknown[] };\n return runQuery(o.sql, o.values ?? []);\n };\n}\n\n/**\n * \u6253\u5F00 SQLite \u6570\u636E\u5E93\u6587\u4EF6\uFF08\u57FA\u4E8E Node.js \u539F\u751F DatabaseSync \u540C\u6B65\u6A21\u5757\uFF09\u3002\n * \u81EA\u52A8\u9012\u5F52\u521B\u5EFA\u6240\u5728\u7236\u7EA7\u76EE\u5F55\uFF0C\u5E76\u521D\u59CB\u5316\u914D\u7F6E\u5916\u952E\u7EA6\u675F\uFF08`foreign_keys = ON`\uFF09\u3001\u9884\u5199\u65E5\u5FD7\uFF08`journal_mode = WAL`\uFF09\u4E0E\u5FD9\u7B49\u5F85\u8D85\u65F6\uFF08`busy_timeout = 5000`\uFF09\u3002\n *\n * @param filePath \u6570\u636E\u5E93\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84\u3002\n * @returns SQLite DatabaseSync \u8FDE\u63A5\u5B9E\u4F8B\u3002\n */\nexport function openDatabase(filePath: string): DatabaseSync {\n mkdirSync(dirname(filePath), { recursive: true });\n const db = new DatabaseSync(filePath);\n db.exec(\"PRAGMA foreign_keys = ON\");\n db.exec(\"PRAGMA journal_mode = WAL\");\n db.exec(\"PRAGMA busy_timeout = 5000\");\n return db;\n}\n\n/**\n * \u4EE5\u53EA\u8BFB\u8FDE\u63A5\u6253\u5F00\u4E1A\u52A1\u5E93\uFF0C\u6267\u884C\u4E00\u6B21\u67E5\u8BE2\u540E\u7ACB\u5373\u5173\u95ED\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1Adb-server \u8FDB\u7A0B\u4E4B\u5916\uFF08CLI \u7BA1\u7406\u901A\u9053\uFF09\u8BFB\u53D6\u6A21\u5757\u8868\uFF0C\u4F8B\u5982 activity.query / activity.stats\u3002\n * \u6587\u4EF6\u4E0D\u5B58\u5728\u65F6\u8C03\u7528 missing() \u8FD4\u56DE\u515C\u5E95\u7ED3\u679C\uFF0C\u4E0D\u521B\u5EFA\u6570\u636E\u5E93\uFF1B\u67E5\u8BE2\u672C\u8EAB\u7684\u5F02\u5E38\u539F\u6837\u629B\u51FA\uFF0C\u4EA4\u7ED9\u8C03\u7528\u65B9\u63D0\u793A\u3002\n *\n * @param filePath \u4E1A\u52A1\u5E93\u7EDD\u5BF9\u8DEF\u5F84\u3002\n * @param missing \u6570\u636E\u5E93\u6587\u4EF6\u4E0D\u5B58\u5728\u65F6\u7684\u515C\u5E95\u7ED3\u679C\u3002\n * @param run \u4F7F\u7528\u53EA\u8BFB QueryFn \u6267\u884C\u7684\u67E5\u8BE2\u3002\n */\nexport function withReadonlyQuery<T>(filePath: string, missing: () => T, run: (query: QueryFn) => T): T {\n if (!existsSync(filePath)) return missing();\n const db = new DatabaseSync(filePath, { readOnly: true });\n try {\n db.exec(\"PRAGMA query_only = ON\");\n db.exec(\"PRAGMA busy_timeout = 5000\");\n return run(createQuery(db, 16));\n } finally {\n try { db.close(); } catch { /* \u53EA\u8BFB\u8FDE\u63A5\u5173\u95ED\u5931\u8D25\u4E0D\u5F71\u54CD\u5DF2\u53D6\u5F97\u7684\u7ED3\u679C */ }\n }\n}\n\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,YAAY,iBAAiB;AACtC,SAAS,eAAe;AACxB,SAAS,cAAc,qBAAqB;AAmBrC,SAAS,YAAY,IAAkB,gBAAwB,KAAc;AAElF,QAAM,QAAQ,oBAAI,IAA2B;AAE7C,WAAS,QAAQ,KAA4B;AAC3C,QAAI,IAAI,MAAM,IAAI,GAAG;AACrB,QAAI,CAAC,GAAG;AACN,UAAI,GAAG,QAAQ,GAAG;AAClB,UAAI,MAAM,QAAQ,eAAe;AAC/B,cAAM,QAAQ,MAAM,KAAK,EAAE,KAAK,EAAE;AAClC,YAAI,UAAU,OAAW,OAAM,OAAO,KAAK;AAAA,MAC7C;AACA,YAAM,IAAI,KAAK,CAAC;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,KAAa,QAAyD;AACtF,UAAM,IAAI,QAAQ,GAAG;AACrB,UAAM,QAAQ,IAAI,KAAK,EAAE,YAAY;AACrC,UAAM,YACJ,MAAM,WAAW,QAAQ,KACzB,MAAM,WAAW,MAAM,KACvB,MAAM,WAAW,QAAQ,KACzB,gBAAgB,KAAK,KAAK;AAC5B,UAAM,OAAO;AACb,QAAI,UAAW,QAAO,EAAE,IAAI,GAAG,IAAI;AACnC,UAAM,IAAI,EAAE,IAAI,GAAG,IAAI;AACvB,WAAO,EAAE,SAAS,EAAE,QAAQ;AAAA,EAC9B;AAEA,SAAO,SAAS,MAAM,KAAmD,aAA+D;AAEtI,QAAI,OAAO,QAAQ,UAAU;AAC3B,aAAO,SAAS,KAAK,eAAe,CAAC,CAAC;AAAA,IACxC;AAEA,UAAM,IAAI;AACV,WAAO,SAAS,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;AAAA,EACvC;AACF;AASO,SAAS,aAAa,UAAgC;AAC3D,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,QAAM,KAAK,IAAI,aAAa,QAAQ;AACpC,KAAG,KAAK,0BAA0B;AAClC,KAAG,KAAK,2BAA2B;AACnC,KAAG,KAAK,4BAA4B;AACpC,SAAO;AACT;AAWO,SAAS,kBAAqB,UAAkB,SAAkB,KAA+B;AACtG,MAAI,CAAC,WAAW,QAAQ,EAAG,QAAO,QAAQ;AAC1C,QAAM,KAAK,IAAI,aAAa,UAAU,EAAE,UAAU,KAAK,CAAC;AACxD,MAAI;AACF,OAAG,KAAK,wBAAwB;AAChC,OAAG,KAAK,4BAA4B;AACpC,WAAO,IAAI,YAAY,IAAI,EAAE,CAAC;AAAA,EAChC,UAAE;AACA,QAAI;AAAE,SAAG,MAAM;AAAA,IAAG,QAAQ;AAAA,IAA0B;AAAA,EACtD;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* routes/bind-confirm.ts — 游戏内 /c:bind 的统一确认入口(平台 HTTP,仅本机)
|
|
3
|
+
*
|
|
4
|
+
* POST /api/sfmc/bind/confirm { code, xuid, name }
|
|
5
|
+
* → 成功 { success: true, provider: "qq" | "wechat", binding }
|
|
6
|
+
* → 失败 { success: false, error, hint? }(错误码与 QQ 原接口一致,新增 wechat_already_bound / player_already_bound_wechat)
|
|
7
|
+
*
|
|
8
|
+
* 按注册的绑定提供方查找持有该短码的渠道并确认(OCP:新增渠道不改本路由)。
|
|
9
|
+
* 旧接口 POST /api/sfmc/qq/bind/confirm 保留,供旧版游戏侧脚本使用。
|
|
10
|
+
*/
|
|
11
|
+
import type http from "node:http";
|
|
12
|
+
import { type BindProvider } from "../domain/bind-providers.js";
|
|
13
|
+
interface Deps {
|
|
14
|
+
body: (req: http.IncomingMessage) => Promise<Record<string, unknown>>;
|
|
15
|
+
json: (res: http.ServerResponse, data: Record<string, unknown>, status?: number) => void;
|
|
16
|
+
/** 已注册的绑定提供方(按顺序询问) */
|
|
17
|
+
providers: () => readonly BindProvider[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 创建统一确认路由。
|
|
21
|
+
* 使用场景:index.ts 装配到平台路由链。
|
|
22
|
+
*/
|
|
23
|
+
declare function createBindConfirmRoutes({ body, json, providers }: Deps): ({ path, method, req, res, }: {
|
|
24
|
+
path: string;
|
|
25
|
+
method: string;
|
|
26
|
+
req: http.IncomingMessage;
|
|
27
|
+
res: http.ServerResponse;
|
|
28
|
+
}) => Promise<boolean>;
|
|
29
|
+
export { createBindConfirmRoutes };
|
|
30
|
+
//# sourceMappingURL=bind-confirm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind-confirm.d.ts","sourceRoot":"","sources":["../../src/routes/bind-confirm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEtF,UAAU,IAAI;IACZ,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtE,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACzF,uBAAuB;IACvB,SAAS,EAAE,MAAM,SAAS,YAAY,EAAE,CAAC;CAC1C;AAED;;;GAGG;AACH,iBAAS,uBAAuB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,iCAM3D;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,IAAI,CAAC,eAAe,CAAC;IAC1B,GAAG,EAAE,IAAI,CAAC,cAAc,CAAC;CAC1B,KAAG,OAAO,CAAC,OAAO,CAAC,CAerB;AAED,OAAO,EAAE,uBAAuB,EAAE,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { confirmWithProviders } from "../domain/bind-providers.js";
|
|
2
|
+
function createBindConfirmRoutes({ body, json, providers }) {
|
|
3
|
+
return async function handle({
|
|
4
|
+
path,
|
|
5
|
+
method,
|
|
6
|
+
req,
|
|
7
|
+
res
|
|
8
|
+
}) {
|
|
9
|
+
if (path !== "/api/sfmc/bind/confirm" || method !== "POST") return false;
|
|
10
|
+
const data = await body(req);
|
|
11
|
+
const code = String(data.code ?? "").trim();
|
|
12
|
+
const xuid = String(data.xuid ?? data.player_xuid ?? "").trim();
|
|
13
|
+
const name = String(data.name ?? data.player_name ?? "").trim();
|
|
14
|
+
if (!code || !xuid) {
|
|
15
|
+
json(res, { success: false, error: "code_and_xuid_required" }, 400);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const result = confirmWithProviders(providers(), { code, xuid, name, now: Date.now() });
|
|
19
|
+
if (result.ok) json(res, { success: true, provider: result.provider, binding: result.binding });
|
|
20
|
+
else json(res, { success: false, error: result.error, ...result.hint ? { hint: result.hint } : {} }, result.status);
|
|
21
|
+
return true;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
createBindConfirmRoutes
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=bind-confirm.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/routes/bind-confirm.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * routes/bind-confirm.ts \u2014 \u6E38\u620F\u5185 /c:bind \u7684\u7EDF\u4E00\u786E\u8BA4\u5165\u53E3\uFF08\u5E73\u53F0 HTTP\uFF0C\u4EC5\u672C\u673A\uFF09\n *\n * POST /api/sfmc/bind/confirm { code, xuid, name }\n * \u2192 \u6210\u529F { success: true, provider: \"qq\" | \"wechat\", binding }\n * \u2192 \u5931\u8D25 { success: false, error, hint? }\uFF08\u9519\u8BEF\u7801\u4E0E QQ \u539F\u63A5\u53E3\u4E00\u81F4\uFF0C\u65B0\u589E wechat_already_bound / player_already_bound_wechat\uFF09\n *\n * \u6309\u6CE8\u518C\u7684\u7ED1\u5B9A\u63D0\u4F9B\u65B9\u67E5\u627E\u6301\u6709\u8BE5\u77ED\u7801\u7684\u6E20\u9053\u5E76\u786E\u8BA4\uFF08OCP\uFF1A\u65B0\u589E\u6E20\u9053\u4E0D\u6539\u672C\u8DEF\u7531\uFF09\u3002\n * \u65E7\u63A5\u53E3 POST /api/sfmc/qq/bind/confirm \u4FDD\u7559\uFF0C\u4F9B\u65E7\u7248\u6E38\u620F\u4FA7\u811A\u672C\u4F7F\u7528\u3002\n */\n\nimport type http from \"node:http\";\nimport { confirmWithProviders, type BindProvider } from \"../domain/bind-providers.js\";\n\ninterface Deps {\n body: (req: http.IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: http.ServerResponse, data: Record<string, unknown>, status?: number) => void;\n /** \u5DF2\u6CE8\u518C\u7684\u7ED1\u5B9A\u63D0\u4F9B\u65B9\uFF08\u6309\u987A\u5E8F\u8BE2\u95EE\uFF09 */\n providers: () => readonly BindProvider[];\n}\n\n/**\n * \u521B\u5EFA\u7EDF\u4E00\u786E\u8BA4\u8DEF\u7531\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1Aindex.ts \u88C5\u914D\u5230\u5E73\u53F0\u8DEF\u7531\u94FE\u3002\n */\nfunction createBindConfirmRoutes({ body, json, providers }: Deps) {\n return async function handle({\n path,\n method,\n req,\n res,\n }: {\n path: string;\n method: string;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n }): Promise<boolean> {\n if (path !== \"/api/sfmc/bind/confirm\" || method !== \"POST\") return false;\n const data = await body(req);\n const code = String(data.code ?? \"\").trim();\n const xuid = String(data.xuid ?? data.player_xuid ?? \"\").trim();\n const name = String(data.name ?? data.player_name ?? \"\").trim();\n if (!code || !xuid) {\n json(res, { success: false, error: \"code_and_xuid_required\" }, 400);\n return true;\n }\n const result = confirmWithProviders(providers(), { code, xuid, name, now: Date.now() });\n if (result.ok) json(res, { success: true, provider: result.provider, binding: result.binding });\n else json(res, { success: false, error: result.error, ...(result.hint ? { hint: result.hint } : {}) }, result.status);\n return true;\n };\n}\n\nexport { createBindConfirmRoutes };\n"],
|
|
5
|
+
"mappings": "AAYA,SAAS,4BAA+C;AAaxD,SAAS,wBAAwB,EAAE,MAAM,MAAM,UAAU,GAAS;AAChE,SAAO,eAAe,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAKqB;AACnB,QAAI,SAAS,4BAA4B,WAAW,OAAQ,QAAO;AACnE,UAAM,OAAO,MAAM,KAAK,GAAG;AAC3B,UAAM,OAAO,OAAO,KAAK,QAAQ,EAAE,EAAE,KAAK;AAC1C,UAAM,OAAO,OAAO,KAAK,QAAQ,KAAK,eAAe,EAAE,EAAE,KAAK;AAC9D,UAAM,OAAO,OAAO,KAAK,QAAQ,KAAK,eAAe,EAAE,EAAE,KAAK;AAC9D,QAAI,CAAC,QAAQ,CAAC,MAAM;AAClB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,yBAAyB,GAAG,GAAG;AAClE,aAAO;AAAA,IACT;AACA,UAAM,SAAS,qBAAqB,UAAU,GAAG,EAAE,MAAM,MAAM,MAAM,KAAK,KAAK,IAAI,EAAE,CAAC;AACtF,QAAI,OAAO,GAAI,MAAK,KAAK,EAAE,SAAS,MAAM,UAAU,OAAO,UAAU,SAAS,OAAO,QAAQ,CAAC;AAAA,QACzF,MAAK,KAAK,EAAE,SAAS,OAAO,OAAO,OAAO,OAAO,GAAI,OAAO,OAAO,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC,EAAG,GAAG,OAAO,MAAM;AACpH,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/routes/qq-bind.d.ts
CHANGED
|
@@ -12,8 +12,10 @@ interface Deps {
|
|
|
12
12
|
query: QueryFn;
|
|
13
13
|
body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
|
|
14
14
|
json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
|
|
15
|
+
/** 短码是否已被任一绑定渠道占用;缺省只查 QQ 待确认表(与原行为一致) */
|
|
16
|
+
isCodeTaken?: (code: string) => boolean;
|
|
15
17
|
}
|
|
16
|
-
declare function createQqBindRoutes({ query, body, json }: Deps): ({ path, method, params, req, res, }: {
|
|
18
|
+
declare function createQqBindRoutes({ query, body, json, isCodeTaken }: Deps): ({ path, method, params, req, res, }: {
|
|
17
19
|
path: string;
|
|
18
20
|
method: string;
|
|
19
21
|
params: URLSearchParams;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qq-bind.d.ts","sourceRoot":"","sources":["../../src/routes/qq-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"qq-bind.d.ts","sourceRoot":"","sources":["../../src/routes/qq-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,QAAA,MAAM,WAAW,QAAmB,CAAC;AAErC,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,0CAA0C;IAC1C,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACzC;AAED,iBAAS,kBAAkB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,WAAqD,EAAE,EAAE,IAAI,yCAOzG;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CA0JrB;AAED,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/routes/qq-bind.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import { randomInt } from "node:crypto";
|
|
2
1
|
import { SQL } from "sql-template-strings";
|
|
2
|
+
import { BIND_CODE_TTL_MS, generateBindCode } from "../domain/bind-providers.js";
|
|
3
3
|
import { loadQqAccountProfile, saveQqAccountProfile } from "../domain/qq-account-profile.js";
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
function
|
|
7
|
-
let s = "";
|
|
8
|
-
for (let i = 0; i < CODE_LEN; i++) s += String(randomInt(0, 10));
|
|
9
|
-
return s;
|
|
10
|
-
}
|
|
11
|
-
function createQqBindRoutes({ query, body, json }) {
|
|
4
|
+
import { confirmQqBind, hasQqPendingCode } from "../domain/qq-bind.js";
|
|
5
|
+
const CODE_TTL_MS = BIND_CODE_TTL_MS;
|
|
6
|
+
function createQqBindRoutes({ query, body, json, isCodeTaken = (code) => hasQqPendingCode(query, code) }) {
|
|
12
7
|
return async function handle({
|
|
13
8
|
path,
|
|
14
9
|
method,
|
|
@@ -91,17 +86,7 @@ function createQqBindRoutes({ query, body, json }) {
|
|
|
91
86
|
}
|
|
92
87
|
const now = Date.now();
|
|
93
88
|
query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE qq_user_openid = ${openid} OR expires_at < ${now}`);
|
|
94
|
-
|
|
95
|
-
for (let attempt = 0; attempt < 8; attempt++) {
|
|
96
|
-
const candidate = genCode();
|
|
97
|
-
const clash = query(
|
|
98
|
-
SQL`SELECT 1 AS ok FROM sfmc_qq_bind_pending WHERE code = ${candidate} LIMIT 1`
|
|
99
|
-
);
|
|
100
|
-
if (clash.length === 0) {
|
|
101
|
-
code = candidate;
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
89
|
+
const code = generateBindCode(isCodeTaken);
|
|
105
90
|
if (!code) {
|
|
106
91
|
json(res, { success: false, error: "code_gen_failed" }, 500);
|
|
107
92
|
return true;
|
|
@@ -123,50 +108,9 @@ function createQqBindRoutes({ query, body, json }) {
|
|
|
123
108
|
json(res, { success: false, error: "code_and_xuid_required" }, 400);
|
|
124
109
|
return true;
|
|
125
110
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
);
|
|
130
|
-
const row = pending[0];
|
|
131
|
-
if (!row) {
|
|
132
|
-
json(res, { success: false, error: "invalid_code" }, 400);
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
if (Number(row.expires_at) < now) {
|
|
136
|
-
query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE code = ${code}`);
|
|
137
|
-
json(res, { success: false, error: "code_expired" }, 400);
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
const byOpenid = query(
|
|
141
|
-
SQL`SELECT player_xuid FROM sfmc_qq_bindings WHERE qq_user_openid = ${row.qq_user_openid} LIMIT 1`
|
|
142
|
-
);
|
|
143
|
-
if (byOpenid[0] && byOpenid[0].player_xuid !== xuid) {
|
|
144
|
-
json(res, { success: false, error: "qq_already_bound", hint: "\u8BF7\u5148\u5728 QQ \u4FA7\u89E3\u7ED1" }, 409);
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
const byXuid = query(
|
|
148
|
-
SQL`SELECT qq_user_openid FROM sfmc_qq_bindings WHERE player_xuid = ${xuid} LIMIT 1`
|
|
149
|
-
);
|
|
150
|
-
if (byXuid[0] && byXuid[0].qq_user_openid !== row.qq_user_openid) {
|
|
151
|
-
json(res, { success: false, error: "player_already_bound", hint: "\u8BF7\u5148\u89E3\u7ED1\u5F53\u524D QQ" }, 409);
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
query(SQL`DELETE FROM sfmc_qq_bindings WHERE qq_user_openid = ${row.qq_user_openid} OR player_xuid = ${xuid}`);
|
|
155
|
-
query(
|
|
156
|
-
SQL`INSERT INTO sfmc_qq_bindings (qq_user_openid, player_xuid, player_name, qq_backend, bound_at)
|
|
157
|
-
VALUES (${row.qq_user_openid}, ${xuid}, ${name}, ${row.qq_backend}, ${now})`
|
|
158
|
-
);
|
|
159
|
-
query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE code = ${code}`);
|
|
160
|
-
json(res, {
|
|
161
|
-
success: true,
|
|
162
|
-
binding: {
|
|
163
|
-
qq_user_openid: row.qq_user_openid,
|
|
164
|
-
player_xuid: xuid,
|
|
165
|
-
player_name: name,
|
|
166
|
-
qq_backend: row.qq_backend,
|
|
167
|
-
bound_at: now
|
|
168
|
-
}
|
|
169
|
-
});
|
|
111
|
+
const result = confirmQqBind(query, { code, xuid, name, now: Date.now() });
|
|
112
|
+
if (result.ok) json(res, { success: true, binding: result.binding });
|
|
113
|
+
else json(res, { success: false, error: result.error, ...result.hint ? { hint: result.hint } : {} }, result.status);
|
|
170
114
|
return true;
|
|
171
115
|
}
|
|
172
116
|
if (path === "/api/sfmc/qq/bind/unbind" && method === "POST") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/routes/qq-bind.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * routes/qq-bind.ts \u2014 QQ\u2194MC \u8EAB\u4EFD\u7ED1\u5B9A\uFF08\u5E73\u53F0 HTTP\uFF09\n *\n * POST /api/sfmc/qq/bind/request \u2014 QQ \u4FA7\u7533\u8BF7\u77ED\u7801\n * POST /api/sfmc/qq/bind/confirm \u2014 \u6E38\u620F\u4FA7\u786E\u8BA4\n * POST /api/sfmc/qq/bind/unbind \u2014 \u89E3\u7ED1\n * GET /api/sfmc/qq/bind/me \u2014 \u67E5\u8BE2\n */\n\nimport {
|
|
5
|
-
"mappings": "AASA,SAAS,
|
|
4
|
+
"sourcesContent": ["/**\n * routes/qq-bind.ts \u2014 QQ\u2194MC \u8EAB\u4EFD\u7ED1\u5B9A\uFF08\u5E73\u53F0 HTTP\uFF09\n *\n * POST /api/sfmc/qq/bind/request \u2014 QQ \u4FA7\u7533\u8BF7\u77ED\u7801\n * POST /api/sfmc/qq/bind/confirm \u2014 \u6E38\u620F\u4FA7\u786E\u8BA4\n * POST /api/sfmc/qq/bind/unbind \u2014 \u89E3\u7ED1\n * GET /api/sfmc/qq/bind/me \u2014 \u67E5\u8BE2\n */\n\nimport { SQL } from \"sql-template-strings\";\nimport { BIND_CODE_TTL_MS, generateBindCode } from \"../domain/bind-providers.js\";\nimport { loadQqAccountProfile, saveQqAccountProfile } from \"../domain/qq-account-profile.js\";\nimport { confirmQqBind, hasQqPendingCode } from \"../domain/qq-bind.js\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\nconst CODE_TTL_MS = BIND_CODE_TTL_MS;\n\ninterface Deps {\n query: QueryFn;\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n /** \u77ED\u7801\u662F\u5426\u5DF2\u88AB\u4EFB\u4E00\u7ED1\u5B9A\u6E20\u9053\u5360\u7528\uFF1B\u7F3A\u7701\u53EA\u67E5 QQ \u5F85\u786E\u8BA4\u8868\uFF08\u4E0E\u539F\u884C\u4E3A\u4E00\u81F4\uFF09 */\n isCodeTaken?: (code: string) => boolean;\n}\n\nfunction createQqBindRoutes({ query, body, json, isCodeTaken = (code) => hasQqPendingCode(query, code) }: Deps) {\n return async function handle({\n path,\n method,\n params,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n if (!path.startsWith(\"/api/sfmc/qq/bind\")) return false;\n\n if (path === \"/api/sfmc/qq/bind/me\" && method === \"GET\") {\n const openid = String(params.get(\"openid\") ?? \"\").trim();\n const xuid = String(params.get(\"xuid\") ?? \"\").trim();\n if (!openid && !xuid) {\n json(res, { success: false, error: \"openid_or_xuid_required\" }, 400);\n return true;\n }\n const rows = openid\n ? (query(SQL`SELECT * FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid} LIMIT 1`) as Array<\n Record<string, unknown>\n >)\n : (query(SQL`SELECT * FROM sfmc_qq_bindings WHERE player_xuid = ${xuid} LIMIT 1`) as Array<\n Record<string, unknown>\n >);\n const row = rows[0] ?? null;\n const playerId = row ? String(row.player_xuid ?? \"\") : \"\";\n const playerName = row ? String(row.player_name ?? \"\") : \"\";\n json(res, {\n success: true,\n bound: !!row,\n binding: row\n ? {\n qq_user_openid: row.qq_user_openid,\n player_xuid: row.player_xuid,\n player_name: row.player_name,\n qq_backend: row.qq_backend,\n bound_at: row.bound_at,\n }\n : null,\n ...(row ? { profile: loadQqAccountProfile(query, playerId, playerName) } : {}),\n });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/bind/profile\" && method === \"POST\") {\n const data = await body(req);\n const playerId = String(data.xuid ?? data.player_xuid ?? \"\").trim();\n const playerName = String(data.name ?? data.player_name ?? \"\").trim();\n if (!playerId) {\n json(res, { success: false, error: \"xuid_required\" }, 400);\n return true;\n }\n const optionalNumber = (value: unknown): number | null => {\n if (value == null || value === \"\") return null;\n const n = Number(value);\n return Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;\n };\n saveQqAccountProfile(query, {\n playerId,\n playerName,\n balance: optionalNumber(data.balance),\n todaySeconds: optionalNumber(data.today_seconds),\n monthSeconds: optionalNumber(data.month_seconds),\n totalSeconds: optionalNumber(data.total_seconds),\n });\n json(res, { success: true });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/bind/request\" && method === \"POST\") {\n const data = await body(req);\n const openid = String(data.openid ?? data.qq_user_openid ?? \"\").trim();\n const backend = String(data.qq_backend ?? \"official\").trim() || \"official\";\n if (!openid) {\n json(res, { success: false, error: \"openid_required\" }, 400);\n return true;\n }\n // \u5DF2\u7ED1\u5B9A\u5219\u62D2\u7EDD\u518D\u7533\u8BF7\uFF08\u987B\u5148\u89E3\u7ED1\uFF09\n const existing = query(\n SQL`SELECT player_name FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid} LIMIT 1`\n ) as Array<{ player_name: string }>;\n if (existing[0]) {\n json(\n res,\n {\n success: false,\n error: \"already_bound\",\n player_name: existing[0].player_name,\n },\n 409\n );\n return true;\n }\n\n const now = Date.now();\n query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE qq_user_openid = ${openid} OR expires_at < ${now}`);\n\n // \u67E5\u91CD\u8303\u56F4\u7531\u5165\u53E3\u6CE8\u5165\uFF08QQ + \u5FAE\u4FE1\u5F85\u786E\u8BA4\u8868\uFF09\uFF0C\u4FDD\u8BC1\u7EDF\u4E00\u786E\u8BA4\u5165\u53E3\u6309\u77ED\u7801\u80FD\u552F\u4E00\u5B9A\u4F4D\u6E20\u9053\n const code = generateBindCode(isCodeTaken);\n if (!code) {\n json(res, { success: false, error: \"code_gen_failed\" }, 500);\n return true;\n }\n\n const expiresAt = now + CODE_TTL_MS;\n query(\n SQL`INSERT INTO sfmc_qq_bind_pending (code, qq_user_openid, qq_backend, created_at, expires_at)\n VALUES (${code}, ${openid}, ${backend}, ${now}, ${expiresAt})`\n );\n json(res, { success: true, code, expires_at: expiresAt, ttl_ms: CODE_TTL_MS });\n return true;\n }\n\n if (path === \"/api/sfmc/qq/bind/confirm\" && method === \"POST\") {\n const data = await body(req);\n const code = String(data.code ?? \"\").trim();\n const xuid = String(data.xuid ?? data.player_xuid ?? \"\").trim();\n const name = String(data.name ?? data.player_name ?? \"\").trim();\n if (!code || !xuid) {\n json(res, { success: false, error: \"code_and_xuid_required\" }, 400);\n return true;\n }\n\n // \u786E\u8BA4\u903B\u8F91\u4E0E\u7EDF\u4E00\u5165\u53E3 /api/sfmc/bind/confirm \u5171\u7528 domain/qq-bind.ts\uFF08DRY\uFF09\uFF0C\u54CD\u5E94\u7ED3\u6784\u4FDD\u6301\u4E0D\u53D8\n const result = confirmQqBind(query, { code, xuid, name, now: Date.now() });\n if (result.ok) json(res, { success: true, binding: result.binding });\n else json(res, { success: false, error: result.error, ...(result.hint ? { hint: result.hint } : {}) }, result.status);\n return true;\n }\n\n if (path === \"/api/sfmc/qq/bind/unbind\" && method === \"POST\") {\n const data = await body(req);\n const openid = String(data.openid ?? data.qq_user_openid ?? \"\").trim();\n const xuid = String(data.xuid ?? data.player_xuid ?? \"\").trim();\n if (!openid && !xuid) {\n json(res, { success: false, error: \"openid_or_xuid_required\" }, 400);\n return true;\n }\n let removed = 0;\n if (openid) {\n const before = query(\n SQL`SELECT qq_user_openid FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid}`\n ) as unknown[];\n query(SQL`DELETE FROM sfmc_qq_bindings WHERE qq_user_openid = ${openid}`);\n query(SQL`DELETE FROM sfmc_qq_bind_pending WHERE qq_user_openid = ${openid}`);\n removed += before.length;\n }\n if (xuid) {\n const before = query(\n SQL`SELECT qq_user_openid FROM sfmc_qq_bindings WHERE player_xuid = ${xuid}`\n ) as unknown[];\n query(SQL`DELETE FROM sfmc_qq_bindings WHERE player_xuid = ${xuid}`);\n removed += before.length;\n }\n json(res, { success: true, unbound: removed > 0, removed });\n return true;\n }\n\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n };\n}\n\nexport { createQqBindRoutes, CODE_TTL_MS };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/routes/status.ts"],"names":[],"mappings":"AAAA,yCAAyC;AAEzC,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/routes/status.ts"],"names":[],"mappings":"AAAA,yCAAyC;AAEzC,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAK5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIhD,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAE7D,QAAA,MAAM,QAAQ,QAAS,CAAC;AAQxB,UAAU,IAAI;IACZ,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,iBAAS,kBAAkB,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,IAAI,GAAG,UAAU,CAAC,YAAY,CAAC,CA2IjG;AAED,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/routes/status.js
CHANGED
|
@@ -2,6 +2,7 @@ import { collectSystemStatus } from "../domain/system-status.js";
|
|
|
2
2
|
import { PROJECT_ROOT } from "../project-root.js";
|
|
3
3
|
import { METRICS_RAW_WINDOW_MS, MetricsHistory, ResourceHistory, clampMetricsSpan, parseMetrics } from "../domain/metrics.js";
|
|
4
4
|
import { collectProcessResources } from "../domain/process-resources.js";
|
|
5
|
+
import { collectTrackedProcesses } from "../domain/tracked-processes.js";
|
|
5
6
|
import fs from "node:fs";
|
|
6
7
|
import pathModule from "node:path";
|
|
7
8
|
import { stateDir } from "@sfmc-bds/sdk/node/config";
|
|
@@ -27,14 +28,21 @@ function createStatusRoutes({ collectSystem, projectRoot, query }) {
|
|
|
27
28
|
db: await collectProcessResources(system.db.pid, system.db.running),
|
|
28
29
|
bds: owned ? await collectProcessResources(system.bds.pid, system.bds.running) : null
|
|
29
30
|
};
|
|
31
|
+
const skip = /* @__PURE__ */ new Set();
|
|
32
|
+
if (processes.db.pid > 0) skip.add(processes.db.pid);
|
|
33
|
+
if (processes.bds && processes.bds.pid > 0) skip.add(processes.bds.pid);
|
|
34
|
+
const parts = await collectTrackedProcesses(root, skip);
|
|
35
|
+
const partsMb = {};
|
|
36
|
+
for (const part of parts) partsMb[part.key] = part.memoryMb;
|
|
30
37
|
resources?.append({
|
|
31
38
|
recordedAt: Date.now(),
|
|
32
39
|
hostUsedMb: system.host.memory.usedMb,
|
|
33
40
|
dbMb: processes.db.memoryMb,
|
|
34
|
-
bdsMb: processes.bds?.memoryMb ?? null
|
|
41
|
+
bdsMb: processes.bds?.memoryMb ?? null,
|
|
42
|
+
...Object.keys(partsMb).length ? { partsMb } : {}
|
|
35
43
|
});
|
|
36
44
|
lastCapture = Date.now();
|
|
37
|
-
return processes;
|
|
45
|
+
return { ...processes, ...parts.length ? { parts } : {} };
|
|
38
46
|
};
|
|
39
47
|
let systemCache;
|
|
40
48
|
let collecting;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/routes/status.ts"],
|
|
4
|
-
"sourcesContent": ["/** GET /api/sfmc/status \u4E0E BDS \u5B9E\u65F6\u5FEB\u7167\u5165\u53E3\u3002 */\n\nimport { collectSystemStatus, type SystemStatusSnapshot } from \"../domain/system-status.js\";\nimport { PROJECT_ROOT } from \"../project-root.js\";\nimport { METRICS_RAW_WINDOW_MS, MetricsHistory, ResourceHistory, clampMetricsSpan, parseMetrics, type MetricsSample } from \"../domain/metrics.js\";\nimport { collectProcessResources } from \"../domain/process-resources.js\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\nimport fs from \"node:fs\";\nimport pathModule from \"node:path\";\nimport { stateDir } from \"@sfmc-bds/sdk/node/config\";\nimport { body, json, type RouteFactory } from \"./_shared.js\";\n\nconst FRESH_MS = 60_000;\n\ninterface LiveSnapshot {\n online: Array<{ name: string }>;\n world: { day: number; difficulty: string };\n updatedAt: number;\n}\n\ninterface Deps {\n collectSystem?: (projectRoot: string) => Promise<SystemStatusSnapshot>;\n projectRoot?: string;\n query?: QueryFn;\n}\n\nfunction createStatusRoutes({ collectSystem, projectRoot, query }: Deps): ReturnType<RouteFactory> {\n const root = projectRoot ?? PROJECT_ROOT;\n const collect = collectSystem ?? collectSystemStatus;\n let live: LiveSnapshot | null = null;\n let metrics: MetricsSample | null = null;\n const history = query ? new MetricsHistory(query) : undefined;\n const resources = query ? new ResourceHistory(query) : undefined;\n let lastCapture = 0;\n const captureResources = async (system: SystemStatusSnapshot | null) => {\n if (!system) return null;\n let ownedPid = 0;\n try { ownedPid = Number(fs.readFileSync(pathModule.join(stateDir(root), \"bds.pid\"), \"utf8\").trim()); } catch { /* \u672A\u6258\u7BA1\u5B9E\u4F8B\u4E0D\u731C\u6D4B\u8FDB\u7A0B\u5F52\u5C5E\u3002 */ }\n const owned = Number.isSafeInteger(ownedPid) && ownedPid > 0 && system.bds.pid === ownedPid;\n const processes = {\n db: await collectProcessResources(system.db.pid, system.db.running),\n bds: owned ? await collectProcessResources(system.bds.pid, system.bds.running) : null,\n };\n resources?.append({\n recordedAt: Date.now(),\n hostUsedMb: system.host.memory.usedMb,\n dbMb: processes.db.memoryMb,\n bdsMb: processes.bds?.memoryMb ?? null,\n });\n lastCapture = Date.now();\n return processes;\n };\n let systemCache: { data: SystemStatusSnapshot; at: number } | undefined;\n let collecting: Promise<SystemStatusSnapshot> | undefined;\n const getSystem = async () => {\n if (systemCache && Date.now() - systemCache.at < 5000) return systemCache.data;\n collecting ??= collect(root).then(data => { systemCache = { data, at: Date.now() }; return data; }).finally(() => { collecting = undefined; });\n return collecting;\n };\n if (resources) {\n const timer = setInterval(() => {\n if (Date.now() - lastCapture < 12_000) return;\n void getSystem().then(system => captureResources(system)).catch(() => undefined);\n }, 15_000);\n timer.unref();\n }\n\n return async function handle({ path, method, params, req, res }): Promise<boolean> {\n if (path === \"/api/sfmc/status/live\" || path === \"/api/sfmc/metrics/live\") {\n if (method !== \"POST\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n const data = await body(req);\n const raw = data.players;\n const day = data.day;\n const difficulty = data.difficulty;\n if (\n !Array.isArray(raw) || raw.length > 1000 ||\n !raw.every((name) => typeof name === \"string\" && name.trim().length > 0 && name.length <= 64) ||\n typeof day !== \"number\" || !Number.isSafeInteger(day) || day < 0 ||\n typeof difficulty !== \"string\" || difficulty.length > 32\n ) {\n json(res, { success: false, error: \"invalid_live_status\" }, 400);\n return true;\n }\n if (path === \"/api/sfmc/metrics/live\") {\n const sample = parseMetrics(data, Date.now());\n if (!sample) { json(res, { success: false, error: \"invalid_metrics\" }, 400); return true; }\n history?.append(sample);\n metrics = sample;\n }\n live = {\n online: raw.map((name: string) => ({ name: name.trim() })),\n world: { day, difficulty },\n updatedAt: Date.now(),\n };\n json(res, { success: true });\n return true;\n }\n\n if (path !== \"/api/sfmc/status\" && path !== \"/api/sfmc/metrics\") return false;\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n\n let system: SystemStatusSnapshot | null = null;\n try {\n system = await getSystem();\n } catch {\n system = null;\n }\n\n const now = Date.now();\n const bds = system?.bds;\n const bdsAgeMs = typeof bds?.uptimeSec === \"number\" ? bds.uptimeSec * 1000 : null;\n const snapshot = live;\n const fresh = snapshot !== null &&\n bds?.state === \"running\" &&\n now - snapshot.updatedAt <= FRESH_MS &&\n (bdsAgeMs === null || now - snapshot.updatedAt <= bdsAgeMs + 2_000);\n const current = fresh ? snapshot : null;\n\n if (path === \"/api/sfmc/metrics\") {\n // \u8D44\u6E90\u6307\u6807\u53EA\u8BA4\u5F53\u524D\u5B9E\u4F8B\u7684 PID\uFF0C\u4E0D\u80FD\u4F7F\u7528\u5168\u673A\u626B\u63CF\u627E\u5230\u7684\u53E6\u4E00\u5957 BDS\u3002\n let ownedPid = 0;\n try { ownedPid = Number(fs.readFileSync(pathModule.join(stateDir(root), \"bds.pid\"), \"utf8\").trim()); } catch { /* \u672A\u6258\u7BA1\u5B9E\u4F8B\u4E0D\u731C\u6D4B\u8FDB\u7A0B\u5F52\u5C5E\u3002 */ }\n const owned = Number.isSafeInteger(ownedPid) && ownedPid > 0 && bds?.pid === ownedPid;\n const metricFresh = owned && metrics !== null && bds?.state === \"running\" && now - metrics.recordedAt <= 60_000 &&\n (bdsAgeMs === null || now - metrics.recordedAt <= bdsAgeMs + 2000);\n const processes = await captureResources(system);\n let span = METRICS_RAW_WINDOW_MS;\n const rawSpan = params.get(\"spanMs\");\n if (rawSpan) {\n try { span = clampMetricsSpan(Number(rawSpan)); } catch {\n json(res, { success: false, error: \"invalid_span\" }, 400);\n return true;\n }\n }\n json(res, { fresh: metricFresh, current: metricFresh ? metrics : null, updatedAt: metrics?.recordedAt ?? null,\n history: history?.read(now, span) ?? [], host: system?.host ?? null, processes, resourcesUpdatedAt: system ? Date.now() : null,\n resourceHistory: resources?.read(now, span) ?? [],\n note: metricFresh ? undefined : bds?.state === \"stopped\" ? \"\u670D\u52A1\u5668\u672A\u8FD0\u884C\" : \"\u7B49\u5F85\u5E73\u53F0\u6307\u6807\u540C\u6B65\" });\n return true;\n }\n\n json(res, {\n online: current?.online ?? [],\n world: current?.world ?? null,\n updatedAt: current?.updatedAt ?? null,\n host: system?.host ?? null,\n processes: system ? { db: system.db, bds: system.bds } : null,\n source: \"bds_script_live\",\n note: fresh ? undefined : bds?.state === \"stopped\" ? \"\u670D\u52A1\u5668\u672A\u8FD0\u884C\" : \"\u7B49\u5F85\u6E38\u620F\u5B9E\u65F6\u72B6\u6001\u540C\u6B65\",\n });\n return true;\n };\n}\n\nexport { createStatusRoutes, FRESH_MS };\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,2BAAsD;AAC/D,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB,gBAAgB,iBAAiB,kBAAkB,oBAAwC;AAC3H,SAAS,+BAA+B;AAExC,OAAO,QAAQ;AACf,OAAO,gBAAgB;AACvB,SAAS,gBAAgB;AACzB,SAAS,MAAM,YAA+B;AAE9C,MAAM,WAAW;AAcjB,SAAS,mBAAmB,EAAE,eAAe,aAAa,MAAM,GAAmC;AACjG,QAAM,OAAO,eAAe;AAC5B,QAAM,UAAU,iBAAiB;AACjC,MAAI,OAA4B;AAChC,MAAI,UAAgC;AACpC,QAAM,UAAU,QAAQ,IAAI,eAAe,KAAK,IAAI;AACpD,QAAM,YAAY,QAAQ,IAAI,gBAAgB,KAAK,IAAI;AACvD,MAAI,cAAc;AAClB,QAAM,mBAAmB,OAAO,WAAwC;AACtE,QAAI,CAAC,OAAQ,QAAO;AACpB,QAAI,WAAW;AACf,QAAI;AAAE,iBAAW,OAAO,GAAG,aAAa,WAAW,KAAK,SAAS,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,KAAK,CAAC;AAAA,IAAG,QAAQ;AAAA,IAAsB;AACnI,UAAM,QAAQ,OAAO,cAAc,QAAQ,KAAK,WAAW,KAAK,OAAO,IAAI,QAAQ;AACnF,UAAM,YAAY;AAAA,MAChB,IAAI,MAAM,wBAAwB,OAAO,GAAG,KAAK,OAAO,GAAG,OAAO;AAAA,MAClE,KAAK,QAAQ,MAAM,wBAAwB,OAAO,IAAI,KAAK,OAAO,IAAI,OAAO,IAAI;AAAA,IACnF;AACA,eAAW,OAAO;AAAA,MAChB,YAAY,KAAK,IAAI;AAAA,MACrB,YAAY,OAAO,KAAK,OAAO;AAAA,MAC/B,MAAM,UAAU,GAAG;AAAA,MACnB,OAAO,UAAU,KAAK,YAAY;AAAA,
|
|
4
|
+
"sourcesContent": ["/** GET /api/sfmc/status \u4E0E BDS \u5B9E\u65F6\u5FEB\u7167\u5165\u53E3\u3002 */\n\nimport { collectSystemStatus, type SystemStatusSnapshot } from \"../domain/system-status.js\";\nimport { PROJECT_ROOT } from \"../project-root.js\";\nimport { METRICS_RAW_WINDOW_MS, MetricsHistory, ResourceHistory, clampMetricsSpan, parseMetrics, type MetricsSample } from \"../domain/metrics.js\";\nimport { collectProcessResources } from \"../domain/process-resources.js\";\nimport { collectTrackedProcesses } from \"../domain/tracked-processes.js\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\nimport fs from \"node:fs\";\nimport pathModule from \"node:path\";\nimport { stateDir } from \"@sfmc-bds/sdk/node/config\";\nimport { body, json, type RouteFactory } from \"./_shared.js\";\n\nconst FRESH_MS = 60_000;\n\ninterface LiveSnapshot {\n online: Array<{ name: string }>;\n world: { day: number; difficulty: string };\n updatedAt: number;\n}\n\ninterface Deps {\n collectSystem?: (projectRoot: string) => Promise<SystemStatusSnapshot>;\n projectRoot?: string;\n query?: QueryFn;\n}\n\nfunction createStatusRoutes({ collectSystem, projectRoot, query }: Deps): ReturnType<RouteFactory> {\n const root = projectRoot ?? PROJECT_ROOT;\n const collect = collectSystem ?? collectSystemStatus;\n let live: LiveSnapshot | null = null;\n let metrics: MetricsSample | null = null;\n const history = query ? new MetricsHistory(query) : undefined;\n const resources = query ? new ResourceHistory(query) : undefined;\n let lastCapture = 0;\n const captureResources = async (system: SystemStatusSnapshot | null) => {\n if (!system) return null;\n let ownedPid = 0;\n try { ownedPid = Number(fs.readFileSync(pathModule.join(stateDir(root), \"bds.pid\"), \"utf8\").trim()); } catch { /* \u672A\u6258\u7BA1\u5B9E\u4F8B\u4E0D\u731C\u6D4B\u8FDB\u7A0B\u5F52\u5C5E\u3002 */ }\n const owned = Number.isSafeInteger(ownedPid) && ownedPid > 0 && system.bds.pid === ownedPid;\n const processes = {\n db: await collectProcessResources(system.db.pid, system.db.running),\n bds: owned ? await collectProcessResources(system.bds.pid, system.bds.running) : null,\n };\n const skip = new Set<number>();\n if (processes.db.pid > 0) skip.add(processes.db.pid);\n if (processes.bds && processes.bds.pid > 0) skip.add(processes.bds.pid);\n const parts = await collectTrackedProcesses(root, skip);\n const partsMb: Record<string, number> = {};\n for (const part of parts) partsMb[part.key] = part.memoryMb;\n resources?.append({\n recordedAt: Date.now(),\n hostUsedMb: system.host.memory.usedMb,\n dbMb: processes.db.memoryMb,\n bdsMb: processes.bds?.memoryMb ?? null,\n ...(Object.keys(partsMb).length ? { partsMb } : {}),\n });\n lastCapture = Date.now();\n return { ...processes, ...(parts.length ? { parts } : {}) };\n };\n let systemCache: { data: SystemStatusSnapshot; at: number } | undefined;\n let collecting: Promise<SystemStatusSnapshot> | undefined;\n const getSystem = async () => {\n if (systemCache && Date.now() - systemCache.at < 5000) return systemCache.data;\n collecting ??= collect(root).then(data => { systemCache = { data, at: Date.now() }; return data; }).finally(() => { collecting = undefined; });\n return collecting;\n };\n if (resources) {\n const timer = setInterval(() => {\n if (Date.now() - lastCapture < 12_000) return;\n void getSystem().then(system => captureResources(system)).catch(() => undefined);\n }, 15_000);\n timer.unref();\n }\n\n return async function handle({ path, method, params, req, res }): Promise<boolean> {\n if (path === \"/api/sfmc/status/live\" || path === \"/api/sfmc/metrics/live\") {\n if (method !== \"POST\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n const data = await body(req);\n const raw = data.players;\n const day = data.day;\n const difficulty = data.difficulty;\n if (\n !Array.isArray(raw) || raw.length > 1000 ||\n !raw.every((name) => typeof name === \"string\" && name.trim().length > 0 && name.length <= 64) ||\n typeof day !== \"number\" || !Number.isSafeInteger(day) || day < 0 ||\n typeof difficulty !== \"string\" || difficulty.length > 32\n ) {\n json(res, { success: false, error: \"invalid_live_status\" }, 400);\n return true;\n }\n if (path === \"/api/sfmc/metrics/live\") {\n const sample = parseMetrics(data, Date.now());\n if (!sample) { json(res, { success: false, error: \"invalid_metrics\" }, 400); return true; }\n history?.append(sample);\n metrics = sample;\n }\n live = {\n online: raw.map((name: string) => ({ name: name.trim() })),\n world: { day, difficulty },\n updatedAt: Date.now(),\n };\n json(res, { success: true });\n return true;\n }\n\n if (path !== \"/api/sfmc/status\" && path !== \"/api/sfmc/metrics\") return false;\n if (method !== \"GET\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n\n let system: SystemStatusSnapshot | null = null;\n try {\n system = await getSystem();\n } catch {\n system = null;\n }\n\n const now = Date.now();\n const bds = system?.bds;\n const bdsAgeMs = typeof bds?.uptimeSec === \"number\" ? bds.uptimeSec * 1000 : null;\n const snapshot = live;\n const fresh = snapshot !== null &&\n bds?.state === \"running\" &&\n now - snapshot.updatedAt <= FRESH_MS &&\n (bdsAgeMs === null || now - snapshot.updatedAt <= bdsAgeMs + 2_000);\n const current = fresh ? snapshot : null;\n\n if (path === \"/api/sfmc/metrics\") {\n // \u8D44\u6E90\u6307\u6807\u53EA\u8BA4\u5F53\u524D\u5B9E\u4F8B\u7684 PID\uFF0C\u4E0D\u80FD\u4F7F\u7528\u5168\u673A\u626B\u63CF\u627E\u5230\u7684\u53E6\u4E00\u5957 BDS\u3002\n let ownedPid = 0;\n try { ownedPid = Number(fs.readFileSync(pathModule.join(stateDir(root), \"bds.pid\"), \"utf8\").trim()); } catch { /* \u672A\u6258\u7BA1\u5B9E\u4F8B\u4E0D\u731C\u6D4B\u8FDB\u7A0B\u5F52\u5C5E\u3002 */ }\n const owned = Number.isSafeInteger(ownedPid) && ownedPid > 0 && bds?.pid === ownedPid;\n const metricFresh = owned && metrics !== null && bds?.state === \"running\" && now - metrics.recordedAt <= 60_000 &&\n (bdsAgeMs === null || now - metrics.recordedAt <= bdsAgeMs + 2000);\n const processes = await captureResources(system);\n let span = METRICS_RAW_WINDOW_MS;\n const rawSpan = params.get(\"spanMs\");\n if (rawSpan) {\n try { span = clampMetricsSpan(Number(rawSpan)); } catch {\n json(res, { success: false, error: \"invalid_span\" }, 400);\n return true;\n }\n }\n json(res, { fresh: metricFresh, current: metricFresh ? metrics : null, updatedAt: metrics?.recordedAt ?? null,\n history: history?.read(now, span) ?? [], host: system?.host ?? null, processes, resourcesUpdatedAt: system ? Date.now() : null,\n resourceHistory: resources?.read(now, span) ?? [],\n note: metricFresh ? undefined : bds?.state === \"stopped\" ? \"\u670D\u52A1\u5668\u672A\u8FD0\u884C\" : \"\u7B49\u5F85\u5E73\u53F0\u6307\u6807\u540C\u6B65\" });\n return true;\n }\n\n json(res, {\n online: current?.online ?? [],\n world: current?.world ?? null,\n updatedAt: current?.updatedAt ?? null,\n host: system?.host ?? null,\n processes: system ? { db: system.db, bds: system.bds } : null,\n source: \"bds_script_live\",\n note: fresh ? undefined : bds?.state === \"stopped\" ? \"\u670D\u52A1\u5668\u672A\u8FD0\u884C\" : \"\u7B49\u5F85\u6E38\u620F\u5B9E\u65F6\u72B6\u6001\u540C\u6B65\",\n });\n return true;\n };\n}\n\nexport { createStatusRoutes, FRESH_MS };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* routes/wechat-activity.ts — 微信玩家查看自己的行为记录(平台 HTTP,仅本机)
|
|
3
|
+
*
|
|
4
|
+
* GET /api/sfmc/wechat/activity?openid=&limit=&offset=&eventTypePrefix=&from=&to=
|
|
5
|
+
* → { success, bound, installed, records, total, limit, offset }
|
|
6
|
+
*
|
|
7
|
+
* 使用场景:mp-gateway 的玩家端 /player/activity 代小程序「我的行为记录」页调用。
|
|
8
|
+
* 先按 openid 找到绑定的游戏账号,再把执行者强制限定为该账号(绑定表的 player_xuid 即游戏内 player.id,
|
|
9
|
+
* 与行为日志的 actor_id 相同),调用方传入的 actorId / actorName 一律忽略,玩家只能看到自己的记录。
|
|
10
|
+
* openid 由 mp-gateway 通过微信 code2session 取得,信任边界与 routes/wechat-bind.ts 相同。
|
|
11
|
+
*/
|
|
12
|
+
import type http from "node:http";
|
|
13
|
+
import type { QueryFn } from "../lib/sqlite.js";
|
|
14
|
+
interface Deps {
|
|
15
|
+
query: QueryFn;
|
|
16
|
+
json: (res: http.ServerResponse, data: Record<string, unknown>, status?: number) => void;
|
|
17
|
+
}
|
|
18
|
+
/** 路由上下文(与其他平台路由一致) */
|
|
19
|
+
interface Ctx {
|
|
20
|
+
path: string;
|
|
21
|
+
method: string;
|
|
22
|
+
params: URLSearchParams;
|
|
23
|
+
req: http.IncomingMessage;
|
|
24
|
+
res: http.ServerResponse;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 创建微信玩家行为记录路由。
|
|
28
|
+
* 使用场景:index.ts 装配到平台路由链。
|
|
29
|
+
*/
|
|
30
|
+
declare function createWechatActivityRoutes({ query, json }: Deps): ({ path, method, params, res }: Ctx) => Promise<boolean>;
|
|
31
|
+
export { createWechatActivityRoutes };
|
|
32
|
+
//# sourceMappingURL=wechat-activity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wechat-activity.d.ts","sourceRoot":"","sources":["../../src/routes/wechat-activity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1F;AAED,uBAAuB;AACvB,UAAU,GAAG;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,IAAI,CAAC,eAAe,CAAC;IAC1B,GAAG,EAAE,IAAI,CAAC,cAAc,CAAC;CAC1B;AAID;;;GAGG;AACH,iBAAS,0BAA0B,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,mCACK,GAAG,KAAG,OAAO,CAAC,OAAO,CAAC,CAgCnF;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ActivityQueryError, parseActivityFilter, parseActivityPaging, queryActivityPage } from "../domain/activity-log.js";
|
|
2
|
+
import { findWechatBindingByOpenid } from "../domain/wechat-bind.js";
|
|
3
|
+
const ROUTE = "/api/sfmc/wechat/activity";
|
|
4
|
+
function createWechatActivityRoutes({ query, json }) {
|
|
5
|
+
return async function handle({ path, method, params, res }) {
|
|
6
|
+
if (path !== ROUTE) return false;
|
|
7
|
+
if (method !== "GET") {
|
|
8
|
+
json(res, { success: false, error: "method_not_allowed" }, 405);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
const openid = String(params.get("openid") ?? "").trim();
|
|
12
|
+
if (!openid) {
|
|
13
|
+
json(res, { success: false, error: "openid_required" }, 400);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
const binding = findWechatBindingByOpenid(query, openid);
|
|
17
|
+
if (!binding?.player_xuid) {
|
|
18
|
+
json(res, { success: true, bound: false, installed: false, records: [], total: 0, limit: 0, offset: 0 });
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const raw = Object.fromEntries(params.entries());
|
|
22
|
+
let page;
|
|
23
|
+
try {
|
|
24
|
+
const { actorId: _actorId, actorName: _actorName, ...filter } = parseActivityFilter(raw);
|
|
25
|
+
page = queryActivityPage(query, { ...filter, actorId: binding.player_xuid }, parseActivityPaging(raw));
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error instanceof ActivityQueryError) {
|
|
28
|
+
json(res, { success: false, error: "invalid_params", message: error.message }, 400);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
json(res, { success: true, bound: true, ...page });
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
createWechatActivityRoutes
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=wechat-activity.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/routes/wechat-activity.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * routes/wechat-activity.ts \u2014 \u5FAE\u4FE1\u73A9\u5BB6\u67E5\u770B\u81EA\u5DF1\u7684\u884C\u4E3A\u8BB0\u5F55\uFF08\u5E73\u53F0 HTTP\uFF0C\u4EC5\u672C\u673A\uFF09\n *\n * GET /api/sfmc/wechat/activity?openid=&limit=&offset=&eventTypePrefix=&from=&to=\n * \u2192 { success, bound, installed, records, total, limit, offset }\n *\n * \u4F7F\u7528\u573A\u666F\uFF1Amp-gateway \u7684\u73A9\u5BB6\u7AEF /player/activity \u4EE3\u5C0F\u7A0B\u5E8F\u300C\u6211\u7684\u884C\u4E3A\u8BB0\u5F55\u300D\u9875\u8C03\u7528\u3002\n * \u5148\u6309 openid \u627E\u5230\u7ED1\u5B9A\u7684\u6E38\u620F\u8D26\u53F7\uFF0C\u518D\u628A\u6267\u884C\u8005\u5F3A\u5236\u9650\u5B9A\u4E3A\u8BE5\u8D26\u53F7\uFF08\u7ED1\u5B9A\u8868\u7684 player_xuid \u5373\u6E38\u620F\u5185 player.id\uFF0C\n * \u4E0E\u884C\u4E3A\u65E5\u5FD7\u7684 actor_id \u76F8\u540C\uFF09\uFF0C\u8C03\u7528\u65B9\u4F20\u5165\u7684 actorId / actorName \u4E00\u5F8B\u5FFD\u7565\uFF0C\u73A9\u5BB6\u53EA\u80FD\u770B\u5230\u81EA\u5DF1\u7684\u8BB0\u5F55\u3002\n * openid \u7531 mp-gateway \u901A\u8FC7\u5FAE\u4FE1 code2session \u53D6\u5F97\uFF0C\u4FE1\u4EFB\u8FB9\u754C\u4E0E routes/wechat-bind.ts \u76F8\u540C\u3002\n */\n\nimport type http from \"node:http\";\nimport { ActivityQueryError, parseActivityFilter, parseActivityPaging, queryActivityPage } from \"../domain/activity-log.js\";\nimport { findWechatBindingByOpenid } from \"../domain/wechat-bind.js\";\nimport type { QueryFn } from \"../lib/sqlite.js\";\n\ninterface Deps {\n query: QueryFn;\n json: (res: http.ServerResponse, data: Record<string, unknown>, status?: number) => void;\n}\n\n/** \u8DEF\u7531\u4E0A\u4E0B\u6587\uFF08\u4E0E\u5176\u4ED6\u5E73\u53F0\u8DEF\u7531\u4E00\u81F4\uFF09 */\ninterface Ctx {\n path: string;\n method: string;\n params: URLSearchParams;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n}\n\nconst ROUTE = \"/api/sfmc/wechat/activity\";\n\n/**\n * \u521B\u5EFA\u5FAE\u4FE1\u73A9\u5BB6\u884C\u4E3A\u8BB0\u5F55\u8DEF\u7531\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1Aindex.ts \u88C5\u914D\u5230\u5E73\u53F0\u8DEF\u7531\u94FE\u3002\n */\nfunction createWechatActivityRoutes({ query, json }: Deps) {\n return async function handle({ path, method, params, res }: Ctx): Promise<boolean> {\n if (path !== ROUTE) return false;\n if (method !== \"GET\") {\n json(res, { success: false, error: \"method_not_allowed\" }, 405);\n return true;\n }\n const openid = String(params.get(\"openid\") ?? \"\").trim();\n if (!openid) {\n json(res, { success: false, error: \"openid_required\" }, 400);\n return true;\n }\n const binding = findWechatBindingByOpenid(query, openid);\n // \u7A7A player_xuid \u4E0D\u80FD\u4F5C\u4E3A\u6267\u884C\u8005\u6761\u4EF6\uFF08\u7A7A\u503C\u4F1A\u88AB\u7B5B\u9009\u6761\u4EF6\u5FFD\u7565\u800C\u67E5\u51FA\u6240\u6709\u4EBA\u7684\u8BB0\u5F55\uFF09\uFF0C\u6309\u672A\u7ED1\u5B9A\u5904\u7406\n if (!binding?.player_xuid) {\n json(res, { success: true, bound: false, installed: false, records: [], total: 0, limit: 0, offset: 0 });\n return true;\n }\n const raw = Object.fromEntries(params.entries());\n let page;\n try {\n const { actorId: _actorId, actorName: _actorName, ...filter } = parseActivityFilter(raw);\n page = queryActivityPage(query, { ...filter, actorId: binding.player_xuid }, parseActivityPaging(raw));\n } catch (error) {\n if (error instanceof ActivityQueryError) {\n json(res, { success: false, error: \"invalid_params\", message: error.message }, 400);\n return true;\n }\n throw error;\n }\n json(res, { success: true, bound: true, ...page });\n return true;\n };\n}\n\nexport { createWechatActivityRoutes };\n"],
|
|
5
|
+
"mappings": "AAaA,SAAS,oBAAoB,qBAAqB,qBAAqB,yBAAyB;AAChG,SAAS,iCAAiC;AAiB1C,MAAM,QAAQ;AAMd,SAAS,2BAA2B,EAAE,OAAO,KAAK,GAAS;AACzD,SAAO,eAAe,OAAO,EAAE,MAAM,QAAQ,QAAQ,IAAI,GAA0B;AACjF,QAAI,SAAS,MAAO,QAAO;AAC3B,QAAI,WAAW,OAAO;AACpB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,qBAAqB,GAAG,GAAG;AAC9D,aAAO;AAAA,IACT;AACA,UAAM,SAAS,OAAO,OAAO,IAAI,QAAQ,KAAK,EAAE,EAAE,KAAK;AACvD,QAAI,CAAC,QAAQ;AACX,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,kBAAkB,GAAG,GAAG;AAC3D,aAAO;AAAA,IACT;AACA,UAAM,UAAU,0BAA0B,OAAO,MAAM;AAEvD,QAAI,CAAC,SAAS,aAAa;AACzB,WAAK,KAAK,EAAE,SAAS,MAAM,OAAO,OAAO,WAAW,OAAO,SAAS,CAAC,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,EAAE,CAAC;AACvG,aAAO;AAAA,IACT;AACA,UAAM,MAAM,OAAO,YAAY,OAAO,QAAQ,CAAC;AAC/C,QAAI;AACJ,QAAI;AACF,YAAM,EAAE,SAAS,UAAU,WAAW,YAAY,GAAG,OAAO,IAAI,oBAAoB,GAAG;AACvF,aAAO,kBAAkB,OAAO,EAAE,GAAG,QAAQ,SAAS,QAAQ,YAAY,GAAG,oBAAoB,GAAG,CAAC;AAAA,IACvG,SAAS,OAAO;AACd,UAAI,iBAAiB,oBAAoB;AACvC,aAAK,KAAK,EAAE,SAAS,OAAO,OAAO,kBAAkB,SAAS,MAAM,QAAQ,GAAG,GAAG;AAClF,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AACA,SAAK,KAAK,EAAE,SAAS,MAAM,OAAO,MAAM,GAAG,KAAK,CAAC;AACjD,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* routes/wechat-bind.ts — 微信↔MC 身份绑定(平台 HTTP,仅本机)
|
|
3
|
+
*
|
|
4
|
+
* POST /api/sfmc/wechat/bind/request — mp-gateway 代小程序玩家申请短码 { openid }
|
|
5
|
+
* POST /api/sfmc/wechat/bind/unbind — 解绑 { openid }
|
|
6
|
+
* GET /api/sfmc/wechat/bind/me — 查询 ?openid=:绑定、账号资料与 SFMC 有效等级
|
|
7
|
+
*
|
|
8
|
+
* 游戏侧确认走统一入口 POST /api/sfmc/bind/confirm(routes/bind-confirm.ts)。
|
|
9
|
+
* openid 由 mp-gateway 通过微信 code2session 取得,db-server 只信任本机调用方(与 QQ 绑定路由同一信任边界)。
|
|
10
|
+
*/
|
|
11
|
+
import type http from "node:http";
|
|
12
|
+
import type { QueryFn } from "../lib/sqlite.js";
|
|
13
|
+
interface Deps {
|
|
14
|
+
query: QueryFn;
|
|
15
|
+
body: (req: http.IncomingMessage) => Promise<Record<string, unknown>>;
|
|
16
|
+
json: (res: http.ServerResponse, data: Record<string, unknown>, status?: number) => void;
|
|
17
|
+
/** 跨渠道短码查重(QQ + 微信) */
|
|
18
|
+
isCodeTaken: (code: string) => boolean;
|
|
19
|
+
/** 按 xuid / 玩家名计算 SFMC 有效等级(DIP:文件位置由入口决定) */
|
|
20
|
+
resolveLevel: (player: {
|
|
21
|
+
xuid: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}) => number;
|
|
24
|
+
}
|
|
25
|
+
/** 路由上下文(与其他平台路由一致) */
|
|
26
|
+
interface Ctx {
|
|
27
|
+
path: string;
|
|
28
|
+
method: string;
|
|
29
|
+
params: URLSearchParams;
|
|
30
|
+
req: http.IncomingMessage;
|
|
31
|
+
res: http.ServerResponse;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 创建微信绑定路由。
|
|
35
|
+
* 使用场景:index.ts 装配到平台路由链。
|
|
36
|
+
*/
|
|
37
|
+
declare function createWechatBindRoutes({ query, body, json, isCodeTaken, resolveLevel }: Deps): ({ path, method, params, req, res }: Ctx) => Promise<boolean>;
|
|
38
|
+
export { createWechatBindRoutes };
|
|
39
|
+
//# sourceMappingURL=wechat-bind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wechat-bind.d.ts","sourceRoot":"","sources":["../../src/routes/wechat-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,UAAU,IAAI;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtE,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACzF,uBAAuB;IACvB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,8CAA8C;IAC9C,YAAY,EAAE,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;CAClE;AAED,uBAAuB;AACvB,UAAU,GAAG;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,IAAI,CAAC,eAAe,CAAC;IAC1B,GAAG,EAAE,IAAI,CAAC,cAAc,CAAC;CAC1B;AAID;;;GAGG;AACH,iBAAS,sBAAsB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,EAAE,IAAI,wCACnB,GAAG,KAAG,OAAO,CAAC,OAAO,CAAC,CAmDxF;AAED,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
|