diff --git a/packages/core/acme-client/package.json b/packages/core/acme-client/package.json index c03e5710c..34d545569 100644 --- a/packages/core/acme-client/package.json +++ b/packages/core/acme-client/package.json @@ -50,7 +50,7 @@ "scripts": { "before-build": "node -e \"const fs=require('fs');fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tsconfig.tsbuildinfo',{force:true});\"", "build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck", - "lint": "eslint \"src/**/*.ts\" \"types/**/*.ts\"", + "lint": "eslint --fix \"src/**/*.ts\" \"types/**/*.ts\"", "lint-types": "tsd --files \"types/index.test-d.ts\"", "prepublishOnly": "npm run build", "test": "mocha -t 60000 \"test/setup.js\" \"test/**/*.spec.js\"", diff --git a/packages/core/basic/package.json b/packages/core/basic/package.json index 7e460b2b4..b1a813132 100644 --- a/packages/core/basic/package.json +++ b/packages/core/basic/package.json @@ -17,7 +17,7 @@ "pub": "npm publish", "compile": "tsc --skipLibCheck --watch", "format": "prettier --write src", - "lint": "eslint --fix" + "lint": "eslint --fix --ext .ts src" }, "dependencies": { "async-lock": "^1.4.1", diff --git a/packages/core/basic/src/utils/util.cache.ts b/packages/core/basic/src/utils/util.cache.ts index b10a2c128..9e35147b3 100644 --- a/packages/core/basic/src/utils/util.cache.ts +++ b/packages/core/basic/src/utils/util.cache.ts @@ -11,9 +11,12 @@ export class LocalCache { cache: Map; constructor(opts: { clearInterval?: number } = {}) { this.cache = new Map(); - const intervalId = setInterval(() => { - this.clearExpires(); - }, opts.clearInterval ?? 5 * 60 * 1000); + const intervalId = setInterval( + () => { + this.clearExpires(); + }, + opts.clearInterval ?? 5 * 60 * 1000 + ); intervalId.unref?.(); } diff --git a/packages/core/basic/src/utils/util.env.ts b/packages/core/basic/src/utils/util.env.ts index 6f65775b1..471fdfd2c 100644 --- a/packages/core/basic/src/utils/util.env.ts +++ b/packages/core/basic/src/utils/util.env.ts @@ -1,4 +1,4 @@ export function isDev() { - const nodeEnv = process.env.NODE_ENV || 'dev'; - return nodeEnv === 'development' || nodeEnv.includes('local') || nodeEnv.startsWith('dev'); + const nodeEnv = process.env.NODE_ENV || "dev"; + return nodeEnv === "development" || nodeEnv.includes("local") || nodeEnv.startsWith("dev"); } diff --git a/packages/core/basic/src/utils/util.file.ts b/packages/core/basic/src/utils/util.file.ts index 603bc12f1..70ef9d6a2 100644 --- a/packages/core/basic/src/utils/util.file.ts +++ b/packages/core/basic/src/utils/util.file.ts @@ -1,8 +1,8 @@ -import fs from 'fs'; +import fs from "fs"; function getFileRootDir(rootDir?: string) { if (rootDir == null) { const userHome = process.env.HOME || process.env.USERPROFILE; - rootDir = userHome + '/.certd/storage/'; + rootDir = userHome + "/.certd/storage/"; } if (!fs.existsSync(rootDir)) { diff --git a/packages/core/basic/src/utils/util.mitter.ts b/packages/core/basic/src/utils/util.mitter.ts index 3ffb07a93..be0f064f4 100644 --- a/packages/core/basic/src/utils/util.mitter.ts +++ b/packages/core/basic/src/utils/util.mitter.ts @@ -1,2 +1,2 @@ -import mitt from 'mitt'; +import mitt from "mitt"; export const mitter = mitt(); diff --git a/packages/core/pipeline/package.json b/packages/core/pipeline/package.json index ecc01e70e..70c1dd20e 100644 --- a/packages/core/pipeline/package.json +++ b/packages/core/pipeline/package.json @@ -18,7 +18,7 @@ "pub": "npm publish", "compile": "tsc --skipLibCheck --watch", "format": "prettier --write src", - "lint": "eslint --fix" + "lint": "eslint --fix --ext .ts src" }, "dependencies": { "@certd/basic": "^1.42.6", diff --git a/packages/libs/lib-huawei/package.json b/packages/libs/lib-huawei/package.json index de95fd22a..d81c536cd 100644 --- a/packages/libs/lib-huawei/package.json +++ b/packages/libs/lib-huawei/package.json @@ -15,7 +15,7 @@ "pub": "npm publish", "compile": "npm run build", "format": "prettier --write src", - "lint": "eslint --fix" + "lint": "eslint --fix --ext .ts src" }, "dependencies": { "axios": "^1.9.0", diff --git a/packages/libs/lib-iframe/package.json b/packages/libs/lib-iframe/package.json index 85b1c01a0..511026292 100644 --- a/packages/libs/lib-iframe/package.json +++ b/packages/libs/lib-iframe/package.json @@ -18,7 +18,7 @@ "pub": "npm publish", "compile": "npm run build", "format": "prettier --write src", - "lint": "eslint --fix" + "lint": "eslint --fix --ext .ts src" }, "dependencies": { "nanoid": "^5.0.7" diff --git a/packages/libs/lib-iframe/src/index.ts b/packages/libs/lib-iframe/src/index.ts index d7e97c011..659a9e004 100644 --- a/packages/libs/lib-iframe/src/index.ts +++ b/packages/libs/lib-iframe/src/index.ts @@ -1 +1 @@ -export * from './lib/iframe.client.js'; +export * from "./lib/iframe.client.js"; diff --git a/packages/libs/lib-iframe/src/lib/iframe.client.ts b/packages/libs/lib-iframe/src/lib/iframe.client.ts index f2d90a5f0..48719676d 100644 --- a/packages/libs/lib-iframe/src/lib/iframe.client.ts +++ b/packages/libs/lib-iframe/src/lib/iframe.client.ts @@ -1,4 +1,4 @@ -import { nanoid } from 'nanoid'; +import { nanoid } from "nanoid"; export type IframeMessageData = { action: string; @@ -32,7 +32,7 @@ export class IframeClient { constructor(iframe?: HTMLIFrameElement, onError?: (e: any) => void) { this.iframe = iframe; this.onError = onError; - window.addEventListener('message', async (event: MessageEvent>) => { + window.addEventListener("message", async (event: MessageEvent>) => { const data = event.data; if (data.action) { console.log(`收到消息[isSub:${this.isInFrame()}]`, data); @@ -40,20 +40,20 @@ export class IframeClient { const handler = this.handlers[data.action]; if (handler) { const res = await handler(data); - if (data.id && data.action !== 'reply') { - await this.send('reply', res, data.id); + if (data.id && data.action !== "reply") { + await this.send("reply", res, data.id); } } else { throw new Error(`action:${data.action} 未注册处理器,可能版本过低`); } } catch (e: any) { console.error(e); - await this.send('reply', {}, data.id, 500, e.message); + await this.send("reply", {}, data.id, 500, e.message); } } }); - this.register('reply', async data => { + this.register("reply", async data => { const req = this.requestQueue[data.replyId!]; if (req) { req.onReply(data); @@ -106,12 +106,12 @@ export class IframeClient { console.log(`send message[isSub:${this.isInFrame()}]:`, reqMessageData); if (!this.iframe) { if (!window.parent) { - reject('当前页面不在 iframe 中'); + reject("当前页面不在 iframe 中"); } - window.parent.postMessage(reqMessageData, '*'); + window.parent.postMessage(reqMessageData, "*"); } else { //子页面 - this.iframe.contentWindow?.postMessage(reqMessageData, '*'); + this.iframe.contentWindow?.postMessage(reqMessageData, "*"); } } catch (e) { console.error(e); diff --git a/packages/libs/lib-jdcloud/package.json b/packages/libs/lib-jdcloud/package.json index da4bce87d..056b13d41 100644 --- a/packages/libs/lib-jdcloud/package.json +++ b/packages/libs/lib-jdcloud/package.json @@ -13,7 +13,7 @@ "pub": "npm publish", "compile": "npm run build", "format": "prettier --write src", - "lint": "eslint --fix" + "lint": "eslint --fix --ext .ts src" }, "author": "", "license": "Apache", diff --git a/packages/libs/lib-jdcloud/src/index.ts b/packages/libs/lib-jdcloud/src/index.ts index d870f61cc..63871d29a 100644 --- a/packages/libs/lib-jdcloud/src/index.ts +++ b/packages/libs/lib-jdcloud/src/index.ts @@ -1,11 +1,11 @@ -import jdCloud from "./lib/core.js"; -import jdService from './lib/service.js' +import jdCloud from './lib/core.js'; +import jdService from './lib/service.js'; -import domainService from './repo/domainservice/v2/domainservice.js' -import cdnService from './repo/cdn/v1/cdn.js' -import sslService from './repo/ssl/v1/ssl.js' +import domainService from './repo/domainservice/v2/domainservice.js'; +import cdnService from './repo/cdn/v1/cdn.js'; +import sslService from './repo/ssl/v1/ssl.js'; export const JDCloud = jdCloud; export const JDService = jdService; export const JDDomainService = domainService; export const JDCdnService = cdnService; -export const JDSslService = sslService; \ No newline at end of file +export const JDSslService = sslService; diff --git a/packages/libs/lib-k8s/package.json b/packages/libs/lib-k8s/package.json index 989b7838a..2f21c7d34 100644 --- a/packages/libs/lib-k8s/package.json +++ b/packages/libs/lib-k8s/package.json @@ -18,7 +18,7 @@ "pub": "npm publish", "compile": "tsc --skipLibCheck --watch", "format": "prettier --write src", - "lint": "eslint --fix" + "lint": "eslint --fix --ext .ts src" }, "dependencies": { "@certd/basic": "^1.42.6", diff --git a/packages/libs/lib-server/package.json b/packages/libs/lib-server/package.json index 34ec9ccba..3217c0dff 100644 --- a/packages/libs/lib-server/package.json +++ b/packages/libs/lib-server/package.json @@ -19,7 +19,7 @@ "pub": "npm publish", "compile": "tsc --skipLibCheck --watch", "format": "prettier --write src", - "lint": "eslint --fix" + "lint": "eslint --fix --ext .ts src" }, "keywords": [], "author": "greper", diff --git a/packages/libs/lib-server/src/basic/constants.ts b/packages/libs/lib-server/src/basic/constants.ts index 73de33205..9ae39c62b 100644 --- a/packages/libs/lib-server/src/basic/constants.ts +++ b/packages/libs/lib-server/src/basic/constants.ts @@ -1,21 +1,21 @@ export const Constants = { - dataDir: './data', + dataDir: "./data", role: { defaultUser: 3, }, per: { //无需登录 - guest: '_guest_', + guest: "_guest_", //无需登录 - anonymous: '_guest_', + anonymous: "_guest_", //无需登录,有 token 时解析当前用户 - guestOptionalAuth: '_guestOptionalAuth_', + guestOptionalAuth: "_guestOptionalAuth_", //仅需要登录 - authOnly: '_authOnly_', + authOnly: "_authOnly_", //仅需要登录 - loginOnly: '_authOnly_', + loginOnly: "_authOnly_", - open: '_open_', + open: "_open_", }, res: { serverError(message: string) { @@ -26,102 +26,102 @@ export const Constants = { }, error: { code: 1, - message: 'Internal server error', + message: "Internal server error", }, success: { code: 0, - message: 'success', + message: "success", }, validation: { code: 10, - message: '参数错误', + message: "参数错误", }, needvip: { code: 88, - message: '需要VIP', + message: "需要VIP", }, needsuite: { code: 89, - message: '需要购买或升级套餐', + message: "需要购买或升级套餐", }, loginError: { code: 2, - message: '登录失败', + message: "登录失败", }, codeError: { code: 3, - message: '验证码错误', + message: "验证码错误", }, auth: { code: 401, - message: '您还未登录或token已过期', + message: "您还未登录或token已过期", }, permission: { code: 402, - message: '您没有权限', + message: "您没有权限", }, param: { code: 400, - message: '参数错误', + message: "参数错误", }, notFound: { code: 404, - message: '页面/文件/资源不存在', + message: "页面/文件/资源不存在", }, preview: { code: 10001, - message: '对不起,预览环境不允许修改此数据', + message: "对不起,预览环境不允许修改此数据", }, - siteOff:{ + siteOff: { code: 10010, - message: '站点已关闭', + message: "站点已关闭", }, - need2fa:{ + need2fa: { code: 10020, - message: '需要2FA认证', + message: "需要2FA认证", }, openKeyError: { code: 20000, - message: 'ApiToken错误', + message: "ApiToken错误", }, openKeySignError: { code: 20001, - message: 'ApiToken签名错误', + message: "ApiToken签名错误", }, openKeyExpiresError: { code: 20002, - message: 'ApiToken时间戳错误', + message: "ApiToken时间戳错误", }, openKeySignTypeError: { code: 20003, - message: 'ApiToken签名类型不支持', + message: "ApiToken签名类型不支持", }, openParamError: { code: 20010, - message: '请求参数错误', + message: "请求参数错误", }, openCertNotFound: { code: 20011, - message: '证书不存在', + message: "证书不存在", }, openCertNotReady: { code: 20012, - message: '证书还未生成', + message: "证书还未生成", }, openCertApplying: { code: 20013, - message: '证书正在申请中,请稍后重新获取', + message: "证书正在申请中,请稍后重新获取", }, - openDomainNoVerifier:{ + openDomainNoVerifier: { code: 20014, - message: '域名校验方式未配置', + message: "域名校验方式未配置", }, openEmailNotFound: { code: 20021, - message: '用户邮箱还未配置', + message: "用户邮箱还未配置", }, }, systemUserId: 0, // 系统级别userid固定为0 - enterpriseUserId: -1 // 企业模式用户id固定为-1 + enterpriseUserId: -1, // 企业模式用户id固定为-1 }; diff --git a/packages/libs/lib-server/src/basic/crud-controller.ts b/packages/libs/lib-server/src/basic/crud-controller.ts index 11f9f8291..312cfbfda 100644 --- a/packages/libs/lib-server/src/basic/crud-controller.ts +++ b/packages/libs/lib-server/src/basic/crud-controller.ts @@ -1,11 +1,11 @@ -import { ALL, Body, Post, Query } from '@midwayjs/core'; -import { BaseController } from './base-controller.js'; +import { ALL, Body, Post, Query } from "@midwayjs/core"; +import { BaseController } from "./base-controller.js"; export abstract class CrudController extends BaseController { // eslint-disable-next-line @typescript-eslint/no-unused-vars abstract getService(); - @Post('/page') + @Post("/page") async page(@Body(ALL) body: any) { const pageRet = await this.getService().page({ query: body.query ?? {}, @@ -16,7 +16,7 @@ export abstract class CrudController extends BaseController { return this.ok(pageRet); } - @Post('/list') + @Post("/list") async list(@Body(ALL) body: any) { const listRet = await this.getService().list({ query: body.query ?? {}, @@ -25,33 +25,33 @@ export abstract class CrudController extends BaseController { return this.ok(listRet); } - @Post('/add') + @Post("/add") async add(@Body(ALL) bean: any) { delete bean.id; const id = await this.getService().add(bean); return this.ok(id); } - @Post('/info') - async info(@Query('id') id: number) { + @Post("/info") + async info(@Query("id") id: number) { const bean = await this.getService().info(id); return this.ok(bean); } - @Post('/update') + @Post("/update") async update(@Body(ALL) bean: any) { await this.getService().update(bean); return this.ok(null); } - @Post('/delete') - async delete(@Query('id') id: number) { + @Post("/delete") + async delete(@Query("id") id: number) { await this.getService().delete([id]); return this.ok(null); } - @Post('/deleteByIds') - async deleteByIds(@Body('ids') ids: number[]) { + @Post("/deleteByIds") + async deleteByIds(@Body("ids") ids: number[]) { await this.getService().delete(ids); return this.ok(null); } diff --git a/packages/libs/lib-server/src/basic/exception/auth-exception.ts b/packages/libs/lib-server/src/basic/exception/auth-exception.ts index 299abdff1..47d9ec50c 100644 --- a/packages/libs/lib-server/src/basic/exception/auth-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/auth-exception.ts @@ -1,19 +1,17 @@ -import { Constants } from '../constants.js'; -import { BaseException } from './base-exception.js'; +import { Constants } from "../constants.js"; +import { BaseException } from "./base-exception.js"; import { TextException } from "./common-exception.js"; /** * 授权异常 */ export class AuthException extends BaseException { - constructor(message?:string) { - super('AuthException', Constants.res.auth.code, message ? message : Constants.res.auth.message); + constructor(message?: string) { + super("AuthException", Constants.res.auth.code, message ? message : Constants.res.auth.message); } } - export class Need2FAException extends TextException { - constructor(message:string,data:any) { - super('Need2FAException', Constants.res.need2fa.code, message ? message : Constants.res.need2fa.message,data); + constructor(message: string, data: any) { + super("Need2FAException", Constants.res.need2fa.code, message ? message : Constants.res.need2fa.message, data); } } - diff --git a/packages/libs/lib-server/src/basic/exception/base-exception.ts b/packages/libs/lib-server/src/basic/exception/base-exception.ts index 65959654d..9de5f56c5 100644 --- a/packages/libs/lib-server/src/basic/exception/base-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/base-exception.ts @@ -3,8 +3,8 @@ */ export class BaseException extends Error { code: number; - data?:any - constructor(name: string, code: number, message: string ,data?:any) { + data?: any; + constructor(name: string, code: number, message: string, data?: any) { super(message); this.name = name; this.code = code; diff --git a/packages/libs/lib-server/src/basic/exception/code-error-exception.ts b/packages/libs/lib-server/src/basic/exception/code-error-exception.ts index cee740520..477de7c0a 100644 --- a/packages/libs/lib-server/src/basic/exception/code-error-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/code-error-exception.ts @@ -1,10 +1,10 @@ -import { Constants } from '../constants.js'; -import { BaseException } from './base-exception.js'; +import { Constants } from "../constants.js"; +import { BaseException } from "./base-exception.js"; /** * 验证码异常 */ export class CodeErrorException extends BaseException { constructor(message) { - super('CodeErrorException', Constants.res.codeError.code, message ? message : Constants.res.codeError.message); + super("CodeErrorException", Constants.res.codeError.code, message ? message : Constants.res.codeError.message); } } diff --git a/packages/libs/lib-server/src/basic/exception/index.ts b/packages/libs/lib-server/src/basic/exception/index.ts index ff2bca7ca..223e13b47 100644 --- a/packages/libs/lib-server/src/basic/exception/index.ts +++ b/packages/libs/lib-server/src/basic/exception/index.ts @@ -1,12 +1,13 @@ -export * from './auth-exception.js'; -export * from './base-exception.js'; -export * from './permission-exception.js'; -export * from './preview-exception.js'; -export * from './validation-exception.js'; -export * from './vip-exception.js'; -export * from './common-exception.js'; -export * from './not-found-exception.js'; -export * from './param-exception.js'; -export * from './site-off-exception.js'; -export * from './login-error-exception.js' -export * from './code-error-exception.js' +export * from "./auth-exception.js"; +export * from "./base-exception.js"; +export * from "./permission-exception.js"; +export * from "./preview-exception.js"; +export * from "./validation-exception.js"; +export * from "./vip-exception.js"; +export * from "./common-exception.js"; +export * from "./not-found-exception.js"; +export * from "./param-exception.js"; +export * from "./site-off-exception.js"; +export * from "./login-error-exception.js"; +export * from "./code-error-exception.js"; +export * from "./non-retryable-exception.js"; diff --git a/packages/libs/lib-server/src/basic/exception/login-error-exception.ts b/packages/libs/lib-server/src/basic/exception/login-error-exception.ts index b84892f55..7e3bc6b9d 100644 --- a/packages/libs/lib-server/src/basic/exception/login-error-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/login-error-exception.ts @@ -1,12 +1,12 @@ -import { Constants } from '../constants.js'; -import { BaseException } from './base-exception.js'; +import { Constants } from "../constants.js"; +import { BaseException } from "./base-exception.js"; /** * 通用异常 */ export class LoginErrorException extends BaseException { leftCount: number; constructor(message, leftCount: number) { - super('LoginErrorException', Constants.res.loginError.code, message ? message : Constants.res.loginError.message); + super("LoginErrorException", Constants.res.loginError.code, message ? message : Constants.res.loginError.message); this.leftCount = leftCount; } } diff --git a/packages/libs/lib-server/src/basic/exception/non-retryable-exception.test.ts b/packages/libs/lib-server/src/basic/exception/non-retryable-exception.test.ts new file mode 100644 index 000000000..630753705 --- /dev/null +++ b/packages/libs/lib-server/src/basic/exception/non-retryable-exception.test.ts @@ -0,0 +1,12 @@ +import assert from "assert"; +import { NonRetryableException } from "./non-retryable-exception.js"; + +describe("NonRetryableException", () => { + it("sets the standard error name and message", () => { + const error = new NonRetryableException("cannot retry"); + + assert.equal(error.name, "NonRetryableException"); + assert.equal(error.message, "cannot retry"); + assert.equal(error instanceof Error, true); + }); +}); diff --git a/packages/libs/lib-server/src/basic/exception/non-retryable-exception.ts b/packages/libs/lib-server/src/basic/exception/non-retryable-exception.ts new file mode 100644 index 000000000..58bc43e20 --- /dev/null +++ b/packages/libs/lib-server/src/basic/exception/non-retryable-exception.ts @@ -0,0 +1,6 @@ +export class NonRetryableException extends Error { + constructor(message: string) { + super(message); + this.name = "NonRetryableException"; + } +} diff --git a/packages/libs/lib-server/src/basic/exception/not-found-exception.ts b/packages/libs/lib-server/src/basic/exception/not-found-exception.ts index f056c29b0..e34d027b1 100644 --- a/packages/libs/lib-server/src/basic/exception/not-found-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/not-found-exception.ts @@ -1,10 +1,10 @@ -import { Constants } from '../constants.js'; -import { BaseException } from './base-exception.js'; +import { Constants } from "../constants.js"; +import { BaseException } from "./base-exception.js"; /** * 资源不存在 */ export class NotFoundException extends BaseException { constructor(message) { - super('NotFoundException', Constants.res.notFound.code, message ? message : Constants.res.notFound.message); + super("NotFoundException", Constants.res.notFound.code, message ? message : Constants.res.notFound.message); } } diff --git a/packages/libs/lib-server/src/basic/exception/param-exception.ts b/packages/libs/lib-server/src/basic/exception/param-exception.ts index 675c224ed..38d2f3afb 100644 --- a/packages/libs/lib-server/src/basic/exception/param-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/param-exception.ts @@ -1,10 +1,10 @@ -import { Constants } from '../constants.js'; -import { BaseException } from './base-exception.js'; +import { Constants } from "../constants.js"; +import { BaseException } from "./base-exception.js"; /** * 参数异常 */ export class ParamException extends BaseException { constructor(message) { - super('ParamException', Constants.res.param.code, message ? message : Constants.res.param.message); + super("ParamException", Constants.res.param.code, message ? message : Constants.res.param.message); } } diff --git a/packages/libs/lib-server/src/basic/exception/permission-exception.ts b/packages/libs/lib-server/src/basic/exception/permission-exception.ts index 4796f282c..5f7f6eaa7 100644 --- a/packages/libs/lib-server/src/basic/exception/permission-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/permission-exception.ts @@ -1,10 +1,10 @@ -import { Constants } from '../constants.js'; -import { BaseException } from './base-exception.js'; +import { Constants } from "../constants.js"; +import { BaseException } from "./base-exception.js"; /** * 授权异常 */ export class PermissionException extends BaseException { constructor(message?: string) { - super('PermissionException', Constants.res.permission.code, message ? message : Constants.res.permission.message); + super("PermissionException", Constants.res.permission.code, message ? message : Constants.res.permission.message); } } diff --git a/packages/libs/lib-server/src/basic/exception/preview-exception.ts b/packages/libs/lib-server/src/basic/exception/preview-exception.ts index 26467647c..58b70b6f0 100644 --- a/packages/libs/lib-server/src/basic/exception/preview-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/preview-exception.ts @@ -1,14 +1,10 @@ -import { Constants } from '../constants.js'; -import { BaseException } from './base-exception.js'; +import { Constants } from "../constants.js"; +import { BaseException } from "./base-exception.js"; /** * 预览模式 */ export class PreviewException extends BaseException { constructor(message) { - super( - 'PreviewException', - Constants.res.preview.code, - message ? message : Constants.res.preview.message - ); + super("PreviewException", Constants.res.preview.code, message ? message : Constants.res.preview.message); } } diff --git a/packages/libs/lib-server/src/basic/exception/site-off-exception.ts b/packages/libs/lib-server/src/basic/exception/site-off-exception.ts index f2527e161..e8f5d4e9c 100644 --- a/packages/libs/lib-server/src/basic/exception/site-off-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/site-off-exception.ts @@ -1,9 +1,9 @@ -import { Constants } from '../constants.js'; -import { BaseException } from './base-exception.js'; +import { Constants } from "../constants.js"; +import { BaseException } from "./base-exception.js"; /** */ export class SiteOffException extends BaseException { constructor(message) { - super('SiteOffException', Constants.res.siteOff.code, message ? message : Constants.res.siteOff.message); + super("SiteOffException", Constants.res.siteOff.code, message ? message : Constants.res.siteOff.message); } } diff --git a/packages/libs/lib-server/src/basic/exception/validation-exception.ts b/packages/libs/lib-server/src/basic/exception/validation-exception.ts index 448558c99..9588a0db5 100644 --- a/packages/libs/lib-server/src/basic/exception/validation-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/validation-exception.ts @@ -1,10 +1,10 @@ -import { Constants } from '../constants.js'; -import { BaseException } from './base-exception.js'; +import { Constants } from "../constants.js"; +import { BaseException } from "./base-exception.js"; /** * 校验异常 */ export class ValidateException extends BaseException { constructor(message) { - super('ValidateException', Constants.res.validation.code, message ? message : Constants.res.validation.message); + super("ValidateException", Constants.res.validation.code, message ? message : Constants.res.validation.message); } } diff --git a/packages/libs/lib-server/src/basic/exception/vip-exception.ts b/packages/libs/lib-server/src/basic/exception/vip-exception.ts index 86f65f69e..c96a4a636 100644 --- a/packages/libs/lib-server/src/basic/exception/vip-exception.ts +++ b/packages/libs/lib-server/src/basic/exception/vip-exception.ts @@ -1,16 +1,16 @@ -import { Constants } from '../constants.js'; -import { BaseException } from './base-exception.js'; +import { Constants } from "../constants.js"; +import { BaseException } from "./base-exception.js"; /** * 需要vip异常 */ export class NeedVIPException extends BaseException { constructor(message) { - super('NeedVIPException', Constants.res.needvip.code, message ? message : Constants.res.needvip.message); + super("NeedVIPException", Constants.res.needvip.code, message ? message : Constants.res.needvip.message); } } export class NeedSuiteException extends BaseException { constructor(message) { - super('NeedSuiteException', Constants.res.needsuite.code, message ? message : Constants.res.needsuite.message); + super("NeedSuiteException", Constants.res.needsuite.code, message ? message : Constants.res.needsuite.message); } } diff --git a/packages/libs/lib-server/src/basic/index.ts b/packages/libs/lib-server/src/basic/index.ts index d012ec35c..9ab818950 100644 --- a/packages/libs/lib-server/src/basic/index.ts +++ b/packages/libs/lib-server/src/basic/index.ts @@ -1,8 +1,8 @@ -export * from './base-controller.js'; -export * from './constants.js'; -export * from './crud-controller.js'; -export * from './enum-item.js'; -export * from './exception/index.js'; -export * from './result.js'; -export * from './base-service.js'; -export * from "./mode.js" \ No newline at end of file +export * from "./base-controller.js"; +export * from "./constants.js"; +export * from "./crud-controller.js"; +export * from "./enum-item.js"; +export * from "./exception/index.js"; +export * from "./result.js"; +export * from "./base-service.js"; +export * from "./mode.js"; diff --git a/packages/libs/lib-server/src/basic/mode.ts b/packages/libs/lib-server/src/basic/mode.ts index 2f8318150..cf5768d27 100644 --- a/packages/libs/lib-server/src/basic/mode.ts +++ b/packages/libs/lib-server/src/basic/mode.ts @@ -1,12 +1,12 @@ -let adminMode = "saas" +let adminMode = "saas"; -export function setAdminMode(mode:string = "saas"){ - adminMode = mode +export function setAdminMode(mode: string = "saas") { + adminMode = mode; } -export function getAdminMode(){ - return adminMode +export function getAdminMode() { + return adminMode; } -export function isEnterprise(){ - return adminMode === "enterprise" -} \ No newline at end of file +export function isEnterprise() { + return adminMode === "enterprise"; +} diff --git a/packages/libs/lib-server/src/configuration.ts b/packages/libs/lib-server/src/configuration.ts index aa47b11f6..4c5b24aaf 100644 --- a/packages/libs/lib-server/src/configuration.ts +++ b/packages/libs/lib-server/src/configuration.ts @@ -1,11 +1,11 @@ -import type { IMidwayContainer } from '@midwayjs/core'; -import { Configuration } from '@midwayjs/core'; -import { logger } from '@certd/basic'; +import type { IMidwayContainer } from "@midwayjs/core"; +import { Configuration } from "@midwayjs/core"; +import { logger } from "@certd/basic"; @Configuration({ - namespace: 'lib-server', + namespace: "lib-server", }) export class LibServerConfiguration { async onReady(container: IMidwayContainer) { - logger.info('lib start...'); + logger.info("lib start..."); } } diff --git a/packages/libs/lib-server/src/index.ts b/packages/libs/lib-server/src/index.ts index f4bfaf593..e3bd04559 100644 --- a/packages/libs/lib-server/src/index.ts +++ b/packages/libs/lib-server/src/index.ts @@ -1,9 +1,9 @@ -import { SysSettingsEntity } from './system/index.js'; -import { AccessEntity } from './user/access/entity/access.js'; +import { SysSettingsEntity } from "./system/index.js"; +import { AccessEntity } from "./user/access/entity/access.js"; import { AddonEntity } from "./user/index.js"; -export * from './basic/index.js'; -export * from './system/index.js'; -export * from './user/index.js'; -export { LibServerConfiguration as Configuration } from './configuration.js'; +export * from "./basic/index.js"; +export * from "./system/index.js"; +export * from "./user/index.js"; +export { LibServerConfiguration as Configuration } from "./configuration.js"; -export const libServerEntities = [SysSettingsEntity, AccessEntity,AddonEntity]; +export const libServerEntities = [SysSettingsEntity, AccessEntity, AddonEntity]; diff --git a/packages/libs/lib-server/src/system/basic/index.ts b/packages/libs/lib-server/src/system/basic/index.ts index e05d3be82..875f2b2f5 100644 --- a/packages/libs/lib-server/src/system/basic/index.ts +++ b/packages/libs/lib-server/src/system/basic/index.ts @@ -1,5 +1,5 @@ -export * from './service/plus-service.js'; -export * from './service/file-service.js'; -export * from './service/encryptor.js'; -export * from './service/ocr-service.js'; -export * from './service/executor-queue.js'; \ No newline at end of file +export * from "./service/plus-service.js"; +export * from "./service/file-service.js"; +export * from "./service/encryptor.js"; +export * from "./service/ocr-service.js"; +export * from "./service/executor-queue.js"; diff --git a/packages/libs/lib-server/src/system/basic/service/encryptor.ts b/packages/libs/lib-server/src/system/basic/service/encryptor.ts index 2c8252a73..c02149856 100644 --- a/packages/libs/lib-server/src/system/basic/service/encryptor.ts +++ b/packages/libs/lib-server/src/system/basic/service/encryptor.ts @@ -1,8 +1,8 @@ -import crypto from 'crypto'; +import crypto from "crypto"; export class Encryptor { secretKey: Buffer; - constructor(encryptSecret: string, encoding: BufferEncoding = 'base64') { + constructor(encryptSecret: string, encoding: BufferEncoding = "base64") { this.secretKey = Buffer.from(encryptSecret, encoding); } // 加密函数 @@ -10,18 +10,18 @@ export class Encryptor { const iv = crypto.randomBytes(16); // 初始化向量 // const secretKey = crypto.randomBytes(32); // const key = Buffer.from(secretKey); - const cipher = crypto.createCipheriv('aes-256-cbc', this.secretKey, iv); + const cipher = crypto.createCipheriv("aes-256-cbc", this.secretKey, iv); let encrypted = cipher.update(text); encrypted = Buffer.concat([encrypted, cipher.final()]); - return iv.toString('hex') + ':' + encrypted.toString('hex'); + return iv.toString("hex") + ":" + encrypted.toString("hex"); } // 解密函数 decrypt(encryptedText: string) { - const textParts = encryptedText.split(':'); - const iv = Buffer.from(textParts.shift(), 'hex'); - const encrypted = Buffer.from(textParts.join(':'), 'hex'); - const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(this.secretKey), iv); + const textParts = encryptedText.split(":"); + const iv = Buffer.from(textParts.shift(), "hex"); + const encrypted = Buffer.from(textParts.join(":"), "hex"); + const decipher = crypto.createDecipheriv("aes-256-cbc", Buffer.from(this.secretKey), iv); let decrypted = decipher.update(encrypted); decrypted = Buffer.concat([decrypted, decipher.final()]); return decrypted.toString(); diff --git a/packages/libs/lib-server/src/system/basic/service/executor-queue.ts b/packages/libs/lib-server/src/system/basic/service/executor-queue.ts index 737c443ec..4e59742ab 100644 --- a/packages/libs/lib-server/src/system/basic/service/executor-queue.ts +++ b/packages/libs/lib-server/src/system/basic/service/executor-queue.ts @@ -1,18 +1,18 @@ import { logger } from "@certd/basic"; export type TaskItem = { - task: ()=>Promise; -} + task: () => Promise; +}; -export class UserTaskQueue{ +export class UserTaskQueue { userId: number; pendingQueue: TaskItem[] = []; runningQueue: TaskItem[] = []; - getMaxRunningCount: ()=>number ; + getMaxRunningCount: () => number; - constructor(req: { userId: number ,getMaxRunningCount: ()=>number }) { + constructor(req: { userId: number; getMaxRunningCount: () => number }) { this.userId = req.userId; - this.getMaxRunningCount = req.getMaxRunningCount ; + this.getMaxRunningCount = req.getMaxRunningCount; } addTask(task: TaskItem) { @@ -34,10 +34,10 @@ export class UserTaskQueue{ } // 执行任务 this.runningQueue.push(task); - const call = async ()=>{ - try{ + const call = async () => { + try { await task.task(); - }finally{ + } finally { // 任务执行完成,从运行队列中移除 const index = this.runningQueue.indexOf(task); if (index > -1) { @@ -46,17 +46,16 @@ export class UserTaskQueue{ // 继续执行下一个任务 this.runTask(); } - } + }; logger.info(`[user_${this.userId}]执行任务,当前运行队列:${this.runningQueue.length}, 等待队列:${this.pendingQueue.length}`); - call() + call(); } } -export class ExecutorQueue{ +export class ExecutorQueue { queues: Record = {}; maxRunningCount: number = 10; - setMaxRunningCount(count: number) { this.maxRunningCount = count; } @@ -64,7 +63,7 @@ export class ExecutorQueue{ getUserQueue(userId: number) { const userQueue = this.queues[userId]; if (!userQueue) { - this.queues[userId] = new UserTaskQueue({ userId, getMaxRunningCount: ()=>this.maxRunningCount }); + this.queues[userId] = new UserTaskQueue({ userId, getMaxRunningCount: () => this.maxRunningCount }); } return this.queues[userId]; } @@ -73,7 +72,6 @@ export class ExecutorQueue{ const userQueue = this.getUserQueue(userId); userQueue.addTask(task); } - } -export const executorQueue = new ExecutorQueue(); \ No newline at end of file +export const executorQueue = new ExecutorQueue(); diff --git a/packages/libs/lib-server/src/system/basic/service/file-service.ts b/packages/libs/lib-server/src/system/basic/service/file-service.ts index 4dc782f8e..f4e92918f 100644 --- a/packages/libs/lib-server/src/system/basic/service/file-service.ts +++ b/packages/libs/lib-server/src/system/basic/service/file-service.ts @@ -1,42 +1,42 @@ -import { Provide, Scope, ScopeEnum } from '@midwayjs/core'; -import dayjs from 'dayjs'; -import path from 'path'; -import fs from 'fs'; -import { cache, logger, utils } from '@certd/basic'; -import { NotFoundException, ParamException, PermissionException } from '../../../basic/index.js'; +import { Provide, Scope, ScopeEnum } from "@midwayjs/core"; +import dayjs from "dayjs"; +import path from "path"; +import fs from "fs"; +import { cache, logger, utils } from "@certd/basic"; +import { NotFoundException, ParamException, PermissionException } from "../../../basic/index.js"; export type UploadFileItem = { filename: string; tmpFilePath: string; }; -const uploadRootDir = './data/upload'; -export const uploadTmpFileCacheKey = 'tmpfile_key_'; +const uploadRootDir = "./data/upload"; +export const uploadTmpFileCacheKey = "tmpfile_key_"; /** */ @Provide() @Scope(ScopeEnum.Request, { allowDowngrade: true }) export class FileService { - async saveFile(userId: number, tmpCacheKey: any, permission: 'public' | 'private') { + async saveFile(userId: number, tmpCacheKey: any, permission: "public" | "private") { if (tmpCacheKey.startsWith(`/${permission}`)) { //已经保存过,不需要再次保存 return tmpCacheKey; } - let fileName = ''; + let fileName = ""; let tmpFilePath = tmpCacheKey; if (uploadTmpFileCacheKey && tmpCacheKey.startsWith(uploadTmpFileCacheKey)) { const tmpFile: UploadFileItem = cache.get(tmpCacheKey); if (!tmpFile) { - throw new ParamException('文件已过期,请重新上传'); + throw new ParamException("文件已过期,请重新上传"); } tmpFilePath = tmpFile.tmpFilePath; fileName = tmpFile.filename || path.basename(tmpFilePath); } if (!tmpFilePath || !fs.existsSync(tmpFilePath)) { - throw new Error('文件不存在,请重新上传'); + throw new Error("文件不存在,请重新上传"); } - const date = dayjs().format('YYYY_MM_DD'); + const date = dayjs().format("YYYY_MM_DD"); const random = Math.random().toString(36).substring(7); - const userIdMd5 = Buffer.from(Buffer.from(userId + '').toString('base64')).toString('hex'); + const userIdMd5 = Buffer.from(Buffer.from(userId + "").toString("base64")).toString("hex"); const key = `/${permission}/${userIdMd5}/${date}/${random}_${fileName}`; let savePath = path.join(uploadRootDir, key); savePath = path.resolve(savePath); @@ -44,7 +44,6 @@ export class FileService { if (!fs.existsSync(parentDir)) { fs.mkdirSync(parentDir, { recursive: true }); } - // eslint-disable-next-line node/no-unsupported-features/node-builtins const copyFile = utils.promises.promisify(fs.copyFile); await copyFile(tmpFilePath, savePath); try { @@ -58,29 +57,29 @@ export class FileService { getFile(key: string, userId?: number, allowAnyPrivateUser = false) { if (!key) { - throw new ParamException('参数错误'); + throw new ParamException("参数错误"); } - if (key.indexOf('..') >= 0) { + if (key.indexOf("..") >= 0) { //安全性判断 - throw new ParamException('参数错误'); + throw new ParamException("参数错误"); } - if (!key.startsWith('/')) { - throw new ParamException('参数错误'); + if (!key.startsWith("/")) { + throw new ParamException("参数错误"); } - const keyArr = key.split('/'); + const keyArr = key.split("/"); const permission = keyArr[1]; const userIdMd5 = keyArr[2]; - if (permission !== 'public' && !allowAnyPrivateUser) { + if (permission !== "public" && !allowAnyPrivateUser) { //非公开文件需要验证用户 - const userIdStr = Buffer.from(Buffer.from(userIdMd5, 'hex').toString('base64')).toString(); + const userIdStr = Buffer.from(Buffer.from(userIdMd5, "hex").toString("base64")).toString(); const userIdInt: number = parseInt(userIdStr, 10); if (userId == null || userIdInt !== userId) { - throw new PermissionException('无访问权限'); + throw new PermissionException("无访问权限"); } } const filePath = path.join(uploadRootDir, key); if (!fs.existsSync(filePath)) { - throw new NotFoundException('文件不存在'); + throw new NotFoundException("文件不存在"); } return filePath; } diff --git a/packages/libs/lib-server/src/system/basic/service/ocr-service.ts b/packages/libs/lib-server/src/system/basic/service/ocr-service.ts index 19558c99f..a2aae3f97 100644 --- a/packages/libs/lib-server/src/system/basic/service/ocr-service.ts +++ b/packages/libs/lib-server/src/system/basic/service/ocr-service.ts @@ -15,10 +15,9 @@ export class OcrService implements IOcrService { url: "/activation/certd/ocr", method: "post", data: { - image: opts.image - } + image: opts.image, + }, }); return res; } - } diff --git a/packages/libs/lib-server/src/system/index.ts b/packages/libs/lib-server/src/system/index.ts index 05c1293ee..bdedd646e 100644 --- a/packages/libs/lib-server/src/system/index.ts +++ b/packages/libs/lib-server/src/system/index.ts @@ -1,2 +1,2 @@ -export * from './settings/index.js'; -export * from './basic/index.js'; +export * from "./settings/index.js"; +export * from "./basic/index.js"; diff --git a/packages/libs/lib-server/src/system/settings/index.ts b/packages/libs/lib-server/src/system/settings/index.ts index 140a4a3c9..2f51b9383 100644 --- a/packages/libs/lib-server/src/system/settings/index.ts +++ b/packages/libs/lib-server/src/system/settings/index.ts @@ -1,3 +1,3 @@ -export * from './service/sys-settings-service.js'; -export * from './service/models.js'; -export * from './entity/sys-settings.js'; +export * from "./service/sys-settings-service.js"; +export * from "./service/models.js"; +export * from "./entity/sys-settings.js"; diff --git a/packages/libs/lib-server/src/system/settings/service/sys-settings-service.ts b/packages/libs/lib-server/src/system/settings/service/sys-settings-service.ts index 2a64bc6b8..f570f678e 100644 --- a/packages/libs/lib-server/src/system/settings/service/sys-settings-service.ts +++ b/packages/libs/lib-server/src/system/settings/service/sys-settings-service.ts @@ -1,15 +1,15 @@ -import { Provide, Scope, ScopeEnum } from '@midwayjs/core'; -import { InjectEntityModel } from '@midwayjs/typeorm'; -import { Repository } from 'typeorm'; -import { SysSettingsEntity } from '../entity/sys-settings.js'; -import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js'; +import { Provide, Scope, ScopeEnum } from "@midwayjs/core"; +import { InjectEntityModel } from "@midwayjs/typeorm"; +import { Repository } from "typeorm"; +import { SysSettingsEntity } from "../entity/sys-settings.js"; +import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from "./models.js"; -import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from '@certd/acme-client'; -import { cache, logger, mergeUtils, setGlobalHeaders, setGlobalProxy } from '@certd/basic'; -import { isPlus } from '@certd/plus-core'; -import * as dns from 'node:dns'; -import { BaseService, setAdminMode } from '../../../basic/index.js'; -import { executorQueue } from '../../basic/service/executor-queue.js'; +import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from "@certd/acme-client"; +import { cache, logger, mergeUtils, setGlobalHeaders, setGlobalProxy } from "@certd/basic"; +import { isPlus } from "@certd/plus-core"; +import * as dns from "node:dns"; +import { BaseService, setAdminMode } from "../../../basic/index.js"; +import { executorQueue } from "../../basic/service/executor-queue.js"; const { merge } = mergeUtils; let lastSaveEnvVars = {}; @@ -138,7 +138,7 @@ export class SysSettingsService extends BaseService { res.reverseProxies[domain] = ""; } } - return res + return res; } async savePrivateSettings(bean: SysPrivateSettings) { @@ -149,14 +149,14 @@ export class SysSettingsService extends BaseService { } async reloadSettings() { - await this.reloadPrivateSettings() - await this.reloadPublicSettings() + await this.reloadPrivateSettings(); + await this.reloadPublicSettings(); } async reloadPublicSettings() { - const publicSetting = await this.getPublicSettings() - if (isPlus()){ - setAdminMode(publicSetting.adminMode ) + const publicSetting = await this.getPublicSettings(); + if (isPlus()) { + setAdminMode(publicSetting.adminMode); } } @@ -169,7 +169,7 @@ export class SysSettingsService extends BaseService { }; setGlobalProxy(opts); setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders)); - + if (privateSetting.dnsResultOrder) { dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any); } @@ -183,29 +183,28 @@ export class SysSettingsService extends BaseService { this.setEnvironmentVars(privateSetting.environmentVars); setWalkFromAuthoritative(privateSetting.acmeWalkFromAuthoritative); - } parseKeyValueText(text: string) { const values = {}; - if (typeof text !== 'string') { + if (typeof text !== "string") { text = ""; } - text.split('\n').forEach(line => { + text.split("\n").forEach(line => { line = line.trim(); - if (!line || line.startsWith('#')) { - return + if (!line || line.startsWith("#")) { + return; } - const arr = line.split("#") + const arr = line.split("#"); if (arr.length > 0) { line = arr[0].trim(); } if (!line.includes("=")) { - return + return; } - const eqIndex = line.indexOf('='); + const eqIndex = line.indexOf("="); const key = line.substring(0, eqIndex).trim(); const value = line.substring(eqIndex + 1).trim(); if (key && value) { @@ -220,7 +219,7 @@ export class SysSettingsService extends BaseService { //先删除旧环境变量 if (lastSaveEnvVars) { for (const key in lastSaveEnvVars) { - delete process.env[key]; + delete process.env[key]; } } @@ -234,7 +233,7 @@ export class SysSettingsService extends BaseService { entity.setting = JSON.stringify(setting); await this.repository.save(entity); } else { - throw new Error('该设置不存在'); + throw new Error("该设置不存在"); } cache.delete(`settings.${key}`); } @@ -246,20 +245,20 @@ export class SysSettingsService extends BaseService { if (settings == null) { const backup = new SysSecretBackup(); if (installInfo.siteId == null || privateSettings.encryptSecret == null) { - logger.error('备份密钥失败,siteId或encryptSecret为空'); + logger.error("备份密钥失败,siteId或encryptSecret为空"); return; } backup.siteId = installInfo.siteId; backup.encryptSecret = privateSettings.encryptSecret; await this.saveSetting(backup); - logger.info('备份密钥成功'); + logger.info("备份密钥成功"); } else { //校验是否有变化 if (settings.siteId !== installInfo.siteId) { throw new Error(`siteId与备份不一致,可能是数据异常,请检查:backup=${settings.siteId}, current=${installInfo.siteId}`); } if (settings.encryptSecret !== privateSettings.encryptSecret) { - throw new Error('encryptSecret与备份不一致,可能是数据异常,请检查'); + throw new Error("encryptSecret与备份不一致,可能是数据异常,请检查"); } } } @@ -271,12 +270,12 @@ export class SysSettingsService extends BaseService { //从备份中读取 const settings = await this.getSettingByKey(SysSecretBackup.__key__); if (settings == null || !settings.encryptSecret) { - throw new Error('密钥备份不存在'); + throw new Error("密钥备份不存在"); } sysSecret.siteId = settings.siteId; sysSecret.encryptSecret = settings.encryptSecret; await this.saveSetting(sysSecret); - logger.info('密钥恢复成功'); + logger.info("密钥恢复成功"); return sysSecret; } } diff --git a/packages/libs/lib-server/src/user/access/entity/access.ts b/packages/libs/lib-server/src/user/access/entity/access.ts index 10fc2c6aa..6167ee95e 100644 --- a/packages/libs/lib-server/src/user/access/entity/access.ts +++ b/packages/libs/lib-server/src/user/access/entity/access.ts @@ -1,46 +1,46 @@ -import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; /** * 授权配置 */ -@Entity('cd_access') +@Entity("cd_access") export class AccessEntity { @PrimaryGeneratedColumn() id: number; - @Column({ name: 'key_id', comment: 'key_id', length: 100 }) + @Column({ name: "key_id", comment: "key_id", length: 100 }) keyId: string; - @Column({ name: 'user_id', comment: '用户id' }) + @Column({ name: "user_id", comment: "用户id" }) userId: number; // 0为系统级别, -1为企业,大于1为用户 - @Column({ comment: '名称', length: 100 }) + @Column({ comment: "名称", length: 100 }) name: string; - @Column({ comment: '类型', length: 100 }) + @Column({ comment: "类型", length: 100 }) type: string; - @Column({ name: 'subtype', comment: '子类型', length: 100, nullable: true }) + @Column({ name: "subtype", comment: "子类型", length: 100, nullable: true }) subtype: string; - @Column({ name: 'setting', comment: '设置', length: 10240, nullable: true }) + @Column({ name: "setting", comment: "设置", length: 10240, nullable: true }) setting: string; - @Column({ name: 'encrypt_setting', comment: '已加密设置', length: 10240, nullable: true }) + @Column({ name: "encrypt_setting", comment: "已加密设置", length: 10240, nullable: true }) encryptSetting: string; - @Column({ name: 'project_id', comment: '项目id' }) + @Column({ name: "project_id", comment: "项目id" }) projectId: number; @Column({ - name: 'create_time', - comment: '创建时间', - default: () => 'CURRENT_TIMESTAMP', + name: "create_time", + comment: "创建时间", + default: () => "CURRENT_TIMESTAMP", }) createTime: Date; @Column({ - name: 'update_time', - comment: '修改时间', - default: () => 'CURRENT_TIMESTAMP', + name: "update_time", + comment: "修改时间", + default: () => "CURRENT_TIMESTAMP", }) updateTime: Date; } diff --git a/packages/libs/lib-server/src/user/access/index.ts b/packages/libs/lib-server/src/user/access/index.ts index 7c711f6ba..74face9a0 100644 --- a/packages/libs/lib-server/src/user/access/index.ts +++ b/packages/libs/lib-server/src/user/access/index.ts @@ -1,5 +1,5 @@ -export * from './entity/access.js'; -export * from './service/access-service.js'; -export * from './service/access-sys-getter.js'; -export * from './service/access-getter.js'; -export * from './service/encrypt-service.js'; +export * from "./entity/access.js"; +export * from "./service/access-service.js"; +export * from "./service/access-sys-getter.js"; +export * from "./service/access-getter.js"; +export * from "./service/encrypt-service.js"; diff --git a/packages/libs/lib-server/src/user/access/service/access-sys-getter.ts b/packages/libs/lib-server/src/user/access/service/access-sys-getter.ts index 73b2b7c5b..ca9ebd117 100644 --- a/packages/libs/lib-server/src/user/access/service/access-sys-getter.ts +++ b/packages/libs/lib-server/src/user/access/service/access-sys-getter.ts @@ -1,5 +1,5 @@ -import { IAccessService } from '@certd/pipeline'; -import { AccessService } from './access-service.js'; +import { IAccessService } from "@certd/pipeline"; +import { AccessService } from "./access-service.js"; export class AccessSysGetter implements IAccessService { accessService: AccessService; diff --git a/packages/libs/lib-server/src/user/access/service/encrypt-service.ts b/packages/libs/lib-server/src/user/access/service/encrypt-service.ts index 732448941..1f9f95e61 100644 --- a/packages/libs/lib-server/src/user/access/service/encrypt-service.ts +++ b/packages/libs/lib-server/src/user/access/service/encrypt-service.ts @@ -1,5 +1,5 @@ -import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; -import { Encryptor, SysSecret, SysSettingsService } from '../../../system/index.js'; +import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; +import { Encryptor, SysSecret, SysSettingsService } from "../../../system/index.js"; /** * 授权 diff --git a/packages/libs/lib-server/src/user/addon/api/decorator.ts b/packages/libs/lib-server/src/user/addon/api/decorator.ts index 280e39c6e..d313e5eff 100644 --- a/packages/libs/lib-server/src/user/addon/api/decorator.ts +++ b/packages/libs/lib-server/src/user/addon/api/decorator.ts @@ -48,8 +48,8 @@ export function AddonInput(input?: AddonInputDefine): PropertyDecorator { }; } -export async function newAddon(addonType:string,type: string, input: any, ctx: AddonContext) { - const key = `${addonType}:${type}` +export async function newAddon(addonType: string, type: string, input: any, ctx: AddonContext) { + const key = `${addonType}:${type}`; const register = addonRegistry.get(key); if (register == null) { throw new Error(`${addonType} ${type} not found`); diff --git a/packages/libs/lib-server/src/user/addon/entity/addon.ts b/packages/libs/lib-server/src/user/addon/entity/addon.ts index 43513e1aa..80fb14797 100644 --- a/packages/libs/lib-server/src/user/addon/entity/addon.ts +++ b/packages/libs/lib-server/src/user/addon/entity/addon.ts @@ -1,49 +1,46 @@ -import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; /** */ -@Entity('cd_addon') +@Entity("cd_addon") export class AddonEntity { @PrimaryGeneratedColumn() id: number; - @Column({ name: 'key_id', comment: 'key_id', length: 100 }) + @Column({ name: "key_id", comment: "key_id", length: 100 }) keyId: string; - @Column({ name: 'user_id', comment: '用户id' }) + @Column({ name: "user_id", comment: "用户id" }) userId: number; - @Column({ comment: '名称', length: 100 }) + @Column({ comment: "名称", length: 100 }) name: string; - - @Column({ name: 'addon_type', comment: 'addon类型', length: 100 }) + @Column({ name: "addon_type", comment: "addon类型", length: 100 }) addonType: string; - - @Column({ comment: '类型', length: 100 }) + @Column({ comment: "类型", length: 100 }) type: string; - @Column({ name: 'setting', comment: '设置', length: 10240, nullable: true }) + @Column({ name: "setting", comment: "设置", length: 10240, nullable: true }) setting: string; - @Column({ name: 'is_system', comment: '是否系统级别', nullable: false, default: false }) + @Column({ name: "is_system", comment: "是否系统级别", nullable: false, default: false }) isSystem: boolean; - @Column({ name: 'is_default', comment: '是否默认', nullable: false, default: false }) + @Column({ name: "is_default", comment: "是否默认", nullable: false, default: false }) isDefault: boolean; - @Column({ name: 'project_id', comment: '项目id' }) + @Column({ name: "project_id", comment: "项目id" }) projectId: number; - @Column({ - name: 'create_time', - comment: '创建时间', - default: () => 'CURRENT_TIMESTAMP', + name: "create_time", + comment: "创建时间", + default: () => "CURRENT_TIMESTAMP", }) createTime: Date; @Column({ - name: 'update_time', - comment: '修改时间', - default: () => 'CURRENT_TIMESTAMP', + name: "update_time", + comment: "修改时间", + default: () => "CURRENT_TIMESTAMP", }) updateTime: Date; } diff --git a/packages/libs/lib-server/src/user/addon/index.ts b/packages/libs/lib-server/src/user/addon/index.ts index 2eafdbc3f..8476c6785 100644 --- a/packages/libs/lib-server/src/user/addon/index.ts +++ b/packages/libs/lib-server/src/user/addon/index.ts @@ -1,3 +1,3 @@ -export * from './api/index.js' -export * from './entity/addon.js' -export * from './service/addon-service.js' +export * from "./api/index.js"; +export * from "./entity/addon.js"; +export * from "./service/addon-service.js"; diff --git a/packages/libs/lib-server/src/user/addon/service/addon-service.ts b/packages/libs/lib-server/src/user/addon/service/addon-service.ts index 505eba2b2..eb56377fb 100644 --- a/packages/libs/lib-server/src/user/addon/service/addon-service.ts +++ b/packages/libs/lib-server/src/user/addon/service/addon-service.ts @@ -49,7 +49,6 @@ export class AddonService extends BaseService { return await super.add(param); } - /** * 修改 * @param param 数据 @@ -59,7 +58,7 @@ export class AddonService extends BaseService { if (oldEntity == null) { throw new ValidateException("该Addon配置不存在,请确认是否已被删除"); } - delete param.keyId + delete param.keyId; return await super.update(param); } @@ -75,11 +74,10 @@ export class AddonService extends BaseService { userId: entity.userId, addonType: entity.addonType, type: entity.type, - projectId: entity.projectId + projectId: entity.projectId, }; } - getDefineList(addonType: string) { return addonRegistry.getDefineList(addonType); } @@ -88,12 +86,11 @@ export class AddonService extends BaseService { return addonRegistry.getDefine(type, prefix) as AddonDefine; } - - async getSimpleByIds(ids: number[], userId: any,projectId?:number) { + async getSimpleByIds(ids: number[], userId: any, projectId?: number) { if (ids.length === 0) { return []; } - if (userId==null) { + if (userId == null) { return []; } const userProjectQuery = this.buildUserProjectQuery(userId, projectId); @@ -109,14 +106,12 @@ export class AddonService extends BaseService { addonType: true, type: true, userId: true, - isSystem: true - } + isSystem: true, + }, }); - } - - async getDefault(userId: number, addonType: string,projectId?:number): Promise { + async getDefault(userId: number, addonType: string, projectId?: number): Promise { const userProjectQuery = this.buildUserProjectQuery(userId, projectId); const res = await this.repository.findOne({ where: { @@ -124,8 +119,8 @@ export class AddonService extends BaseService { ...userProjectQuery, }, order: { - isDefault: "DESC" - } + isDefault: "DESC", + }, }); if (!res) { return null; @@ -143,15 +138,15 @@ export class AddonService extends BaseService { name: res.name, userId: res.userId, setting, - projectId: res.projectId + projectId: res.projectId, }; } - async setDefault(id: number, userId: number, addonType: string,projectId?:number) { + async setDefault(id: number, userId: number, addonType: string, projectId?: number) { if (!id) { throw new ValidateException("id不能为空"); } - if (userId==null) { + if (userId == null) { throw new ValidateException("userId不能为空"); } const userProjectQuery = this.buildUserProjectQuery(userId, projectId); @@ -160,24 +155,27 @@ export class AddonService extends BaseService { ...userProjectQuery, }; await this.repository.update(query, { - isDefault: false - }); - await this.repository.update({ ...query, id }, { - isDefault: true + isDefault: false, }); + await this.repository.update( + { ...query, id }, + { + isDefault: true, + } + ); } - async getOrCreateDefault(opts: { addonType: string, type: string, inputs: any, userId: any,projectId?:number }) { - const { addonType, type, inputs, userId,projectId } = opts; + async getOrCreateDefault(opts: { addonType: string; type: string; inputs: any; userId: any; projectId?: number }) { + const { addonType, type, inputs, userId, projectId } = opts; const addonDefine = this.getDefineByType(type, addonType); - const defaultConfig = await this.getDefault(userId, addonType,projectId); + const defaultConfig = await this.getDefault(userId, addonType, projectId); if (defaultConfig) { return defaultConfig; } const setting = { - ...inputs + ...inputs, }; const res = await this.repository.save({ userId, @@ -186,19 +184,19 @@ export class AddonService extends BaseService { name: addonDefine.title, setting: JSON.stringify(setting), isDefault: true, - projectId + projectId, }); return this.buildAddonInstanceConfig(res); } - async getOneByType(req:{addonType:string,type:string,userId:number,projectId?:number}) { + async getOneByType(req: { addonType: string; type: string; userId: number; projectId?: number }) { const userProjectQuery = this.buildUserProjectQuery(req.userId, req.projectId); return await this.repository.findOne({ where: { addonType: req.addonType, type: req.type, ...userProjectQuery, - } + }, }); } } diff --git a/packages/libs/lib-server/src/user/index.ts b/packages/libs/lib-server/src/user/index.ts index f0fce929a..6d14e3b1c 100644 --- a/packages/libs/lib-server/src/user/index.ts +++ b/packages/libs/lib-server/src/user/index.ts @@ -1,2 +1,2 @@ -export * from './access/index.js'; -export * from './addon/index.js'; +export * from "./access/index.js"; +export * from "./addon/index.js"; diff --git a/packages/libs/midway-flyway-js/package.json b/packages/libs/midway-flyway-js/package.json index 5433c1218..b83a16046 100644 --- a/packages/libs/midway-flyway-js/package.json +++ b/packages/libs/midway-flyway-js/package.json @@ -19,7 +19,7 @@ "pub": "npm publish", "compile": "npm run build", "format": "prettier --write src", - "lint": "eslint --fix" + "lint": "eslint --fix --ext .ts src" }, "keywords": [], "author": "greper", diff --git a/packages/libs/midway-flyway-js/src/configuration.ts b/packages/libs/midway-flyway-js/src/configuration.ts index 28baac1e5..76f0e34c5 100644 --- a/packages/libs/midway-flyway-js/src/configuration.ts +++ b/packages/libs/midway-flyway-js/src/configuration.ts @@ -1,11 +1,11 @@ -import { Config, Configuration, Logger } from '@midwayjs/core'; -import { Flyway } from './flyway.js'; -import type { ILogger } from '@midwayjs/logger'; -import { TypeORMDataSourceManager } from '@midwayjs/typeorm'; -import type { IMidwayContainer } from '@midwayjs/core'; +import { Config, Configuration, Logger } from "@midwayjs/core"; +import { Flyway } from "./flyway.js"; +import type { ILogger } from "@midwayjs/logger"; +import { TypeORMDataSourceManager } from "@midwayjs/typeorm"; +import type { IMidwayContainer } from "@midwayjs/core"; @Configuration({ - namespace: 'flyway', + namespace: "flyway", //importConfigs: [join(__dirname, './config')], }) export class FlywayConfiguration { @@ -14,9 +14,9 @@ export class FlywayConfiguration { @Logger() logger!: ILogger; async onReady(container: IMidwayContainer) { - this.logger.info('flyway start:' + JSON.stringify(this.flyway)); + this.logger.info("flyway start:" + JSON.stringify(this.flyway)); const dataSourceManager = await container.getAsync(TypeORMDataSourceManager); - const dataSourceName = this.flyway.dataSourceName || 'default'; + const dataSourceName = this.flyway.dataSourceName || "default"; const connection = dataSourceManager.getDataSource(dataSourceName); await new Flyway({ ...this.flyway, logger: this.logger, connection }).run(); } diff --git a/packages/libs/midway-flyway-js/src/entity.ts b/packages/libs/midway-flyway-js/src/entity.ts index 03306a82e..eef559279 100644 --- a/packages/libs/midway-flyway-js/src/entity.ts +++ b/packages/libs/midway-flyway-js/src/entity.ts @@ -1,23 +1,23 @@ -import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; -@Entity('flyway_history') +@Entity("flyway_history") export class FlywayHistory { @PrimaryGeneratedColumn() id?: number; - @Column({ comment: '文件名', length: 100 }) + @Column({ comment: "文件名", length: 100 }) name?: string; - @Column({ comment: 'hash', length: 32 }) + @Column({ comment: "hash", length: 32 }) hash?: string; @Column({ - comment: '执行时间', + comment: "执行时间", }) timestamp?: Date; @Column({ - comment: '执行成功', + comment: "执行成功", default: true, }) success?: boolean; diff --git a/packages/libs/midway-flyway-js/src/index.ts b/packages/libs/midway-flyway-js/src/index.ts index 4e2fcc41f..843b5b668 100644 --- a/packages/libs/midway-flyway-js/src/index.ts +++ b/packages/libs/midway-flyway-js/src/index.ts @@ -1,3 +1,3 @@ -export { FlywayConfiguration as Configuration } from './configuration.js'; -export { Flyway, setFlywayLogger } from './flyway.js'; -export { FlywayHistory } from './entity.js'; +export { FlywayConfiguration as Configuration } from "./configuration.js"; +export { Flyway, setFlywayLogger } from "./flyway.js"; +export { FlywayHistory } from "./entity.js"; diff --git a/packages/plugins/plugin-cert/package.json b/packages/plugins/plugin-cert/package.json index be2217e75..3aea3a613 100644 --- a/packages/plugins/plugin-cert/package.json +++ b/packages/plugins/plugin-cert/package.json @@ -17,7 +17,7 @@ "pub": "npm publish", "compile": "tsc --skipLibCheck --watch", "format": "prettier --write src", - "lint": "eslint --fix" + "lint": "eslint --fix --ext .ts src" }, "dependencies": { "@certd/plugin-lib": "^1.42.6" diff --git a/packages/plugins/plugin-lib/package.json b/packages/plugins/plugin-lib/package.json index 51ad14983..5b0035fb1 100644 --- a/packages/plugins/plugin-lib/package.json +++ b/packages/plugins/plugin-lib/package.json @@ -14,7 +14,7 @@ "pub": "npm publish", "compile": "tsc --skipLibCheck --watch", "format": "prettier --write src", - "lint": "eslint --fix" + "lint": "eslint --fix --ext .ts src" }, "dependencies": { "@certd/acme-client": "^1.42.6", diff --git a/packages/plugins/plugin-lib/src/index.ts b/packages/plugins/plugin-lib/src/index.ts index ac0f1be1d..aeb40dc35 100644 --- a/packages/plugins/plugin-lib/src/index.ts +++ b/packages/plugins/plugin-lib/src/index.ts @@ -1,4 +1,4 @@ export * from "./common/index.js"; export * from "./lib/index.js"; export * from "./service/index.js"; -export * from "./cert/index.js"; \ No newline at end of file +export * from "./cert/index.js"; diff --git a/packages/ui/certd-client/src/locales/langs/en-US/preferences.ts b/packages/ui/certd-client/src/locales/langs/en-US/preferences.ts index 669a1e77a..08912e48d 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/preferences.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/preferences.ts @@ -31,6 +31,15 @@ export default { copyPreferences: "Copy Preferences", copyPreferencesSuccessTitle: "Copy successful", copyPreferencesSuccess: "Copy successful, please override in `src/preferences.ts` under app", + importPreferences: "Import from Clipboard", + importPreferencesSuccessTitle: "Import successful", + importPreferencesSuccess: "Preferences imported from clipboard", + importPreferencesErrorTitle: "Import failed", + importPreferencesError: "Clipboard content is invalid. Please copy preferences JSON first", + saveToAccount: "Save to Account", + saveToAccountSuccess: "Preferences saved to account", + saveToAccountError: "Failed to save preferences to account", + saveToAccountNeedLogin: "Please sign in before saving to account", clearAndLogout: "Clear Cache & Logout", mode: "Mode", general: "General", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/preferences.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/preferences.ts index 404ec2e7b..323741063 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/preferences.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/preferences.ts @@ -31,6 +31,15 @@ export default { copyPreferences: "复制偏好设置", copyPreferencesSuccessTitle: "复制成功", copyPreferencesSuccess: "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖", + importPreferences: "从剪切板导入", + importPreferencesSuccessTitle: "导入成功", + importPreferencesSuccess: "已从剪切板导入偏好设置", + importPreferencesErrorTitle: "导入失败", + importPreferencesError: "剪切板内容无效,请先复制偏好设置 JSON", + saveToAccount: "保存到账号", + saveToAccountSuccess: "偏好设置已保存到账号", + saveToAccountError: "保存到账号失败", + saveToAccountNeedLogin: "请先登录后再保存到账号", clearAndLogout: "清空缓存 & 退出登录", mode: "模式", general: "通用", diff --git a/packages/ui/certd-client/src/store/settings/index.tsx b/packages/ui/certd-client/src/store/settings/index.tsx index 7f97acc64..c1e67ea48 100644 --- a/packages/ui/certd-client/src/store/settings/index.tsx +++ b/packages/ui/certd-client/src/store/settings/index.tsx @@ -390,4 +390,10 @@ export const useSettingStore = defineStore({ mitter.on("app.login", async () => { await useSettingStore().init(); + try { + const { loadPreferencesFromAccount } = await import("/@/vben/layouts/widgets/preferences/account-sync"); + await loadPreferencesFromAccount(); + } catch (e) { + console.error("加载账号偏好设置失败", e); + } }); diff --git a/packages/ui/certd-client/src/vben/icons/lucide.ts b/packages/ui/certd-client/src/vben/icons/lucide.ts index b57a610ea..aa9715abe 100644 --- a/packages/ui/certd-client/src/vben/icons/lucide.ts +++ b/packages/ui/certd-client/src/vben/icons/lucide.ts @@ -18,6 +18,8 @@ export { CircleCheckBig, CircleHelp, Copy, + ClipboardPaste, + CloudUpload, CornerDownLeft, Ellipsis, Expand, diff --git a/packages/ui/certd-client/src/vben/layouts/widgets/preferences/account-sync.ts b/packages/ui/certd-client/src/vben/layouts/widgets/preferences/account-sync.ts new file mode 100644 index 000000000..adcbb6f23 --- /dev/null +++ b/packages/ui/certd-client/src/vben/layouts/widgets/preferences/account-sync.ts @@ -0,0 +1,66 @@ +import { message as antdMessage } from "ant-design-vue"; + +import { $t, loadLocaleMessages } from "/@/locales"; +import { updatePreferences } from "/@/vben/preferences"; + +import { PreferencesSettingsGet, PreferencesSettingsSave } from "./api"; + +const PREFERENCES_KNOWN_KEYS = ["app", "theme", "logo", "sidebar", "header", "tabbar", "breadcrumb", "navigation", "widget", "footer", "copyright", "shortcutKeys", "transition"]; + +export function isPreferencesPayload(value: unknown): value is Record { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return false; + } + return PREFERENCES_KNOWN_KEYS.some(key => Object.prototype.hasOwnProperty.call(value, key)); +} + +/** 保存到账号:允许空对象(重置后的默认偏好) */ +export function isPreferencesSavePayload(value: unknown): value is Record { + if (value == null || typeof value !== "object" || Array.isArray(value)) { + return false; + } + const keys = Object.keys(value as Record); + if (keys.length === 0) { + return true; + } + return isPreferencesPayload(value); +} + +export async function applyPreferencesFromAccount(data: Record) { + updatePreferences(data); + if (data.app?.locale) { + await loadLocaleMessages(data.app.locale); + } +} + +/** + * 登录后从账号加载偏好并应用到本地; + * - null / {}:账号无有效配置(含保存为空),默认继续使用本地偏好 + */ +export async function loadPreferencesFromAccount() { + const data = await PreferencesSettingsGet(); + if (data == null) { + return false; + } + if (typeof data !== "object" || Array.isArray(data)) { + return false; + } + // 保存为空时,后续拉取到空数据后默认从本地读取,不覆盖本地 + if (Object.keys(data).length === 0) { + return false; + } + if (!isPreferencesPayload(data)) { + return false; + } + await applyPreferencesFromAccount(data); + return true; +} + +export async function savePreferencesToAccount(preferencesPayload: Record | null | undefined) { + const payload = preferencesPayload && typeof preferencesPayload === "object" ? preferencesPayload : {}; + if (!isPreferencesSavePayload(payload)) { + throw new Error("invalid preferences payload"); + } + await PreferencesSettingsSave(payload); + antdMessage.success($t("preferences.saveToAccountSuccess")); +} diff --git a/packages/ui/certd-client/src/vben/layouts/widgets/preferences/api.ts b/packages/ui/certd-client/src/vben/layouts/widgets/preferences/api.ts new file mode 100644 index 000000000..b9bde5538 --- /dev/null +++ b/packages/ui/certd-client/src/vben/layouts/widgets/preferences/api.ts @@ -0,0 +1,21 @@ +// @ts-ignore +import { request } from "/@/api/service"; + +const apiPrefix = "/user/settings/preferences"; + +export async function PreferencesSettingsGet(): Promise | null> { + const res = await request({ + url: apiPrefix + "/get", + method: "post", + showErrorNotify: false, + }); + return (res as Record) || null; +} + +export async function PreferencesSettingsSave(preferences: Record) { + return await request({ + url: apiPrefix + "/save", + method: "post", + data: { preferences }, + }); +} diff --git a/packages/ui/certd-client/src/vben/layouts/widgets/preferences/preferences-drawer.vue b/packages/ui/certd-client/src/vben/layouts/widgets/preferences/preferences-drawer.vue index a131f740b..ce3fc6baa 100644 --- a/packages/ui/certd-client/src/vben/layouts/widgets/preferences/preferences-drawer.vue +++ b/packages/ui/certd-client/src/vben/layouts/widgets/preferences/preferences-drawer.vue @@ -16,21 +16,27 @@ import type { SegmentedItem } from "/@/vben//shadcn-ui"; import { computed, ref } from "vue"; -import { Copy, RotateCw, X } from "/@/vben/icons"; +import { ClipboardPaste, CloudUpload, Copy, RotateCw, X } from "/@/vben/icons"; import { $t, loadLocaleMessages } from "/@/locales"; import { clearPreferencesCache, preferences, resetPreferences, usePreferences } from "/@/vben/preferences"; import { useVbenDrawer } from "/@/vben//popup-ui"; import { VbenButton, VbenIconButton, VbenSegmented } from "/@/vben//shadcn-ui"; import { globalShareState } from "/@/vben//shared/global-state"; +import { useUserStore } from "/@/store/user"; import { useClipboard } from "@vueuse/core"; import { Animation, Block, Breadcrumb, BuiltinTheme, ColorMode, Content, Copyright, Footer, General, GlobalShortcutKeys, Header, Layout, Navigation, Radius, Sidebar, Tabbar, Theme, Widget } from "./blocks"; +import { applyPreferencesFromAccount, isPreferencesPayload, savePreferencesToAccount } from "./account-sync"; + +import { message as antdMessage } from "ant-design-vue"; const emit = defineEmits<{ clearPreferencesAndLogout: [] }>(); const message = globalShareState.getMessage(); +const userStore = useUserStore(); +const savingToAccount = ref(false); const appLocale = defineModel("appLocale"); const appDynamicTitle = defineModel("appDynamicTitle"); @@ -150,6 +156,41 @@ async function handleCopy() { await copy(JSON.stringify(diffPreference.value, null, 2)); message.copyPreferencesSuccess?.($t("preferences.copyPreferencesSuccessTitle"), $t("preferences.copyPreferencesSuccess")); + antdMessage.success($t("preferences.copyPreferencesSuccessTitle")); +} + +async function handleImport() { + try { + const text = await navigator.clipboard.readText(); + const data = JSON.parse(text); + if (!isPreferencesPayload(data)) { + throw new Error("invalid preferences payload"); + } + await applyPreferencesFromAccount(data); + message.copyPreferencesSuccess?.($t("preferences.importPreferencesSuccessTitle"), $t("preferences.importPreferencesSuccess")); + antdMessage.success($t("preferences.importPreferencesSuccess")); + } catch { + antdMessage.error($t("preferences.importPreferencesError")); + } +} + +async function handleSaveToAccount() { + if (!userStore.isLogined) { + antdMessage.warning($t("preferences.saveToAccountNeedLogin")); + return; + } + if (savingToAccount.value) { + return; + } + savingToAccount.value = true; + try { + // 重置后 diff 为空时保存 {};后续登录拉取到空数据则继续使用本地偏好 + await savePreferencesToAccount((diffPreference.value as Record) || {}); + } catch (e: any) { + antdMessage.error(e?.message || $t("preferences.saveToAccountError")); + } finally { + savingToAccount.value = false; + } } async function handleClearCache() { @@ -310,13 +351,25 @@ async function handleReset() { diff --git a/packages/ui/certd-server/src/controller/user/mine/user-preferences.test.ts b/packages/ui/certd-server/src/controller/user/mine/user-preferences.test.ts new file mode 100644 index 000000000..f15352251 --- /dev/null +++ b/packages/ui/certd-server/src/controller/user/mine/user-preferences.test.ts @@ -0,0 +1,40 @@ +/// +/// + +import assert from "node:assert/strict"; + +import { parseUserPreferencesPayload } from "./user-preferences.js"; + +describe("parseUserPreferencesPayload", () => { + it("parses wrapped preferences payload", () => { + const result = parseUserPreferencesPayload({ + preferences: { + theme: { mode: "dark" }, + }, + }); + assert.deepEqual(result, { theme: { mode: "dark" } }); + }); + + it("parses direct preferences payload", () => { + const result = parseUserPreferencesPayload({ + app: { locale: "en-US" }, + theme: { mode: "light" }, + }); + assert.deepEqual(result, { + app: { locale: "en-US" }, + theme: { mode: "light" }, + }); + }); + + it("parses empty preferences as reset payload", () => { + assert.deepEqual(parseUserPreferencesPayload({}), {}); + assert.deepEqual(parseUserPreferencesPayload({ preferences: {} }), {}); + }); + + it("returns null for invalid payload", () => { + assert.equal(parseUserPreferencesPayload(null), null); + assert.equal(parseUserPreferencesPayload([]), null); + assert.equal(parseUserPreferencesPayload({ foo: 1 }), null); + assert.equal(parseUserPreferencesPayload({ preferences: { foo: 1 } }), null); + }); +}); diff --git a/packages/ui/certd-server/src/controller/user/mine/user-preferences.ts b/packages/ui/certd-server/src/controller/user/mine/user-preferences.ts new file mode 100644 index 000000000..c6b53e412 --- /dev/null +++ b/packages/ui/certd-server/src/controller/user/mine/user-preferences.ts @@ -0,0 +1,41 @@ +const PREFERENCES_KNOWN_KEYS = [ + "app", + "theme", + "logo", + "sidebar", + "header", + "tabbar", + "breadcrumb", + "navigation", + "widget", + "footer", + "copyright", + "shortcutKeys", + "transition", +]; + +/** + * 解析用户偏好设置 payload。 + * 兼容 `{ preferences: {...} }` 与直接传偏好对象两种格式。 + * 空对象 `{}` 表示已重置为默认偏好,视为合法。 + */ +export function parseUserPreferencesPayload(value: unknown): Record | null { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return null; + } + const raw = value as Record; + const preferences = + raw.preferences != null && typeof raw.preferences === "object" && !Array.isArray(raw.preferences) + ? (raw.preferences as Record) + : raw; + const preferenceKeys = Object.keys(preferences); + // 空对象表示重置后的默认偏好 + if (preferenceKeys.length === 0) { + return {}; + } + const hasKnownKey = PREFERENCES_KNOWN_KEYS.some(key => Object.prototype.hasOwnProperty.call(preferences, key)); + if (!hasKnownKey) { + return null; + } + return preferences; +} diff --git a/packages/ui/certd-server/src/controller/user/mine/user-settings-controller.ts b/packages/ui/certd-server/src/controller/user/mine/user-settings-controller.ts index 1eb45bccf..b9f37cdeb 100644 --- a/packages/ui/certd-server/src/controller/user/mine/user-settings-controller.ts +++ b/packages/ui/certd-server/src/controller/user/mine/user-settings-controller.ts @@ -2,10 +2,11 @@ import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/c import { Constants, CrudController } from "@certd/lib-server"; import { UserSettingsService } from "../../../modules/mine/service/user-settings-service.js"; import { UserSettingsEntity } from "../../../modules/mine/entity/user-settings.js"; -import { UserGrantSetting } from "../../../modules/mine/service/models.js"; +import { UserGrantSetting, UserPreferencesSetting } from "../../../modules/mine/service/models.js"; import { isPlus } from "@certd/plus-core"; import { merge } from "lodash-es"; import { ApiTags } from "@midwayjs/swagger"; +import { parseUserPreferencesPayload } from "./user-preferences.js"; /** */ @@ -90,4 +91,37 @@ export class UserSettingsController extends CrudController await this.service.saveSetting(userId, null, setting); return this.ok({}); } + + @Post("/preferences/get", { description: Constants.per.authOnly, summary: "获取用户偏好设置" }) + async preferencesGet() { + const userId = this.getUserId(); + const entity = await this.service.getByKey(UserPreferencesSetting.__key__, userId, null); + if (!entity?.setting) { + return this.ok(null); + } + let parsed: unknown; + try { + parsed = JSON.parse(entity.setting); + } catch { + return this.ok(null); + } + return this.ok(parseUserPreferencesPayload(parsed)); + } + + @Post("/preferences/save", { description: Constants.per.authOnly, summary: "保存用户偏好设置" }) + async preferencesSave(@Body(ALL) bean: any) { + const userId = this.getUserId(); + const preferences = parseUserPreferencesPayload(bean); + if (!preferences) { + throw new Error("偏好设置内容无效"); + } + // 整份替换,避免 saveSetting 深合并留下已恢复为默认值的旧字段 + const entity = new UserSettingsEntity(); + entity.key = UserPreferencesSetting.__key__; + entity.title = UserPreferencesSetting.__title__; + entity.userId = userId; + entity.setting = JSON.stringify({ preferences }); + await this.service.save(entity); + return this.ok({}); + } } diff --git a/packages/ui/certd-server/src/modules/mine/service/models.ts b/packages/ui/certd-server/src/modules/mine/service/models.ts index c0843f9ed..e426d0018 100644 --- a/packages/ui/certd-server/src/modules/mine/service/models.ts +++ b/packages/ui/certd-server/src/modules/mine/service/models.ts @@ -52,6 +52,14 @@ export class UserGrantSetting extends BaseSettings { allowAdminViewCerts = false; } +export class UserPreferencesSetting extends BaseSettings { + static __title__ = "用户偏好设置"; + static __key__ = "user.preferences"; + + /** 偏好差异配置(相对默认值),与前端 diffPreference 结构一致 */ + preferences: Record = {}; +} + export class UserDomainImportSetting extends BaseSettings { static __title__ = "用户域名导入设置"; static __key__ = "user.domain.import"; diff --git a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts index 6d09decdd..6eaa89a46 100644 --- a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts @@ -5,6 +5,7 @@ import { IContext } from "@certd/pipeline"; import { IDnsProvider, IDomainParser } from "@certd/plugin-lib"; import punycode from "punycode.js"; import { IOssClient } from "../../../plugin-lib/index.js"; +import { NonRetryableException } from "@certd/lib-server"; export type CnameVerifyPlan = { type?: string; domain: string; @@ -531,38 +532,47 @@ export class AcmeService { }; /* 自动申请证书 */ const challengePriority = domainsVerifyPlan && Object.values(domainsVerifyPlan).some((item: any) => item?.type === "dns-persist") ? ["dns-persist-01"] : ["dns-01", "http-01"]; - const crt = await client.auto({ - csr, - email: email, - termsOfServiceAgreed: true, - skipChallengeVerification: this.skipLocalVerify, - challengePriority, - challengeCreateFn: async ( - authz: acme.Authorization, - keyAuthorizationGetter: (challenge: Challenge) => Promise - ): Promise<{ recordReq?: any; recordRes?: any; dnsProvider?: any; challenge: Challenge; keyAuthorization: string }> => { - return await this.challengeCreateFn(authz, keyAuthorizationGetter, providers); - }, - challengeRemoveFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider: IDnsProvider, httpUploader: IOssClient): Promise => { - return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider, httpUploader); - }, - signal: this.options.signal, - profile, - preferredChain, - waitDnsDiffuseTime: this.options.waitDnsDiffuseTime, - }); + try { + const crt = await client.auto({ + csr, + email: email, + termsOfServiceAgreed: true, + skipChallengeVerification: this.skipLocalVerify, + challengePriority, + challengeCreateFn: async ( + authz: acme.Authorization, + keyAuthorizationGetter: (challenge: Challenge) => Promise + ): Promise<{ recordReq?: any; recordRes?: any; dnsProvider?: any; challenge: Challenge; keyAuthorization: string }> => { + return await this.challengeCreateFn(authz, keyAuthorizationGetter, providers); + }, + challengeRemoveFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider: IDnsProvider, httpUploader: IOssClient): Promise => { + return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider, httpUploader); + }, + signal: this.options.signal, + profile, + preferredChain, + waitDnsDiffuseTime: this.options.waitDnsDiffuseTime, + }); - const crtString = crt.toString(); - const cert: CertInfo = { - crt: crtString, - key: key.toString(), - csr: csr.toString(), - }; - /* Done */ - this.logger.debug(`CSR:\n${cert.csr}`); - this.logger.debug(`Certificate:\n${cert.crt}`); - this.logger.info("证书申请成功"); - return cert; + const crtString = crt.toString(); + const cert: CertInfo = { + crt: crtString, + key: key.toString(), + csr: csr.toString(), + }; + /* Done */ + this.logger.debug(`CSR:\n${cert.csr}`); + this.logger.debug(`Certificate:\n${cert.crt}`); + this.logger.info("证书申请成功"); + return cert; + } catch (e) { + const message = e?.message; + const REDUNDANT_WILDCARD_DOMAIN_ERROR = "redundant with a wildcard domain in the same request"; + if (message != null && message.indexOf(REDUNDANT_WILDCARD_DOMAIN_ERROR) >= 0) { + throw new NonRetryableException(`通配符域名已经包含了普通域名,请删除其中一个(${message})`); + } + throw e; + } } buildCommonNameByDomains(domains: string | string[]): { diff --git a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/apply.test.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/apply.test.ts index 8e4f39eb1..46c4a32d0 100644 --- a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/apply.test.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/apply.test.ts @@ -1,4 +1,6 @@ import assert from "assert"; +import { utils } from "@certd/basic"; +import { NonRetryableException } from "@certd/lib-server"; import { CertApplyPlugin } from "./apply.js"; describe("CertApplyPlugin dns-persist verify plan", () => { @@ -45,3 +47,114 @@ describe("CertApplyPlugin dns-persist verify plan", () => { assert.equal(plan["handfree.work"].dnsPersistVerifyPlan?.recordValue, "letsencrypt.org; accounturi=https://acme.example/acct/1; policy=wildcard"); }); }); + +describe("CertApplyPlugin certificate apply retry", () => { + it("does not retry by default", async () => { + const plugin: any = new CertApplyPlugin(); + let orderCount = 0; + const error = new Error("apply failed"); + plugin.logger = { warn() {} }; + plugin.acme = { + async order() { + orderCount++; + throw error; + }, + }; + + await assert.rejects(plugin.orderWithRetry({}), error); + assert.equal(orderCount, 1); + assert.equal(plugin.certApplyRetryCount, 0); + }); + + it("retries after a 30-second cooldown and succeeds before reaching the limit", async () => { + const plugin: any = new CertApplyPlugin(); + let orderCount = 0; + const waitTimes: number[] = []; + plugin.certApplyRetryCount = 2; + plugin.logger = { warn() {} }; + plugin.acme = { + async order() { + orderCount++; + if (orderCount < 3) { + throw new Error(`apply failed ${orderCount}`); + } + return { crt: "certificate", key: "private-key" }; + }, + }; + const originalSleep = utils.sleep; + utils.sleep = async (waitTime: number) => { + waitTimes.push(waitTime); + }; + + try { + const cert = await plugin.orderWithRetry({}); + + assert.deepEqual(cert, { crt: "certificate", key: "private-key" }); + assert.equal(orderCount, 3); + assert.deepEqual(waitTimes, [30_000, 30_000]); + } finally { + utils.sleep = originalSleep; + } + }); + + it("throws the last error after reaching the retry limit", async () => { + const plugin: any = new CertApplyPlugin(); + let orderCount = 0; + const error = new Error("apply failed"); + plugin.certApplyRetryCount = 1; + plugin.logger = { warn() {} }; + plugin.acme = { + async order() { + orderCount++; + throw error; + }, + }; + const originalSleep = utils.sleep; + utils.sleep = async () => {}; + + try { + await assert.rejects(plugin.orderWithRetry({}), error); + assert.equal(orderCount, 2); + } finally { + utils.sleep = originalSleep; + } + }); + + it("does not retry a cancelled apply", async () => { + const plugin: any = new CertApplyPlugin(); + let orderCount = 0; + const error: any = new Error("cancelled"); + error.name = "CancelError"; + plugin.certApplyRetryCount = 2; + plugin.logger = { warn() {} }; + plugin.acme = { + async order() { + orderCount++; + throw error; + }, + }; + + await assert.rejects(plugin.orderWithRetry({}), error); + assert.equal(orderCount, 1); + }); + + it("throws a non-retryable error for wildcard and normal domain conflicts", async () => { + const plugin: any = new CertApplyPlugin(); + let orderCount = 0; + const message = "example.com is redundant with a wildcard domain in the same request"; + plugin.certApplyRetryCount = 2; + plugin.logger = { warn() {} }; + plugin.acme = { + async order() { + orderCount++; + throw new NonRetryableException(`通配符域名已经包含了普通域名,请删除其中一个(${message})`); + }, + }; + await assert.rejects(plugin.orderWithRetry({}), (error: any) => { + assert.equal(error instanceof NonRetryableException, true); + assert.equal(error.message, `通配符域名已经包含了普通域名,请删除其中一个(${message})`); + return true; + }); + assert.equal(orderCount, 1); + }); +}); diff --git a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/apply.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/apply.ts index e48c52b71..dbe33494c 100644 --- a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/apply.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/apply.ts @@ -1,5 +1,6 @@ -import { CancelError, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { utils } from "@certd/basic"; +import { NonRetryableException } from "@certd/lib-server"; import { AcmeAccountInfo, AcmeService, DomainsVerifyPlan, DomainVerifyPlan, PrivateKeyType, SSLProvider } from "./acme.js"; import { createDnsProvider, DnsProviderContext, DnsVerifier, DomainVerifiers, HttpVerifier, IDnsProvider, IDomainVerifierGetter, ISubDomainsGetter } from "@certd/plugin-lib"; @@ -61,6 +62,7 @@ const preferredChainConfigs = { } as const; const preferredChainSupportedProviders = Object.keys(preferredChainConfigs); +const CERT_APPLY_RETRY_DELAY_MS = 30_000; const preferredChainMergeScript = (() => { const configs = JSON.stringify(preferredChainConfigs); @@ -551,6 +553,20 @@ export class CertApplyPlugin extends CertApplyBasePlugin { }) waitDnsDiffuseTime = 30; + @TaskInput({ + title: "证书申请失败重试次数", + value: 0, + component: { + name: "a-input-number", + vModel: "value", + min: 0, + step: 1, + }, + maybeNeed: true, + helper: "证书申请失败后,等待30秒再自动重试;0表示不重试", + }) + certApplyRetryCount = 0; + acme!: AcmeService; eab!: EabAccess; @@ -651,34 +667,53 @@ export class CertApplyPlugin extends CertApplyBasePlugin { dnsProvider = await this.createDnsProvider(dnsProviderType, access); } - try { - const cert = await this.acme.order({ - email, - domains, - dnsProvider, - domainsVerifyPlan, - csrInfo, - privateKeyType: this.privateKeyType, - profile: this.certProfile, - preferredChain: this.preferredChain, - acmeAccount, - }); + const cert = await this.orderWithRetry({ + email, + domains, + dnsProvider, + domainsVerifyPlan, + csrInfo, + privateKeyType: this.privateKeyType, + profile: this.certProfile, + preferredChain: this.preferredChain, + acmeAccount, + }); - const certInfo = this.formatCerts(cert); - return new CertReader(certInfo); - } catch (e: any) { - const message: string = e?.message; - if (message != null && message.indexOf("redundant with a wildcard domain in the same request") >= 0) { - this.logger.error(e); - throw new Error(`通配符域名已经包含了普通域名,请删除其中一个(${message})`); + const certInfo = this.formatCerts(cert); + return new CertReader(certInfo); + } + + private async orderWithRetry(orderOptions: Parameters[0]) { + const maxRetryCount = this.getCertApplyRetryCount(); + let retryCount = 0; + + while (true) { + try { + return await this.acme.order(orderOptions); + } catch (e: any) { + if (e instanceof NonRetryableException) { + throw e; + } + if (e?.name === "CancelError" || retryCount >= maxRetryCount) { + throw e; + } + + retryCount++; + this.logger.warn(`证书申请失败,等待30秒后重试(${retryCount}/${maxRetryCount})`, e); + await utils.sleep(CERT_APPLY_RETRY_DELAY_MS); } - if (e.name === "CancelError") { - throw new CancelError(e.message); - } - throw e; } } + private getCertApplyRetryCount() { + const retryCount = Number(this.certApplyRetryCount); + if (!Number.isFinite(retryCount) || retryCount <= 0) { + return 0; + } + return Math.floor(retryCount); + } + + async createDnsProvider(dnsProviderType: string, dnsProviderAccess: any): Promise { const domainParser = this.acme.options.domainParser; const context: DnsProviderContext = {