perf: 重构自动加载模块并优化EAB授权处理

refactor(ui): 将分散的auto-*模块整合为统一命名的auto-register模块
perf(plugin-cert): 增强EAB授权功能,支持账号私钥刷新和类型选择
test: 添加EAB授权服务和ACME账号配置的单元测试
docs: 更新AGENTS.md补充ACME/EAB使用注意事项
chore: 统一各package.json中的测试脚本配置
This commit is contained in:
xiaojunnuo
2026-05-10 16:57:12 +08:00
parent 37d03c10f9
commit 4755216505
32 changed files with 911 additions and 105 deletions
@@ -1,7 +1,7 @@
import { logger } from '@certd/basic';
import { SysSettingsService, SysSiteInfo } from '@certd/lib-server';
import { getPlusInfo, isPlus } from "@certd/plus-core";
import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import dayjs from "dayjs";
import { Between } from "typeorm";
import { DomainService } from '../cert/service/domain-service.js';
@@ -14,9 +14,9 @@ import { PipelineService } from '../pipeline/service/pipeline-service.js';
import { UserService } from "../sys/authority/service/user-service.js";
import { ProjectService } from '../sys/enterprise/service/project-service.js';
@Autoload()
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoCRegisterCron {
export class AutoCron {
@Inject()
pipelineService: PipelineService;
@@ -53,7 +53,6 @@ export class AutoCRegisterCron {
@Init()
async init() {
logger.info('加载定时trigger开始');
await this.pipelineService.onStartup(this.immediateTriggerOnce, this.onlyAdminUser);
@@ -0,0 +1,182 @@
import assert from "assert";
import esmock from "esmock";
import { AutoFix, buildEabAccountKeyValue, buildLegacyGoogleAccountConfigWhere, parseStorageValue } from "./auto-fix.js";
function createAutoFix(options: { pluginConfigService?: any; accessService?: any; storageService?: any }) {
const autoFix = new AutoFix();
autoFix.pluginConfigService = options.pluginConfigService;
autoFix.accessService = options.accessService;
autoFix.storageService = options.storageService;
return autoFix;
}
describe("AutoFix", () => {
it("parses legacy storage values", () => {
const config = parseStorageValue(
JSON.stringify({
value: {
key: "legacy-private-key",
accountUrl: "https://example.com/acct/1",
},
})
);
assert.equal(config.key, "legacy-private-key");
});
it("builds the EAB account key payload", () => {
const payload = JSON.parse(buildEabAccountKeyValue("kid-1", "private-key"));
assert.deepEqual(payload, {
kid: "kid-1",
privateKey: "private-key",
});
});
it("builds legacy Google account config query by exact email key only", () => {
assert.deepEqual(buildLegacyGoogleAccountConfigWhere("user@example.com"), {
userId: 1,
scope: "user",
namespace: "1",
key: "acme.config.google.user@example.com",
});
});
it("finds legacy Google account config by exact email key only", async () => {
let findOneWhere: any;
let findCalled = false;
const autoFix = createAutoFix({
pluginConfigService: null as any,
accessService: null as any,
storageService: {
getRepository() {
return {
async findOne(options: any) {
findOneWhere = options.where;
return {
value: JSON.stringify({
value: {
privateKey: "legacy-private-key",
},
}),
};
},
async find() {
findCalled = true;
return [];
},
};
},
} as any,
});
const config = await autoFix.getLegacyGoogleAccountConfig("user@example.com");
assert.equal(config.privateKey, "legacy-private-key");
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
assert.equal(findCalled, false);
});
it("does not query legacy Google account config without email", async () => {
let repositoryCalled = false;
const autoFix = createAutoFix({
pluginConfigService: null as any,
accessService: null as any,
storageService: {
getRepository() {
repositoryCalled = true;
return {};
},
} as any,
});
const config = await autoFix.getLegacyGoogleAccountConfig();
assert.equal(config, null);
assert.equal(repositoryCalled, false);
});
it("skips Google common EAB account key fix outside commercial edition", async () => {
let pluginConfigCalled = false;
const autoFix = createAutoFix({
pluginConfigService: {
async getPluginConfig() {
pluginConfigCalled = true;
return null;
},
} as any,
accessService: null as any,
storageService: null as any,
});
await autoFix.init();
assert.equal(pluginConfigCalled, false);
});
it("fixes Google common EAB account key in commercial edition", async () => {
const { AutoFix: MockedAutoFix } = await esmock("./auto-fix.js", {
"@certd/plus-core": {
isComm: () => true,
},
});
let getAccessByIdArgs: any[] = [];
let findOneWhere: any;
let updateAccessParam: any;
const autoFix = new MockedAutoFix();
autoFix.pluginConfigService = {
async getPluginConfig(options: any) {
assert.deepEqual(options, {
name: "CertApply",
type: "builtIn",
});
return {
sysSetting: {
input: {
googleCommonEabAccessId: 12,
},
},
};
},
};
autoFix.accessService = {
async getAccessById(...args: any[]) {
getAccessByIdArgs = args;
return {
kid: "kid-1",
email: "user@example.com",
};
},
async updateAccess(param: any) {
updateAccessParam = param;
},
};
autoFix.storageService = {
getRepository() {
return {
async findOne(options: any) {
findOneWhere = options.where;
return {
value: JSON.stringify({
value: {
privateKey: "legacy-private-key",
},
}),
};
},
};
},
};
await autoFix.fixGoogleCommonEabAccountKey();
assert.deepEqual(getAccessByIdArgs, [12, false]);
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
assert.deepEqual(updateAccessParam, {
id: 12,
eabType: "google",
accountKey: buildEabAccountKeyValue("kid-1", "legacy-private-key"),
});
});
});
@@ -0,0 +1,107 @@
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { logger } from "@certd/basic";
import { AccessService } from "@certd/lib-server";
import { isComm } from "@certd/plus-core";
import { PluginConfigService } from "../plugin/service/plugin-config-service.js";
import { StorageService } from "../pipeline/service/storage-service.js";
export function parseStorageValue(value?: string) {
if (!value) {
return null;
}
try {
const parsed = JSON.parse(value);
return parsed?.value || null;
} catch {
return null;
}
}
export function buildEabAccountKeyValue(kid: string, privateKey: string) {
return JSON.stringify({
kid,
privateKey,
});
}
export function buildLegacyGoogleAccountConfigWhere(email: string) {
return {
userId: 1,
scope: "user",
namespace: "1",
key: `acme.config.google.${email}`,
};
}
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoFix {
@Inject()
pluginConfigService: PluginConfigService;
@Inject()
accessService: AccessService;
@Inject()
storageService: StorageService;
async init() {
await this.fixGoogleCommonEabAccountKey();
}
async fixGoogleCommonEabAccountKey() {
if (!isComm()) {
return;
}
try {
const certApplyConfig = await this.pluginConfigService.getPluginConfig({
name: "CertApply",
type: "builtIn",
});
const googleCommonEabAccessId = certApplyConfig?.sysSetting?.input?.googleCommonEabAccessId;
if (!googleCommonEabAccessId) {
return;
}
const eabAccess = await this.accessService.getAccessById(googleCommonEabAccessId, false);
if (eabAccess.accountKey) {
return;
}
if (!eabAccess.kid) {
logger.info("公共Google EAB授权缺少KID,跳过历史ACME账号私钥修复");
return;
}
const accountConfig = await this.getLegacyGoogleAccountConfig(eabAccess.email);
const privateKey = accountConfig?.privateKey || accountConfig?.key || accountConfig?.accountKey;
if (!privateKey) {
logger.info("未找到可迁移到公共Google EAB授权的历史ACME账号私钥");
return;
}
const accountKey = buildEabAccountKeyValue(eabAccess.kid, privateKey);
await this.accessService.updateAccess({ id: googleCommonEabAccessId, eabType: "google", accountKey });
logger.info(`已修复公共Google EAB授权的ACME账号私钥,accessId=${googleCommonEabAccessId}`);
} catch (e: any) {
logger.error("修复公共Google EAB授权ACME账号私钥失败", e);
}
}
async getLegacyGoogleAccountConfig(email?: string) {
if (!email) {
return null;
}
const repository = this.storageService.getRepository();
const exact = await repository.findOne({
where: buildLegacyGoogleAccountConfigWhere(email),
});
const exactValue = this.parseStorageValue(exact?.value);
if (exactValue?.key || exactValue?.privateKey || exactValue?.accountKey) {
return exactValue;
}
return null;
}
parseStorageValue(value?: string) {
return parseStorageValue(value);
}
}
@@ -1,14 +1,14 @@
import { logger } from '@certd/basic';
import { PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from '@certd/lib-server';
import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import crypto from 'crypto';
import { nanoid } from 'nanoid';
import { UserService } from '../sys/authority/service/user-service.js';
import { SafeService } from "../sys/settings/safe-service.js";
@Autoload()
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoAInitSite {
export class AutoInitSite {
@Inject()
userService: UserService;
@@ -22,7 +22,6 @@ export class AutoAInitSite {
@Inject()
safeService: SafeService;
@Init()
async init() {
logger.info('初始化站点开始');
await this.startOptimizeDb();
@@ -1,16 +1,15 @@
import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/core";
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { logger } from "@certd/basic";
import { PluginService } from "../plugin/service/plugin-service.js";
import { registerPaymentProviders } from "../suite/payments/index.js";
@Autoload()
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoBLoadPlugins {
export class AutoLoadPlugins {
@Inject()
pluginService: PluginService;
@Init()
async init() {
logger.info(`加载插件开始,加载模式:${process.env.certd_plugin_loadmode}`);
if (process.env.certd_plugin_loadmode === "metadata") {
@@ -1,14 +1,13 @@
import { logger, utils } from '@certd/basic';
import { UserSuiteService } from '@certd/commercial-core';
import { Autoload, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
@Autoload()
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoDMitterRegister {
export class AutoMitterRegister {
@Inject()
userSuiteService: UserSuiteService;
@Init()
async init() {
await this.registerOnNewUser();
}
@@ -1,21 +1,21 @@
import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/core";
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { CertInfoService } from "../monitor/index.js";
import { pipelineEmitter } from "@certd/pipeline";
import { CertInfo, EVENT_CERT_APPLY_SUCCESS } from "@certd/plugin-cert";
import { PipelineEvent } from "@certd/pipeline";
@Autoload()
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoEPipelineEmitterRegister {
export class AutoPipelineEmitterRegister {
@Inject()
certInfoService: CertInfoService;
@Init()
async init() {
await this.onCertApplySuccess();
}
async onCertApplySuccess() {
pipelineEmitter.on(EVENT_CERT_APPLY_SUCCESS, async (event: PipelineEvent<{cert:CertInfo,file:string}>) => {
pipelineEmitter.on(EVENT_CERT_APPLY_SUCCESS, async (event: PipelineEvent<{ cert: CertInfo; file: string }>) => {
await this.certInfoService.updateCertByPipelineId(event.pipeline.id, event.event.cert, event.event.file);
});
}
@@ -1,4 +1,4 @@
import { App, Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { App, Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { getPlusInfo, isPlus } from '@certd/plus-core';
import { isDev, logger } from '@certd/basic';
@@ -11,9 +11,9 @@ import { UserService } from '../sys/authority/service/user-service.js';
import { UserSettingsService } from '../mine/service/user-settings-service.js';
import { startProxyServer } from './proxy/server.js';
@Autoload()
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoZPrint {
export class AutoPrint {
@Inject()
sysSettingsService: SysSettingsService;
@@ -34,7 +34,6 @@ export class AutoZPrint {
@Config('system.resetAdminPasswd')
private resetAdminPasswd: boolean;
@Init()
async init() {
//监听https
this.startHttpsServer();
@@ -0,0 +1,44 @@
import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/core";
import { AutoInitSite } from "./auto-init-site.js";
import { AutoLoadPlugins } from "./auto-load-plugins.js";
import { AutoCron } from "./auto-cron.js";
import { AutoMitterRegister } from "./auto-mitter-register.js";
import { AutoPipelineEmitterRegister } from "./auto-pipeline-emitter-register.js";
import { AutoFix } from "./auto-fix.js";
import { AutoPrint } from "./auto-print.js";
@Autoload()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoRegister {
@Inject()
autoInitSite: AutoInitSite;
@Inject()
autoLoadPlugins: AutoLoadPlugins;
@Inject()
autoCron: AutoCron;
@Inject()
autoMitterRegister: AutoMitterRegister;
@Inject()
autoPipelineEmitterRegister: AutoPipelineEmitterRegister;
@Inject()
autoPrint: AutoPrint;
@Inject()
autoFix: AutoFix;
@Init()
async init() {
await this.autoInitSite.init();
await this.autoLoadPlugins.init();
await this.autoCron.init();
await this.autoMitterRegister.init();
await this.autoPipelineEmitterRegister.init();
await this.autoFix.init();
await this.autoPrint.init();
}
}