@oj-module/cli 0.2.66 → 0.2.68
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/bin/.oj-version +1 -0
- package/bin/oj +4 -0
- package/devkit/README.md +15 -7
- package/devkit/SKILL.md +38 -4
- package/devkit/framework-dev.md +10 -4
- package/devkit/manual.md +146 -24
- package/devkit/menu-rules.md +19 -4
- package/devkit/module-dev-and-cdn-playground.md +4 -0
- package/devkit/publish-walkthrough.md +132 -37
- package/devkit/wecom-internal-app.md +8 -3
- package/devkit/wecom-multi-corp-links.md +163 -0
- package/package.json +2 -2
- package/shell-dist/assets/{index.i0kqbxp050.js → index.6alqyx6ae0.js} +21 -1
- package/shell-dist/assets/{runtime.1xfsxcajj0.js → runtime.1xzahckt10.js} +4863 -4847
- package/shell-dist/index.html +4 -4
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/cli.test.ts +31 -0
- package/src/__tests__/new-scaffold.test.ts +13 -0
- package/src/__tests__/publish-keygen.test.ts +157 -0
- package/src/__tests__/templates-migration-schema-parity.test.ts +540 -0
- package/src/__tests__/templates-schema-columns.test.ts +15 -2
- package/src/__tests__/templates-tenant-scope.test.ts +6 -2
- package/src/build/module-def.ts +1 -0
- package/src/builtin.ts +3 -2
- package/src/bundle.ts +8 -3
- package/src/cli.ts +21 -5
- package/src/dev.ts +15 -10
- package/src/init.ts +47 -41
- package/src/install.ts +11 -4
- package/src/keygen.ts +54 -0
- package/src/lifecycle/__tests__/bundle.test.ts +21 -3
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +1 -1
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +2 -2
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +5 -2
- package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +1 -1
- package/src/lifecycle/__tests__/manifest-contract.test.ts +75 -3
- package/src/lifecycle/__tests__/smoke.test.ts +2 -2
- package/src/manifest-contract.ts +91 -8
- package/src/market/bootstrap.ts +26 -2
- package/src/market/command.ts +1 -0
- package/src/market/publish.ts +8 -1
- package/src/market/walkthrough.ts +24 -8
- package/src/new.ts +2 -0
- package/src/scaffold-guard.ts +227 -0
- package/src/site.ts +2 -0
- package/src/usage.ts +13 -9
- package/src/vendor.ts +5 -1
- package/templates/config.yaml +4 -2
- package/templates/init.include.yaml +10 -3
- package/templates/openapi.json +460 -1
- package/templates/src/README.md +37 -0
- package/templates/src/app/api/_platform/_lifecycle/compensate.ts +136 -0
- package/templates/src/app/api/_platform/_lifecycle/installer.ts +78 -25
- package/templates/src/app/api/_platform/_lifecycle/lock.ts +23 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/deploy-web.ts +28 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/preflight.ts +46 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/reload.ts +100 -36
- package/templates/src/app/api/_platform/_lifecycle/steps/smoke.ts +7 -15
- package/templates/src/app/api/_platform/_lifecycle/text-edit.ts +71 -19
- package/templates/src/app/api/_platform/_lifecycle/types.ts +26 -2
- package/templates/src/app/api/_platform/_lifecycle/uninstall.ts +93 -14
- package/templates/src/app/api/_platform/_shared/service/domain/settings.ts +8 -4
- package/templates/src/app/api/_platform/_shared/service/domain/solution-perms.ts +25 -10
- package/templates/src/app/api/_platform/manifest.yaml +1 -0
- package/templates/src/app/api/_platform/market/api.ts +235 -10
- package/templates/src/app/api/_platform/migrations/0001__baseline.mysql.sql +178 -0
- package/templates/src/app/api/_platform/migrations/0001__baseline.pg.sql +173 -0
- package/templates/src/app/api/_platform/migrations/0001__baseline.sql +188 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.mysql.sql +17 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.pg.sql +16 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.sql +33 -0
- package/templates/src/app/api/_platform/schema.yaml +18 -0
- package/templates/src/app/api/_platform/seed.mysql.sql +10 -0
- package/templates/src/app/api/_platform/seed.pg.sql +10 -0
- package/templates/src/app/api/auth/_shared/wecom/client.ts +24 -4
- package/templates/src/app/api/home/migrations/0001__create_home_tables.mysql.sql +14 -0
- package/templates/src/app/api/home/migrations/0001__create_home_tables.pg.sql +14 -0
- package/templates/src/app/api/home/migrations/0002__tenant.pg.sql +4 -0
- package/templates/src/app/api/home/seed.mysql.sql +71 -0
- package/templates/src/app/api/home/seed.pg.sql +71 -0
- package/templates/src/app/api/notification/_shared/notify.ts +6 -6
- package/templates/src/app/api/notification/migrations/0001__create_notifications.mysql.sql +11 -0
- package/templates/src/app/api/notification/migrations/0001__create_notifications.pg.sql +11 -0
- package/templates/src/app/api/notification/migrations/0002__notifications_user.mysql.sql +8 -0
- package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.mysql.sql +21 -0
- package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.pg.sql +22 -0
- package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.mysql.sql +10 -0
- package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.pg.sql +11 -0
- package/templates/src/app/api/notification/migrations/0005__tenant.pg.sql +6 -0
- package/templates/src/app/api/notification/seed.mysql.sql +8 -0
- package/templates/src/app/api/notification/seed.pg.sql +11 -0
- package/templates/src/app/api/publish/_shared/identity.ts +69 -0
- package/templates/src/app/api/publish/api.ts +349 -0
- package/templates/src/app/api/publish/manifest.yaml +8 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.mysql.sql +23 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.pg.sql +23 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.sql +39 -0
- package/templates/src/app/api/publish/schema.yaml +33 -0
- package/templates/src/app/api/system/login-methods/api.ts +2 -1
- package/templates/src/app/web/_platform/__client/api.schemas.ts +15 -0
- package/templates/src/app/web/_platform/__client/index.ts +25 -1
- package/templates/src/app/web/entry.ts +9 -5
- package/templates/src/app/web/home/routes.ts +5 -0
- package/templates/src/app/web/login/locales/en-US.json +7 -1
- package/templates/src/app/web/login/locales/zh-CN.json +7 -1
- package/templates/src/app/web/login/pages/login.tsx +79 -86
- package/templates/src/app/web/login/routes.ts +54 -1
- package/templates/src/app/web/personal-center/locales/en-US.json +1 -0
- package/templates/src/app/web/personal-center/locales/zh-CN.json +1 -0
- package/templates/src/app/web/personal-center/pages/my-profile/index.tsx +5 -0
- package/templates/src/app/web/system/__client/api.schemas.ts +2 -1
- package/templates/src/app/web/system/locales/en-US.json +1 -8
- package/templates/src/app/web/system/locales/zh-CN.json +1 -8
- package/templates/src/app/web/system/pages/settings/index.tsx +17 -4
- package/templates/src/app/web/system/routes.ts +0 -6
- package/templates/src/oj-market/web/__tests__/CardGrid.test.tsx +33 -0
- package/templates/src/oj-market/web/__tests__/DetailDrawer.test.tsx +69 -0
- package/templates/src/oj-market/web/__tests__/marketClient.fake.ts +7 -1
- package/templates/src/oj-market/web/api/client.ts +3 -0
- package/templates/src/oj-market/web/api/defaultClient.ts +5 -0
- package/templates/src/oj-market/web/components/CardItem.tsx +44 -1
- package/templates/src/oj-market/web/components/DetailDrawer.tsx +17 -1
- package/templates/src/oj-market/web/entry.ts +31 -2
- package/templates/src/oj-market/web/locales/en-US.json +5 -1
- package/templates/src/oj-market/web/locales/zh-CN.json +5 -1
- package/templates/src/oj-market/web/publish/__client/api.schemas.ts +61 -0
- package/templates/src/oj-market/web/publish/__client/index.ts +164 -0
- package/templates/src/oj-market/web/publish/locales/en-US.json +80 -0
- package/templates/src/oj-market/web/publish/locales/zh-CN.json +80 -0
- package/templates/src/oj-market/web/publish/pages/history.tsx +112 -0
- package/templates/src/oj-market/web/publish/pages/index.tsx +196 -0
- package/templates/src/oj-market/web/publish/pages/model.ts +52 -0
- package/templates/src/oj-market/web/publish/pages/token-modal.tsx +82 -0
- package/templates/src/oj-market/web/publish/routes.ts +41 -0
- package/templates/src/oj-market/web/types.ts +30 -0
- package/templates/tests/lifecycle/compensate.spec.ts +207 -0
- package/templates/tests/lifecycle/deploy-smoke.spec.ts +38 -14
- package/templates/tests/lifecycle/installer.spec.ts +28 -9
- package/templates/tests/lifecycle/market-api.spec.ts +164 -12
- package/templates/tests/lifecycle/preflight.spec.ts +68 -0
- package/templates/tests/lifecycle/register.spec.ts +22 -0
- package/templates/tests/lifecycle/role-title-translate.spec.ts +4 -34
- package/templates/tests/lifecycle/text-edit.spec.ts +45 -27
- package/templates/tests/lifecycle/uninstall.spec.ts +36 -0
- package/templates/tests/publish-credential.spec.ts +354 -0
- package/templates/tests/publish-identity.spec.ts +44 -0
- package/templates/tests/rbac-solution-perms.spec.ts +48 -3
- package/templates/tests/wecom-client-timeout.spec.ts +33 -3
- package/templates/tests/wecom-landing.spec.ts +63 -1
- package/templates/tsconfig.paths.json +1 -0
- package/templates/web.config.ts +8 -4
- package/vendor/host-versions.json +3 -3
- package/templates/seed.mysql.sql +0 -6
- package/templates/seed.pg.sql +0 -6
- package/templates/src/app/api/_platform/migrations/0001__create_users.mysql.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0001__create_users.pg.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0001__create_users.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.mysql.sql +0 -5
- package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.sql +0 -5
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.mysql.sql +0 -70
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.pg.sql +0 -67
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.sql +0 -67
- package/templates/src/app/api/_platform/migrations/0005__users_must_change.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0005__users_must_change.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0007__users_email.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.mysql.sql +0 -10
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.pg.sql +0 -10
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.sql +0 -11
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.mysql.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.pg.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.sql +0 -13
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.mysql.sql +0 -17
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.pg.sql +0 -16
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.sql +0 -18
- package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.mysql.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0012__market_history.mysql.sql +0 -13
- package/templates/src/app/api/_platform/migrations/0012__market_history.pg.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0012__market_history.sql +0 -14
- package/templates/src/app/api/_platform/migrations/0013__users_profile.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.mysql.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.pg.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.sql +0 -8
- package/templates/src/app/api/_platform/migrations/0015__tenant.mysql.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0015__tenant.pg.sql +0 -11
- package/templates/src/app/api/_platform/migrations/0015__tenant.sql +0 -44
- package/templates/src/app/api/_platform/migrations/0016__org.mysql.sql +0 -41
- package/templates/src/app/api/_platform/migrations/0016__org.pg.sql +0 -40
- package/templates/src/app/api/_platform/migrations/0016__org.sql +0 -62
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +0 -8
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.pg.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.sql +0 -8
- package/templates/src/app/web/system/pages/login-methods/index.tsx +0 -30
|
@@ -33,6 +33,7 @@ import { createTrustStore } from "../_lifecycle/trust";
|
|
|
33
33
|
import { computeUninstallImpact, runUninstall } from "../_lifecycle/uninstall";
|
|
34
34
|
import { respond } from "../_shared/respond";
|
|
35
35
|
import { assertSuperAdmin } from "../_shared/service/infra/access";
|
|
36
|
+
import { pageArgs } from "../_shared/service/infra/query";
|
|
36
37
|
import { journalHeadOf, readCanonicalOrigin, siteOrigin, withSiteOrigin } from "../_shared/site-facade";
|
|
37
38
|
import { BOOTSTRAP_KEY, seedBootstrapKey } from "./bootstrap";
|
|
38
39
|
import { httpIndexSource, indexPathOf } from "./index-source";
|
|
@@ -64,7 +65,7 @@ export interface MarketEnv {
|
|
|
64
65
|
modulesJsonPath: string
|
|
65
66
|
/** API 前缀(smoke 探针 URL 前缀) */
|
|
66
67
|
apiPrefix: string
|
|
67
|
-
/** 站点 config.yaml 路径(reloading 段
|
|
68
|
+
/** 站点 config.yaml 路径(reloading 段 anonymous_paths 接线落盘处;缺省 config.yaml) */
|
|
68
69
|
configPath?: string
|
|
69
70
|
/** canonical origin(REG-CS6:相对 URL 补全用,取自 config.yaml vars.canonical_origin,绝不读请求头 Host) */
|
|
70
71
|
canonicalOrigin?: string
|
|
@@ -236,7 +237,14 @@ function execOf(env: MarketEnv): ExecFace {
|
|
|
236
237
|
|
|
237
238
|
function reloadOf(env: MarketEnv): ReloadFace {
|
|
238
239
|
const g = globalThis as { server?: { reloadConfig: ReloadFace["reloadConfig"] } };
|
|
239
|
-
|
|
240
|
+
// env 显式注入优先;其次平台 server.reloadConfig(生产 oj 提供,oj 0.1.123+ 支持 force 透传);
|
|
241
|
+
// 均缺失(dev/test 无 server)回落 no-op——等价于「不 reload」,保持卸载旧语义(reload 实际掉
|
|
242
|
+
// 挂载依赖 US-1 上游 manifests 摘要驱动,未到位前回落 no-op 不阻断卸载主流程);force 透传给宿主。
|
|
243
|
+
if (env.reload !== undefined)
|
|
244
|
+
return env.reload;
|
|
245
|
+
if (g.server?.reloadConfig !== undefined)
|
|
246
|
+
return { reloadConfig: (force?: boolean) => g.server!.reloadConfig(force) };
|
|
247
|
+
return { reloadConfig: async () => ({ unchanged: true, applied: [], restart_pending: [] }) };
|
|
240
248
|
}
|
|
241
249
|
|
|
242
250
|
function sha384Of(env: MarketEnv): (data: Uint8Array) => string {
|
|
@@ -249,6 +257,23 @@ function sha256Of(env: MarketEnv): (data: Uint8Array) => string {
|
|
|
249
257
|
return env.sha256Hex ?? ((data: Uint8Array) => g.crypto!.sha256Hex!(data));
|
|
250
258
|
}
|
|
251
259
|
|
|
260
|
+
/**
|
|
261
|
+
* 宿主能力集(P0 §3.9 / §2.3):从 ojInfo().build.capabilities 读取(v0.1.122 增量能力位闭集:
|
|
262
|
+
* reload.assemble_modules / reload.anon_per_reload / reload.cron_survives)。注入安装上下文后,
|
|
263
|
+
* preflight 据 manifest.requiresCapabilities 做写锁前门禁——宿主缺能力即 fail-closed(零痕迹)。
|
|
264
|
+
* ojInfo 不可用时回落空数组(等同未上报 → 若模板声明了 requiresCapabilities 则拒装)。
|
|
265
|
+
*/
|
|
266
|
+
function capabilitiesOf(): string[] {
|
|
267
|
+
try {
|
|
268
|
+
const info = (globalThis as { ojInfo?: () => any }).ojInfo?.();
|
|
269
|
+
const caps = info?.build?.capabilities;
|
|
270
|
+
return Array.isArray(caps) ? caps : [];
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
252
277
|
/** CDN 基址:注入面优先,缺省读设置表(值/缺省值俱空 = 未配置;表异常视同未配置,消费端给可读错误) */
|
|
253
278
|
async function cdnBaseOf(env: MarketEnv): Promise<string | null> {
|
|
254
279
|
if (env.cdnBase) {
|
|
@@ -694,6 +719,8 @@ async function installBody(env: MarketEnv): Promise<{ task_id: string }> {
|
|
|
694
719
|
if (name === "")
|
|
695
720
|
fail(400, "name is required");
|
|
696
721
|
const reqVersion = body.version == null || body.version === "" ? null : String(body.version);
|
|
722
|
+
// force 透传(oj 0.1.123+ reloadConfig(force)):body.force 或 query.force=1 任一为真即强制重装配
|
|
723
|
+
const force = body.force === true || String((http as { query?: Record<string, unknown> }).query?.force ?? "") === "1";
|
|
697
724
|
|
|
698
725
|
let manifest: unknown;
|
|
699
726
|
let version: string;
|
|
@@ -786,7 +813,10 @@ async function installBody(env: MarketEnv): Promise<{ task_id: string }> {
|
|
|
786
813
|
hostVersions: await hostVersionsOf(env),
|
|
787
814
|
// 宿主平台编排面 API 版本(C2-11 peerPlatform 比对真源;与 meta 端点 apiVersion、_platform/manifest.yaml 版本同源,手工同步)
|
|
788
815
|
hostApiVersion: API_VERSION,
|
|
816
|
+
// 宿主能力集(P0 §3.9):ojInfo().build.capabilities,preflight 的 requiresCapabilities 门禁据此生效
|
|
817
|
+
capabilities: capabilitiesOf(),
|
|
789
818
|
trustPath: env.trustPath,
|
|
819
|
+
force,
|
|
790
820
|
registryId: env.registryId,
|
|
791
821
|
cancel: () => {
|
|
792
822
|
if (!cancelled) {
|
|
@@ -818,10 +848,16 @@ interface IndexRow {
|
|
|
818
848
|
available: string[]
|
|
819
849
|
/** 最新版 changelog(C2-08,R27 卡片版本对比;分片条目缺 changelog → null) */
|
|
820
850
|
changelog: unknown
|
|
821
|
-
/** 最新版发布方(manifest author,C2-07;分片条目缺 author → null
|
|
851
|
+
/** 最新版发布方(manifest author,C2-07;分片条目缺 author → null,自报字段) */
|
|
822
852
|
publisher: string | null
|
|
853
|
+
/** 最新版包级签名 key_id(Form B archive.signature.key_id,经 Ed25519 验签的发布身份;缺签名 → null) */
|
|
854
|
+
publisher_key: string | null
|
|
823
855
|
/** 最新版许可(manifest license,C2-07 SPDX 简化式;分片条目缺/非字符串 → null) */
|
|
824
856
|
license: string | null
|
|
857
|
+
/** 方案简介(manifest summary,C2-04 单行 1..200;分片条目缺/非字符串 → null) */
|
|
858
|
+
summary: string | null
|
|
859
|
+
/** 方案详细描述(manifest description,C2-05 ≤2000;分片条目缺/非字符串 → null) */
|
|
860
|
+
description: string | null
|
|
825
861
|
/** 本地安装的发布方 key 状态(P2-6 sidecar;未装 → null) */
|
|
826
862
|
trust_state: "trusted" | "untrusted" | "absent" | null
|
|
827
863
|
/** 本地安装记录的签名 key id(sidecar archive.key_id;未装/P1 时代条目 → null) */
|
|
@@ -853,19 +889,32 @@ async function lastInstallOperatorOf(name: string): Promise<string | null> {
|
|
|
853
889
|
*
|
|
854
890
|
* 三级化后市场页的拉取形态:1 catalog + catalog 指向的全部分片(去重)——`available`
|
|
855
891
|
* 回滚候选要版本全集,故页面比安装多拉几片;安装侧仍 ≤ 1 catalog + 1 shard(resolveCdn)。
|
|
856
|
-
* 版本条目即 Form B manifest:latest 的 changelog/publisher
|
|
857
|
-
*
|
|
892
|
+
* 版本条目即 Form B manifest:latest 的 changelog/publisher/summary/description 直接从
|
|
893
|
+
* 分片条目取,不再有独立的聚合清单拉取。分片拉取/验签失败即 502(REG-CS1:可用性不再
|
|
894
|
+
* 压过完整性)。
|
|
858
895
|
*/
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
896
|
+
interface IndexSnapshot {
|
|
897
|
+
base: string
|
|
898
|
+
/** catalog 的签名时效(发布时间兜底;索引缺时效 → null) */
|
|
899
|
+
issuedAt: number | null
|
|
900
|
+
solutions: Array<{ name: string, shards: number[] }>
|
|
901
|
+
shardDocs: Map<number, ShardSolution[]>
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* 读整份签名索引(catalog + 它指向的全部分片)。
|
|
906
|
+
*
|
|
907
|
+
* 市场卡片(indexBody)与发布历史(syncReleases)都要「全量版本视图」,读取与验签只有这一份:
|
|
908
|
+
* 两处各写一遍 `fetchIndex` 循环,迟早漂移成「卡片看得见、历史看不见」。
|
|
909
|
+
*/
|
|
910
|
+
async function readIndexSnapshot(env: MarketEnv): Promise<IndexSnapshot> {
|
|
863
911
|
const base = await cdnBaseOf(env);
|
|
864
912
|
if (base === null)
|
|
865
913
|
fail(503, "未配置市场地址(framework.market_cdn_url)——卡片区显示「未配置市场地址」(PRD §5.3)");
|
|
866
914
|
await assertOriginForRelativeBase(env, base);
|
|
867
915
|
const source = await indexSourceOf(env, base);
|
|
868
916
|
const catalog = await fetchIndex(env, source, base, "catalog");
|
|
917
|
+
const times = await indexTimesOfText(catalog.text);
|
|
869
918
|
const { pins, solutions } = await parseCatalog(catalog.text, base);
|
|
870
919
|
// 拉取 catalog 指向的全部分片(去重、升序确定性);每片都过签名 + 钉值同代校验
|
|
871
920
|
const shardDocs = new Map<number, ShardSolution[]>();
|
|
@@ -873,6 +922,14 @@ async function indexBody(env: MarketEnv): Promise<IndexRow[]> {
|
|
|
873
922
|
const shard = await fetchIndex(env, source, base, "shard", no, pins.get(no)!);
|
|
874
923
|
shardDocs.set(no, await parseShard(shard.text, `shards/${no}.yaml`));
|
|
875
924
|
}
|
|
925
|
+
return { base, issuedAt: times?.issued_at ?? null, solutions, shardDocs };
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
async function indexBody(env: MarketEnv): Promise<IndexRow[]> {
|
|
929
|
+
// 对账即服务(P1-10 接线③):市场页刷新即对账时点——锁 ↔ 账本 head 自愈 + enabled 不一致
|
|
930
|
+
// 自动修复先行(失败只记日志,不阻断索引),失败站点的市场页每次打开都在尝试自愈
|
|
931
|
+
await reconcileOf(env);
|
|
932
|
+
const { solutions, shardDocs } = await readIndexSnapshot(env);
|
|
876
933
|
const local = (await createStateStore(env.statePath)).read().solutions;
|
|
877
934
|
const rows: IndexRow[] = [];
|
|
878
935
|
const seen = new Set<string>();
|
|
@@ -898,6 +955,11 @@ async function indexBody(env: MarketEnv): Promise<IndexRow[]> {
|
|
|
898
955
|
const manifestOf = (version: string | null): Record<string, unknown> | undefined =>
|
|
899
956
|
version === null ? undefined : merged.find(v => String(v.version) === version);
|
|
900
957
|
const m = manifestOf(latest) ?? manifestOf(side?.version ?? null);
|
|
958
|
+
// 发布者:取最新版 Form B archive.signature.key_id(Ed25519 验签过的签名身份,离线可验);
|
|
959
|
+
// manifest.author 是自报字段,二者不同。缺签名/畸形 → null(不拿自报值冒充)。
|
|
960
|
+
const rawArchive: unknown = m?.archive;
|
|
961
|
+
const rawSig: unknown = isObj(rawArchive) ? rawArchive.signature : undefined;
|
|
962
|
+
const publisherKey = isObj(rawSig) && typeof rawSig.key_id === "string" ? rawSig.key_id : null;
|
|
901
963
|
rows.push({
|
|
902
964
|
name,
|
|
903
965
|
latest,
|
|
@@ -907,7 +969,10 @@ async function indexBody(env: MarketEnv): Promise<IndexRow[]> {
|
|
|
907
969
|
available,
|
|
908
970
|
changelog: m?.changelog ?? null,
|
|
909
971
|
publisher: typeof m?.author === "string" ? m.author : null,
|
|
972
|
+
publisher_key: publisherKey,
|
|
910
973
|
license: typeof m?.license === "string" ? m.license : null,
|
|
974
|
+
summary: typeof m?.summary === "string" ? m.summary : null,
|
|
975
|
+
description: typeof m?.description === "string" ? m.description : null,
|
|
911
976
|
trust_state: side?.trust_state ?? null,
|
|
912
977
|
key_id: side?.archive?.key_id ?? null,
|
|
913
978
|
operator: await lastInstallOperatorOf(name),
|
|
@@ -924,7 +989,10 @@ async function indexBody(env: MarketEnv): Promise<IndexRow[]> {
|
|
|
924
989
|
available: [],
|
|
925
990
|
changelog: null,
|
|
926
991
|
publisher: null,
|
|
992
|
+
publisher_key: null,
|
|
927
993
|
license: null,
|
|
994
|
+
summary: null,
|
|
995
|
+
description: null,
|
|
928
996
|
trust_state: side.trust_state ?? null,
|
|
929
997
|
key_id: side.archive?.key_id ?? null,
|
|
930
998
|
operator: await lastInstallOperatorOf(name),
|
|
@@ -934,6 +1002,106 @@ async function indexBody(env: MarketEnv): Promise<IndexRow[]> {
|
|
|
934
1002
|
return rows;
|
|
935
1003
|
}
|
|
936
1004
|
|
|
1005
|
+
/** 发布历史行(市场索引里出现过的每个 product@version) */
|
|
1006
|
+
interface ReleaseRow {
|
|
1007
|
+
id: number
|
|
1008
|
+
product: string
|
|
1009
|
+
version: string
|
|
1010
|
+
key_id: string | null
|
|
1011
|
+
sha256: string | null
|
|
1012
|
+
size: number | null
|
|
1013
|
+
published_at: number
|
|
1014
|
+
/** 时间来源:file = tgz 的 Last-Modified;index = 索引生成时间兜底 */
|
|
1015
|
+
published_at_source: "file" | "index"
|
|
1016
|
+
yanked: number
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* 取单版本的「发布时间」。
|
|
1021
|
+
*
|
|
1022
|
+
* 优先 HEAD 分发包读 `Last-Modified`——那是包落进 CDN 的时刻,也就是真正的发版时间。
|
|
1023
|
+
* 读不到(源不吐该头 / 网络失败 / file 名缺失)才回落到索引生成时间:`ojm market publish`
|
|
1024
|
+
* 与 `build-index` 在推荐流程里紧邻执行,两者差值只有重建索引的耗时。
|
|
1025
|
+
*
|
|
1026
|
+
* 只对**首次入库**的版本调一次(已入库的 published_at 不再改写)——历史一旦记录就是事实,
|
|
1027
|
+
* 不该被后续重新发布/重新建索引刷新。
|
|
1028
|
+
*/
|
|
1029
|
+
async function archivePublishedAt(env: MarketEnv, base: string, file: unknown, fallback: number): Promise<{ at: number, source: "file" | "index" }> {
|
|
1030
|
+
if (typeof file !== "string" || file === "")
|
|
1031
|
+
return { at: fallback, source: "index" };
|
|
1032
|
+
try {
|
|
1033
|
+
// HEAD 不在 FetchInitFace 的最小形状里(安装面只需要 GET),但运行时 fetch 原生支持:
|
|
1034
|
+
// 这里显式窄化一次,避免为「读一个头」再造第二种下载实现。
|
|
1035
|
+
const head = await fetchOf(env) as unknown as (url: string, init?: { method?: string }) => Promise<{ headers?: { get: (n: string) => string | null } }>;
|
|
1036
|
+
const res = await head(`${base}/${encodeURIComponent(file)}`, { method: "HEAD" });
|
|
1037
|
+
const raw = res.headers?.get?.("last-modified") ?? res.headers?.get?.("Last-Modified") ?? null;
|
|
1038
|
+
if (raw !== null && raw !== "") {
|
|
1039
|
+
const at = Math.floor(Date.parse(raw) / 1000);
|
|
1040
|
+
if (Number.isFinite(at) && at > 0)
|
|
1041
|
+
return { at, source: "file" };
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
catch {}
|
|
1045
|
+
return { at: fallback, source: "index" };
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* 把索引里出现过的版本同步进 `market_release`(幂等:只补新行)。
|
|
1050
|
+
*
|
|
1051
|
+
* 为什么不做成「发版时由 CLI 上报」:`ojm market publish` 是纯本地操作(不登录、可离线),
|
|
1052
|
+
* 加一条上报链路就得再造一套写端授权的信任面;而**签名索引本身就是发版事实**——
|
|
1053
|
+
* 站点已经在读它,历史直接从这里落库,既不用新协议,也不会出现「上报成功但包里没有」的分叉。
|
|
1054
|
+
*
|
|
1055
|
+
* 索引拉取/验签失败由调用方兜住(历史是观测面,不阻断页面:返回已入库行 + syncError)。
|
|
1056
|
+
*/
|
|
1057
|
+
async function syncReleases(env: MarketEnv): Promise<void> {
|
|
1058
|
+
const snapshot = await readIndexSnapshot(env);
|
|
1059
|
+
const fallbackAt = snapshot.issuedAt ?? (env.now?.() ?? Math.floor(Date.now() / 1000));
|
|
1060
|
+
const now = env.now?.() ?? Math.floor(Date.now() / 1000);
|
|
1061
|
+
|
|
1062
|
+
const existingRows = await db.table("market_release").select(["id", "product", "version", "yanked"]).all() as Array<Record<string, unknown>>;
|
|
1063
|
+
const existing = new Map(existingRows.map(r => [`${String(r.product)}@${String(r.version)}`, { id: Number(r.id), yanked: Number(r.yanked) }]));
|
|
1064
|
+
|
|
1065
|
+
for (const sol of snapshot.shardDocs.values()) {
|
|
1066
|
+
for (const entry of sol) {
|
|
1067
|
+
for (const v of entry.versions) {
|
|
1068
|
+
const version = typeof v.version === "string" ? v.version : "";
|
|
1069
|
+
if (version === "")
|
|
1070
|
+
continue;
|
|
1071
|
+
const dedupeKey = `${entry.name}@${version}`;
|
|
1072
|
+
const yanked = v.yanked === true ? 1 : 0;
|
|
1073
|
+
const prior = existing.get(dedupeKey);
|
|
1074
|
+
if (prior !== undefined) {
|
|
1075
|
+
// 已入库的 published_at 是事实、不再改写;但 yank 状态是「当前是否在架」,跟随索引
|
|
1076
|
+
if (prior.yanked !== yanked)
|
|
1077
|
+
await db.table("market_release").update({ yanked }).where({ field: "id", op: "eq", value: prior.id }).run();
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
const archive = isObj(v.archive) ? v.archive : {};
|
|
1081
|
+
const sig = isObj(archive.signature) ? archive.signature : {};
|
|
1082
|
+
const { at, source } = await archivePublishedAt(env, snapshot.base, archive.file, fallbackAt);
|
|
1083
|
+
try {
|
|
1084
|
+
await db.table("market_release").insert({
|
|
1085
|
+
product: entry.name,
|
|
1086
|
+
version,
|
|
1087
|
+
key_id: typeof sig.key_id === "string" ? sig.key_id : null,
|
|
1088
|
+
sha256: typeof archive.sha256 === "string" ? archive.sha256 : null,
|
|
1089
|
+
size: typeof archive.size === "number" ? archive.size : null,
|
|
1090
|
+
published_at: at,
|
|
1091
|
+
published_at_source: source,
|
|
1092
|
+
yanked,
|
|
1093
|
+
first_seen_at: now,
|
|
1094
|
+
}).run();
|
|
1095
|
+
existing.set(dedupeKey, { id: -1, yanked });
|
|
1096
|
+
}
|
|
1097
|
+
catch {
|
|
1098
|
+
// 并发同步(两个页面同时打开)撞唯一键:另一路已写入,跳过即可
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
937
1105
|
/** 四端点组(oj v0.1.64 嵌套组:组名 = 路径段);全业务体经 respond 收口(status 语义不落 500) */
|
|
938
1106
|
export function createMarketApi(env: MarketEnv) {
|
|
939
1107
|
return {
|
|
@@ -1131,6 +1299,7 @@ export function createMarketApi(env: MarketEnv) {
|
|
|
1131
1299
|
state: await createStateStore(env.statePath),
|
|
1132
1300
|
modulesJsonPath: env.modulesJsonPath,
|
|
1133
1301
|
operator: (await operatorOf()) ?? undefined,
|
|
1302
|
+
reload: reloadOf(env),
|
|
1134
1303
|
});
|
|
1135
1304
|
// P3-11 宿主打磨:方案已不存在 = 资源缺失语义 → 404(非 200 {ok:false});
|
|
1136
1305
|
// 其余领域拒绝(空锁拒启/级联失败等)维持 200 {ok:false} 人话在 message。
|
|
@@ -1156,6 +1325,42 @@ export function createMarketApi(env: MarketEnv) {
|
|
|
1156
1325
|
});
|
|
1157
1326
|
},
|
|
1158
1327
|
},
|
|
1328
|
+
|
|
1329
|
+
// GET /api/_platform/market/releases —— 发布历史(登录即可读;与市场卡片同口径的「浏览开放」)。
|
|
1330
|
+
//
|
|
1331
|
+
// 数据源是签名索引本身:每次读取先把索引里出现过的 product@version 补进 market_release
|
|
1332
|
+
// (只补新行,published_at 一次写定),再按发布时间倒序分页返回。索引拉不到/验签失败
|
|
1333
|
+
// 不阻断查询:返回已入库行 + syncError,页面照常显示历史并提示「暂时读不到索引」。
|
|
1334
|
+
"releases": {
|
|
1335
|
+
async get() {
|
|
1336
|
+
await respond(async () => {
|
|
1337
|
+
const uid = (http as { user?: { id?: unknown } }).user?.id;
|
|
1338
|
+
if (uid == null)
|
|
1339
|
+
fail(401, "unauthorized");
|
|
1340
|
+
let syncError: string | null = null;
|
|
1341
|
+
try {
|
|
1342
|
+
await syncReleases(env);
|
|
1343
|
+
}
|
|
1344
|
+
catch (e) {
|
|
1345
|
+
syncError = e instanceof Error ? e.message : String(e);
|
|
1346
|
+
}
|
|
1347
|
+
const q = (http.query ?? {}) as Record<string, unknown>;
|
|
1348
|
+
const { page, size, offset } = pageArgs({ page: q.page, size: q.limit });
|
|
1349
|
+
const total = Number(
|
|
1350
|
+
(await db.table("market_release").select([{ fn: "count", as: "c" }]).all() as Array<Record<string, unknown>>)[0]?.c ?? 0,
|
|
1351
|
+
);
|
|
1352
|
+
const list = total === 0
|
|
1353
|
+
? []
|
|
1354
|
+
: await db.table("market_release")
|
|
1355
|
+
.select(["id", "product", "version", "key_id", "sha256", "size", "published_at", "published_at_source", "yanked"])
|
|
1356
|
+
.orderBy([{ field: "published_at", dir: "desc" }, { field: "id", dir: "desc" }])
|
|
1357
|
+
.limit(size)
|
|
1358
|
+
.offset(offset)
|
|
1359
|
+
.all() as unknown as ReleaseRow[];
|
|
1360
|
+
return { list, total, page, size, syncError };
|
|
1361
|
+
});
|
|
1362
|
+
},
|
|
1363
|
+
},
|
|
1159
1364
|
};
|
|
1160
1365
|
}
|
|
1161
1366
|
|
|
@@ -1169,7 +1374,7 @@ marketApi.install.post.schema = {
|
|
|
1169
1374
|
body: {
|
|
1170
1375
|
type: "object",
|
|
1171
1376
|
required: ["source", "name"],
|
|
1172
|
-
properties: { source: { type: "string", enum: ["cdn", "file"] }, name: { type: "string" }, version: { type: ["string", "null"] } },
|
|
1377
|
+
properties: { source: { type: "string", enum: ["cdn", "file"] }, name: { type: "string" }, version: { type: ["string", "null"] }, force: { type: "boolean" } },
|
|
1173
1378
|
},
|
|
1174
1379
|
};
|
|
1175
1380
|
marketApi.install.post.response = { type: "object", properties: { task_id: { type: "string" } }, additionalProperties: true };
|
|
@@ -1193,5 +1398,25 @@ marketApi["uninstall-impact"].get.response = { type: "object", additionalPropert
|
|
|
1193
1398
|
marketApi.uninstall.post.schema = { body: { type: "object", required: ["name"], properties: { name: { type: "string" } } } };
|
|
1194
1399
|
marketApi.uninstall.post.response = { type: "object", additionalProperties: true };
|
|
1195
1400
|
marketApi.history.get.response = { type: "array", items: { type: "object", additionalProperties: true } };
|
|
1401
|
+
marketApi.releases.get.schema = {
|
|
1402
|
+
query: {
|
|
1403
|
+
type: "object",
|
|
1404
|
+
properties: {
|
|
1405
|
+
page: { type: "number", minimum: 1 },
|
|
1406
|
+
limit: { type: "number", minimum: 1, maximum: 200 },
|
|
1407
|
+
},
|
|
1408
|
+
},
|
|
1409
|
+
};
|
|
1410
|
+
marketApi.releases.get.response = {
|
|
1411
|
+
type: "object",
|
|
1412
|
+
required: ["list", "total", "page", "size"],
|
|
1413
|
+
properties: {
|
|
1414
|
+
list: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
1415
|
+
total: { type: "number" },
|
|
1416
|
+
page: { type: "number" },
|
|
1417
|
+
size: { type: "number" },
|
|
1418
|
+
syncError: { type: ["string", "null"] },
|
|
1419
|
+
},
|
|
1420
|
+
};
|
|
1196
1421
|
|
|
1197
1422
|
export default marketApi;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
-- 0001__baseline(mysql 方言):_platform 全部表结构基线。压平口径见 0001__baseline.sql 头注释。
|
|
2
|
+
--
|
|
3
|
+
-- 方言差异(沿用历史迁移的既有惯例,逐条都有先例):
|
|
4
|
+
-- · TEXT 不能带字面默认值、不能直接做唯一键/主键 → 需要的那几列收窄为 VARCHAR;
|
|
5
|
+
-- · 字符串默认值写成表达式 DEFAULT ('x')(mysql ≥8.0.13 对 TEXT 系只允许表达式默认);
|
|
6
|
+
-- · AUTOINCREMENT → BIGINT AUTO_INCREMENT;秒级 epoch 的整型取 BIGINT;
|
|
7
|
+
-- · 保留字(key / group / order)用反引号(mysql 默认 sql_mode 下双引号是字符串字面量);
|
|
8
|
+
-- · 无 CREATE INDEX IF NOT EXISTS → 索引写在 CREATE TABLE 内,由迁移账本保证只执行一次。
|
|
9
|
+
|
|
10
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
11
|
+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
12
|
+
tenant_id VARCHAR(255) NOT NULL DEFAULT 'default',
|
|
13
|
+
corp_id VARCHAR(255) NULL,
|
|
14
|
+
username VARCHAR(255) NOT NULL,
|
|
15
|
+
password_hash TEXT NOT NULL,
|
|
16
|
+
avatar_base64 TEXT NOT NULL DEFAULT (''),
|
|
17
|
+
source TEXT NOT NULL DEFAULT ('local'),
|
|
18
|
+
display_name TEXT,
|
|
19
|
+
must_change BIGINT NOT NULL DEFAULT 0,
|
|
20
|
+
is_forbidden BIGINT NOT NULL DEFAULT 0,
|
|
21
|
+
email TEXT,
|
|
22
|
+
phone_number TEXT,
|
|
23
|
+
description TEXT,
|
|
24
|
+
activated INTEGER NOT NULL DEFAULT 0,
|
|
25
|
+
position VARCHAR(255) NULL,
|
|
26
|
+
external_status INTEGER NULL,
|
|
27
|
+
employee_no VARCHAR(64) NULL,
|
|
28
|
+
UNIQUE KEY uk_users_tenant_username (tenant_id, username),
|
|
29
|
+
-- 无部分索引:NULL 在 mysql 唯一键里可重复出现,效果等同「有值才唯一」
|
|
30
|
+
UNIQUE KEY uk_users_employee_no (tenant_id, employee_no)
|
|
31
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
32
|
+
|
|
33
|
+
CREATE TABLE IF NOT EXISTS sys_setting (
|
|
34
|
+
`key` VARCHAR(255) PRIMARY KEY,
|
|
35
|
+
module TEXT NOT NULL,
|
|
36
|
+
`group` TEXT NOT NULL,
|
|
37
|
+
type TEXT NOT NULL DEFAULT ('string'),
|
|
38
|
+
value TEXT,
|
|
39
|
+
default_value TEXT,
|
|
40
|
+
label_key TEXT,
|
|
41
|
+
options TEXT,
|
|
42
|
+
secret BIGINT NOT NULL DEFAULT 0,
|
|
43
|
+
update_time BIGINT NOT NULL DEFAULT 0,
|
|
44
|
+
deprecated BIGINT NOT NULL DEFAULT 0,
|
|
45
|
+
group_label_key TEXT
|
|
46
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS sys_oplog (
|
|
49
|
+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
50
|
+
create_time BIGINT NOT NULL,
|
|
51
|
+
user_id BIGINT NOT NULL DEFAULT 0,
|
|
52
|
+
username TEXT NOT NULL DEFAULT ('system'),
|
|
53
|
+
action TEXT NOT NULL,
|
|
54
|
+
module TEXT NOT NULL DEFAULT ('framework'),
|
|
55
|
+
version TEXT,
|
|
56
|
+
target TEXT,
|
|
57
|
+
detail TEXT,
|
|
58
|
+
level BIGINT NOT NULL DEFAULT 6,
|
|
59
|
+
tenant_id VARCHAR(255) NOT NULL DEFAULT 'default',
|
|
60
|
+
KEY idx_sys_oplog_time (create_time, id)
|
|
61
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
62
|
+
|
|
63
|
+
CREATE TABLE IF NOT EXISTS sys_solution_route (
|
|
64
|
+
path VARCHAR(255) PRIMARY KEY,
|
|
65
|
+
-- module 参与索引 → 必须收窄为 VARCHAR:mysql 的 TEXT 列建索引必须给前缀长度
|
|
66
|
+
-- (历史 0010 的 mysql 版在这里直接 ERROR 1170,MySQL 全新安装根本起不来)
|
|
67
|
+
module VARCHAR(255) NOT NULL,
|
|
68
|
+
category TEXT NOT NULL,
|
|
69
|
+
`order` BIGINT NOT NULL DEFAULT 0,
|
|
70
|
+
title_key TEXT,
|
|
71
|
+
required_permission TEXT,
|
|
72
|
+
deprecated BIGINT NOT NULL DEFAULT 0,
|
|
73
|
+
enabled BIGINT NOT NULL DEFAULT 1,
|
|
74
|
+
KEY idx_sys_solution_route_module (module)
|
|
75
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
76
|
+
|
|
77
|
+
CREATE TABLE IF NOT EXISTS sys_role (
|
|
78
|
+
name VARCHAR(255) PRIMARY KEY,
|
|
79
|
+
module TEXT NOT NULL,
|
|
80
|
+
title TEXT,
|
|
81
|
+
description TEXT,
|
|
82
|
+
builtin BIGINT NOT NULL DEFAULT 0,
|
|
83
|
+
deprecated BIGINT NOT NULL DEFAULT 0,
|
|
84
|
+
title_key TEXT
|
|
85
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
86
|
+
|
|
87
|
+
CREATE TABLE IF NOT EXISTS sys_permission (
|
|
88
|
+
code VARCHAR(255) PRIMARY KEY,
|
|
89
|
+
module TEXT NOT NULL,
|
|
90
|
+
title_key TEXT,
|
|
91
|
+
deprecated BIGINT NOT NULL DEFAULT 0
|
|
92
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
93
|
+
|
|
94
|
+
CREATE TABLE IF NOT EXISTS sys_role_permission (
|
|
95
|
+
role_name VARCHAR(255) NOT NULL,
|
|
96
|
+
permission_code VARCHAR(255) NOT NULL,
|
|
97
|
+
PRIMARY KEY (role_name, permission_code)
|
|
98
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
99
|
+
|
|
100
|
+
CREATE TABLE IF NOT EXISTS sys_user_role (
|
|
101
|
+
user_id BIGINT NOT NULL,
|
|
102
|
+
role_name VARCHAR(255) NOT NULL,
|
|
103
|
+
tenant_id VARCHAR(255) NOT NULL DEFAULT 'default',
|
|
104
|
+
PRIMARY KEY (user_id, role_name)
|
|
105
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
106
|
+
|
|
107
|
+
CREATE TABLE IF NOT EXISTS sys_refresh_token (
|
|
108
|
+
token_hash VARCHAR(255) PRIMARY KEY,
|
|
109
|
+
user_id BIGINT NOT NULL,
|
|
110
|
+
expires_at BIGINT NOT NULL,
|
|
111
|
+
revoked BIGINT NOT NULL DEFAULT 0,
|
|
112
|
+
created_at BIGINT NOT NULL,
|
|
113
|
+
tenant_id VARCHAR(255) NOT NULL DEFAULT 'default',
|
|
114
|
+
KEY idx_sys_refresh_token_user (user_id)
|
|
115
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
116
|
+
|
|
117
|
+
CREATE TABLE IF NOT EXISTS sys_rate_window (
|
|
118
|
+
rate_key VARCHAR(255) PRIMARY KEY,
|
|
119
|
+
window_start BIGINT NOT NULL,
|
|
120
|
+
count BIGINT NOT NULL
|
|
121
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
122
|
+
|
|
123
|
+
CREATE TABLE IF NOT EXISTS sys_login_throttle (
|
|
124
|
+
pair_key VARCHAR(255) PRIMARY KEY,
|
|
125
|
+
fail_count BIGINT NOT NULL,
|
|
126
|
+
blocked_until BIGINT NOT NULL DEFAULT 0
|
|
127
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
128
|
+
|
|
129
|
+
CREATE TABLE IF NOT EXISTS market_history (
|
|
130
|
+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
131
|
+
solution VARCHAR(255) NOT NULL,
|
|
132
|
+
version VARCHAR(255) NOT NULL,
|
|
133
|
+
action VARCHAR(64) NOT NULL,
|
|
134
|
+
status VARCHAR(64) NOT NULL,
|
|
135
|
+
operator VARCHAR(255),
|
|
136
|
+
message TEXT,
|
|
137
|
+
created_at BIGINT NOT NULL
|
|
138
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
139
|
+
|
|
140
|
+
CREATE TABLE IF NOT EXISTS reg_source_seq (
|
|
141
|
+
-- 主键参与索引 → 受 InnoDB 3072 字节上限约束:utf8mb4 每字符 4 字节,
|
|
142
|
+
-- 768 字符正好顶满,取 700 留余量(历史 0014 用 1024 → ERROR 1071,同样起不来)
|
|
143
|
+
source_url VARCHAR(700) NOT NULL,
|
|
144
|
+
max_seq BIGINT NOT NULL,
|
|
145
|
+
updated_at BIGINT NOT NULL,
|
|
146
|
+
PRIMARY KEY (source_url)
|
|
147
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
148
|
+
|
|
149
|
+
CREATE TABLE IF NOT EXISTS sys_department (
|
|
150
|
+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
151
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
152
|
+
origin VARCHAR(32) NOT NULL DEFAULT 'local',
|
|
153
|
+
origin_id VARCHAR(128) NOT NULL,
|
|
154
|
+
parent_id BIGINT NULL,
|
|
155
|
+
name VARCHAR(255),
|
|
156
|
+
name_en VARCHAR(255),
|
|
157
|
+
order_no INTEGER NOT NULL DEFAULT 0,
|
|
158
|
+
sync_visible INTEGER NOT NULL DEFAULT 1,
|
|
159
|
+
deleted INTEGER NOT NULL DEFAULT 0,
|
|
160
|
+
seen_job_id BIGINT,
|
|
161
|
+
update_time BIGINT NOT NULL,
|
|
162
|
+
UNIQUE KEY uk_sys_department_origin (tenant_id, origin, origin_id),
|
|
163
|
+
KEY idx_sys_department_parent (tenant_id, parent_id, deleted, order_no)
|
|
164
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
165
|
+
|
|
166
|
+
CREATE TABLE IF NOT EXISTS sys_dept_member (
|
|
167
|
+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
168
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
169
|
+
user_id BIGINT NOT NULL,
|
|
170
|
+
dept_id BIGINT NOT NULL,
|
|
171
|
+
order_no INTEGER NOT NULL DEFAULT 0,
|
|
172
|
+
is_leader INTEGER NOT NULL DEFAULT 0,
|
|
173
|
+
is_main INTEGER NOT NULL DEFAULT 0,
|
|
174
|
+
update_time BIGINT NOT NULL,
|
|
175
|
+
UNIQUE KEY uk_sys_dept_member (tenant_id, user_id, dept_id),
|
|
176
|
+
KEY idx_sys_dept_member_dept (tenant_id, dept_id, order_no),
|
|
177
|
+
KEY idx_sys_dept_member_user (tenant_id, user_id, is_main)
|
|
178
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|