perf: OpenAPI支持autoApply参数

v2-dev-order
xiaojunnuo 2025-07-14 23:02:47 +08:00
parent 609ac9c9a2
commit 42f4d1477d
10 changed files with 414 additions and 211 deletions

View File

@ -107,5 +107,13 @@ export const Constants = {
code: 20012,
message: '证书还未生成',
},
openCertApplying: {
code: 20013,
message: '证书正在申请中,请稍后重新获取(需要事先在“域名管理”页面配置好校验方式)',
},
openEmailNotFound: {
code: 20021,
message: '用户邮箱还未配置',
},
},
};

View File

@ -11,13 +11,13 @@ export class CommonException extends BaseException {
}
export class CodeException extends BaseException {
constructor(res: { code: number; message: string }) {
super("CodeException", res.code, res.message);
constructor(res: { code: number; message: string; data?: any }) {
super("CodeException", res.code, res.message, res.data);
}
}
export class TextException extends BaseException {
constructor(name, code,message, data?) {
constructor(name, code, message, data?) {
super(name, code, message, data);
}
}

View File

@ -470,6 +470,9 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
const domain = fullDomain.replaceAll("*.", "");
const mainDomain = await domainParser.parse(domain);
const planSetting: DomainVerifyPlanInput = verifyPlanSetting[mainDomain];
if (planSetting == null) {
throw new Error(`没有找到域名(${domain})的校验计划`);
}
if (planSetting.type === "dns") {
plan[domain] = await this.createDnsDomainVerifyPlan(planSetting, domain, mainDomain);
} else if (planSetting.type === "cname") {
@ -498,6 +501,9 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
for (const domain in verifiers) {
const verifier = verifiers[domain];
if (verifier == null) {
throw new Error(`没有找到与该域名(${domain})匹配的校验方式,请先到‘域名管理’页面添加校验方式`);
}
if (verifier.type === "dns") {
plan[domain] = await this.createDnsDomainVerifyPlan(verifier.dns, domain, verifier.mainDomain);
} else if (verifier.type === "cname") {

View File

@ -46,6 +46,20 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const { openSiteIpMonitorDialog } = useSiteIpMonitor();
const { openSiteImportDialog } = useSiteImport();
function checkAll() {
Modal.confirm({
title: t("certd.monitor.confirmTitle"), // "确认"
content: t("certd.monitor.confirmContent"), // "确认触发检查全部站点证书吗?"
onOk: async () => {
await siteInfoApi.CheckAll();
notification.success({
message: t("certd.monitor.checkSubmitted"), // "检查任务已提交"
description: t("certd.monitor.pleaseRefresh"), // "请稍后刷新页面查看结果"
});
},
});
}
return {
id: "siteMonitorCrud",
crudOptions: {
@ -114,6 +128,14 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
});
},
},
checkAll: {
show: true,
text: t("certd.monitor.checkAll"),
type: "primary",
click() {
checkAll();
},
},
},
},
rowHandle: {

View File

@ -14,9 +14,6 @@
</div>
</div>
</div>
<div class="more">
<a-button type="primary" @click="checkAll">{{ t("certd.monitor.checkAll") }}</a-button>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
@ -35,19 +32,6 @@ defineOptions({
name: "SiteCertMonitor",
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
function checkAll() {
Modal.confirm({
title: t("certd.monitor.confirmTitle"), // ""
content: t("certd.monitor.confirmContent"), // "?"
onOk: async () => {
await siteInfoApi.CheckAll();
notification.success({
message: t("certd.monitor.checkSubmitted"), // ""
description: t("certd.monitor.pleaseRefresh"), // ""
});
},
});
}
//
onMounted(() => {

View File

@ -125,6 +125,7 @@ export function useCertPipelineCreator() {
const pluginStore = usePluginStore();
const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60);
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
const groupDictRef = dict({
url: "/pi/pipeline/group/all",
@ -193,7 +194,7 @@ export function useCertPipelineCreator() {
title: t("certd.pipelineForm.triggerCronTitle"),
type: "text",
form: {
value: `0 ${randomMin} ${randomHour} * * *`,
value: randomCron,
component: {
name: "cron-editor",
vModel: "modelValue",

View File

@ -1,13 +1,15 @@
import { ALL, Body, Controller, Get, Inject, Post, Provide, Query } from '@midwayjs/core';
import { CodeException, Constants, EncryptService } from '@certd/lib-server';
import { CertInfoService } from '../../../modules/monitor/service/cert-info-service.js';
import { CertInfo } from '@certd/plugin-cert';
import { OpenKey } from '../../../modules/open/service/open-key-service.js';
import { BaseOpenController } from '../base-open-controller.js';
import { ALL, Body, Controller, Get, Inject, Post, Provide, Query } from "@midwayjs/core";
import { CodeException, Constants, EncryptService } from "@certd/lib-server";
import { CertInfo } from "@certd/plugin-cert";
import { OpenKey } from "../../../modules/open/service/open-key-service.js";
import { BaseOpenController } from "../base-open-controller.js";
import { CertInfoFacade } from "../../../modules/monitor/facade/cert-info-facade.js";
import { merge } from "lodash-es";
export type CertGetReq = {
domains?: string;
certId: number;
autoApply?:boolean;
};
/**
@ -16,7 +18,7 @@ export type CertGetReq = {
@Controller('/api/v1/cert')
export class OpenCertController extends BaseOpenController {
@Inject()
certInfoService: CertInfoService;
certInfoFacade: CertInfoFacade;
@Inject()
encryptService: EncryptService;
@ -29,10 +31,13 @@ export class OpenCertController extends BaseOpenController {
throw new CodeException(Constants.res.openKeyError);
}
const res: CertInfo = await this.certInfoService.getCertInfo({
const req = merge({}, bean, query)
const res: CertInfo = await this.certInfoFacade.getCertInfo({
userId,
domains: bean.domains || query.domains,
certId: bean.certId || query.certId,
domains: req.domains,
certId: req.certId,
autoApply: req.autoApply??false,
});
return this.ok(res);
}

View File

@ -0,0 +1,96 @@
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { CodeException, Constants } from "@certd/lib-server";
import { CertInfoEntity } from "../entity/cert-info.js";
import { utils } from "@certd/basic";
import { PipelineService } from "../../pipeline/service/pipeline-service.js";
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
import { UserEmailSetting } from "../../mine/service/models.js";
import { PipelineEntity } from "../../pipeline/entity/pipeline.js";
import { CertInfoService } from "../service/cert-info-service.js";
@Provide("CertInfoFacade")
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class CertInfoFacade {
@Inject()
pipelineService: PipelineService;
@Inject()
certInfoService: CertInfoService ;
@Inject()
userSettingsService : UserSettingsService
async getCertInfo(req: { domains?: string; certId?: number; userId: number,autoApply?:boolean }) {
const { domains, certId, userId } = req;
if (certId) {
return await this.certInfoService.getCertInfoById({ id: certId, userId });
}
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){
//自动申请,先创建自动申请流水线
const pipeline:PipelineEntity = await this.createAutoPipeline({domains:domainArr,userId})
await this.triggerApplyPipeline({pipelineId:pipeline.id})
}else{
throw new CodeException(Constants.res.openCertNotFound);
}
}
matched = null;
for (const item of matchedList) {
if (item.expiresTime>0 && item.expiresTime < new Date().getTime()) {
matched = item;
break
}
}
if (!matched) {
if(req.autoApply === true){
//如果没有找到有效期内的证书,则自动触发一次申请
const first = matchedList[0]
await this.triggerApplyPipeline({pipelineId:first.pipelineId})
return
}else{
throw new CodeException(Constants.res.openCertNotFound);
}
}
return await this.certInfoService.getCertInfoById({ id: matched.id, userId: userId });
}
async createAutoPipeline(req:{domains:string[],userId:number}){
const userEmailSetting = await this.userSettingsService.getSetting<UserEmailSetting>(req.userId,UserEmailSetting)
if(!userEmailSetting.list){
throw new CodeException(Constants.res.openEmailNotFound)
}
const email = userEmailSetting.list[0]
return await this.pipelineService.createAutoPipeline({
domains: req.domains,
email,
userId: req.userId,
from:"OpenAPI"
})
}
async triggerApplyPipeline(req:{pipelineId:number}){
//查询流水线状态
const status = await this.pipelineService.getStatus(req.pipelineId)
if (status != 'running') {
await this.pipelineService.trigger(req.pipelineId)
await utils.sleep(1000)
}
throw new CodeException({
...Constants.res.openCertApplying,
data:{
pipelineId:req.pipelineId
}
});
}
}

View File

@ -1,10 +1,10 @@
import {Provide, Scope, ScopeEnum} from "@midwayjs/core";
import {BaseService, CodeException, Constants, PageReq} from "@certd/lib-server";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {MoreThan, Repository} from "typeorm";
import {CertInfoEntity} from "../entity/cert-info.js";
import {utils} from "@certd/basic";
import {CertInfo, CertReader} from "@certd/plugin-cert";
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, CodeException, Constants, PageReq } from "@certd/lib-server";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { CertInfoEntity } from "../entity/cert-info.js";
import { utils } from "@certd/basic";
import { CertInfo, CertReader } from "@certd/plugin-cert";
export type UploadCertReq = {
id?: number;
@ -71,6 +71,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
}
await this.addOrUpdate(bean);
return bean.id
}
async deleteByPipelineId(id: number) {
@ -82,44 +83,28 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
});
}
async getCertInfo(params: { domains?: string; certId?: number; userId: number }) {
const { domains, certId, userId } = params;
if (certId) {
return await this.getCertInfoById({ id: certId, userId });
}
return await this.getCertInfoByDomains({
domains,
userId,
});
}
private async getCertInfoByDomains(params: { domains: string; userId: number }) {
async getMatchCertList(params: { domains: string[]; userId: number }) {
const { domains, userId } = params;
if (!domains) {
throw new CodeException(Constants.res.openCertNotFound);
}
const domainArr = domains.split(',');
const list = await this.find({
select: {
id: true,
domains: true,
expiresTime:true,
pipelineId:true,
},
where: {
userId,
expiresTime: MoreThan(new Date().getTime())
},
});
//遍历查找
const matched = list.find(item => {
return list.filter(item => {
const itemDomains = item.domains.split(',');
return utils.domain.match(domainArr, itemDomains);
return utils.domain.match(domains, itemDomains);
});
if (!matched) {
throw new CodeException(Constants.res.openCertNotFound);
}
return await this.getCertInfoById({ id: matched.id, userId: userId });
}
async getCertInfoById(req: { id: number; userId: number }) {

View File

@ -1,6 +1,6 @@
import {Config, Inject, Provide, Scope, ScopeEnum, sleep} from "@midwayjs/core";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {In, MoreThan, Repository} from "typeorm";
import { Config, Inject, Provide, Scope, ScopeEnum, sleep } from "@midwayjs/core";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { In, MoreThan, Repository } from "typeorm";
import {
AccessService,
BaseService,
@ -11,8 +11,8 @@ import {
SysSettingsService,
SysSiteInfo
} from "@certd/lib-server";
import {PipelineEntity} from "../entity/pipeline.js";
import {PipelineDetail} from "../entity/vo/pipeline-detail.js";
import { PipelineEntity } from "../entity/pipeline.js";
import { PipelineDetail } from "../entity/vo/pipeline-detail.js";
import {
Executor,
IAccessService,
@ -25,33 +25,32 @@ import {
SysInfo,
UserInfo
} from "@certd/pipeline";
import {DbStorage} from "./db-storage.js";
import {StorageService} from "./storage-service.js";
import {Cron} from "../../cron/cron.js";
import {HistoryService} from "./history-service.js";
import {HistoryEntity} from "../entity/history.js";
import {HistoryLogEntity} from "../entity/history-log.js";
import {HistoryLogService} from "./history-log-service.js";
import {EmailService} from "../../basic/service/email-service.js";
import {UserService} from "../../sys/authority/service/user-service.js";
import {CnameRecordService} from "../../cname/service/cname-record-service.js";
import {PluginConfigGetter} from "../../plugin/service/plugin-config-getter.js";
import { DbStorage } from "./db-storage.js";
import { StorageService } from "./storage-service.js";
import { Cron } from "../../cron/cron.js";
import { HistoryService } from "./history-service.js";
import { HistoryEntity } from "../entity/history.js";
import { HistoryLogEntity } from "../entity/history-log.js";
import { HistoryLogService } from "./history-log-service.js";
import { EmailService } from "../../basic/service/email-service.js";
import { UserService } from "../../sys/authority/service/user-service.js";
import { CnameRecordService } from "../../cname/service/cname-record-service.js";
import { PluginConfigGetter } from "../../plugin/service/plugin-config-getter.js";
import dayjs from "dayjs";
import {DbAdapter} from "../../db/index.js";
import {isComm, isPlus} from "@certd/plus-core";
import {logger} from "@certd/basic";
import {UrlService} from "./url-service.js";
import {NotificationService} from "./notification-service.js";
import {UserSuiteEntity, UserSuiteService} from "@certd/commercial-core";
import {CertInfoService} from "../../monitor/service/cert-info-service.js";
import {TaskServiceBuilder} from "./getter/task-service-getter.js";
import {nanoid} from "nanoid";
import {set} from "lodash-es";
import { DbAdapter } from "../../db/index.js";
import { isComm, isPlus } from "@certd/plus-core";
import { logger } from "@certd/basic";
import { UrlService } from "./url-service.js";
import { NotificationService } from "./notification-service.js";
import { UserSuiteEntity, UserSuiteService } from "@certd/commercial-core";
import { CertInfoService } from "../../monitor/service/cert-info-service.js";
import { TaskServiceBuilder } from "./getter/task-service-getter.js";
import { nanoid } from "nanoid";
import { set } from "lodash-es";
const runningTasks: Map<string | number, Executor> = new Map();
/**
*
*/
@ -91,7 +90,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
@Inject()
cron: Cron;
@Config('certd')
@Config("certd")
private certdConfig: any;
@Inject()
@ -112,31 +111,31 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
async add(bean: PipelineEntity) {
bean.status = ResultType.none
bean.status = ResultType.none;
await this.save(bean);
return bean;
}
async page(pageReq: PageReq<PipelineEntity>) {
//模版流水线不要被查询出来
set(pageReq,"query.isTemplate",false)
set(pageReq, "query.isTemplate", false);
const result = await super.page(pageReq);
await this.fillLastVars(result.records);
for (const item of result.records) {
if (!item.content){
continue
if (!item.content) {
continue;
}
const pipeline = JSON.parse(item.content);
let stepCount = 0
let stepCount = 0;
RunnableCollection.each(pipeline.stages, (runnable: any) => {
stepCount++
})
stepCount++;
});
// @ts-ignore
item.stepCount = stepCount
item.stepCount = stepCount;
// @ts-ignore
item.triggerCount = pipeline.triggers.length
delete item.content
item.triggerCount = pipeline.triggers.length;
delete item.content;
}
return result;
@ -148,7 +147,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
for (const record of records) {
pipelineIds.push(record.id);
recordMap[record.id] = record;
record.title = record.title + '';
record.title = record.title + "";
}
if (pipelineIds?.length > 0) {
const vars = await this.storageService.findPipelineVars(pipelineIds);
@ -169,17 +168,17 @@ export class PipelineService extends BaseService<PipelineEntity> {
const info = await this.info(pipelineId);
if (info && !info.disabled) {
const pipeline = JSON.parse(info.content);
this.registerTriggers(pipeline,false);
this.registerTriggers(pipeline, false);
}
}
public async registerTrigger(info:PipelineEntity) {
public async registerTrigger(info: PipelineEntity) {
if (info == null) {
return;
}
if (info && !info.disabled) {
const pipeline = JSON.parse(info.content);
this.registerTriggers(pipeline,false);
this.registerTriggers(pipeline, false);
}
}
@ -206,12 +205,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
const isUpdate = bean.id > 0 && old != null;
const pipeline = JSON.parse(bean.content || '{}');
const pipeline = JSON.parse(bean.content || "{}");
RunnableCollection.initPipelineRunnableType(pipeline);
let domains = [];
if (pipeline.stages) {
RunnableCollection.each(pipeline.stages, (runnable: any) => {
if (runnable.runnableType === 'step' && runnable.type.indexOf('CertApply')>=0) {
if (runnable.runnableType === "step" && runnable.type.indexOf("CertApply") >= 0) {
domains = runnable.input.domains || [];
}
});
@ -222,7 +221,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
await this.checkMaxPipelineCount(bean, pipeline, domains);
}
if (!bean.status ){
if (!bean.status) {
bean.status = ResultType.none;
}
if (!isUpdate) {
@ -233,9 +232,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
await this.doUpdatePipelineJson(bean, pipeline);
//保存域名信息到certInfo表
let fromType = 'pipeline';
if (bean.type === 'cert_upload') {
fromType = 'upload';
let fromType = "pipeline";
if (bean.type === "cert_upload") {
fromType = "upload";
}else if (bean.type === "cert_auto") {
fromType = "auto";
}
await this.certInfoService.updateDomains(pipeline.id, pipeline.userId || bean.userId, domains, fromType);
return bean;
@ -246,7 +247,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
* @param bean
* @param pipeline
*/
async doUpdatePipelineJson(bean: PipelineEntity, pipeline:Pipeline) {
async doUpdatePipelineJson(bean: PipelineEntity, pipeline: Pipeline) {
await this.clearTriggers(bean);
if (pipeline.title) {
bean.title = pipeline.title;
@ -277,7 +278,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
}
}
}else{
} else {
//非商业版校验用户最大流水线数量
const userId = bean.userId;
const userIsAdmin = await this.userService.isAdmin(userId);
@ -296,12 +297,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
async foreachPipeline(callback: (pipeline: PipelineEntity) => void) {
const idEntityList = await this.repository.find({
select: {
id: true,
id: true
},
where: {
disabled: false,
templateId: 0,
},
templateId: 0
}
});
const ids = idEntityList.map(item => {
return item.id;
@ -321,7 +322,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
//分段加载记录
for (const idArr of idsSpan) {
const list = await this.repository.findBy({
id: In(idArr),
id: In(idArr)
});
for (const entity of list) {
@ -346,14 +347,14 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (onlyAdminUser && entity.userId !== 1) {
return;
}
const pipeline = JSON.parse(entity.content ?? '{}');
const pipeline = JSON.parse(entity.content ?? "{}");
try {
await this.registerTriggers(pipeline, immediateTriggerOnce);
} catch (e) {
logger.error('加载定时trigger失败', e);
logger.error("加载定时trigger失败", e);
}
});
logger.info('定时器数量:', this.cron.getTaskSize());
logger.info("定时器数量:", this.cron.getTaskSize());
}
async registerTriggers(pipeline?: Pipeline, immediateTriggerOnce = false) {
@ -375,18 +376,18 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (isComm()) {
await this.checkHasDeployCount(id, entity.userId);
}
await this.checkUserStatus(entity.userId)
await this.checkUserStatus(entity.userId);
this.cron.register({
name: `pipeline.${id}.trigger.once`,
cron: null,
job: async () => {
logger.info('用户手动启动job');
logger.info("用户手动启动job");
try {
await this.doRun(entity, null, stepId);
} catch (e) {
logger.error('手动job执行失败', e);
logger.error("手动job执行失败", e);
}
},
}
});
}
@ -398,7 +399,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
logger.error(e.message);
await this.update({
id: pipelineId,
status: 'no_deploy_count',
status: "no_deploy_count"
});
}
throw e;
@ -406,7 +407,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
//@ts-ignore
async delete(id:any) {
async delete(id: any) {
await this.clearTriggers(id);
//TODO 删除storage
// const storage = new DbStorage(pipeline.userId, this.storageService);
@ -421,11 +422,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (id == null) {
return;
}
let pipeline:PipelineEntity = null
if (typeof id === 'number') {
let pipeline: PipelineEntity = null;
if (typeof id === "number") {
pipeline = await this.info(id);
}else{
pipeline = id
} else {
pipeline = id;
}
if (!pipeline) {
return;
@ -445,7 +446,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
registerCron(pipelineId, trigger) {
if (pipelineId == null) {
logger.warn('pipelineId为空无法注册定时任务');
logger.warn("pipelineId为空无法注册定时任务");
return;
}
@ -454,11 +455,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
return;
}
cron = cron.trim();
if (cron.startsWith('* *')) {
cron = cron.replace('* *', '0 0');
if (cron.startsWith("* *")) {
cron = cron.replace("* *", "0 0");
}
if (cron.startsWith('*')) {
cron = cron.replace('*', '0');
if (cron.startsWith("*")) {
cron = cron.replace("*", "0");
}
const triggerId = trigger.id;
const name = this.buildCronKey(pipelineId, triggerId);
@ -467,19 +468,19 @@ export class PipelineService extends BaseService<PipelineEntity> {
name,
cron,
job: async () => {
logger.info('定时任务触发:', pipelineId, triggerId);
logger.info("定时任务触发:", pipelineId, triggerId);
if (pipelineId == null) {
logger.warn('pipelineId为空,无法执行');
logger.warn("pipelineId为空,无法执行");
return;
}
try {
await this.run(pipelineId, triggerId);
} catch (e) {
logger.error('定时job执行失败', e);
logger.error("定时job执行失败", e);
}
},
}
});
logger.info('当前定时器数量:', this.cron.getTaskSize());
logger.info("当前定时器数量:", this.cron.getTaskSize());
}
/**
@ -499,11 +500,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (isComm()) {
suite = await this.checkHasDeployCount(id, entity.userId);
}
try{
await this.checkUserStatus(entity.userId)
}catch (e) {
logger.info(e.message)
return
try {
await this.checkUserStatus(entity.userId);
} catch (e) {
logger.info(e.message);
return;
}
@ -521,7 +522,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
return;
}
if (triggerType === 'timer') {
if (triggerType === "timer") {
if (entity.disabled) {
return;
}
@ -530,25 +531,25 @@ export class PipelineService extends BaseService<PipelineEntity> {
const onChanged = async (history: RunHistory) => {
//保存执行历史
try {
logger.info('保存执行历史:', history.id);
logger.info("保存执行历史:", history.id);
await this.saveHistory(history);
} catch (e) {
const pipelineEntity = new PipelineEntity();
pipelineEntity.id = id;
pipelineEntity.status = 'error';
pipelineEntity.status = "error";
pipelineEntity.lastHistoryTime = history.pipeline.status.startTime;
await this.update(pipelineEntity);
logger.error('保存执行历史失败:', e);
logger.error("保存执行历史失败:", e);
throw e;
}
};
const userId = entity.userId;
const historyId = await this.historyService.start(entity,triggerType);
const historyId = await this.historyService.start(entity, triggerType);
const userIsAdmin = await this.userService.isAdmin(userId);
const user: UserInfo = {
id: userId,
role: userIsAdmin ? 'admin' : 'user',
role: userIsAdmin ? "admin" : "user"
};
@ -559,11 +560,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
const taskServiceGetter = this.taskServiceBuilder.create({
userId,
})
const accessGetter = await taskServiceGetter.get<IAccessService>("accessService")
const notificationGetter =await taskServiceGetter.get<INotificationService>("notificationService")
const cnameProxyService =await taskServiceGetter.get<ICnameProxyService>("cnameProxyService")
userId
});
const accessGetter = await taskServiceGetter.get<IAccessService>("accessService");
const notificationGetter = await taskServiceGetter.get<INotificationService>("notificationService");
const cnameProxyService = await taskServiceGetter.get<ICnameProxyService>("cnameProxyService");
const executor = new Executor({
user,
pipeline,
@ -577,7 +578,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
notificationService: notificationGetter,
fileRootDir: this.certdConfig.fileRootDir,
sysInfo,
serviceGetter:taskServiceGetter
serviceGetter: taskServiceGetter
});
try {
runningTasks.set(historyId, executor);
@ -595,7 +596,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
}
} catch (e) {
logger.error('执行失败:', e);
logger.error("执行失败:", e);
// throw e;
} finally {
runningTasks.delete(historyId);
@ -619,7 +620,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
private getTriggerType(triggerId, pipeline) {
let triggerType = 'user';
let triggerType = "user";
if (triggerId != null) {
//如果不是手动触发
//查找trigger
@ -629,8 +630,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
this.cron.remove(this.buildCronKey(pipeline.id, triggerId));
triggerType = null;
} else {
logger.info('timer trigger:' + found.id, found.title, found.cron);
triggerType = 'timer';
logger.info("timer trigger:" + found.id, found.title, found.cron);
triggerType = "timer";
}
}
return triggerType;
@ -653,7 +654,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
//修改pipeline状态
const pipelineEntity = new PipelineEntity();
pipelineEntity.id = parseInt(history.pipeline.id);
pipelineEntity.status = history.pipeline.status.result + '';
pipelineEntity.status = history.pipeline.status.result + "";
pipelineEntity.lastHistoryTime = history.pipeline.status.startTime;
await this.update(pipelineEntity);
@ -677,8 +678,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
async count(param: { userId?: any }) {
const count = await this.repository.count({
where: {
userId: param.userId,
},
userId: param.userId
}
});
return count;
}
@ -686,12 +687,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
async statusCount(param: { userId?: any } = {}) {
const statusCount = await this.repository
.createQueryBuilder()
.select('status')
.addSelect('count(1)', 'count')
.select("status")
.addSelect("count(1)", "count")
.where({
userId: param.userId,
userId: param.userId
})
.groupBy('status')
.groupBy("status")
.getRawMany();
return statusCount;
}
@ -701,11 +702,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
select: {
id: true,
title: true,
status: true,
status: true
},
where: {
userId,
},
userId
}
});
await this.fillLastVars(list);
list = list.filter(item => {
@ -719,16 +720,16 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
async createCountPerDay(param: { days: number } = { days: 7 }) {
const todayEnd = dayjs().endOf('day');
const todayEnd = dayjs().endOf("day");
const result = await this.getRepository()
.createQueryBuilder('main')
.select(`${this.dbAdapter.date('main.createTime')} AS date`) // 将UNIX时间戳转换为日期
.addSelect('COUNT(1) AS count')
.createQueryBuilder("main")
.select(`${this.dbAdapter.date("main.createTime")} AS date`) // 将UNIX时间戳转换为日期
.addSelect("COUNT(1) AS count")
.where({
// 0点
createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()),
createTime: MoreThan(todayEnd.add(-param.days, "day").toDate())
})
.groupBy('date')
.groupBy("date")
.getRawMany();
return result;
@ -745,48 +746,48 @@ export class PipelineService extends BaseService<PipelineEntity> {
await this.repository.update(
{
id: In(ids),
userId,
userId
},
{ groupId }
);
}
async batchUpdateTrigger(ids: number[], trigger: any, userId: any){
async batchUpdateTrigger(ids: number[], trigger: any, userId: any) {
const list = await this.find({
where:{
where: {
id: In(ids),
userId
}
})
});
for (const item of list) {
const pipeline = JSON.parse(item.content);
pipeline.triggers = [{
id: nanoid(),
title: '定时触发',
title: "定时触发",
...trigger
}]
await this.doUpdatePipelineJson(item,pipeline)
}];
await this.doUpdatePipelineJson(item, pipeline);
}
}
async batchUpdateNotifications(ids: number[], notification: Notification, userId: any){
async batchUpdateNotifications(ids: number[], notification: Notification, userId: any) {
const list = await this.find({
where:{
where: {
id: In(ids),
userId
}
})
});
for (const item of list) {
const pipeline = JSON.parse(item.content);
pipeline.notifications = [{
id: nanoid(),
title: '通知',
title: "通知",
/**
* type: NotificationType;
* when: NotificationWhen[];
@ -797,44 +798,44 @@ export class PipelineService extends BaseService<PipelineEntity> {
*/
type: "other",
...notification
}]
await this.doUpdatePipelineJson(item,pipeline)
}];
await this.doUpdatePipelineJson(item, pipeline);
}
}
async batchRerun(ids: number[], userId: any) {
if (!isPlus()){
throw new NeedVIPException("此功能需要升级专业版")
if (!isPlus()) {
throw new NeedVIPException("此功能需要升级专业版");
}
if (!userId || ids.length === 0) {
return;
}
const list = await this.repository.find({
select:{
id:true
select: {
id: true
},
where:{
where: {
id: In(ids),
userId
}
})
});
ids = list.map(item=>item.id)
ids = list.map(item => item.id);
//异步执行
this.startBatchRerun(ids)
this.startBatchRerun(ids);
}
async startBatchRerun(ids: number[]){
async startBatchRerun(ids: number[]) {
//20条一批
const batchSize = 20;
for (let i = 0; i < ids.length; i += batchSize) {
const batchIds = ids.slice(i, i + batchSize);
const batchPromises = batchIds.map(async (id)=>{
await this.run(id,null,"ALL")
const batchPromises = batchIds.map(async (id) => {
await this.run(id, null, "ALL");
});
await Promise.all(batchPromises)
await Promise.all(batchPromises);
}
}
@ -847,35 +848,130 @@ export class PipelineService extends BaseService<PipelineEntity> {
return await this.repository.find({
select: {
id: true,
title: true,
title: true
},
where: {
id: In(pipelineIds),
userId,
},
userId
}
});
}
private async checkUserStatus(userId: number) {
const userEntity = await this.userService.info(userId);
if(userEntity == null){
throw new Error('用户不存在');
if (userEntity == null) {
throw new Error("用户不存在");
}
if(userEntity.status === 0){
const message = `账户${userId}已被禁用,禁止运行流水线`
throw new Error(message)
if (userEntity.status === 0) {
const message = `账户${userId}已被禁用,禁止运行流水线`;
throw new Error(message);
}
const sysPublic = await this.sysSettingsService.getPublicSettings()
if(isPlus() && sysPublic.userValidTimeEnabled === true){
const sysPublic = await this.sysSettingsService.getPublicSettings();
if (isPlus() && sysPublic.userValidTimeEnabled === true) {
//校验用户有效期是否设置
if(userEntity.validTime!= null && userEntity.validTime > 0){
if(userEntity.validTime < new Date().getTime()){
if (userEntity.validTime != null && userEntity.validTime > 0) {
if (userEntity.validTime < new Date().getTime()) {
//用户已过期
const message = `账户${userId}已过有效期,禁止运行流水线`
throw new Error(message)
const message = `账户${userId}已过有效期,禁止运行流水线`;
throw new Error(message);
}
}
}
}
async createAutoPipeline(req: { domains: string[]; email: string; userId: number ,from:string}) {
const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60);
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
let pipeline: any = {
title: req.domains[0] + `证书自动申请【${req.from??"OpenAPI"}`,
runnableType: "pipeline",
triggers: [
{
id: nanoid(),
title: "定时触发",
cron: randomCron,
type: "cron"
}
],
notifications: [
{
id: nanoid(),
type: "custom",
when: ["error", "turnToSuccess", "success"],
notificationId: 0,
title: "默认通知",
}
],
stages: [
{
id: nanoid(),
title: "证书申请阶段",
maxTaskCount: 1,
runnableType: "stage",
tasks: [
{
id: nanoid(),
title: "证书申请任务",
runnableType: "task",
steps: [
{
id: nanoid(),
title: "申请证书",
runnableType: "step",
input: {
renewDays: 35,
domains: req.domains,
email: req.email,
"challengeType": "auto",
"sslProvider": "letsencrypt",
"privateKeyType": "rsa_2048",
"certProfile": "classic",
"useProxy": false,
"skipLocalVerify": false,
"maxCheckRetryCount": 20,
"waitDnsDiffuseTime": 30,
"pfxArgs": "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
"successNotify": true
},
strategy: {
runStrategy: 0 // 正常执行
},
type: "CertApply"
}
]
}
]
}
]
};
const bean = new PipelineEntity();
bean.title = pipeline.title;
bean.content = JSON.stringify(pipeline);
bean.userId = req.userId;
bean.status = "none";
bean.type = "cert_auto";
bean.disabled = false
bean.keepHistoryCount = 30
await this.save(bean)
return bean;
}
async getStatus(pipelineId: number) {
const res = await this.repository.findOne({
select: {
status: true
},
where: {
id: pipelineId
}
});
return res?.status;
}
}