Merge branch 'v2-dev' into v2-dev-buy

This commit is contained in:
xiaojunnuo
2025-08-29 16:54:11 +08:00
78 changed files with 1559 additions and 225 deletions
@@ -83,7 +83,7 @@ export class CodeService {
}
const smsType = sysSettings.sms.type;
const smsConfig = sysSettings.sms.config;
const sender: ISmsService = SmsServiceFactory.createSmsService(smsType);
const sender: ISmsService = await SmsServiceFactory.createSmsService(smsType);
const accessGetter = new AccessSysGetter(this.accessService);
sender.setCtx({
accessService: accessGetter,
@@ -1,25 +1,28 @@
import { AliyunSmsService } from './aliyun-sms.js';
import { YfySmsService } from './yfy-sms.js';
export class SmsServiceFactory {
static createSmsService(type: string) {
const cls = this.GetClassByType(type);
static async createSmsService(type: string) {
const cls = await this.GetClassByType(type);
return new cls();
}
static GetClassByType(type: string) {
static async GetClassByType(type: string) {
switch (type) {
case 'aliyun':
const {AliyunSmsService} = await import("./aliyun-sms.js")
return AliyunSmsService;
case 'yfysms':
const {YfySmsService} = await import("./yfy-sms.js")
return YfySmsService;
case 'tencent':
const {TencentSmsService} = await import("./tencent-sms.js")
return TencentSmsService;
default:
throw new Error('不支持的短信服务类型');
}
}
static getDefine(type: string) {
const cls = this.GetClassByType(type);
static async getDefine(type: string) {
const cls = await this.GetClassByType(type);
return cls.getDefine();
}
}
@@ -0,0 +1,124 @@
import {ISmsService, PluginInputs, SmsPluginCtx} from './api.js';
import {TencentAccess} from "@certd/plugin-lib";
export type TencentSmsConfig = {
accessId: string;
signName: string;
codeTemplateId: string;
appId: string;
region: string;
};
export class TencentSmsService implements ISmsService {
static getDefine() {
return {
name: 'tencent',
desc: '腾讯云短信服务',
input: {
accessId: {
title: '腾讯云授权',
component: {
name: 'access-selector',
type: 'tencent',
},
required: true,
},
region: {
title: '区域',
value:"ap-beijing",
component: {
name: 'a-select',
vModel: 'value',
options:[
{value:"ap-beijing",label:"华北地区(北京)"},
{value:"ap-guangzhou",label:"华南地区(广州)"},
{value:"ap-nanjing",label:"华东地区(南京)"},
]
},
helper:"随便选一个",
required: true,
},
signName: {
title: '签名',
component: {
name: 'a-input',
vModel: 'value',
},
required: true,
},
appId: {
title: '应用ID',
component: {
name: 'a-input',
vModel: 'value',
},
required: true,
},
codeTemplateId: {
title: '验证码模板Id',
component: {
name: 'a-input',
vModel: 'value',
},
required: true,
},
} as PluginInputs<TencentSmsConfig>,
};
}
ctx: SmsPluginCtx<TencentSmsConfig>;
setCtx(ctx: any) {
this.ctx = ctx;
}
async getClient() {
const sdk = await import('tencentcloud-sdk-nodejs/tencentcloud/services/sms/v20210111/index.js');
const client = sdk.v20210111.Client;
const access = await this.ctx.accessService.getById<TencentAccess>(this.ctx.config.accessId);
// const region = this.region;
const clientConfig = {
credential: {
secretId: access.secretId,
secretKey: access.secretKey,
},
region: this.ctx.config.region,
profile: {
httpProfile: {
endpoint: `sms.${access.intlDomain()}tencentcloudapi.com`,
},
},
};
return new client(clientConfig);
}
async sendSmsCode(opts: { mobile: string; code: string; phoneCode: string }) {
const { mobile, code, phoneCode } = opts;
const client = await this.getClient();
const smsConfig = this.ctx.config;
const params = {
"PhoneNumberSet": [
`+${phoneCode}${mobile}`
],
"SmsSdkAppId": smsConfig.appId,
"TemplateId": smsConfig.codeTemplateId,
"SignName": smsConfig.signName,
"TemplateParamSet": [
code
]
};
const ret = await client.SendSms(params);
this.checkRet(ret);
}
checkRet(ret: any) {
if (!ret || ret.Error) {
throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message);
}
}
}
@@ -27,7 +27,7 @@ export class CertInfoFacade {
@Inject()
userSettingsService : UserSettingsService
async getCertInfo(req: { domains?: string; certId?: number; userId: number,autoApply?:boolean }) {
async getCertInfo(req: { domains?: string; certId?: number; userId: number,autoApply?:boolean,format?:string }) {
const { domains, certId, userId } = req;
if (certId) {
return await this.certInfoService.getCertInfoById({ id: certId, userId });
@@ -41,7 +41,7 @@ export class CertInfoFacade {
const domainArr = domains.split(',');
const matchedList = await this.certInfoService.getMatchCertList({domains:domainArr,userId})
let matched: CertInfoEntity = null
if (matchedList.length === 0 ) {
if(req.autoApply === true){
//自动申请,先创建自动申请流水线
@@ -54,13 +54,7 @@ export class CertInfoFacade {
});
}
}
matched = null;
for (const item of matchedList) {
if (item.expiresTime>0 && item.expiresTime > new Date().getTime()) {
matched = item;
break
}
}
let matched = this.getMinixMatched(matchedList);
if (!matched) {
if(req.autoApply === true){
//如果没有找到有效期内的证书,则自动触发一次申请
@@ -75,7 +69,38 @@ export class CertInfoFacade {
}
}
return await this.certInfoService.getCertInfoById({ id: matched.id, userId: userId });
return await this.certInfoService.getCertInfoById({ id: matched.id, userId: userId,format:req.format });
}
public getMinixMatched(matchedList: CertInfoEntity[]) {
let matched: CertInfoEntity = null;
for (const item of matchedList) {
if (item.expiresTime > 0 && item.expiresTime > new Date().getTime()) {
if (matched) {
//如果前面已经有match的值,判断范围是否比上一个小
const currentStars = `-${item.domains}`.split("*");
const matchedStars = `-${matched.domains}`.split("*");
const currentLength = item.domains.split(",");
const matchedLength = matched.domains.split(",");
if (currentStars.length < matchedStars.length) {
//如果*的数量比上一个少,则替换为当前
matched = item;
} else if (currentStars.length == matchedStars.length) {
//如果*的数量相同,则比较域名数量
if (currentLength.length < matchedLength.length) {
matched = item;
}
}
} else {
matched = item;
}
}
}
return matched;
}
async createAutoPipeline(req:{domains:string[],userId:number}){
@@ -113,7 +113,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
});
}
async getCertInfoById(req: { id: number; userId: number }) {
async getCertInfoById(req: { id: number; userId: number,format?:string }) {
const entity = await this.info(req.id);
if (!entity || entity.userId !== req.userId) {
throw new CodeException(Constants.res.openCertNotFound);
@@ -124,7 +124,14 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
}
const certInfo = JSON.parse(entity.certInfo) as CertInfo;
const certReader = new CertReader(certInfo);
return certReader.toCertInfo();
return {
...certReader.toCertInfo(req.format),
detail: {
id: entity.id,
domains: entity.domains.split(','),
notAfter: certReader.expires,
},
};
}
async updateCertByPipelineId(pipelineId: number, cert: CertInfo,file?:string,fromType = 'pipeline') {
@@ -3,9 +3,10 @@ import { PluginService } from './plugin-service.js';
export type PluginConfig = {
name: string;
disabled: boolean;
disabled?: boolean;
sysSetting: {
input?: Record<string, any>;
metadata?: Record<string, any>;
};
};
@@ -37,10 +38,12 @@ export class PluginConfigService {
}
async saveCommPluginConfig(config: CommPluginConfig) {
await this.savePluginConfig('CertApply', config.CertApply);
config.CertApply.name = 'CertApply';
await this.savePluginConfig(config.CertApply);
}
async savePluginConfig(name: string, config: PluginConfig) {
async savePluginConfig( config: PluginConfig) {
const name = config.name;
const sysSetting = config?.sysSetting;
if (!sysSetting) {
throw new Error(`${name}.sysSetting is required`);
@@ -57,7 +60,14 @@ export class PluginConfigService {
author: "certd",
});
} else {
await this.pluginService.getRepository().update({ name }, { sysSetting: JSON.stringify(sysSetting) });
let setting = JSON.parse(pluginEntity.sysSetting || "{}");
if (sysSetting.metadata) {
setting.metadata = sysSetting.metadata;
}
if (sysSetting.input) {
setting.input = sysSetting.input;
}
await this.pluginService.getRepository().update({ name }, { sysSetting: JSON.stringify(setting) });
}
}
@@ -1,16 +1,16 @@
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, PageReq } from "@certd/lib-server";
import { PluginEntity } from "../entity/plugin.js";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { isComm } from "@certd/plus-core";
import { BuiltInPluginService } from "../../pipeline/service/builtin-plugin-service.js";
import { merge } from "lodash-es";
import { accessRegistry, notificationRegistry, pluginRegistry } from "@certd/pipeline";
import { dnsProviderRegistry } from "@certd/plugin-cert";
import { logger } from "@certd/basic";
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
import {BaseService, PageReq} from "@certd/lib-server";
import {PluginEntity} from "../entity/plugin.js";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {IsNull, Not, Repository} from "typeorm";
import {isComm} from "@certd/plus-core";
import {BuiltInPluginService} from "../../pipeline/service/builtin-plugin-service.js";
import {merge} from "lodash-es";
import {accessRegistry, notificationRegistry, pluginRegistry} from "@certd/pipeline";
import {dnsProviderRegistry} from "@certd/plugin-cert";
import {logger} from "@certd/basic";
import yaml from "js-yaml";
import { getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin } from "./default-plugin.js";
import {getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin} from "./default-plugin.js";
import fs from "fs";
import path from "path";
@@ -57,9 +57,9 @@ export class PluginService extends BaseService<PluginEntity> {
};
}
async getEnabledBuildInGroup(isSimple = false) {
async getEnabledBuildInGroup(opts?:{isSimple?:boolean,withSetting?:boolean}) {
const groups = this.builtInPluginService.getGroups();
if (isSimple) {
if (opts?.isSimple) {
for (const key in groups) {
const group = groups[key];
group.plugins.forEach(item => {
@@ -72,9 +72,43 @@ export class PluginService extends BaseService<PluginEntity> {
if (!isComm()) {
return groups;
}
// 初始化设置
const settingPlugins = await this.repository.find({
select:{
id:true,
name:true,
sysSetting:true
},
where: {
sysSetting : Not(IsNull())
}
})
//合并插件配置
const pluginSettingMap:any = {}
for (const item of settingPlugins) {
if (!item.sysSetting) {
continue;
}
pluginSettingMap[item.name] = JSON.parse(item.sysSetting);
}
for (const key in groups) {
const group = groups[key];
if (!group.plugins) {
continue;
}
for (const item of group.plugins) {
const pluginSetting = pluginSettingMap[item.name];
if (pluginSetting){
item.sysSetting = pluginSetting
}
}
}
//排除禁用的
const list = await this.list({
query: {
type: "builtIn",
disabled: true
}
});