mirror of
https://github.com/certd/certd.git
synced 2026-07-06 20:37:34 +08:00
chore: format
This commit is contained in:
@@ -9,7 +9,8 @@ import { AutoPrint } from "./auto-print.js";
|
||||
|
||||
@Autoload()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoARegister { //这个A是必须,让他排在第一个 进行init,否则会被其他init模块抢先注册导致报错
|
||||
export class AutoARegister {
|
||||
//这个A是必须,让他排在第一个 进行init,否则会被其他init模块抢先注册导致报错
|
||||
@Inject()
|
||||
autoInitSite: AutoInitSite;
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { logger } from '@certd/basic';
|
||||
import { SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
||||
import { logger } from "@certd/basic";
|
||||
import { SysSettingsService, SysSiteInfo } from "@certd/lib-server";
|
||||
import { getPlusInfo, isPlus } from "@certd/plus-core";
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import dayjs from "dayjs";
|
||||
import { Between } from "typeorm";
|
||||
import { DomainService } from '../cert/service/domain-service.js';
|
||||
import { Cron } from '../cron/cron.js';
|
||||
import { DomainService } from "../cert/service/domain-service.js";
|
||||
import { Cron } from "../cron/cron.js";
|
||||
import { UserSiteMonitorSetting } from "../mine/service/models.js";
|
||||
import { UserSettingsService } from "../mine/service/user-settings-service.js";
|
||||
import { SiteInfoService } from '../monitor/index.js';
|
||||
import { SiteInfoService } from "../monitor/index.js";
|
||||
import { NotificationService } from "../pipeline/service/notification-service.js";
|
||||
import { PipelineService } from '../pipeline/service/pipeline-service.js';
|
||||
import { PipelineService } from "../pipeline/service/pipeline-service.js";
|
||||
import { UserService } from "../sys/authority/service/user-service.js";
|
||||
import { ProjectService } from '../sys/enterprise/service/project-service.js';
|
||||
import { ProjectService } from "../sys/enterprise/service/project-service.js";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -20,13 +20,13 @@ export class AutoCron {
|
||||
@Inject()
|
||||
pipelineService: PipelineService;
|
||||
|
||||
@Config('cron.onlyAdminUser')
|
||||
@Config("cron.onlyAdminUser")
|
||||
private onlyAdminUser: boolean;
|
||||
|
||||
@Config('cron.immediateTriggerOnce')
|
||||
@Config("cron.immediateTriggerOnce")
|
||||
private immediateTriggerOnce = false;
|
||||
|
||||
@Config('cron.immediateTriggerSiteMonitor')
|
||||
@Config("cron.immediateTriggerSiteMonitor")
|
||||
private immediateTriggerSiteMonitor = false;
|
||||
|
||||
@Inject()
|
||||
@@ -51,12 +51,10 @@ export class AutoCron {
|
||||
@Inject()
|
||||
projectService: ProjectService;
|
||||
|
||||
|
||||
|
||||
async init() {
|
||||
logger.info('加载定时trigger开始');
|
||||
logger.info("加载定时trigger开始");
|
||||
await this.pipelineService.onStartup(this.immediateTriggerOnce, this.onlyAdminUser);
|
||||
logger.info('加载定时trigger完成');
|
||||
logger.info("加载定时trigger完成");
|
||||
//
|
||||
// const meta = getClassMetadata(CLASS_KEY, this.echoPlugin);
|
||||
// console.log('meta', meta);
|
||||
@@ -64,183 +62,180 @@ export class AutoCron {
|
||||
// console.log('metas', metas);
|
||||
await this.registerSiteMonitorCron();
|
||||
|
||||
|
||||
await this.registerPlusExpireCheckCron();
|
||||
|
||||
await this.registerUserExpireCheckCron();
|
||||
|
||||
await this.registerDomainExpireCheckCron();
|
||||
|
||||
}
|
||||
|
||||
async registerSiteMonitorCron() {
|
||||
//先注册公共job
|
||||
logger.info(`注册公共站点证书检查定时任务`)
|
||||
const randomMinute = Math.floor(Math.random() * 60)
|
||||
logger.info(`注册公共站点证书检查定时任务`);
|
||||
const randomMinute = Math.floor(Math.random() * 60);
|
||||
this.cron.register({
|
||||
name: 'siteMonitor',
|
||||
name: "siteMonitor",
|
||||
cron: `0 ${randomMinute} 0 * * *`,
|
||||
job:async ()=>{
|
||||
logger.info(`开始公共站点证书检查任务`)
|
||||
await this.siteInfoService.triggerCommonJob()
|
||||
logger.info(`公共站点证书检查任务完成`)
|
||||
job: async () => {
|
||||
logger.info(`开始公共站点证书检查任务`);
|
||||
await this.siteInfoService.triggerCommonJob();
|
||||
logger.info(`公共站点证书检查任务完成`);
|
||||
},
|
||||
});
|
||||
logger.info(`注册公共站点证书检查定时任务完成`)
|
||||
|
||||
|
||||
logger.info(`注册公共站点证书检查定时任务完成`);
|
||||
|
||||
//注册用户独立的检查时间
|
||||
logger.info(`注册用户独立站点证书检查定时任务`)
|
||||
logger.info(`注册用户独立站点证书检查定时任务`);
|
||||
const monitorSettingList = await this.userSettingsService.list({
|
||||
query:{
|
||||
query: {
|
||||
key: UserSiteMonitorSetting.__key__,
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
for (const item of monitorSettingList) {
|
||||
const setting = item.setting ? JSON.parse(item.setting):{}
|
||||
if(!setting?.cron){
|
||||
continue
|
||||
const setting = item.setting ? JSON.parse(item.setting) : {};
|
||||
if (!setting?.cron) {
|
||||
continue;
|
||||
}
|
||||
await this.siteInfoService.registerSiteMonitorJob(item.userId,item.projectId)
|
||||
await this.siteInfoService.registerSiteMonitorJob(item.userId, item.projectId);
|
||||
}
|
||||
logger.info(`注册用户独立站点证书检查定时任务完成`)
|
||||
logger.info(`注册用户独立站点证书检查定时任务完成`);
|
||||
|
||||
if (this.immediateTriggerSiteMonitor) {
|
||||
logger.info(`立即触发一次公共站点证书检查任务`)
|
||||
await this.siteInfoService.triggerCommonJob()
|
||||
logger.info(`立即触发一次公共站点证书检查任务`);
|
||||
await this.siteInfoService.triggerCommonJob();
|
||||
}
|
||||
}
|
||||
|
||||
registerPlusExpireCheckCron(){
|
||||
registerPlusExpireCheckCron() {
|
||||
// 添加plus即将到期检查任务
|
||||
this.cron.register({
|
||||
name: 'plus-expire-check',
|
||||
name: "plus-expire-check",
|
||||
cron: `0 10 9 * * *`, // 一天只能检查一次,否则会重复发送通知
|
||||
job: async () => {
|
||||
const plusInfo = getPlusInfo()
|
||||
if (!plusInfo.originVipType || plusInfo.originVipType==="free" ) {
|
||||
return
|
||||
const plusInfo = getPlusInfo();
|
||||
if (!plusInfo.originVipType || plusInfo.originVipType === "free") {
|
||||
return;
|
||||
}
|
||||
let label ="专业版"
|
||||
if( plusInfo.originVipType === 'comm'){
|
||||
label = "商业版"
|
||||
let label = "专业版";
|
||||
if (plusInfo.originVipType === "comm") {
|
||||
label = "商业版";
|
||||
}
|
||||
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo)
|
||||
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
||||
|
||||
const appTitle = siteInfo.title || "certd"
|
||||
const expiresDate = dayjs(plusInfo.expireTime).format("YYYY-MM-DD")
|
||||
const appTitle = siteInfo.title || "certd";
|
||||
const expiresDate = dayjs(plusInfo.expireTime).format("YYYY-MM-DD");
|
||||
// plusInfo.expireTime= dayjs("2025-06-10").valueOf()
|
||||
let expiresDays =Math.floor((plusInfo.expireTime - new Date().getTime())/ 1000 / 60 / 60 / 24)
|
||||
let title = ""
|
||||
let content =""
|
||||
if(expiresDays === 20 ||expiresDays === 10 || expiresDays === 3 || expiresDays === 1 || expiresDays === 0){
|
||||
title = `vip(${label})即将到期`
|
||||
content = `您的${appTitle} vip (${label})剩余${expiresDays}天(${expiresDate})到期,请及时续期,以免影响业务`
|
||||
}else if (expiresDays === -1 || expiresDays === -3 || expiresDays === -7) {
|
||||
title = `vip(${label})已过期`
|
||||
content = `您的${appTitle} vip (${label})已过期${Math.abs(expiresDays)}天(${expiresDate}),请尽快续期,以免影响业务`
|
||||
const expiresDays = Math.floor((plusInfo.expireTime - new Date().getTime()) / 1000 / 60 / 60 / 24);
|
||||
let title = "";
|
||||
let content = "";
|
||||
if (expiresDays === 20 || expiresDays === 10 || expiresDays === 3 || expiresDays === 1 || expiresDays === 0) {
|
||||
title = `vip(${label})即将到期`;
|
||||
content = `您的${appTitle} vip (${label})剩余${expiresDays}天(${expiresDate})到期,请及时续期,以免影响业务`;
|
||||
} else if (expiresDays === -1 || expiresDays === -3 || expiresDays === -7) {
|
||||
title = `vip(${label})已过期`;
|
||||
content = `您的${appTitle} vip (${label})已过期${Math.abs(expiresDays)}天(${expiresDate}),请尽快续期,以免影响业务`;
|
||||
}
|
||||
if(title){
|
||||
logger.warn(title)
|
||||
logger.warn(content)
|
||||
if (title) {
|
||||
logger.warn(title);
|
||||
logger.warn(content);
|
||||
const url = await this.notificationService.getBindUrl("");
|
||||
const adminUsers = await this.userService.getAdmins()
|
||||
const adminUsers = await this.userService.getAdmins();
|
||||
for (const adminUser of adminUsers) {
|
||||
logger.info(`发送vip到期通知给管理员:${adminUser.username}`)
|
||||
await this.notificationService.send({
|
||||
useDefault: true,
|
||||
logger: logger,
|
||||
body:{
|
||||
title,
|
||||
content,
|
||||
errorMessage:title,
|
||||
url,
|
||||
notificationType: "vipExpireRemind",
|
||||
}
|
||||
},adminUser.id)
|
||||
logger.info(`发送vip到期通知给管理员:${adminUser.username}`);
|
||||
await this.notificationService.send(
|
||||
{
|
||||
useDefault: true,
|
||||
logger: logger,
|
||||
body: {
|
||||
title,
|
||||
content,
|
||||
errorMessage: title,
|
||||
url,
|
||||
notificationType: "vipExpireRemind",
|
||||
},
|
||||
},
|
||||
adminUser.id
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
registerUserExpireCheckCron() {
|
||||
// 添加plus即将到期检查任务
|
||||
this.cron.register({
|
||||
name: 'user-expire-check',
|
||||
name: "user-expire-check",
|
||||
cron: `0 20 9 * * *`, // 一天只能检查一次,否则会重复发送通知
|
||||
job: async () => {
|
||||
|
||||
const getExpiresDaysUsers = async (days: number) => {
|
||||
const targetDate = dayjs().add(days, 'day')
|
||||
const startTime = targetDate.startOf('day').valueOf()
|
||||
const endTime = targetDate.endOf('day').valueOf()
|
||||
const targetDate = dayjs().add(days, "day");
|
||||
const startTime = targetDate.startOf("day").valueOf();
|
||||
const endTime = targetDate.endOf("day").valueOf();
|
||||
return await this.userService.find({
|
||||
where: {
|
||||
validTime: Between(startTime, endTime),
|
||||
status: 1
|
||||
}
|
||||
})
|
||||
}
|
||||
status: 1,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const notifyExpiresDaysUsers = async (days: number) => {
|
||||
const list = await getExpiresDaysUsers(days)
|
||||
const list = await getExpiresDaysUsers(days);
|
||||
if (list.length === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
let title = `账号即将到期`
|
||||
let content = `您的账号剩余${days}天到期,请及时续期,以免影响业务`
|
||||
let title = `账号即将到期`;
|
||||
let content = `您的账号剩余${days}天到期,请及时续期,以免影响业务`;
|
||||
if (days <= 0) {
|
||||
title = `账号已过期`
|
||||
content = `您的账号已过期${Math.abs(days)}天,请尽快续期,以免影响业务`
|
||||
title = `账号已过期`;
|
||||
content = `您的账号已过期${Math.abs(days)}天,请尽快续期,以免影响业务`;
|
||||
}
|
||||
const url = await this.notificationService.getBindUrl("");
|
||||
for (const user of list) {
|
||||
logger.info(`发送到期通知给用户:${user.username}`)
|
||||
await this.notificationService.send({
|
||||
useDefault: true,
|
||||
logger: logger,
|
||||
body: {
|
||||
title,
|
||||
content,
|
||||
errorMessage: title,
|
||||
url,
|
||||
notificationType: "userExpireRemind",
|
||||
}
|
||||
}, user.id)
|
||||
logger.info(`发送到期通知给用户:${user.username}`);
|
||||
await this.notificationService.send(
|
||||
{
|
||||
useDefault: true,
|
||||
logger: logger,
|
||||
body: {
|
||||
title,
|
||||
content,
|
||||
errorMessage: title,
|
||||
url,
|
||||
notificationType: "userExpireRemind",
|
||||
},
|
||||
},
|
||||
user.id
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await notifyExpiresDaysUsers(7)
|
||||
await notifyExpiresDaysUsers(3)
|
||||
await notifyExpiresDaysUsers(1)
|
||||
await notifyExpiresDaysUsers(0)
|
||||
await notifyExpiresDaysUsers(-1)
|
||||
await notifyExpiresDaysUsers(-3)
|
||||
}
|
||||
})
|
||||
await notifyExpiresDaysUsers(7);
|
||||
await notifyExpiresDaysUsers(3);
|
||||
await notifyExpiresDaysUsers(1);
|
||||
await notifyExpiresDaysUsers(0);
|
||||
await notifyExpiresDaysUsers(-1);
|
||||
await notifyExpiresDaysUsers(-3);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
registerDomainExpireCheckCron(){
|
||||
if (!isPlus()){
|
||||
return
|
||||
registerDomainExpireCheckCron() {
|
||||
if (!isPlus()) {
|
||||
return;
|
||||
}
|
||||
// 添加域名即将到期同步任务
|
||||
const randomWeek = Math.floor(Math.random() * 7) + 1
|
||||
const randomHour = Math.floor(Math.random() * 24)
|
||||
const randomMinute = Math.floor(Math.random() * 60)
|
||||
logger.info(`注册域名注册过期时间同步任务,每周${randomWeek} ${randomHour}:${randomMinute}检查一次`)
|
||||
const randomWeek = Math.floor(Math.random() * 7) + 1;
|
||||
const randomHour = Math.floor(Math.random() * 24);
|
||||
const randomMinute = Math.floor(Math.random() * 60);
|
||||
logger.info(`注册域名注册过期时间同步任务,每周${randomWeek} ${randomHour}:${randomMinute}检查一次`);
|
||||
this.cron.register({
|
||||
name: 'domain-expire-check',
|
||||
name: "domain-expire-check",
|
||||
cron: `0 ${randomMinute} ${randomHour} ? * ${randomWeek}`, // 每周随机一天检查一次
|
||||
job: async () => {
|
||||
await this.domainService.startSyncExpirationTask({})
|
||||
}
|
||||
})
|
||||
await this.domainService.startSyncExpirationTask({});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { logger } from '@certd/basic';
|
||||
import { EncryptService, PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from '@certd/lib-server';
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import crypto from 'crypto';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { UserService } from '../sys/authority/service/user-service.js';
|
||||
import { logger } from "@certd/basic";
|
||||
import { EncryptService, PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from "@certd/lib-server";
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import crypto from "crypto";
|
||||
import { nanoid } from "nanoid";
|
||||
import { UserService } from "../sys/authority/service/user-service.js";
|
||||
import { SafeService } from "../sys/settings/safe-service.js";
|
||||
|
||||
@Provide()
|
||||
@@ -12,7 +12,7 @@ export class AutoInitSite {
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
|
||||
@Config('typeorm.dataSource.default.type')
|
||||
@Config("typeorm.dataSource.default.type")
|
||||
dbType: string;
|
||||
|
||||
@Inject()
|
||||
@@ -24,10 +24,9 @@ export class AutoInitSite {
|
||||
|
||||
@Inject()
|
||||
encryptService: EncryptService;
|
||||
|
||||
|
||||
async init() {
|
||||
logger.info('初始化站点开始');
|
||||
logger.info("初始化站点开始");
|
||||
await this.startOptimizeDb();
|
||||
//安装信息
|
||||
const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
|
||||
@@ -45,7 +44,7 @@ export class AutoInitSite {
|
||||
|
||||
if (!privateInfo.encryptSecret) {
|
||||
const secretKey = crypto.randomBytes(32);
|
||||
privateInfo.encryptSecret = secretKey.toString('base64');
|
||||
privateInfo.encryptSecret = secretKey.toString("base64");
|
||||
await this.sysSettingsService.saveSetting(privateInfo);
|
||||
}
|
||||
|
||||
@@ -61,32 +60,32 @@ export class AutoInitSite {
|
||||
try {
|
||||
await this.plusService.verify();
|
||||
} catch (e) {
|
||||
logger.error('授权许可验证失败', e);
|
||||
logger.error("授权许可验证失败", e);
|
||||
}
|
||||
|
||||
|
||||
//加载设置
|
||||
await this.sysSettingsService.reloadSettings();
|
||||
|
||||
//加载站点隐藏配置
|
||||
await this.safeService.reloadHiddenStatus(true)
|
||||
logger.info('初始化站点完成');
|
||||
await this.safeService.reloadHiddenStatus(true);
|
||||
logger.info("初始化站点完成");
|
||||
}
|
||||
|
||||
async startOptimizeDb() {
|
||||
//优化数据库
|
||||
//检查当前数据库类型为sqlite
|
||||
if (this.dbType === 'better-sqlite3') {
|
||||
const res = await this.userService.repository.query('PRAGMA auto_vacuum;');
|
||||
if (this.dbType === "better-sqlite3") {
|
||||
const res = await this.userService.repository.query("PRAGMA auto_vacuum;");
|
||||
if (!(res && res.length > 0 && res[0].auto_vacuum > 0)) {
|
||||
//未开启自动优化
|
||||
await this.userService.repository.query('PRAGMA auto_vacuum = INCREMENTAL;');
|
||||
logger.info('sqlite数据库自动优化已开启');
|
||||
await this.userService.repository.query("PRAGMA auto_vacuum = INCREMENTAL;");
|
||||
logger.info("sqlite数据库自动优化已开启");
|
||||
}
|
||||
|
||||
const optimizeDb = async () => {
|
||||
logger.info('sqlite数据库空间优化开始');
|
||||
await this.userService.repository.query('VACUUM');
|
||||
logger.info('sqlite数据库空间优化完成');
|
||||
logger.info("sqlite数据库空间优化开始");
|
||||
await this.userService.repository.query("VACUUM");
|
||||
logger.info("sqlite数据库空间优化完成");
|
||||
};
|
||||
await optimizeDb();
|
||||
setInterval(optimizeDb, 1000 * 60 * 60 * 24);
|
||||
|
||||
@@ -9,28 +9,26 @@ export class AutoLoadPlugins {
|
||||
@Inject()
|
||||
pluginService: PluginService;
|
||||
|
||||
|
||||
async init() {
|
||||
logger.info(`加载插件开始,加载模式:${process.env.certd_plugin_loadmode}`);
|
||||
if (process.env.certd_plugin_loadmode === "metadata") {
|
||||
await this.pluginService.registerFromLocal("./metadata")
|
||||
}else{
|
||||
await this.pluginService.registerFromLocal("./metadata");
|
||||
} else {
|
||||
// await import("../../plugins/index.js")
|
||||
const fs = await import("fs");
|
||||
const list = fs.readdirSync("./dist/plugins");
|
||||
console.log("list", list);
|
||||
for (const file of list) {
|
||||
if (!file.includes(".")){
|
||||
if (!file.includes(".")) {
|
||||
logger.info(`加载插件文件:${file}`);
|
||||
await import(`../../plugins/${file}/index.js`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// await import("../../plugins/index.js")
|
||||
await this.pluginService.registerFromDb()
|
||||
await this.pluginService.registerFromDb();
|
||||
|
||||
await registerPaymentProviders();
|
||||
logger.info(`加载插件完成,加载模式:${process.env.certd_plugin_loadmode}`);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { logger, utils } from '@certd/basic';
|
||||
import { UserSuiteService } from '@certd/commercial-core';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { logger, utils } from "@certd/basic";
|
||||
import { UserSuiteService } from "@certd/commercial-core";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -12,8 +12,8 @@ export class AutoMitterRegister {
|
||||
await this.registerOnNewUser();
|
||||
}
|
||||
async registerOnNewUser() {
|
||||
utils.mitter.on('register', async (req: { userId: number }) => {
|
||||
logger.info('register event', req.userId);
|
||||
utils.mitter.on("register", async (req: { userId: number }) => {
|
||||
logger.info("register event", req.userId);
|
||||
await this.userSuiteService.presentGiftSuite(req.userId);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { App, Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { getPlusInfo, isPlus } from '@certd/plus-core';
|
||||
import { isDev, logger } from '@certd/basic';
|
||||
import { App, Config, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { getPlusInfo, isPlus } from "@certd/plus-core";
|
||||
import { isDev, logger } from "@certd/basic";
|
||||
|
||||
import { SysInstallInfo, SysSettingsService } from '@certd/lib-server';
|
||||
import { getVersion } from '../../utils/version.js';
|
||||
import dayjs from 'dayjs';
|
||||
import { Application } from '@midwayjs/koa';
|
||||
import { httpsServer, HttpsServerOptions } from './https/server.js';
|
||||
import { UserService } from '../sys/authority/service/user-service.js';
|
||||
import { UserSettingsService } from '../mine/service/user-settings-service.js';
|
||||
import { startProxyServer } from './proxy/server.js';
|
||||
import { SysInstallInfo, SysSettingsService } from "@certd/lib-server";
|
||||
import { getVersion } from "../../utils/version.js";
|
||||
import dayjs from "dayjs";
|
||||
import { Application } from "@midwayjs/koa";
|
||||
import { httpsServer, HttpsServerOptions } from "./https/server.js";
|
||||
import { UserService } from "../sys/authority/service/user-service.js";
|
||||
import { UserSettingsService } from "../mine/service/user-settings-service.js";
|
||||
import { startProxyServer } from "./proxy/server.js";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -20,18 +20,18 @@ export class AutoPrint {
|
||||
@App()
|
||||
app: Application;
|
||||
|
||||
@Config('https')
|
||||
@Config("https")
|
||||
httpsConfig: HttpsServerOptions;
|
||||
@Config('koa')
|
||||
@Config("koa")
|
||||
koaConfig: any;
|
||||
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
|
||||
|
||||
@Inject()
|
||||
userSettingsService: UserSettingsService;
|
||||
|
||||
@Config('system.resetAdminPasswd')
|
||||
@Config("system.resetAdminPasswd")
|
||||
private resetAdminPasswd: boolean;
|
||||
|
||||
async init() {
|
||||
@@ -43,31 +43,31 @@ export class AutoPrint {
|
||||
this.startHeapLog();
|
||||
}
|
||||
const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
|
||||
logger.info('=========================================');
|
||||
logger.info('当前站点ID:', installInfo.siteId);
|
||||
logger.info("=========================================");
|
||||
logger.info("当前站点ID:", installInfo.siteId);
|
||||
const version = await getVersion();
|
||||
logger.info(`当前版本:${version}`);
|
||||
const plusInfo = getPlusInfo();
|
||||
if (isPlus()) {
|
||||
logger.info(`授权信息:${plusInfo.vipType},${plusInfo.expireTime === -1 ? '永久' : dayjs(plusInfo.expireTime).format('YYYY-MM-DD')}`);
|
||||
logger.info(`授权信息:${plusInfo.vipType},${plusInfo.expireTime === -1 ? "永久" : dayjs(plusInfo.expireTime).format("YYYY-MM-DD")}`);
|
||||
}
|
||||
logger.info('Certd已启动');
|
||||
logger.info('=========================================');
|
||||
logger.info("Certd已启动");
|
||||
logger.info("=========================================");
|
||||
await this.resetPasswd();
|
||||
}
|
||||
|
||||
async resetPasswd(){
|
||||
async resetPasswd() {
|
||||
if (this.resetAdminPasswd === true) {
|
||||
logger.info('开始重置1号管理员用户的密码');
|
||||
const newPasswd = '123456';
|
||||
logger.info("开始重置1号管理员用户的密码");
|
||||
const newPasswd = "123456";
|
||||
await this.userService.resetPassword(1, newPasswd);
|
||||
await this.userService.updateStatus(1, 1);
|
||||
await this.userSettingsService.deleteWhere({
|
||||
userId: 1,
|
||||
key:"user.two.factor"
|
||||
})
|
||||
const publicSettings = await this.sysSettingsService.getPublicSettings()
|
||||
publicSettings.captchaEnabled = false
|
||||
key: "user.two.factor",
|
||||
});
|
||||
const publicSettings = await this.sysSettingsService.getPublicSettings();
|
||||
publicSettings.captchaEnabled = false;
|
||||
await this.sysSettingsService.savePublicSettings(publicSettings);
|
||||
|
||||
const user = await this.userService.info(1);
|
||||
@@ -77,7 +77,7 @@ export class AutoPrint {
|
||||
|
||||
startHeapLog() {
|
||||
function format(bytes: any) {
|
||||
return (bytes / 1024 / 1024).toFixed(2) + ' MB';
|
||||
return (bytes / 1024 / 1024).toFixed(2) + " MB";
|
||||
}
|
||||
function printHeapLog() {
|
||||
const mu = process.memoryUsage();
|
||||
@@ -89,7 +89,7 @@ export class AutoPrint {
|
||||
|
||||
startHttpsServer() {
|
||||
if (!this.httpsConfig.enabled) {
|
||||
logger.info('Https server is not enabled');
|
||||
logger.info("Https server is not enabled");
|
||||
return;
|
||||
}
|
||||
httpsServer.start({
|
||||
@@ -100,6 +100,6 @@ export class AutoPrint {
|
||||
}
|
||||
|
||||
startProxyServer() {
|
||||
startProxyServer({port: 7003});
|
||||
startProxyServer({ port: 7003 });
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -38,10 +38,10 @@ export class CertInfoWildcardDomainCountFix {
|
||||
if (fixedCount > 0) {
|
||||
logger.info(`已修复证书泛域名数量历史数据,数量=${fixedCount}`);
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
logger.error("修复证书泛域名数量历史数据失败", e);
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,6 @@ export class CommonEabToAcmeAccountFix {
|
||||
@Inject()
|
||||
storageService: StorageService;
|
||||
|
||||
|
||||
async init() {
|
||||
try {
|
||||
const certApplyConfig = await this.pluginConfigService.getPluginConfig({
|
||||
|
||||
@@ -56,12 +56,12 @@ export class GoogleCommonEabAccountKeyFix {
|
||||
});
|
||||
const googleCommonEabAccessId = certApplyConfig?.sysSetting?.input?.googleCommonEabAccessId;
|
||||
if (!googleCommonEabAccessId) {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
const eabAccess = await this.accessService.getAccessById(googleCommonEabAccessId, false);
|
||||
if (eabAccess.accountKey) {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
if (!eabAccess.kid) {
|
||||
logger.info("公共Google EAB授权缺少KID,跳过历史ACME账号私钥修复");
|
||||
@@ -82,7 +82,7 @@ export class GoogleCommonEabAccountKeyFix {
|
||||
} catch (e: any) {
|
||||
logger.error("修复公共Google EAB授权ACME账号私钥失败", e);
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
async getLegacyGoogleAccountConfig(email?: string) {
|
||||
|
||||
@@ -74,7 +74,6 @@ export class LegacyAcmeAccountAccessFix {
|
||||
@Inject()
|
||||
accessService: AccessService;
|
||||
|
||||
|
||||
async init() {
|
||||
try {
|
||||
const repository = this.storageService.getRepository();
|
||||
|
||||
@@ -41,7 +41,7 @@ export class OauthSubtypeBoundTypeFix {
|
||||
await this.convertLegacyAddonLoginTypeToArray(addonEntity, legacyLoginType, manager);
|
||||
}
|
||||
});
|
||||
return true
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
logger.error("修复OAuth subtype绑定历史数据失败", e);
|
||||
}
|
||||
|
||||
+2
-3
@@ -14,7 +14,6 @@ export function fixSuiteContentWildcardDomainCount(contentValue?: string) {
|
||||
return JSON.stringify(content);
|
||||
}
|
||||
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class SuiteContentWildcardDomainCountFix {
|
||||
@@ -33,11 +32,11 @@ export class SuiteContentWildcardDomainCountFix {
|
||||
if (fixedCount > 0) {
|
||||
logger.info(`已修复套餐最大泛域名数量历史数据,数量=${fixedCount}`);
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
logger.error("修复套餐最大泛域名数量历史数据失败", e);
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
private async fixSuiteContentWildcardDomainCountByTable(entityManager: any, tableName: string) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { logger } from '@certd/basic';
|
||||
import fs from 'fs';
|
||||
import { logger } from "@certd/basic";
|
||||
import fs from "fs";
|
||||
// @ts-ignore
|
||||
import forge from 'node-forge';
|
||||
import path from 'path';
|
||||
import forge from "node-forge";
|
||||
import path from "path";
|
||||
|
||||
export function createSelfCertificate(opts: { crtPath: string; keyPath: string }) {
|
||||
// 生成密钥对
|
||||
@@ -11,14 +11,14 @@ export function createSelfCertificate(opts: { crtPath: string; keyPath: string }
|
||||
// 创建自签名证书
|
||||
const cert = forge.pki.createCertificate();
|
||||
cert.publicKey = keypair.publicKey;
|
||||
cert.serialNumber = '01';
|
||||
cert.serialNumber = "01";
|
||||
cert.validFrom = new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString(); // 1天前
|
||||
cert.validTo = new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 10).toISOString(); // 10年后
|
||||
// 创建主题
|
||||
const attrs = [
|
||||
{
|
||||
name: 'commonName',
|
||||
value: 'self-certificate.certd', // 或者你的域名
|
||||
name: "commonName",
|
||||
value: "self-certificate.certd", // 或者你的域名
|
||||
},
|
||||
];
|
||||
cert.setSubject(attrs);
|
||||
@@ -30,7 +30,7 @@ export function createSelfCertificate(opts: { crtPath: string; keyPath: string }
|
||||
const pemKey = forge.pki.privateKeyToPem(keypair.privateKey);
|
||||
|
||||
// 写入文件
|
||||
logger.info('生成自签名证书成功');
|
||||
logger.info("生成自签名证书成功");
|
||||
logger.info(`自签证书保存路径: ${opts.crtPath}`);
|
||||
logger.info(`自签私钥保存路径: ${opts.keyPath}`);
|
||||
const crtDir = path.dirname(opts.crtPath);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import https from 'node:https';
|
||||
import fs from 'fs';
|
||||
import { Application } from '@midwayjs/koa';
|
||||
import { createSelfCertificate } from './self-certificate.js';
|
||||
import {logger, safePromise} from '@certd/basic';
|
||||
import https from "node:https";
|
||||
import fs from "fs";
|
||||
import { Application } from "@midwayjs/koa";
|
||||
import { createSelfCertificate } from "./self-certificate.js";
|
||||
import { logger, safePromise } from "@certd/basic";
|
||||
|
||||
export type HttpsServerOptions = {
|
||||
enabled: boolean;
|
||||
@@ -33,24 +33,24 @@ export class HttpsServer {
|
||||
|
||||
start(opts: HttpsServerOptions) {
|
||||
if (!opts) {
|
||||
logger.error('https配置不能为空');
|
||||
logger.error("https配置不能为空");
|
||||
return;
|
||||
}
|
||||
this.opts = opts;
|
||||
logger.info('=========================================');
|
||||
logger.info("=========================================");
|
||||
if (!opts.key || !opts.cert) {
|
||||
logger.error('证书路径未配置,无法启动https服务,请先配置:koa.https.key和koa.https.cert');
|
||||
logger.error("证书路径未配置,无法启动https服务,请先配置:koa.https.key和koa.https.cert");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(opts.key) || !fs.existsSync(opts.cert)) {
|
||||
logger.info('证书文件不存在,将生成自签名证书');
|
||||
logger.info("证书文件不存在,将生成自签名证书");
|
||||
createSelfCertificate({
|
||||
crtPath: opts.cert,
|
||||
keyPath: opts.key,
|
||||
});
|
||||
}
|
||||
logger.info('准备启动https服务');
|
||||
logger.info("准备启动https服务");
|
||||
const httpServer = https.createServer(
|
||||
{
|
||||
cert: fs.readFileSync(opts.cert),
|
||||
@@ -59,7 +59,7 @@ export class HttpsServer {
|
||||
opts.app.callback()
|
||||
);
|
||||
this.server = httpServer;
|
||||
let hostname = opts.hostname || '::';
|
||||
let hostname = opts.hostname || "::";
|
||||
// A function that runs in the context of the http server
|
||||
// and reports what type of server listens on which port
|
||||
function listeningReporter() {
|
||||
@@ -71,19 +71,18 @@ export class HttpsServer {
|
||||
httpServer.listen(opts.port, hostname, listeningReporter);
|
||||
return httpServer;
|
||||
} catch (e) {
|
||||
if ( e.message?.includes("address family not supported")) {
|
||||
hostname = "0.0.0.0"
|
||||
if (e.message?.includes("address family not supported")) {
|
||||
hostname = "0.0.0.0";
|
||||
logger.error(`${e.message},尝试监听${hostname}`, e);
|
||||
try{
|
||||
try {
|
||||
httpServer.listen(opts.port, hostname, listeningReporter);
|
||||
return httpServer;
|
||||
}catch (e) {
|
||||
logger.error('启动https服务失败', e);
|
||||
} catch (e) {
|
||||
logger.error("启动https服务失败", e);
|
||||
}
|
||||
}else{
|
||||
logger.error('启动https服务失败', e);
|
||||
} else {
|
||||
logger.error("启动https服务失败", e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// proxy-server.js
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import url from 'url';
|
||||
import net from 'net';
|
||||
import { logger } from '@certd/basic';
|
||||
import http from "http";
|
||||
import https from "https";
|
||||
import url from "url";
|
||||
import net from "net";
|
||||
import { logger } from "@certd/basic";
|
||||
|
||||
|
||||
export function startProxyServer(opts:{port:number}) {
|
||||
const {port} = opts;
|
||||
export function startProxyServer(opts: { port: number }) {
|
||||
const { port } = opts;
|
||||
|
||||
// 创建 HTTP 代理服务器
|
||||
const proxyServer = http.createServer((clientReq, clientRes) => {
|
||||
@@ -17,31 +16,31 @@ export function startProxyServer(opts:{port:number}) {
|
||||
const parsedUrl = url.parse(clientReq.url);
|
||||
const options = {
|
||||
hostname: parsedUrl.hostname,
|
||||
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
||||
port: parsedUrl.port || (parsedUrl.protocol === "https:" ? 443 : 80),
|
||||
path: parsedUrl.path,
|
||||
method: clientReq.method,
|
||||
headers: clientReq.headers
|
||||
headers: clientReq.headers,
|
||||
};
|
||||
|
||||
// 根据协议选择不同的模块
|
||||
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
||||
const protocol = parsedUrl.protocol === "https:" ? https : http;
|
||||
|
||||
// 移除可能会引起问题的 headers
|
||||
delete options.headers['proxy-connection'];
|
||||
delete options.headers['connection'];
|
||||
delete options.headers['keep-alive'];
|
||||
delete options.headers["proxy-connection"];
|
||||
delete options.headers["connection"];
|
||||
delete options.headers["keep-alive"];
|
||||
|
||||
// 创建到目标服务器的请求
|
||||
const proxyReq = protocol.request(options, (proxyRes) => {
|
||||
const proxyReq = protocol.request(options, proxyRes => {
|
||||
// 将目标服务器的响应返回给客户端
|
||||
clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||
proxyRes.pipe(clientRes);
|
||||
});
|
||||
|
||||
proxyReq.on('error', (err) => {
|
||||
logger.error('[proxy] 代理请求错误:', err);
|
||||
proxyReq.on("error", err => {
|
||||
logger.error("[proxy] 代理请求错误:", err);
|
||||
clientRes.writeHead(500);
|
||||
clientRes.end('代理服务器错误');
|
||||
clientRes.end("代理服务器错误");
|
||||
});
|
||||
|
||||
// 将客户端请求体转发到目标服务器
|
||||
@@ -49,18 +48,16 @@ export function startProxyServer(opts:{port:number}) {
|
||||
});
|
||||
|
||||
// 处理 CONNECT 方法(HTTPS 代理)
|
||||
proxyServer.on('connect', (req, clientSocket, head) => {
|
||||
proxyServer.on("connect", (req, clientSocket, head) => {
|
||||
logger.log(`[proxy] HTTPS 连接请求: ${req.url}`);
|
||||
|
||||
const [hostname, port] = req.url.split(':');
|
||||
const [hostname, port] = req.url.split(":");
|
||||
const portNum = parseInt(port) || 443;
|
||||
|
||||
// 连接到目标服务器
|
||||
const serverSocket = net.connect(portNum, hostname, () => {
|
||||
// 告诉客户端连接已建立
|
||||
clientSocket.write('HTTP/1.1 200 Connection Established\r\n' +
|
||||
'Proxy-agent: Node.js-Proxy\r\n' +
|
||||
'\r\n');
|
||||
clientSocket.write("HTTP/1.1 200 Connection Established\r\n" + "Proxy-agent: Node.js-Proxy\r\n" + "\r\n");
|
||||
|
||||
// 建立双向数据流
|
||||
serverSocket.write(head);
|
||||
@@ -68,25 +65,25 @@ export function startProxyServer(opts:{port:number}) {
|
||||
clientSocket.pipe(serverSocket);
|
||||
});
|
||||
|
||||
serverSocket.on('error', (err) => {
|
||||
logger.error('[proxy] HTTPS 代理错误:', err);
|
||||
serverSocket.on("error", err => {
|
||||
logger.error("[proxy] HTTPS 代理错误:", err);
|
||||
clientSocket.end();
|
||||
});
|
||||
|
||||
clientSocket.on('error', (err) => {
|
||||
logger.error('[proxy] 客户端 socket 错误:', err);
|
||||
clientSocket.on("error", err => {
|
||||
logger.error("[proxy] 客户端 socket 错误:", err);
|
||||
serverSocket.end();
|
||||
});
|
||||
});
|
||||
|
||||
// 监听端口
|
||||
proxyServer.listen(port, () => {
|
||||
logger.info(`[proxy] 正向代理服务器运行在 http://0.0.0.0:${port}`);
|
||||
logger.info(`[proxy] 正向代理服务器运行在 http://0.0.0.0:${port}`);
|
||||
});
|
||||
|
||||
proxyServer.close(() => {
|
||||
logger.info('[proxy] 正向代理服务器已关闭');
|
||||
logger.info("[proxy] 正向代理服务器已关闭");
|
||||
});
|
||||
|
||||
return proxyServer
|
||||
}
|
||||
return proxyServer;
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
export const GROUP_TYPE_SITE = 'site';
|
||||
export const GROUP_TYPE_SITE = "site";
|
||||
|
||||
@Entity('cd_group')
|
||||
@Entity("cd_group")
|
||||
export class GroupEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'name', comment: '分组名称' })
|
||||
@Column({ name: "name", comment: "分组名称" })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'icon', comment: '图标' })
|
||||
@Column({ name: "icon", comment: "图标" })
|
||||
icon: string;
|
||||
|
||||
@Column({ name: 'favorite', comment: '收藏' })
|
||||
@Column({ name: "favorite", comment: "收藏" })
|
||||
favorite: boolean;
|
||||
|
||||
@Column({ name: 'type', comment: '类型', length: 512 })
|
||||
@Column({ name: "type", comment: "类型", length: 512 })
|
||||
type: 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;
|
||||
}
|
||||
|
||||
@@ -12,15 +12,14 @@ export class CaptchaService {
|
||||
@Inject()
|
||||
addonGetterService: AddonGetterService;
|
||||
|
||||
|
||||
async getCaptcha(captchaAddonId?: number) {
|
||||
if (!captchaAddonId) {
|
||||
const settings = await this.sysSettingsService.getPublicSettings();
|
||||
captchaAddonId = settings.captchaAddonId ?? 0;
|
||||
}
|
||||
const addon: ICaptchaAddon = await this.addonGetterService.getAddonById(captchaAddonId, true, 0,null, {
|
||||
const addon: ICaptchaAddon = await this.addonGetterService.getAddonById(captchaAddonId, true, 0, null, {
|
||||
type: "captcha",
|
||||
name: "image"
|
||||
name: "image",
|
||||
});
|
||||
if (!addon) {
|
||||
throw new Error("验证码插件还未配置");
|
||||
@@ -28,8 +27,7 @@ export class CaptchaService {
|
||||
return await addon.getCaptcha();
|
||||
}
|
||||
|
||||
|
||||
async doValidate(opts: { form: any, must?: boolean, captchaAddonId?: number,req:CaptchaRequest }) {
|
||||
async doValidate(opts: { form: any; must?: boolean; captchaAddonId?: number; req: CaptchaRequest }) {
|
||||
if (!opts.captchaAddonId) {
|
||||
const settings = await this.sysSettingsService.getPublicSettings();
|
||||
opts.captchaAddonId = settings.captchaAddonId ?? 0;
|
||||
@@ -46,13 +44,11 @@ export class CaptchaService {
|
||||
if (!opts.form) {
|
||||
throw new Error("请输入验证码");
|
||||
}
|
||||
const res = await addon.onValidate(opts.form,opts.req);
|
||||
const res = await addon.onValidate(opts.form, opts.req);
|
||||
if (!res) {
|
||||
throw new Error("验证码错误");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { cache, isDev, randomNumber, simpleNanoId } from '@certd/basic';
|
||||
import { AccessService, AccessSysGetter, CodeErrorException, SysSettingsService } from '@certd/lib-server';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { ISmsService } from '../sms/api.js';
|
||||
import { SmsServiceFactory } from '../sms/factory.js';
|
||||
import { cache, isDev, randomNumber, simpleNanoId } from "@certd/basic";
|
||||
import { AccessService, AccessSysGetter, CodeErrorException, SysSettingsService } from "@certd/lib-server";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { ISmsService } from "../sms/api.js";
|
||||
import { SmsServiceFactory } from "../sms/factory.js";
|
||||
import { CaptchaService } from "./captcha-service.js";
|
||||
import { EmailService } from './email-service.js';
|
||||
import { CaptchaRequest } from '../../../plugins/plugin-captcha/api.js';
|
||||
import { EmailService } from "./email-service.js";
|
||||
import { CaptchaRequest } from "../../../plugins/plugin-captcha/api.js";
|
||||
|
||||
// {data: '<svg.../svg>', text: 'abcd'}
|
||||
/**
|
||||
@@ -24,32 +24,30 @@ export class CodeService {
|
||||
@Inject()
|
||||
captchaService: CaptchaService;
|
||||
|
||||
|
||||
|
||||
async checkCaptcha(body:any,req:CaptchaRequest) {
|
||||
return await this.captchaService.doValidate({form:body,req});
|
||||
async checkCaptcha(body: any, req: CaptchaRequest) {
|
||||
return await this.captchaService.doValidate({ form: body, req });
|
||||
}
|
||||
/**
|
||||
*/
|
||||
async sendSmsCode(
|
||||
phoneCode = '86',
|
||||
phoneCode = "86",
|
||||
mobile: string,
|
||||
opts?: {
|
||||
duration?: number,
|
||||
verificationType?: string,
|
||||
verificationCodeLength?: number,
|
||||
},
|
||||
duration?: number;
|
||||
verificationType?: string;
|
||||
verificationCodeLength?: number;
|
||||
}
|
||||
) {
|
||||
if (!mobile) {
|
||||
throw new Error('手机号不能为空');
|
||||
throw new Error("手机号不能为空");
|
||||
}
|
||||
|
||||
const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4));
|
||||
const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4));
|
||||
const duration = Math.floor(Math.max(Math.min(opts?.duration || 5, 15), 1));
|
||||
|
||||
const sysSettings = await this.sysSettingsService.getPrivateSettings();
|
||||
if (!sysSettings.sms?.config?.accessId) {
|
||||
throw new Error('当前站点还未配置短信');
|
||||
throw new Error("当前站点还未配置短信");
|
||||
}
|
||||
const smsType = sysSettings.sms.type;
|
||||
const smsConfig = sysSettings.sms.config;
|
||||
@@ -66,7 +64,7 @@ export class CodeService {
|
||||
phoneCode,
|
||||
});
|
||||
|
||||
const key = this.buildSmsCodeKey(phoneCode, mobile, opts?.verificationType);
|
||||
const key = this.buildSmsCodeKey(phoneCode, mobile, opts?.verificationType);
|
||||
cache.set(key, smsCode, {
|
||||
ttl: duration * 60 * 1000, //5分钟
|
||||
});
|
||||
@@ -81,38 +79,38 @@ export class CodeService {
|
||||
async sendEmailCode(
|
||||
email: string,
|
||||
opts?: {
|
||||
duration?: number,
|
||||
verificationType?: string,
|
||||
verificationCodeLength?: number,
|
||||
},
|
||||
duration?: number;
|
||||
verificationType?: string;
|
||||
verificationCodeLength?: number;
|
||||
}
|
||||
) {
|
||||
if (!email) {
|
||||
throw new Error('Email不能为空');
|
||||
throw new Error("Email不能为空");
|
||||
}
|
||||
|
||||
|
||||
const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4));
|
||||
const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4));
|
||||
const duration = Math.floor(Math.max(Math.min(opts?.duration || 5, 15), 1));
|
||||
|
||||
const code = randomNumber(verificationCodeLength);
|
||||
|
||||
|
||||
const templateData = {
|
||||
code, duration,
|
||||
code,
|
||||
duration,
|
||||
title: "验证码",
|
||||
content:`您的验证码是${code},请勿泄露`,
|
||||
notificationType: "registerCode"
|
||||
}
|
||||
if (opts?.verificationType === 'forgotPassword') {
|
||||
templateData.title = '找回密码';
|
||||
templateData.notificationType = "forgotPassword"
|
||||
content: `您的验证码是${code},请勿泄露`,
|
||||
notificationType: "registerCode",
|
||||
};
|
||||
if (opts?.verificationType === "forgotPassword") {
|
||||
templateData.title = "找回密码";
|
||||
templateData.notificationType = "forgotPassword";
|
||||
}
|
||||
await this.emailService.sendByTemplate({
|
||||
type: templateData.notificationType,
|
||||
data: templateData,
|
||||
receivers: [email],
|
||||
type: templateData.notificationType,
|
||||
data: templateData,
|
||||
receivers: [email],
|
||||
});
|
||||
|
||||
const key = this.buildEmailCodeKey(email,opts?.verificationType);
|
||||
const key = this.buildEmailCodeKey(email, opts?.verificationType);
|
||||
cache.set(key, code, {
|
||||
ttl: duration * 60 * 1000, //5分钟
|
||||
});
|
||||
@@ -122,44 +120,43 @@ export class CodeService {
|
||||
/**
|
||||
* checkSms
|
||||
*/
|
||||
async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; verificationType?: string; throwError: boolean; maxErrorCount?: number }) {
|
||||
async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; verificationType?: string; throwError: boolean; maxErrorCount?: number }) {
|
||||
const key = this.buildSmsCodeKey(opts.phoneCode, opts.mobile, opts.verificationType);
|
||||
return this.checkValidateCode("sms",key, opts.smsCode, opts.throwError, opts.maxErrorCount);
|
||||
|
||||
return this.checkValidateCode("sms", key, opts.smsCode, opts.throwError, opts.maxErrorCount);
|
||||
}
|
||||
|
||||
buildSmsCodeKey(phoneCode: string, mobile: string, verificationType?: string) {
|
||||
return ['sms', verificationType, phoneCode, mobile].filter(item => !!item).join(':');
|
||||
return ["sms", verificationType, phoneCode, mobile].filter(item => !!item).join(":");
|
||||
}
|
||||
|
||||
buildEmailCodeKey(email: string, verificationType?: string) {
|
||||
return ['email', verificationType, email].filter(item => !!item).join(':');
|
||||
return ["email", verificationType, email].filter(item => !!item).join(":");
|
||||
}
|
||||
checkValidateCode(type:string,key: string, userCode: string, throwError = true, maxErrorCount = 3) {
|
||||
checkValidateCode(type: string, key: string, userCode: string, throwError = true, maxErrorCount = 3) {
|
||||
// 记录异常次数key
|
||||
if (isDev() && userCode==="1234567") {
|
||||
if (isDev() && userCode === "1234567") {
|
||||
return true;
|
||||
}
|
||||
const err_num_key = key + ':err_num';
|
||||
const err_num_key = key + ":err_num";
|
||||
//验证邮件验证码
|
||||
const code = cache.get(key);
|
||||
if (code == null || code !== userCode) {
|
||||
let maxRetryCount = false;
|
||||
if (!!code && maxErrorCount > 0) {
|
||||
const err_num = cache.get(err_num_key) || 0
|
||||
if(err_num >= maxErrorCount - 1) {
|
||||
const err_num = cache.get(err_num_key) || 0;
|
||||
if (err_num >= maxErrorCount - 1) {
|
||||
maxRetryCount = true;
|
||||
cache.delete(key);
|
||||
cache.delete(err_num_key);
|
||||
} else {
|
||||
cache.set(err_num_key, err_num + 1, {
|
||||
ttl: 30 * 60 * 1000
|
||||
ttl: 30 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (throwError) {
|
||||
const label = type ==='sms' ? '手机' : '邮箱';
|
||||
throw new CodeErrorException(!maxRetryCount ? `${label}验证码错误`: `${label}验证码错误请获取新的验证码`);
|
||||
const label = type === "sms" ? "手机" : "邮箱";
|
||||
throw new CodeErrorException(!maxRetryCount ? `${label}验证码错误` : `${label}验证码错误请获取新的验证码`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -169,8 +166,8 @@ export class CodeService {
|
||||
}
|
||||
|
||||
checkEmailCode(opts: { validateCode: string; email: string; verificationType?: string; throwError: boolean; maxErrorCount?: number }) {
|
||||
const key = this.buildEmailCodeKey(opts.email, opts.verificationType);
|
||||
return this.checkValidateCode('email',key, opts.validateCode, opts.throwError, opts.maxErrorCount);
|
||||
const key = this.buildEmailCodeKey(opts.email, opts.verificationType);
|
||||
return this.checkValidateCode("email", key, opts.validateCode, opts.throwError, opts.maxErrorCount);
|
||||
}
|
||||
|
||||
compile(templateString: string) {
|
||||
@@ -183,11 +180,10 @@ export class CodeService {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
buildValidationValueKey(code:string) {
|
||||
buildValidationValueKey(code: string) {
|
||||
return `validationValue:${code}`;
|
||||
}
|
||||
setValidationValue(value:any) {
|
||||
setValidationValue(value: any) {
|
||||
const randomCode = simpleNanoId(12);
|
||||
const key = this.buildValidationValueKey(randomCode);
|
||||
cache.set(key, value, {
|
||||
@@ -195,7 +191,7 @@ export class CodeService {
|
||||
});
|
||||
return randomCode;
|
||||
}
|
||||
getValidationValue(code:string) {
|
||||
getValidationValue(code: string) {
|
||||
return cache.get(this.buildValidationValueKey(code));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import type { EmailSend, EmailSendByTemplateReq } from '@certd/pipeline';
|
||||
import { IEmailService } from '@certd/pipeline';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import type { EmailSend, EmailSendByTemplateReq } from "@certd/pipeline";
|
||||
import { IEmailService } from "@certd/pipeline";
|
||||
|
||||
import { logger } from '@certd/basic';
|
||||
import { isComm, isPlus } from '@certd/plus-core';
|
||||
import { logger } from "@certd/basic";
|
||||
import { isComm, isPlus } from "@certd/plus-core";
|
||||
|
||||
import nodemailer from 'nodemailer';
|
||||
import { SendMailOptions } from 'nodemailer';
|
||||
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
|
||||
import { AddonService, PlusService, SysEmailConf, SysInstallInfo, SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
||||
import { getEmailSettings } from '../../sys/settings/fix.js';
|
||||
import nodemailer from "nodemailer";
|
||||
import { SendMailOptions } from "nodemailer";
|
||||
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
|
||||
import { AddonService, PlusService, SysEmailConf, SysInstallInfo, SysSettingsService, SysSiteInfo } from "@certd/lib-server";
|
||||
import { getEmailSettings } from "../../sys/settings/fix.js";
|
||||
import { UserEmailSetting } from "../../mine/service/models.js";
|
||||
import { AddonGetterService } from '../../pipeline/service/addon-getter-service.js';
|
||||
import { EmailContent, ITemplateProvider } from '../../../plugins/plugin-template/api.js';
|
||||
import { AddonGetterService } from "../../pipeline/service/addon-getter-service.js";
|
||||
import { EmailContent, ITemplateProvider } from "../../../plugins/plugin-template/api.js";
|
||||
|
||||
export type EmailConfig = {
|
||||
host: string;
|
||||
@@ -43,12 +43,11 @@ export class EmailService implements IEmailService {
|
||||
@Inject()
|
||||
addonGetterService: AddonGetterService;
|
||||
@Inject()
|
||||
addonService: AddonService
|
||||
|
||||
addonService: AddonService;
|
||||
|
||||
async sendByPlus(email: EmailSend) {
|
||||
if (!isPlus()) {
|
||||
throw new Error('plus not enabled');
|
||||
throw new Error("plus not enabled");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,13 +62,13 @@ export class EmailService implements IEmailService {
|
||||
/**
|
||||
*/
|
||||
async send(email: EmailSend) {
|
||||
logger.info('sendEmail', email);
|
||||
logger.info("sendEmail", email);
|
||||
|
||||
if (!email.receivers || email.receivers.length === 0) {
|
||||
throw new Error('收件人不能为空');
|
||||
throw new Error("收件人不能为空");
|
||||
}
|
||||
|
||||
let sysTitle = 'Certd';
|
||||
let sysTitle = "Certd";
|
||||
if (isComm()) {
|
||||
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
||||
if (siteInfo) {
|
||||
@@ -79,11 +78,10 @@ export class EmailService implements IEmailService {
|
||||
let subject = email.subject;
|
||||
|
||||
if (!subject) {
|
||||
logger.error(new Error('邮件标题不能为空'));
|
||||
logger.error(new Error("邮件标题不能为空"));
|
||||
subject = `邮件标题为空,请联系管理员排查`;
|
||||
}
|
||||
|
||||
|
||||
if (!subject.includes(`【${sysTitle}】`)) {
|
||||
subject = `【${sysTitle}】${subject}`;
|
||||
}
|
||||
@@ -96,27 +94,25 @@ export class EmailService implements IEmailService {
|
||||
//自动使用plus发邮件
|
||||
return await this.sendByPlus(email);
|
||||
}
|
||||
throw new Error('邮件服务器还未设置');
|
||||
throw new Error("邮件服务器还未设置");
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (emailConf.usePlus && isPlus()) {
|
||||
return await this.sendByPlus(email);
|
||||
}
|
||||
await this.sendByCustom(emailConf, email, sysTitle);
|
||||
logger.info('sendEmail complete: ', email);
|
||||
logger.info("sendEmail complete: ", email);
|
||||
}
|
||||
|
||||
private async sendByCustom(emailConfig: EmailConfig, email: EmailSend, sysTitle: string) {
|
||||
const transporter = nodemailer.createTransport(emailConfig);
|
||||
let from = `${sysTitle} <${emailConfig.sender}>`;
|
||||
if (emailConfig.sender.includes('<')) {
|
||||
if (emailConfig.sender.includes("<")) {
|
||||
from = emailConfig.sender;
|
||||
}
|
||||
const mailOptions = {
|
||||
from: from,
|
||||
to: email.receivers.join(', '), // list of receivers
|
||||
to: email.receivers.join(", "), // list of receivers
|
||||
subject: email.subject,
|
||||
text: email.content,
|
||||
html: email.html,
|
||||
@@ -129,8 +125,8 @@ export class EmailService implements IEmailService {
|
||||
await this.sendByTemplate({
|
||||
type: "common",
|
||||
data: {
|
||||
title: '测试邮件,from certd',
|
||||
content: '测试邮件,from certd',
|
||||
title: "测试邮件,from certd",
|
||||
content: "测试邮件,from certd",
|
||||
url: await this.getTestEmailUrl(),
|
||||
},
|
||||
receivers: [receiver],
|
||||
@@ -147,41 +143,41 @@ export class EmailService implements IEmailService {
|
||||
}
|
||||
|
||||
async list(userId: any) {
|
||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,null, UserEmailSetting)
|
||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, null, UserEmailSetting);
|
||||
return userEmailSetting.list;
|
||||
}
|
||||
|
||||
async delete(userId: any, email: string) {
|
||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, null, UserEmailSetting)
|
||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, null, UserEmailSetting);
|
||||
userEmailSetting.list = userEmailSetting.list.filter(item => item !== email);
|
||||
await this.settingsService.saveSetting(userId, null, userEmailSetting)
|
||||
await this.settingsService.saveSetting(userId, null, userEmailSetting);
|
||||
}
|
||||
async add(userId: any, email: string) {
|
||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, null, UserEmailSetting)
|
||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, null, UserEmailSetting);
|
||||
//如果已存在
|
||||
if (userEmailSetting.list.includes(email)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
userEmailSetting.list.unshift(email)
|
||||
await this.settingsService.saveSetting(userId, null, userEmailSetting)
|
||||
userEmailSetting.list.unshift(email);
|
||||
await this.settingsService.saveSetting(userId, null, userEmailSetting);
|
||||
}
|
||||
|
||||
async sendByTemplate(req: EmailSendByTemplateReq) {
|
||||
let content = null
|
||||
let content = null;
|
||||
const emailConf = await this.sysSettingsService.getSetting<SysEmailConf>(SysEmailConf);
|
||||
const template = emailConf?.templates?.[req.type]
|
||||
if (isPlus() && template && template.addonId) {
|
||||
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getAddonById(template.addonId, true, 0,null)
|
||||
const template = emailConf?.templates?.[req.type];
|
||||
if (isPlus() && template && template.addonId) {
|
||||
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getAddonById(template.addonId, true, 0, null);
|
||||
if (addon) {
|
||||
content = await addon.buildContent({ data: req.data })
|
||||
content = await addon.buildContent({ data: req.data });
|
||||
}
|
||||
}
|
||||
if (isPlus() && !content ) {
|
||||
if (isPlus() && !content) {
|
||||
//看看有没有通用模版
|
||||
if (emailConf?.templates?.common && emailConf?.templates?.common.addonId) {
|
||||
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getAddonById(emailConf.templates.common.addonId, true, 0,null)
|
||||
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getAddonById(emailConf.templates.common.addonId, true, 0, null);
|
||||
if (addon) {
|
||||
content = await addon.buildContent({ data: req.data })
|
||||
content = await addon.buildContent({ data: req.data });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,21 +185,21 @@ export class EmailService implements IEmailService {
|
||||
// 没有找到模版,使用默认模版
|
||||
if (!content) {
|
||||
try {
|
||||
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getBlank("emailTemplate", req.type)
|
||||
content = await addon.buildDefaultContent({ data: req.data })
|
||||
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getBlank("emailTemplate", req.type);
|
||||
content = await addon.buildDefaultContent({ data: req.data });
|
||||
} catch (e) {
|
||||
// 对应的通知类型模版可能没有注册或者开发
|
||||
}
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getBlank("emailTemplate", "common")
|
||||
content = await addon.buildDefaultContent({ data: req.data })
|
||||
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getBlank("emailTemplate", "common");
|
||||
content = await addon.buildDefaultContent({ data: req.data });
|
||||
}
|
||||
return await this.send({
|
||||
...content,
|
||||
receivers: req.receivers,
|
||||
attachments: req.attachments,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { BaseService } from '@certd/lib-server';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { merge } from 'lodash-es';
|
||||
import { GroupEntity } from '../entity/group.js';
|
||||
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { BaseService } from "@certd/lib-server";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { merge } from "lodash-es";
|
||||
import { GroupEntity } from "../entity/group.js";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -18,7 +18,7 @@ export class GroupService extends BaseService<GroupEntity> {
|
||||
|
||||
async add(bean: any) {
|
||||
if (!bean.type) {
|
||||
throw new Error('type is required');
|
||||
throw new Error("type is required");
|
||||
}
|
||||
bean = merge(
|
||||
{
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import { logger } from "@certd/basic"
|
||||
import { logger } from "@certd/basic";
|
||||
|
||||
export class BackTaskExecutor {
|
||||
tasks: Record<string, Record<string, BackTask>> = {}
|
||||
tasks: Record<string, Record<string, BackTask>> = {};
|
||||
|
||||
start(task: BackTask) {
|
||||
const type = task.type || 'default'
|
||||
const type = task.type || "default";
|
||||
if (!this.tasks[type]) {
|
||||
this.tasks[type] = {}
|
||||
this.tasks[type] = {};
|
||||
}
|
||||
const oldTask = this.tasks[type][task.key]
|
||||
if (oldTask ){
|
||||
const oldTask = this.tasks[type][task.key];
|
||||
if (oldTask) {
|
||||
if (oldTask.status === "running") {
|
||||
throw new Error(`任务 ${task.title} 正在运行中`)
|
||||
throw new Error(`任务 ${task.title} 正在运行中`);
|
||||
}
|
||||
this.clear(type, task.key);
|
||||
}
|
||||
this.tasks[type][task.key] = task
|
||||
this.tasks[type][task.key] = task;
|
||||
this.run(task);
|
||||
}
|
||||
|
||||
get(type: string, key: string) {
|
||||
if (!this.tasks[type]) {
|
||||
this.tasks[type] = {}
|
||||
this.tasks[type] = {};
|
||||
}
|
||||
return this.tasks[type][key]
|
||||
return this.tasks[type][key];
|
||||
}
|
||||
|
||||
removeIsEnd(type: string, key: string) {
|
||||
const task = this.tasks[type]?.[key]
|
||||
const task = this.tasks[type]?.[key];
|
||||
if (task && task.status !== "running") {
|
||||
this.clear(type, key);
|
||||
}
|
||||
}
|
||||
|
||||
clear(type: string, key: string) {
|
||||
const task = this.tasks[type]?.[key]
|
||||
const task = this.tasks[type]?.[key];
|
||||
if (task) {
|
||||
task.clearTimeout();
|
||||
delete this.tasks[type][key]
|
||||
delete this.tasks[type][key];
|
||||
}
|
||||
}
|
||||
|
||||
private async run(task: BackTask) {
|
||||
const type = task.type || 'default'
|
||||
const type = task.type || "default";
|
||||
if (task.status === "running") {
|
||||
throw new Error(`任务 ${type}—${task.key} 正在运行中`)
|
||||
throw new Error(`任务 ${type}—${task.key} 正在运行中`);
|
||||
}
|
||||
task.startTime = Date.now();
|
||||
task.clearTimeout();
|
||||
@@ -54,84 +54,78 @@ export class BackTaskExecutor {
|
||||
} catch (e) {
|
||||
logger.error(`任务 ${task.title}[${type}-${task.key}] 执行失败`, e.message);
|
||||
task.status = "failed";
|
||||
task.addError(e.message)
|
||||
task.addError(e.message);
|
||||
} finally {
|
||||
task.endTime = Date.now();
|
||||
task.status = "done";
|
||||
task.setTimeoutId(setTimeout(() => {
|
||||
this.clear(type, task.key);
|
||||
}, 24 * 60 * 60 * 1000));
|
||||
task.setTimeoutId(
|
||||
setTimeout(() => {
|
||||
this.clear(type, task.key);
|
||||
}, 24 * 60 * 60 * 1000)
|
||||
);
|
||||
delete task.run;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
export class BackTask {
|
||||
type: string;
|
||||
key: string;
|
||||
title: string;
|
||||
total: number = 0;
|
||||
current: number = 0;
|
||||
skip: number = 0;
|
||||
total = 0;
|
||||
current = 0;
|
||||
skip = 0;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
status: string = "pending";
|
||||
status = "pending";
|
||||
errors?: string[] = [];
|
||||
private _timeoutId?: NodeJS.Timeout;
|
||||
|
||||
|
||||
|
||||
private _run: (task: BackTask) => Promise<void>;
|
||||
|
||||
constructor(opts: {
|
||||
type: string,
|
||||
key: string, title: string, run: (task: BackTask) => Promise<void>
|
||||
}) {
|
||||
const { key, title, run, type } = opts
|
||||
this.type = type
|
||||
constructor(opts: { type: string; key: string; title: string; run: (task: BackTask) => Promise<void> }) {
|
||||
const { key, title, run, type } = opts;
|
||||
this.type = type;
|
||||
this.key = key;
|
||||
this.title = title;
|
||||
this._run = run;
|
||||
|
||||
Object.defineProperty(this, '_run', {
|
||||
Object.defineProperty(this, "_run", {
|
||||
enumerable: false,
|
||||
value: this._run
|
||||
})
|
||||
Object.defineProperty(this, '_timeoutId', {
|
||||
enumerable: false,
|
||||
value: null
|
||||
})
|
||||
|
||||
Object.defineProperty(this, 'progress', {
|
||||
get: ()=> {
|
||||
return this.getProgress()
|
||||
},
|
||||
enumerable: true, // 关键:设置为可枚举
|
||||
configurable: true
|
||||
value: this._run,
|
||||
});
|
||||
Object.defineProperty(this, 'successCount', {
|
||||
get: ()=> {
|
||||
return this.getSuccessCount()
|
||||
Object.defineProperty(this, "_timeoutId", {
|
||||
enumerable: false,
|
||||
value: null,
|
||||
});
|
||||
|
||||
Object.defineProperty(this, "progress", {
|
||||
get: () => {
|
||||
return this.getProgress();
|
||||
},
|
||||
enumerable: true, // 关键:设置为可枚举
|
||||
configurable: true
|
||||
})
|
||||
Object.defineProperty(this, 'errorCount', {
|
||||
get: ()=> {
|
||||
return this.getErrorCount()
|
||||
enumerable: true, // 关键:设置为可枚举
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(this, "successCount", {
|
||||
get: () => {
|
||||
return this.getSuccessCount();
|
||||
},
|
||||
enumerable: true, // 关键:设置为可枚举
|
||||
configurable: true
|
||||
})
|
||||
Object.defineProperty(this, 'skipCount', {
|
||||
get: ()=> {
|
||||
return this.getSkipCount()
|
||||
enumerable: true, // 关键:设置为可枚举
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(this, "errorCount", {
|
||||
get: () => {
|
||||
return this.getErrorCount();
|
||||
},
|
||||
enumerable: true, // 关键:设置为可枚举
|
||||
configurable: true
|
||||
})
|
||||
enumerable: true, // 关键:设置为可枚举
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(this, "skipCount", {
|
||||
get: () => {
|
||||
return this.getSkipCount();
|
||||
},
|
||||
enumerable: true, // 关键:设置为可枚举
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
async run(task: BackTask) {
|
||||
@@ -145,7 +139,6 @@ export class BackTask {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setTimeoutId(timeoutId: NodeJS.Timeout) {
|
||||
this.clearTimeout();
|
||||
this._timeoutId = timeoutId;
|
||||
@@ -155,34 +148,33 @@ export class BackTask {
|
||||
this.total = total;
|
||||
}
|
||||
incrementCurrent() {
|
||||
this.current++
|
||||
this.current++;
|
||||
}
|
||||
|
||||
addError(error: string) {
|
||||
logger.error(error)
|
||||
this.errors.push(error)
|
||||
logger.error(error);
|
||||
this.errors.push(error);
|
||||
}
|
||||
|
||||
getErrorCount() {
|
||||
return this.errors.length
|
||||
return this.errors.length;
|
||||
}
|
||||
|
||||
getSkipCount() {
|
||||
return this.skip
|
||||
return this.skip;
|
||||
}
|
||||
|
||||
getSuccessCount() {
|
||||
return this.current - this.errors.length
|
||||
return this.current - this.errors.length;
|
||||
}
|
||||
|
||||
getProgress() {
|
||||
return (this.current * 1.0 / this.total * 100.0);
|
||||
return ((this.current * 1.0) / this.total) * 100.0;
|
||||
}
|
||||
|
||||
|
||||
incrementSkip() {
|
||||
this.skip++
|
||||
this.skip++;
|
||||
}
|
||||
}
|
||||
|
||||
export const taskExecutor = new BackTaskExecutor();
|
||||
export const taskExecutor = new BackTaskExecutor();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { logger } from '@certd/basic';
|
||||
import { ISmsService, PluginInputs, SmsPluginCtx } from './api.js';
|
||||
import { AliyunAccess, AliyunClient } from '../../../plugins/plugin-lib/aliyun/index.js';
|
||||
import { logger } from "@certd/basic";
|
||||
import { ISmsService, PluginInputs, SmsPluginCtx } from "./api.js";
|
||||
import { AliyunAccess, AliyunClient } from "../../../plugins/plugin-lib/aliyun/index.js";
|
||||
|
||||
export type AliyunSmsConfig = {
|
||||
accessId: string;
|
||||
@@ -11,30 +11,30 @@ export type AliyunSmsConfig = {
|
||||
export class AliyunSmsService implements ISmsService {
|
||||
static getDefine() {
|
||||
return {
|
||||
name: 'aliyun',
|
||||
desc: '阿里云短信服务',
|
||||
name: "aliyun",
|
||||
desc: "阿里云短信服务",
|
||||
input: {
|
||||
accessId: {
|
||||
title: '阿里云授权',
|
||||
title: "阿里云授权",
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'aliyun',
|
||||
name: "access-selector",
|
||||
type: "aliyun",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
signName: {
|
||||
title: '签名',
|
||||
title: "签名",
|
||||
component: {
|
||||
name: 'a-input',
|
||||
vModel: 'value',
|
||||
name: "a-input",
|
||||
vModel: "value",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
codeTemplateId: {
|
||||
title: '验证码模板Id',
|
||||
title: "验证码模板Id",
|
||||
component: {
|
||||
name: 'a-input',
|
||||
vModel: 'value',
|
||||
name: "a-input",
|
||||
vModel: "value",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
@@ -55,8 +55,8 @@ export class AliyunSmsService implements ISmsService {
|
||||
await aliyunClinet.init({
|
||||
accessKeyId: access.accessKeyId,
|
||||
accessKeySecret: access.accessKeySecret,
|
||||
endpoint: 'https://dysmsapi.aliyuncs.com',
|
||||
apiVersion: '2017-05-25',
|
||||
endpoint: "https://dysmsapi.aliyuncs.com",
|
||||
apiVersion: "2017-05-25",
|
||||
});
|
||||
const smsConfig = this.ctx.config;
|
||||
const phoneNumber = phoneCode + mobile;
|
||||
@@ -67,6 +67,6 @@ export class AliyunSmsService implements ISmsService {
|
||||
TemplateParam: `{"code":"${code}"}`,
|
||||
};
|
||||
|
||||
await aliyunClinet.request('SendSms', params);
|
||||
await aliyunClinet.request("SendSms", params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FormItemProps, IAccessService } from '@certd/pipeline';
|
||||
import { FormItemProps, IAccessService } from "@certd/pipeline";
|
||||
|
||||
export interface ISmsService {
|
||||
sendSmsCode(opts: { mobile: string; code: string; phoneCode: string }): Promise<void>;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
export class SmsServiceFactory {
|
||||
static async createSmsService(type: string) {
|
||||
const cls = await this.GetClassByType(type);
|
||||
@@ -7,17 +6,17 @@ export class SmsServiceFactory {
|
||||
|
||||
static async GetClassByType(type: string) {
|
||||
switch (type) {
|
||||
case 'aliyun':
|
||||
const {AliyunSmsService} = await import("./aliyun-sms.js")
|
||||
case "aliyun":
|
||||
const { AliyunSmsService } = await import("./aliyun-sms.js");
|
||||
return AliyunSmsService;
|
||||
case 'yfysms':
|
||||
const {YfySmsService} = await import("./yfy-sms.js")
|
||||
case "yfysms":
|
||||
const { YfySmsService } = await import("./yfy-sms.js");
|
||||
return YfySmsService;
|
||||
case 'tencent':
|
||||
const {TencentSmsService} = await import("./tencent-sms.js")
|
||||
case "tencent":
|
||||
const { TencentSmsService } = await import("./tencent-sms.js");
|
||||
return TencentSmsService;
|
||||
default:
|
||||
throw new Error('不支持的短信服务类型');
|
||||
throw new Error("不支持的短信服务类型");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TencentAccess } from '../../../plugins/plugin-lib/tencent/access.js';
|
||||
import {ISmsService, PluginInputs, SmsPluginCtx} from './api.js';
|
||||
import { TencentAccess } from "../../../plugins/plugin-lib/tencent/access.js";
|
||||
import { ISmsService, PluginInputs, SmsPluginCtx } from "./api.js";
|
||||
|
||||
export type TencentSmsConfig = {
|
||||
accessId: string;
|
||||
@@ -12,53 +12,53 @@ export type TencentSmsConfig = {
|
||||
export class TencentSmsService implements ISmsService {
|
||||
static getDefine() {
|
||||
return {
|
||||
name: 'tencent',
|
||||
desc: '腾讯云短信服务',
|
||||
name: "tencent",
|
||||
desc: "腾讯云短信服务",
|
||||
input: {
|
||||
accessId: {
|
||||
title: '腾讯云授权',
|
||||
title: "腾讯云授权",
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'tencent',
|
||||
name: "access-selector",
|
||||
type: "tencent",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
region: {
|
||||
title: '区域',
|
||||
value:"ap-beijing",
|
||||
title: "区域",
|
||||
value: "ap-beijing",
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
options:[
|
||||
{value:"ap-beijing",label:"华北地区(北京)"},
|
||||
{value:"ap-guangzhou",label:"华南地区(广州)"},
|
||||
{value:"ap-nanjing",label:"华东地区(南京)"},
|
||||
]
|
||||
name: "a-select",
|
||||
vModel: "value",
|
||||
options: [
|
||||
{ value: "ap-beijing", label: "华北地区(北京)" },
|
||||
{ value: "ap-guangzhou", label: "华南地区(广州)" },
|
||||
{ value: "ap-nanjing", label: "华东地区(南京)" },
|
||||
],
|
||||
},
|
||||
helper:"随便选一个",
|
||||
helper: "随便选一个",
|
||||
required: true,
|
||||
},
|
||||
signName: {
|
||||
title: '签名',
|
||||
title: "签名",
|
||||
component: {
|
||||
name: 'a-input',
|
||||
vModel: 'value',
|
||||
name: "a-input",
|
||||
vModel: "value",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
appId: {
|
||||
title: '应用ID',
|
||||
title: "应用ID",
|
||||
component: {
|
||||
name: 'a-input',
|
||||
vModel: 'value',
|
||||
name: "a-input",
|
||||
vModel: "value",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
codeTemplateId: {
|
||||
title: '验证码模板Id',
|
||||
title: "验证码模板Id",
|
||||
component: {
|
||||
name: 'a-input',
|
||||
vModel: 'value',
|
||||
name: "a-input",
|
||||
vModel: "value",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
@@ -72,13 +72,11 @@ export class TencentSmsService implements ISmsService {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
|
||||
async getClient() {
|
||||
const sdk = await import('tencentcloud-sdk-nodejs/tencentcloud/services/sms/v20210111/index.js');
|
||||
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: {
|
||||
@@ -102,15 +100,11 @@ export class TencentSmsService implements ISmsService {
|
||||
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
|
||||
]
|
||||
PhoneNumberSet: [`+${phoneCode}${mobile}`],
|
||||
SmsSdkAppId: smsConfig.appId,
|
||||
TemplateId: smsConfig.codeTemplateId,
|
||||
SignName: smsConfig.signName,
|
||||
TemplateParamSet: [code],
|
||||
};
|
||||
const ret = await client.SendSms(params);
|
||||
this.checkRet(ret);
|
||||
@@ -118,7 +112,7 @@ export class TencentSmsService implements ISmsService {
|
||||
|
||||
checkRet(ret: any) {
|
||||
if (!ret || ret.Error) {
|
||||
throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message);
|
||||
throw new Error("执行失败:" + ret.Error.Code + "," + ret.Error.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { http, utils } from '@certd/basic';
|
||||
import { ISmsService, PluginInputs, SmsPluginCtx } from './api.js';
|
||||
import { YfySmsAccess } from '../../../plugins/plugin-plus/yidun/access-sms.js';
|
||||
import { http, utils } from "@certd/basic";
|
||||
import { ISmsService, PluginInputs, SmsPluginCtx } from "./api.js";
|
||||
import { YfySmsAccess } from "../../../plugins/plugin-plus/yidun/access-sms.js";
|
||||
|
||||
export type YfySmsConfig = {
|
||||
accessId: string;
|
||||
@@ -10,22 +10,22 @@ export type YfySmsConfig = {
|
||||
export class YfySmsService implements ISmsService {
|
||||
static getDefine() {
|
||||
return {
|
||||
name: 'yfysms',
|
||||
desc: '易发云短信',
|
||||
name: "yfysms",
|
||||
desc: "易发云短信",
|
||||
input: {
|
||||
accessId: {
|
||||
title: '易发云短信授权',
|
||||
title: "易发云短信授权",
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'yfysms',
|
||||
name: "access-selector",
|
||||
type: "yfysms",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
signName: {
|
||||
title: '签名',
|
||||
title: "签名",
|
||||
component: {
|
||||
name: 'a-input',
|
||||
vModel: 'value',
|
||||
name: "a-input",
|
||||
vModel: "value",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
@@ -44,10 +44,10 @@ export class YfySmsService implements ISmsService {
|
||||
const access = await this.ctx.accessService.getById<YfySmsAccess>(this.ctx.config.accessId);
|
||||
|
||||
const res = await http.request({
|
||||
url: 'http://sms.yfyidc.cn/sms/',
|
||||
method: 'post',
|
||||
url: "http://sms.yfyidc.cn/sms/",
|
||||
method: "post",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
data: {
|
||||
/**
|
||||
@@ -75,40 +75,40 @@ export class YfySmsService implements ISmsService {
|
||||
* 9 用户已封禁
|
||||
* 10 未实名认证
|
||||
*/
|
||||
let message = '';
|
||||
let message = "";
|
||||
switch (res) {
|
||||
case 1:
|
||||
message = '余额不足';
|
||||
message = "余额不足";
|
||||
break;
|
||||
case 2:
|
||||
message = '用户不存在';
|
||||
message = "用户不存在";
|
||||
break;
|
||||
case 3:
|
||||
message = 'KEY错误';
|
||||
message = "KEY错误";
|
||||
break;
|
||||
case 4:
|
||||
message = '发送失败';
|
||||
message = "发送失败";
|
||||
break;
|
||||
case 5:
|
||||
message = '签名不存在';
|
||||
message = "签名不存在";
|
||||
break;
|
||||
case 6:
|
||||
message = '签名审核未通过';
|
||||
message = "签名审核未通过";
|
||||
break;
|
||||
case 7:
|
||||
message = '当前发信短信已达到上限';
|
||||
message = "当前发信短信已达到上限";
|
||||
break;
|
||||
case 8:
|
||||
message = '有违规词';
|
||||
message = "有违规词";
|
||||
break;
|
||||
case 9:
|
||||
message = '用户已封禁';
|
||||
message = "用户已封禁";
|
||||
break;
|
||||
case 10:
|
||||
message = '未实名认证';
|
||||
message = "未实名认证";
|
||||
break;
|
||||
default:
|
||||
message = '未知错误';
|
||||
message = "未知错误";
|
||||
}
|
||||
throw new Error(`发送短信失败:${message}`);
|
||||
}
|
||||
|
||||
@@ -1,62 +1,61 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
/**
|
||||
* 域名管理
|
||||
*/
|
||||
@Entity('cd_domain')
|
||||
@Entity("cd_domain")
|
||||
export class DomainEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ comment: '用户ID', name: 'user_id' })
|
||||
@Column({ comment: "用户ID", name: "user_id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ comment: '主域名', length: 100 })
|
||||
@Column({ comment: "主域名", length: 100 })
|
||||
domain: string;
|
||||
|
||||
@Column({ comment: '校验类型', name: 'challenge_type', length: 50 })
|
||||
challengeType : string;
|
||||
@Column({ comment: "校验类型", name: "challenge_type", length: 50 })
|
||||
challengeType: string;
|
||||
|
||||
@Column({ comment: 'DNS提供商', name: 'dns_provider_type', length: 50 })
|
||||
@Column({ comment: "DNS提供商", name: "dns_provider_type", length: 50 })
|
||||
dnsProviderType: string;
|
||||
|
||||
@Column({ comment: 'DNS提供商授权', name: 'dns_provider_access' })
|
||||
@Column({ comment: "DNS提供商授权", name: "dns_provider_access" })
|
||||
dnsProviderAccess: number;
|
||||
|
||||
@Column({ comment: '是否禁用', name: 'disabled' })
|
||||
@Column({ comment: "是否禁用", name: "disabled" })
|
||||
disabled: boolean;
|
||||
|
||||
@Column({ comment: '注册时间', name: 'registration_date' })
|
||||
@Column({ comment: "注册时间", name: "registration_date" })
|
||||
registrationDate: number;
|
||||
|
||||
@Column({ comment: '过期时间', name: 'expiration_date' })
|
||||
@Column({ comment: "过期时间", name: "expiration_date" })
|
||||
expirationDate: number;
|
||||
|
||||
@Column({ comment: '来源', name: 'from_type', length: 50 })
|
||||
@Column({ comment: "来源", name: "from_type", length: 50 })
|
||||
fromType: string;
|
||||
|
||||
|
||||
@Column({ comment: 'http上传类型', name: 'http_uploader_type', length: 50 })
|
||||
@Column({ comment: "http上传类型", name: "http_uploader_type", length: 50 })
|
||||
httpUploaderType: string;
|
||||
|
||||
@Column({ comment: 'http上传授权', name: 'http_uploader_access' })
|
||||
@Column({ comment: "http上传授权", name: "http_uploader_access" })
|
||||
httpUploaderAccess: number;
|
||||
|
||||
@Column({ comment: 'http上传根目录', name: 'http_upload_root_dir', length: 512 })
|
||||
@Column({ comment: "http上传根目录", name: "http_upload_root_dir", length: 512 })
|
||||
httpUploadRootDir: string;
|
||||
|
||||
@Column({ name: 'project_id', comment: '项目Id' })
|
||||
@Column({ name: "project_id", comment: "项目Id" })
|
||||
projectId: number;
|
||||
|
||||
@Column({
|
||||
comment: '创建时间',
|
||||
name: 'create_time',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
comment: "创建时间",
|
||||
name: "create_time",
|
||||
default: () => "CURRENT_TIMESTAMP",
|
||||
})
|
||||
createTime: Date;
|
||||
@Column({
|
||||
comment: '修改时间',
|
||||
name: 'update_time',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
comment: "修改时间",
|
||||
name: "update_time",
|
||||
default: () => "CURRENT_TIMESTAMP",
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { http, logger, utils } from '@certd/basic';
|
||||
import { AccessService, BaseService, isEnterprise } from '@certd/lib-server';
|
||||
import { doPageTurn, Pager, PageRes } from '@certd/pipeline';
|
||||
import { http, logger, utils } from "@certd/basic";
|
||||
import { AccessService, BaseService, isEnterprise } from "@certd/lib-server";
|
||||
import { doPageTurn, Pager, PageRes } from "@certd/pipeline";
|
||||
import { DomainVerifiers } from "@certd/plugin-cert";
|
||||
import { createDnsProvider, dnsProviderRegistry, DomainParser } from "@certd/plugin-lib";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import dayjs from 'dayjs';
|
||||
import { merge } from 'lodash-es';
|
||||
import { In, LessThan, Not, Repository } from 'typeorm';
|
||||
import { BackTask, taskExecutor } from '../../basic/service/task-executor.js';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import dayjs from "dayjs";
|
||||
import { merge } from "lodash-es";
|
||||
import { In, LessThan, Not, Repository } from "typeorm";
|
||||
import { BackTask, taskExecutor } from "../../basic/service/task-executor.js";
|
||||
import { CnameRecordEntity } from "../../cname/entity/cname-record.js";
|
||||
import { CnameRecordService } from '../../cname/service/cname-record-service.js';
|
||||
import { Cron } from '../../cron/cron.js';
|
||||
import { UserDomainImportSetting, UserDomainMonitorSetting } from '../../mine/service/models.js';
|
||||
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
|
||||
import { JobHistoryService } from '../../monitor/service/job-history-service.js';
|
||||
import { TaskServiceBuilder } from '../../pipeline/service/getter/task-service-getter.js';
|
||||
import { CnameRecordService } from "../../cname/service/cname-record-service.js";
|
||||
import { Cron } from "../../cron/cron.js";
|
||||
import { UserDomainImportSetting, UserDomainMonitorSetting } from "../../mine/service/models.js";
|
||||
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
|
||||
import { JobHistoryService } from "../../monitor/service/job-history-service.js";
|
||||
import { TaskServiceBuilder } from "../../pipeline/service/getter/task-service-getter.js";
|
||||
import { SubDomainService } from "../../pipeline/service/sub-domain-service.js";
|
||||
import { DomainEntity } from '../entity/domain.js';
|
||||
import { TldClient } from './tld-client.js';
|
||||
import { DomainEntity } from "../entity/domain.js";
|
||||
import { TldClient } from "./tld-client.js";
|
||||
|
||||
export interface SyncFromProviderReq {
|
||||
userId: number;
|
||||
@@ -27,12 +27,10 @@ export interface SyncFromProviderReq {
|
||||
dnsProviderAccessId: number;
|
||||
}
|
||||
|
||||
const DOMAIN_IMPORT_TASK_TYPE = "domainImportTask";
|
||||
const DOMAIN_EXPIRE_TASK_TYPE = "domainExpirationSyncTask";
|
||||
|
||||
const DOMAIN_IMPORT_TASK_TYPE = 'domainImportTask'
|
||||
const DOMAIN_EXPIRE_TASK_TYPE = 'domainExpirationSyncTask'
|
||||
|
||||
const DOMAIN_EXPIRE_CHECK_TYPE = 'domainExpirationCheck'
|
||||
|
||||
const DOMAIN_EXPIRE_CHECK_TYPE = "domainExpirationCheck";
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -63,8 +61,6 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
@Inject()
|
||||
cron: Cron;
|
||||
|
||||
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
@@ -72,50 +68,48 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
|
||||
async add(param) {
|
||||
if (param.userId == null) {
|
||||
throw new Error('userId 不能为空');
|
||||
throw new Error("userId 不能为空");
|
||||
}
|
||||
if (!param.domain) {
|
||||
throw new Error('domain 不能为空');
|
||||
throw new Error("domain 不能为空");
|
||||
}
|
||||
const old = await this.repository.findOne({
|
||||
where: {
|
||||
domain: param.domain,
|
||||
userId: param.userId
|
||||
}
|
||||
userId: param.userId,
|
||||
},
|
||||
});
|
||||
if (old) {
|
||||
throw new Error(`域名(${param.domain})不能重复`);
|
||||
}
|
||||
if (!param.fromType) {
|
||||
param.fromType = 'manual'
|
||||
param.fromType = "manual";
|
||||
}
|
||||
return await super.add(param);
|
||||
}
|
||||
|
||||
async update(param) {
|
||||
if (!param.id) {
|
||||
throw new Error('id 不能为空');
|
||||
throw new Error("id 不能为空");
|
||||
}
|
||||
const old = await this.info(param.id)
|
||||
const old = await this.info(param.id);
|
||||
if (!old) {
|
||||
throw new Error('domain记录不存在');
|
||||
throw new Error("domain记录不存在");
|
||||
}
|
||||
|
||||
const same = await this.repository.findOne({
|
||||
where: {
|
||||
domain: param.domain,
|
||||
userId: old.userId,
|
||||
id: Not(param.id)
|
||||
}
|
||||
id: Not(param.id),
|
||||
},
|
||||
});
|
||||
|
||||
if (same) {
|
||||
throw new Error(`域名(${param.domain})不能重复`);
|
||||
}
|
||||
delete param.userId
|
||||
delete param.userId;
|
||||
return await super.update(param);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,23 +118,22 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
* @param domains //去除* 且去重之后的域名列表
|
||||
*/
|
||||
async getDomainVerifiers(userId: number, projectId: number, domains: string[]): Promise<DomainVerifiers> {
|
||||
|
||||
const mainDomainMap: Record<string, string> = {}
|
||||
const mainDomainMap: Record<string, string> = {};
|
||||
const taskService = this.taskServiceBuilder.create({ userId: userId, projectId: projectId });
|
||||
const subDomainGetter = await taskService.getSubDomainsGetter();
|
||||
const domainParser = new DomainParser(subDomainGetter)
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
const mainDomains = []
|
||||
const mainDomains = [];
|
||||
for (const domain of domains) {
|
||||
const mainDomain = await domainParser.parse(domain);
|
||||
mainDomainMap[domain] = mainDomain;
|
||||
mainDomains.push(mainDomain)
|
||||
mainDomains.push(mainDomain);
|
||||
}
|
||||
|
||||
//匹配DNS记录
|
||||
let allDomains = [...domains, ...mainDomains]
|
||||
let allDomains = [...domains, ...mainDomains];
|
||||
//去重
|
||||
allDomains = [...new Set(allDomains)]
|
||||
allDomains = [...new Set(allDomains)];
|
||||
|
||||
//从 domain 表中获取配置
|
||||
const domainRecords = await this.find({
|
||||
@@ -149,19 +142,22 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
userId,
|
||||
projectId,
|
||||
disabled: false,
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
const dnsMap = domainRecords.filter(item => item.challengeType === 'dns').reduce((pre, item) => {
|
||||
pre[item.domain] = item
|
||||
return pre
|
||||
}, {})
|
||||
|
||||
const httpMap = domainRecords.filter(item => item.challengeType === 'http').reduce((pre, item) => {
|
||||
pre[item.domain] = item
|
||||
return pre
|
||||
}, {})
|
||||
const dnsMap = domainRecords
|
||||
.filter(item => item.challengeType === "dns")
|
||||
.reduce((pre, item) => {
|
||||
pre[item.domain] = item;
|
||||
return pre;
|
||||
}, {});
|
||||
|
||||
const httpMap = domainRecords
|
||||
.filter(item => item.challengeType === "http")
|
||||
.reduce((pre, item) => {
|
||||
pre[item.domain] = item;
|
||||
return pre;
|
||||
}, {});
|
||||
|
||||
//从cname record表中获取配置
|
||||
const cnameRecords = await this.cnameRecordService.find({
|
||||
@@ -170,91 +166,95 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
userId,
|
||||
projectId,
|
||||
status: "valid",
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
const cnameMap = cnameRecords.reduce((pre, item) => {
|
||||
pre[item.domain] = item
|
||||
return pre
|
||||
}, {})
|
||||
pre[item.domain] = item;
|
||||
return pre;
|
||||
}, {});
|
||||
|
||||
//构建域名验证计划
|
||||
const domainVerifiers: DomainVerifiers = {}
|
||||
const domainVerifiers: DomainVerifiers = {};
|
||||
|
||||
for (const domain of domains) {
|
||||
const mainDomain = mainDomainMap[domain]
|
||||
const mainDomain = mainDomainMap[domain];
|
||||
|
||||
const dnsRecord = dnsMap[mainDomain]
|
||||
const dnsRecord = dnsMap[mainDomain];
|
||||
if (dnsRecord) {
|
||||
domainVerifiers[domain] = {
|
||||
domain,
|
||||
mainDomain,
|
||||
type: 'dns',
|
||||
type: "dns",
|
||||
dns: {
|
||||
dnsProviderType: dnsRecord.dnsProviderType,
|
||||
dnsProviderAccessId: dnsRecord.dnsProviderAccess
|
||||
}
|
||||
}
|
||||
continue
|
||||
dnsProviderAccessId: dnsRecord.dnsProviderAccess,
|
||||
},
|
||||
};
|
||||
continue;
|
||||
}
|
||||
const cnameRecord: CnameRecordEntity = cnameMap[domain]
|
||||
const cnameRecord: CnameRecordEntity = cnameMap[domain];
|
||||
if (cnameRecord) {
|
||||
domainVerifiers[domain] = {
|
||||
domain,
|
||||
mainDomain,
|
||||
type: 'cname',
|
||||
type: "cname",
|
||||
cname: {
|
||||
domain: cnameRecord.domain,
|
||||
hostRecord: cnameRecord.hostRecord,
|
||||
recordValue: cnameRecord.recordValue
|
||||
}
|
||||
}
|
||||
continue
|
||||
recordValue: cnameRecord.recordValue,
|
||||
},
|
||||
};
|
||||
continue;
|
||||
}
|
||||
const httpRecord = httpMap[domain]
|
||||
const httpRecord = httpMap[domain];
|
||||
if (httpRecord) {
|
||||
domainVerifiers[domain] = {
|
||||
domain,
|
||||
mainDomain,
|
||||
type: 'http',
|
||||
type: "http",
|
||||
http: {
|
||||
httpUploaderType: httpRecord.httpUploaderType,
|
||||
httpUploaderAccess: httpRecord.httpUploaderAccess,
|
||||
httpUploadRootDir: httpRecord.httpUploadRootDir
|
||||
}
|
||||
}
|
||||
continue
|
||||
httpUploadRootDir: httpRecord.httpUploadRootDir,
|
||||
},
|
||||
};
|
||||
continue;
|
||||
}
|
||||
domainVerifiers[domain] = null
|
||||
domainVerifiers[domain] = null;
|
||||
}
|
||||
|
||||
return domainVerifiers;
|
||||
}
|
||||
|
||||
async startDomainImportTask(req: { userId: number; projectId: number; key: string }) {
|
||||
const key = req.key;
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(req.userId, req.projectId, UserDomainImportSetting);
|
||||
|
||||
async startDomainImportTask(req: { userId: number, projectId: number, key: string }) {
|
||||
const key = req.key
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(req.userId, req.projectId, UserDomainImportSetting)
|
||||
|
||||
const item = setting.domainImportList.find(item => item.key === key)
|
||||
const item = setting.domainImportList.find(item => item.key === key);
|
||||
if (!item) {
|
||||
throw new Error(`域名导入任务配置(${key})还未注册`)
|
||||
throw new Error(`域名导入任务配置(${key})还未注册`);
|
||||
}
|
||||
const { dnsProviderType, dnsProviderAccessId, title } = item
|
||||
const { dnsProviderType, dnsProviderAccessId, title } = item;
|
||||
|
||||
taskExecutor.start(new BackTask({
|
||||
type: DOMAIN_IMPORT_TASK_TYPE,
|
||||
key,
|
||||
title: title,
|
||||
run: async (task: BackTask) => {
|
||||
await this._syncFromProvider({
|
||||
userId: req.userId,
|
||||
projectId: req.projectId,
|
||||
dnsProviderType,
|
||||
dnsProviderAccessId,
|
||||
}, task)
|
||||
},
|
||||
}))
|
||||
taskExecutor.start(
|
||||
new BackTask({
|
||||
type: DOMAIN_IMPORT_TASK_TYPE,
|
||||
key,
|
||||
title: title,
|
||||
run: async (task: BackTask) => {
|
||||
await this._syncFromProvider(
|
||||
{
|
||||
userId: req.userId,
|
||||
projectId: req.projectId,
|
||||
dnsProviderType,
|
||||
dnsProviderAccessId,
|
||||
},
|
||||
task
|
||||
);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private async _syncFromProvider(req: SyncFromProviderReq, task: BackTask) {
|
||||
@@ -262,28 +262,28 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
|
||||
const serviceGetter = this.taskServiceBuilder.create({ userId, projectId });
|
||||
const subDomainGetter = await serviceGetter.getSubDomainsGetter();
|
||||
const domainParser = new DomainParser(subDomainGetter)
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
const access = await this.accessService.getById(dnsProviderAccessId, userId, projectId);
|
||||
const context = { access, logger, http, utils, domainParser, serviceGetter };
|
||||
// 翻页查询dns的记录
|
||||
const dnsProvider = await createDnsProvider({ dnsProviderType, context })
|
||||
const dnsProvider = await createDnsProvider({ dnsProviderType, context });
|
||||
|
||||
const pager = new Pager({
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
})
|
||||
const challengeType = "dns"
|
||||
});
|
||||
const challengeType = "dns";
|
||||
|
||||
const getPage = async (pager: Pager) => {
|
||||
return await dnsProvider.getDomainListPage(pager)
|
||||
}
|
||||
return await dnsProvider.getDomainListPage(pager);
|
||||
};
|
||||
|
||||
const itemHandle = async (domainRecord: any) => {
|
||||
task.incrementCurrent()
|
||||
let domain = domainRecord.domain
|
||||
task.incrementCurrent();
|
||||
let domain = domainRecord.domain;
|
||||
if (domain.endsWith(".")) {
|
||||
domain = domain.slice(0, -1)
|
||||
domain = domain.slice(0, -1);
|
||||
}
|
||||
|
||||
const old = await this.findOne({
|
||||
@@ -291,8 +291,8 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
domain,
|
||||
userId,
|
||||
projectId,
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
if (old) {
|
||||
// if (old.fromType !== 'auto') {
|
||||
// //如果是手动的,跳过更新校验配置
|
||||
@@ -300,17 +300,17 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
// }
|
||||
if (old) {
|
||||
//如果old存在,直接跳过
|
||||
task.incrementSkip()
|
||||
return
|
||||
task.incrementSkip();
|
||||
return;
|
||||
}
|
||||
const updateObj: any = {
|
||||
id: old.id,
|
||||
dnsProviderType,
|
||||
dnsProviderAccess: dnsProviderAccessId,
|
||||
challengeType,
|
||||
}
|
||||
};
|
||||
//更新
|
||||
await super.update(updateObj)
|
||||
await super.update(updateObj);
|
||||
} else {
|
||||
//添加
|
||||
await this.add({
|
||||
@@ -321,81 +321,80 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
dnsProviderAccess: dnsProviderAccessId,
|
||||
challengeType,
|
||||
disabled: false,
|
||||
fromType: 'manual',
|
||||
})
|
||||
logger.info(`导入域名${domain}到用户${userId}`)
|
||||
fromType: "manual",
|
||||
});
|
||||
logger.info(`导入域名${domain}到用户${userId}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
const batchHandle = async (pageRes: PageRes<any>) => {
|
||||
task.setTotal(pageRes.total || 0)
|
||||
}
|
||||
await doPageTurn({ pager, getPage, itemHandle, batchHandle })
|
||||
const key = `user_${userId || 0}`
|
||||
logger.info(`从域名提供商${dnsProviderType}导入域名完成(${key}),共导入${task.total}个域名,跳过${task.getSkipCount()}个域名,成功${task.getSuccessCount()}个域名,失败${task.getErrorCount()}个域名`)
|
||||
task.setTotal(pageRes.total || 0);
|
||||
};
|
||||
await doPageTurn({ pager, getPage, itemHandle, batchHandle });
|
||||
const key = `user_${userId || 0}`;
|
||||
logger.info(`从域名提供商${dnsProviderType}导入域名完成(${key}),共导入${task.total}个域名,跳过${task.getSkipCount()}个域名,成功${task.getSuccessCount()}个域名,失败${task.getErrorCount()}个域名`);
|
||||
}
|
||||
|
||||
async getDomainImportTaskStatus(req: { userId?: number, projectId?: number }) {
|
||||
const userId = req.userId || 0
|
||||
const projectId = req.projectId
|
||||
async getDomainImportTaskStatus(req: { userId?: number; projectId?: number }) {
|
||||
const userId = req.userId || 0;
|
||||
const projectId = req.projectId;
|
||||
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(userId, projectId, UserDomainImportSetting)
|
||||
const list = setting?.domainImportList || []
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(userId, projectId, UserDomainImportSetting);
|
||||
const list = setting?.domainImportList || [];
|
||||
|
||||
const taskList: any = []
|
||||
const taskList: any = [];
|
||||
|
||||
for (const item of list) {
|
||||
const { key } = item
|
||||
const { key } = item;
|
||||
|
||||
const task = taskExecutor.get(DOMAIN_IMPORT_TASK_TYPE, key)
|
||||
const task = taskExecutor.get(DOMAIN_IMPORT_TASK_TYPE, key);
|
||||
|
||||
taskList.push({
|
||||
...item,
|
||||
task: task,
|
||||
})
|
||||
});
|
||||
}
|
||||
return taskList
|
||||
return taskList;
|
||||
}
|
||||
|
||||
async getProviderTitle(req: { userId?: number, projectId?: number, dnsProviderType: string, dnsProviderAccessId: number }) {
|
||||
const userId = req.userId || 0
|
||||
const projectId = req.projectId
|
||||
const { dnsProviderType, dnsProviderAccessId } = req
|
||||
const dnsProviderDefine = dnsProviderRegistry.getDefine(dnsProviderType)
|
||||
async getProviderTitle(req: { userId?: number; projectId?: number; dnsProviderType: string; dnsProviderAccessId: number }) {
|
||||
const userId = req.userId || 0;
|
||||
const projectId = req.projectId;
|
||||
const { dnsProviderType, dnsProviderAccessId } = req;
|
||||
const dnsProviderDefine = dnsProviderRegistry.getDefine(dnsProviderType);
|
||||
if (!dnsProviderDefine) {
|
||||
throw new Error(`该域名提供商(${dnsProviderType})不存在,请检查是否已被注册`)
|
||||
throw new Error(`该域名提供商(${dnsProviderType})不存在,请检查是否已被注册`);
|
||||
}
|
||||
const access = await this.accessService.getSimpleInfo(dnsProviderAccessId)
|
||||
const access = await this.accessService.getSimpleInfo(dnsProviderAccessId);
|
||||
if (!access || access.userId !== userId) {
|
||||
throw new Error(`该授权(${dnsProviderAccessId})不存在,请检查是否已被删除`)
|
||||
throw new Error(`该授权(${dnsProviderAccessId})不存在,请检查是否已被删除`);
|
||||
}
|
||||
if (projectId && access.projectId !== projectId) {
|
||||
throw new Error(`该授权(${dnsProviderAccessId})不存在,请检查是否已被删除`)
|
||||
throw new Error(`该授权(${dnsProviderAccessId})不存在,请检查是否已被删除`);
|
||||
}
|
||||
return {
|
||||
title: `${dnsProviderDefine.title}_${access.name || ''}`,
|
||||
title: `${dnsProviderDefine.title}_${access.name || ""}`,
|
||||
//@ts-ignore
|
||||
icon: dnsProviderDefine.icon || '',
|
||||
}
|
||||
icon: dnsProviderDefine.icon || "",
|
||||
};
|
||||
}
|
||||
|
||||
async addDomainImportTask(req: { userId?: number, projectId?: number, dnsProviderType: string, dnsProviderAccessId: number, index?: number }) {
|
||||
const userId = req.userId || 0
|
||||
const projectId = req.projectId
|
||||
const { dnsProviderType, dnsProviderAccessId, index = 0 } = req
|
||||
const key = `user_${userId}_${dnsProviderType}_${dnsProviderAccessId}`
|
||||
async addDomainImportTask(req: { userId?: number; projectId?: number; dnsProviderType: string; dnsProviderAccessId: number; index?: number }) {
|
||||
const userId = req.userId || 0;
|
||||
const projectId = req.projectId;
|
||||
const { dnsProviderType, dnsProviderAccessId, index = 0 } = req;
|
||||
const key = `user_${userId}_${dnsProviderType}_${dnsProviderAccessId}`;
|
||||
|
||||
const { title, icon } = await this.getProviderTitle(req)
|
||||
const { title, icon } = await this.getProviderTitle(req);
|
||||
|
||||
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(userId, projectId, UserDomainImportSetting)
|
||||
setting.domainImportList = setting.domainImportList || []
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(userId, projectId, UserDomainImportSetting);
|
||||
setting.domainImportList = setting.domainImportList || [];
|
||||
if (setting.domainImportList.find(item => item.key === key)) {
|
||||
throw new Error(`该域名导入任务${key}已存在`)
|
||||
throw new Error(`该域名导入任务${key}已存在`);
|
||||
}
|
||||
|
||||
const access = await this.accessService.getAccessById(dnsProviderAccessId, true, userId, projectId)
|
||||
const access = await this.accessService.getAccessById(dnsProviderAccessId, true, userId, projectId);
|
||||
if (!access) {
|
||||
throw new Error(`该授权(${dnsProviderAccessId})不存在,请检查是否已被删除`)
|
||||
throw new Error(`该授权(${dnsProviderAccessId})不存在,请检查是否已被删除`);
|
||||
}
|
||||
|
||||
const item = {
|
||||
@@ -403,100 +402,98 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
dnsProviderAccessId,
|
||||
key,
|
||||
title,
|
||||
icon: icon || '',
|
||||
}
|
||||
setting.domainImportList.splice(index, 0, item)
|
||||
await this.userSettingService.saveSetting(userId, projectId, setting)
|
||||
icon: icon || "",
|
||||
};
|
||||
setting.domainImportList.splice(index, 0, item);
|
||||
await this.userSettingService.saveSetting(userId, projectId, setting);
|
||||
|
||||
return item
|
||||
return item;
|
||||
}
|
||||
|
||||
async deleteDomainImportTask(req: { userId?: number, projectId?: number, key: string }) {
|
||||
const userId = req.userId || 0
|
||||
const { key } = req
|
||||
async deleteDomainImportTask(req: { userId?: number; projectId?: number; key: string }) {
|
||||
const userId = req.userId || 0;
|
||||
const { key } = req;
|
||||
|
||||
const projectId = req.projectId
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(userId, projectId, UserDomainImportSetting)
|
||||
setting.domainImportList = setting.domainImportList || []
|
||||
const index = setting.domainImportList.findIndex(item => item.key === key)
|
||||
const projectId = req.projectId;
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(userId, projectId, UserDomainImportSetting);
|
||||
setting.domainImportList = setting.domainImportList || [];
|
||||
const index = setting.domainImportList.findIndex(item => item.key === key);
|
||||
if (index === -1) {
|
||||
throw new Error(`该域名导入任务${key}不存在`)
|
||||
throw new Error(`该域名导入任务${key}不存在`);
|
||||
}
|
||||
setting.domainImportList.splice(index, 1)
|
||||
taskExecutor.clear(DOMAIN_IMPORT_TASK_TYPE, key)
|
||||
await this.userSettingService.saveSetting(userId, projectId, setting)
|
||||
setting.domainImportList.splice(index, 1);
|
||||
taskExecutor.clear(DOMAIN_IMPORT_TASK_TYPE, key);
|
||||
await this.userSettingService.saveSetting(userId, projectId, setting);
|
||||
}
|
||||
|
||||
async saveDomainImportTask(req: { userId?: number, projectId?: number, dnsProviderType: string, dnsProviderAccessId: number, key?: string }) {
|
||||
const userId = req.userId || 0
|
||||
const projectId = req.projectId
|
||||
const { dnsProviderType, dnsProviderAccessId, key } = req
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(userId, projectId, UserDomainImportSetting)
|
||||
setting.domainImportList = setting.domainImportList || []
|
||||
async saveDomainImportTask(req: { userId?: number; projectId?: number; dnsProviderType: string; dnsProviderAccessId: number; key?: string }) {
|
||||
const userId = req.userId || 0;
|
||||
const projectId = req.projectId;
|
||||
const { dnsProviderType, dnsProviderAccessId, key } = req;
|
||||
const setting = await this.userSettingService.getSetting<UserDomainImportSetting>(userId, projectId, UserDomainImportSetting);
|
||||
setting.domainImportList = setting.domainImportList || [];
|
||||
|
||||
let index = 0
|
||||
let index = 0;
|
||||
if (key) {
|
||||
index = setting.domainImportList.findIndex(item => item.key === key)
|
||||
index = setting.domainImportList.findIndex(item => item.key === key);
|
||||
if (index === -1) {
|
||||
throw new Error(`该域名导入任务${key}不存在`)
|
||||
throw new Error(`该域名导入任务${key}不存在`);
|
||||
}
|
||||
await this.deleteDomainImportTask({ userId, projectId, key })
|
||||
await this.deleteDomainImportTask({ userId, projectId, key });
|
||||
}
|
||||
|
||||
return await this.addDomainImportTask({ userId, projectId, dnsProviderType, dnsProviderAccessId, index })
|
||||
return await this.addDomainImportTask({ userId, projectId, dnsProviderType, dnsProviderAccessId, index });
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
async getSyncExpirationTaskStatus(req: { userId?: number, projectId?: number }) {
|
||||
const userId = req.userId ?? 'all'
|
||||
const projectId = req.projectId
|
||||
let key = `user_${userId}`
|
||||
async getSyncExpirationTaskStatus(req: { userId?: number; projectId?: number }) {
|
||||
const userId = req.userId ?? "all";
|
||||
const projectId = req.projectId;
|
||||
let key = `user_${userId}`;
|
||||
if (projectId != null) {
|
||||
key += `_${projectId}`
|
||||
key += `_${projectId}`;
|
||||
}
|
||||
const task = taskExecutor.get(DOMAIN_EXPIRE_TASK_TYPE, key)
|
||||
return task
|
||||
const task = taskExecutor.get(DOMAIN_EXPIRE_TASK_TYPE, key);
|
||||
return task;
|
||||
}
|
||||
|
||||
async startSyncExpirationTask(req: { userId?: number, projectId?: number }) {
|
||||
const userId = req.userId
|
||||
const projectId = req.projectId
|
||||
let key = `user_${userId ?? 'all'}`
|
||||
async startSyncExpirationTask(req: { userId?: number; projectId?: number }) {
|
||||
const userId = req.userId;
|
||||
const projectId = req.projectId;
|
||||
let key = `user_${userId ?? "all"}`;
|
||||
if (projectId != null) {
|
||||
key += `_${projectId}`
|
||||
key += `_${projectId}`;
|
||||
}
|
||||
taskExecutor.start(new BackTask({
|
||||
type: DOMAIN_EXPIRE_TASK_TYPE,
|
||||
key,
|
||||
title: `同步注册域名过期时间(${key}))`,
|
||||
run: async (task: BackTask) => {
|
||||
await this._syncDomainsExpirationDate({ userId, projectId, task })
|
||||
if (userId != null) {
|
||||
await this.startCheckDomainExpiration({ userId, projectId })
|
||||
}
|
||||
}
|
||||
}))
|
||||
taskExecutor.start(
|
||||
new BackTask({
|
||||
type: DOMAIN_EXPIRE_TASK_TYPE,
|
||||
key,
|
||||
title: `同步注册域名过期时间(${key}))`,
|
||||
run: async (task: BackTask) => {
|
||||
await this._syncDomainsExpirationDate({ userId, projectId, task });
|
||||
if (userId != null) {
|
||||
await this.startCheckDomainExpiration({ userId, projectId });
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private async _syncDomainsExpirationDate(req: { userId?: number, projectId?: number, task: BackTask }) {
|
||||
|
||||
private async _syncDomainsExpirationDate(req: { userId?: number; projectId?: number; task: BackTask }) {
|
||||
//同步所有域名的过期时间
|
||||
const pager = new Pager({
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
})
|
||||
});
|
||||
|
||||
const tldClient = new TldClient();
|
||||
const query: any = {
|
||||
challengeType: "dns",
|
||||
}
|
||||
};
|
||||
if (req.userId != null) {
|
||||
query.userId = req.userId
|
||||
query.userId = req.userId;
|
||||
}
|
||||
if (req.projectId != null) {
|
||||
query.projectId = req.projectId
|
||||
query.projectId = req.projectId;
|
||||
}
|
||||
const getDomainPage = async (pager: Pager) => {
|
||||
const pageRes = await this.page({
|
||||
@@ -507,62 +504,61 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
page: {
|
||||
offset: pager.getOffset(),
|
||||
limit: pager.pageSize,
|
||||
}
|
||||
})
|
||||
req.task.total = pageRes.total
|
||||
},
|
||||
});
|
||||
req.task.total = pageRes.total;
|
||||
return {
|
||||
list: pageRes.records,
|
||||
total: pageRes.total,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const itemHandle = async (item: any) => {
|
||||
req.task.incrementCurrent()
|
||||
req.task.incrementCurrent();
|
||||
try {
|
||||
const res = await tldClient.getDomainExpirationDate(item.domain)
|
||||
const { expirationDate, registrationDate } = res
|
||||
const res = await tldClient.getDomainExpirationDate(item.domain);
|
||||
const { expirationDate, registrationDate } = res;
|
||||
if (!expirationDate) {
|
||||
req.task.addError(`【${item.domain}】获取域名${item.domain}过期时间失败`)
|
||||
return
|
||||
req.task.addError(`【${item.domain}】获取域名${item.domain}过期时间失败`);
|
||||
return;
|
||||
}
|
||||
logger.info(`【${item.domain}】更新域名过期时间:${dayjs(expirationDate).format('YYYY-MM-DD')}`)
|
||||
logger.info(`【${item.domain}】更新域名过期时间:${dayjs(expirationDate).format("YYYY-MM-DD")}`);
|
||||
const updateObj: any = {
|
||||
id: item.id,
|
||||
expirationDate: expirationDate,
|
||||
registrationDate: registrationDate,
|
||||
}
|
||||
};
|
||||
//更新
|
||||
await super.update(updateObj)
|
||||
await super.update(updateObj);
|
||||
} catch (error) {
|
||||
const errorMsg = `【${item.domain}】${error.message ?? error}`
|
||||
req.task.addError(errorMsg)
|
||||
logger.error(errorMsg)
|
||||
const errorMsg = `【${item.domain}】${error.message ?? error}`;
|
||||
req.task.addError(errorMsg);
|
||||
logger.error(errorMsg);
|
||||
} finally {
|
||||
await utils.sleep(1000)
|
||||
await utils.sleep(1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await doPageTurn({ pager, getPage: getDomainPage, itemHandle: itemHandle })
|
||||
const key = `user_${req.userId || 'all'}`
|
||||
const log = `同步用户(${key})注册域名过期时间完成(${req.task.getSuccessCount()}个成功,${req.task.getErrorCount()}个失败)`
|
||||
logger.info(log)
|
||||
await doPageTurn({ pager, getPage: getDomainPage, itemHandle: itemHandle });
|
||||
const key = `user_${req.userId || "all"}`;
|
||||
const log = `同步用户(${key})注册域名过期时间完成(${req.task.getSuccessCount()}个成功,${req.task.getErrorCount()}个失败)`;
|
||||
logger.info(log);
|
||||
}
|
||||
|
||||
|
||||
public async startCheckDomainExpiration(req: { userId?: number, projectId?: number }) {
|
||||
const { userId, projectId } = req
|
||||
public async startCheckDomainExpiration(req: { userId?: number; projectId?: number }) {
|
||||
const { userId, projectId } = req;
|
||||
if (userId == null) {
|
||||
throw new Error('userId is required');
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
|
||||
if (projectId && !isEnterprise()) {
|
||||
logger.warn(`当前未开启企业模式,跳过检查项目(${projectId})的域名过期时间`)
|
||||
return
|
||||
logger.warn(`当前未开启企业模式,跳过检查项目(${projectId})的域名过期时间`);
|
||||
return;
|
||||
}
|
||||
|
||||
const setting = await this.monitorSettingGet({ userId, projectId })
|
||||
const setting = await this.monitorSettingGet({ userId, projectId });
|
||||
if (!setting || !setting.enabled) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const jobHistory: any = {
|
||||
@@ -572,46 +568,46 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
title: `检查注册域名过期时间`,
|
||||
startAt: dayjs().valueOf(),
|
||||
result: "start",
|
||||
}
|
||||
await this.jobHistoryService.add(jobHistory)
|
||||
};
|
||||
await this.jobHistoryService.add(jobHistory);
|
||||
|
||||
const expireDays = setting.willExpireDays || 30
|
||||
const ltTime = dayjs().add(expireDays, 'day').valueOf()
|
||||
const expireDays = setting.willExpireDays || 30;
|
||||
const ltTime = dayjs().add(expireDays, "day").valueOf();
|
||||
|
||||
const total = await this.repository.count({
|
||||
where:{
|
||||
where: {
|
||||
userId,
|
||||
projectId,
|
||||
disabled: false,
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
//开始检查域名过期时间
|
||||
const list = await this.repository.find({
|
||||
where: {
|
||||
userId,
|
||||
projectId,
|
||||
disabled: false,
|
||||
expirationDate: LessThan(ltTime)
|
||||
}
|
||||
})
|
||||
expirationDate: LessThan(ltTime),
|
||||
},
|
||||
});
|
||||
|
||||
const now = dayjs().valueOf()
|
||||
let willExpireDomains = []
|
||||
let hasExpireDomains = []
|
||||
const now = dayjs().valueOf();
|
||||
const willExpireDomains = [];
|
||||
const hasExpireDomains = [];
|
||||
|
||||
for (const item of list) {
|
||||
const { expirationDate } = item
|
||||
const leftDays = dayjs(expirationDate).diff(dayjs(), 'day')
|
||||
const { expirationDate } = item;
|
||||
const leftDays = dayjs(expirationDate).diff(dayjs(), "day");
|
||||
//@ts-ignore
|
||||
item.leftDays = leftDays
|
||||
item.leftDays = leftDays;
|
||||
if (expirationDate < now) {
|
||||
hasExpireDomains.push(item)
|
||||
hasExpireDomains.push(item);
|
||||
} else {
|
||||
willExpireDomains.push(item)
|
||||
willExpireDomains.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
const title = `域名过期检查:即将过期 ${willExpireDomains.length} 个域名,已过期 ${hasExpireDomains.length} 个域名,共 ${total} 个域名`
|
||||
const title = `域名过期检查:即将过期 ${willExpireDomains.length} 个域名,已过期 ${hasExpireDomains.length} 个域名,共 ${total} 个域名`;
|
||||
|
||||
try {
|
||||
await this.jobHistoryService.update({
|
||||
@@ -619,30 +615,29 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
content: title,
|
||||
result: "done",
|
||||
endAt: dayjs().valueOf(),
|
||||
})
|
||||
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`更新域名过期检查任务状态失败:${error.message ?? error}`)
|
||||
logger.error(`更新域名过期检查任务状态失败:${error.message ?? error}`);
|
||||
}
|
||||
|
||||
if (list.length == 0) {
|
||||
//没有过期域名 不发通知
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
//发送通知
|
||||
const willExpireDomainsStr = willExpireDomains.map(item => `${item.domain} (剩余${item.leftDays}天)`).join('\n ')
|
||||
const hasExpireDomainsStr = hasExpireDomains.map(item => `${item.domain} (已过期${item.leftDays}天)`).join('\n ')
|
||||
const willExpireDomainsStr = willExpireDomains.map(item => `${item.domain} (剩余${item.leftDays}天)`).join("\n ");
|
||||
const hasExpireDomainsStr = hasExpireDomains.map(item => `${item.domain} (已过期${item.leftDays}天)`).join("\n ");
|
||||
const content = `您有域名即将过期,请尽快续费
|
||||
|
||||
即将过期域名: ${willExpireDomains.length} 个 (有效期<${expireDays}天)
|
||||
${willExpireDomainsStr}
|
||||
|
||||
已过期域名: ${hasExpireDomains.length} 个
|
||||
${hasExpireDomainsStr}`
|
||||
${hasExpireDomainsStr}`;
|
||||
const taskService = this.taskServiceBuilder.create({ userId: userId, projectId: projectId });
|
||||
|
||||
const notificationService = await taskService.getNotificationService()
|
||||
const notificationService = await taskService.getNotificationService();
|
||||
const url = await notificationService.getBindUrl("#/certd/cert/domain");
|
||||
await notificationService.send({
|
||||
id: setting.notificationId,
|
||||
@@ -656,39 +651,37 @@ export class DomainService extends BaseService<DomainEntity> {
|
||||
notificationType: DOMAIN_EXPIRE_CHECK_TYPE,
|
||||
willExpireDomains,
|
||||
hasExpireDomains,
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public async monitorSettingGet(req: { userId?: number, projectId?: number }) {
|
||||
const { userId, projectId } = req
|
||||
const setting = await this.userSettingService.getSetting<UserDomainMonitorSetting>(userId, projectId, UserDomainMonitorSetting)
|
||||
return setting || {}
|
||||
public async monitorSettingGet(req: { userId?: number; projectId?: number }) {
|
||||
const { userId, projectId } = req;
|
||||
const setting = await this.userSettingService.getSetting<UserDomainMonitorSetting>(userId, projectId, UserDomainMonitorSetting);
|
||||
return setting || {};
|
||||
}
|
||||
|
||||
public async monitorSettingSave(req: { userId?: number, projectId?: number, setting?: any }) {
|
||||
const { userId, projectId, setting } = req
|
||||
const bean: UserDomainMonitorSetting = new UserDomainMonitorSetting()
|
||||
merge(bean, setting)
|
||||
await this.userSettingService.saveSetting<UserDomainMonitorSetting>(userId, projectId, bean)
|
||||
await this.registerMonitorCron({ userId, projectId })
|
||||
public async monitorSettingSave(req: { userId?: number; projectId?: number; setting?: any }) {
|
||||
const { userId, projectId, setting } = req;
|
||||
const bean: UserDomainMonitorSetting = new UserDomainMonitorSetting();
|
||||
merge(bean, setting);
|
||||
await this.userSettingService.saveSetting<UserDomainMonitorSetting>(userId, projectId, bean);
|
||||
await this.registerMonitorCron({ userId, projectId });
|
||||
}
|
||||
|
||||
public async registerMonitorCron(req: { userId?: number, projectId?: number }) {
|
||||
const { userId, projectId } = req
|
||||
const setting = await this.monitorSettingGet(req)
|
||||
const key = `${DOMAIN_EXPIRE_CHECK_TYPE}:${userId}_${projectId || ''}`
|
||||
this.cron.remove(key)
|
||||
public async registerMonitorCron(req: { userId?: number; projectId?: number }) {
|
||||
const { userId, projectId } = req;
|
||||
const setting = await this.monitorSettingGet(req);
|
||||
const key = `${DOMAIN_EXPIRE_CHECK_TYPE}:${userId}_${projectId || ""}`;
|
||||
this.cron.remove(key);
|
||||
if (setting.enabled) {
|
||||
this.cron.register({
|
||||
cron: setting.cron,
|
||||
name: key,
|
||||
job: async () => {
|
||||
await this.startCheckDomainExpiration({ userId, projectId })
|
||||
await this.startCheckDomainExpiration({ userId, projectId });
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export class TldClient {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
let res: DomainInfo = {};
|
||||
const res: DomainInfo = {};
|
||||
const events = rdap.events || [];
|
||||
for (const item of events) {
|
||||
if (item.eventAction === "expiration") {
|
||||
@@ -96,7 +96,7 @@ export class TldClient {
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
let res: DomainInfo = {};
|
||||
const res: DomainInfo = {};
|
||||
/**
|
||||
* {
|
||||
"Domain Status": [
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
/**
|
||||
* cname配置
|
||||
*/
|
||||
@Entity('cd_cname_provider')
|
||||
@Entity("cd_cname_provider")
|
||||
export class CnameProviderEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
@Column({ comment: 'userId', name: 'user_id' })
|
||||
@Column({ comment: "userId", name: "user_id" })
|
||||
userId: number;
|
||||
@Column({ comment: '域名', length: 100 })
|
||||
@Column({ comment: "域名", length: 100 })
|
||||
domain: string;
|
||||
@Column({ comment: '子域名托管', length: 100, nullable: true })
|
||||
@Column({ comment: "子域名托管", length: 100, nullable: true })
|
||||
subdomain: string;
|
||||
@Column({ comment: 'DNS提供商类型', name: 'dns_provider_type', length: 20 })
|
||||
@Column({ comment: "DNS提供商类型", name: "dns_provider_type", length: 20 })
|
||||
dnsProviderType: string;
|
||||
@Column({ comment: 'DNS授权Id', name: 'access_id' })
|
||||
@Column({ comment: "DNS授权Id", name: "access_id" })
|
||||
accessId: number;
|
||||
@Column({ comment: '是否默认', name: 'is_default' })
|
||||
@Column({ comment: "是否默认", name: "is_default" })
|
||||
isDefault: boolean;
|
||||
@Column({ comment: '是否禁用', name: 'disabled' })
|
||||
@Column({ comment: "是否禁用", name: "disabled" })
|
||||
disabled: boolean;
|
||||
@Column({ comment: '备注', length: 200 })
|
||||
@Column({ comment: "备注", length: 200 })
|
||||
remark: string;
|
||||
|
||||
@Column({
|
||||
comment: '创建时间',
|
||||
name: 'create_time',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
comment: "创建时间",
|
||||
name: "create_time",
|
||||
default: () => "CURRENT_TIMESTAMP",
|
||||
})
|
||||
createTime: Date;
|
||||
@Column({
|
||||
comment: '修改时间',
|
||||
name: 'update_time',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
comment: "修改时间",
|
||||
name: "update_time",
|
||||
default: () => "CURRENT_TIMESTAMP",
|
||||
})
|
||||
updateTime: Date;
|
||||
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
export type CnameRecordStatusType = 'cname' | 'validating' | 'valid' | 'error' | 'timeout';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
export type CnameRecordStatusType = "cname" | "validating" | "valid" | "error" | "timeout";
|
||||
/**
|
||||
* cname record配置
|
||||
*/
|
||||
@Entity('cd_cname_record')
|
||||
@Entity("cd_cname_record")
|
||||
export class CnameRecordEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ comment: '用户ID', name: 'user_id' })
|
||||
@Column({ comment: "用户ID", name: "user_id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ comment: '证书申请域名', length: 100 })
|
||||
@Column({ comment: "证书申请域名", length: 100 })
|
||||
domain: string;
|
||||
@Column({ comment: '主域名', name: 'main_domain', length: 100 })
|
||||
mainDomain:string;
|
||||
@Column({ comment: "主域名", name: "main_domain", length: 100 })
|
||||
mainDomain: string;
|
||||
|
||||
@Column({ comment: '主机记录', name: 'host_record', length: 100 })
|
||||
@Column({ comment: "主机记录", name: "host_record", length: 100 })
|
||||
hostRecord: string;
|
||||
|
||||
@Column({ comment: '记录值', name: 'record_value', length: 200 })
|
||||
@Column({ comment: "记录值", name: "record_value", length: 200 })
|
||||
recordValue: string;
|
||||
|
||||
@Column({ comment: 'CNAME提供者', name: 'cname_provider_id' })
|
||||
@Column({ comment: "CNAME提供者", name: "cname_provider_id" })
|
||||
cnameProviderId: number;
|
||||
|
||||
@Column({ comment: '验证状态', length: 20 })
|
||||
@Column({ comment: "验证状态", length: 20 })
|
||||
status: string;
|
||||
|
||||
@Column({ comment: '错误信息' })
|
||||
error: string
|
||||
@Column({ comment: "错误信息" })
|
||||
error: string;
|
||||
|
||||
@Column({ name: 'project_id', comment: '项目Id' })
|
||||
@Column({ name: "project_id", comment: "项目Id" })
|
||||
projectId: number;
|
||||
|
||||
@Column({
|
||||
comment: '创建时间',
|
||||
name: 'create_time',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
comment: "创建时间",
|
||||
name: "create_time",
|
||||
default: () => "CURRENT_TIMESTAMP",
|
||||
})
|
||||
createTime: Date;
|
||||
@Column({
|
||||
comment: '修改时间',
|
||||
name: 'update_time',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
comment: "修改时间",
|
||||
name: "update_time",
|
||||
default: () => "CURRENT_TIMESTAMP",
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BaseService, ListReq, SysPrivateSettings, SysSettingsService, ValidateException } from '@certd/lib-server';
|
||||
import { CnameProviderEntity } from '../entity/cname-provider.js';
|
||||
import { CommonProviders } from './common-provider.js';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { BaseService, ListReq, SysPrivateSettings, SysSettingsService, ValidateException } from "@certd/lib-server";
|
||||
import { CnameProviderEntity } from "../entity/cname-provider.js";
|
||||
import { CommonProviders } from "./common-provider.js";
|
||||
|
||||
/**
|
||||
* 授权
|
||||
@@ -63,7 +63,7 @@ export class CnameProviderService extends BaseService<CnameProviderEntity> {
|
||||
for (const id of ids) {
|
||||
const info = await this.info(id);
|
||||
if (info.isDefault) {
|
||||
throw new ValidateException('默认的CNAME服务不能删除,请先修改为非默认值');
|
||||
throw new ValidateException("默认的CNAME服务不能删除,请先修改为非默认值");
|
||||
}
|
||||
}
|
||||
await super.delete(ids);
|
||||
@@ -85,7 +85,7 @@ export class CnameProviderService extends BaseService<CnameProviderEntity> {
|
||||
if (def) {
|
||||
return def;
|
||||
}
|
||||
const founds = await this.repository.find({ take: 1, order: { createTime: 'DESC' }, where: { disabled: false } });
|
||||
const founds = await this.repository.find({ take: 1, order: { createTime: "DESC" }, where: { disabled: false } });
|
||||
if (founds && founds.length > 0) {
|
||||
return founds[0];
|
||||
}
|
||||
@@ -127,5 +127,4 @@ export class CnameProviderService extends BaseService<CnameProviderEntity> {
|
||||
}
|
||||
return await super.info(id, infoIgnoreProperty);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { createChallengeFn, getAuthoritativeDnsResolver } from "@certd/acme-client";
|
||||
import { cache, http, isDev, logger, utils } from "@certd/basic";
|
||||
import {
|
||||
AccessService,
|
||||
BaseService,
|
||||
PlusService,
|
||||
SysInstallInfo,
|
||||
SysSettingsService,
|
||||
ValidateException
|
||||
} from "@certd/lib-server";
|
||||
import { AccessService, BaseService, PlusService, SysInstallInfo, SysSettingsService, ValidateException } from "@certd/lib-server";
|
||||
import { CnameProvider, CnameRecord } from "@certd/pipeline";
|
||||
import { createDnsProvider, DomainParser, IDnsProvider } from "@certd/plugin-cert";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
@@ -58,7 +51,6 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
@Inject()
|
||||
taskServiceBuilder: TaskServiceBuilder;
|
||||
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
@@ -135,7 +127,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
throw new ValidateException("id不能为空");
|
||||
}
|
||||
//hostRecord包含所有权校验信息,不允许用户修改hostRecord
|
||||
delete param.hostRecord
|
||||
delete param.hostRecord;
|
||||
|
||||
const old = await this.info(param.id);
|
||||
if (!old) {
|
||||
@@ -164,32 +156,32 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
// // 3. 检查原域名是否有cname记录
|
||||
// }
|
||||
|
||||
async getWithAccessByDomain(domain: string, userId: number,projectId?:number) {
|
||||
const record: CnameRecord = await this.getByDomain(domain, userId,projectId);
|
||||
async getWithAccessByDomain(domain: string, userId: number, projectId?: number) {
|
||||
const record: CnameRecord = await this.getByDomain(domain, userId, projectId);
|
||||
if (record.cnameProvider.id > 0) {
|
||||
//自定义cname服务
|
||||
record.cnameProvider.access = await this.accessService.getAccessById(record.cnameProvider.accessId, false);
|
||||
} else {
|
||||
record.commonDnsProvider = new CommonDnsProvider({
|
||||
config: record.cnameProvider,
|
||||
plusService: this.plusService
|
||||
plusService: this.plusService,
|
||||
});
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
async getByDomain(domain: string, userId: number,projectId?:number, createOnNotFound = true) {
|
||||
async getByDomain(domain: string, userId: number, projectId?: number, createOnNotFound = true) {
|
||||
if (!domain) {
|
||||
throw new ValidateException("domain不能为空");
|
||||
}
|
||||
if (userId == null) {
|
||||
throw new ValidateException("userId不能为空");
|
||||
}
|
||||
let record = await this.getRepository().findOne({ where: { domain, userId,projectId } });
|
||||
let record = await this.getRepository().findOne({ where: { domain, userId, projectId } });
|
||||
if (record == null) {
|
||||
if (createOnNotFound) {
|
||||
record = await this.add({ domain, userId,projectId });
|
||||
record = await this.add({ domain, userId, projectId });
|
||||
} else {
|
||||
throw new ValidateException(`找不到${domain}的CNAME记录`);
|
||||
}
|
||||
@@ -205,8 +197,8 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return {
|
||||
...record,
|
||||
cnameProvider: {
|
||||
...provider
|
||||
} as CnameProvider
|
||||
...provider,
|
||||
} as CnameProvider,
|
||||
} as CnameRecord;
|
||||
}
|
||||
|
||||
@@ -222,17 +214,16 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
if (domainPrefix) {
|
||||
const prefixStr = domainPrefix + ".";
|
||||
record.mainDomain = record.domain.substring(prefixStr.length);
|
||||
}else{
|
||||
} else {
|
||||
record.mainDomain = record.domain;
|
||||
}
|
||||
|
||||
if (update) {
|
||||
await this.update({
|
||||
id: record.id,
|
||||
mainDomain: record.mainDomain
|
||||
mainDomain: record.mainDomain,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,8 +232,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
* @param id
|
||||
*/
|
||||
async verify(id: number) {
|
||||
|
||||
const {walkTxtRecord} = createChallengeFn({logger});
|
||||
const { walkTxtRecord } = createChallengeFn({ logger });
|
||||
const bean = await this.info(id);
|
||||
if (!bean) {
|
||||
throw new ValidateException(`CnameRecord:${id} 不存在`);
|
||||
@@ -251,7 +241,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return true;
|
||||
}
|
||||
|
||||
await this.getByDomain(bean.domain, bean.userId,bean.projectId);
|
||||
await this.getByDomain(bean.domain, bean.userId, bean.projectId);
|
||||
const taskService = this.taskServiceBuilder.create({ userId: bean.userId, projectId: bean.projectId });
|
||||
const subDomainGetter = await taskService.getSubDomainsGetter();
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
@@ -263,7 +253,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
value = {
|
||||
validating: false,
|
||||
pass: false,
|
||||
startTime: new Date().getTime()
|
||||
startTime: new Date().getTime(),
|
||||
};
|
||||
}
|
||||
let ttl = 5 * 60 * 1000;
|
||||
@@ -285,17 +275,17 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
//公共CNAME
|
||||
return new CommonDnsProvider({
|
||||
config: cnameProvider,
|
||||
plusService: this.plusService
|
||||
plusService: this.plusService,
|
||||
});
|
||||
}
|
||||
|
||||
const record = await this.getWithAccessByDomain(bean.domain, bean.userId,bean.projectId);
|
||||
const record = await this.getWithAccessByDomain(bean.domain, bean.userId, bean.projectId);
|
||||
|
||||
const access = record.cnameProvider.access
|
||||
const context = { access, logger, http, utils, domainParser, serviceGetter:taskService };
|
||||
const access = record.cnameProvider.access;
|
||||
const context = { access, logger, http, utils, domainParser, serviceGetter: taskService };
|
||||
const dnsProvider: IDnsProvider = await createDnsProvider({
|
||||
dnsProviderType: cnameProvider.dnsProviderType,
|
||||
context
|
||||
context,
|
||||
});
|
||||
return dnsProvider;
|
||||
};
|
||||
@@ -309,7 +299,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
await dnsProvider.removeRecord({
|
||||
recordReq: value.recordReq,
|
||||
recordRes: value.recordRes
|
||||
recordRes: value.recordRes,
|
||||
});
|
||||
logger.info("删除CNAME的校验DNS记录成功");
|
||||
} catch (e) {
|
||||
@@ -329,7 +319,6 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const originDomain = await domainParser.parse(bean.domain);
|
||||
const fullDomain = `${bean.hostRecord}.${originDomain}`;
|
||||
|
||||
@@ -366,7 +355,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
|
||||
cache.set(cacheKey, value, {
|
||||
ttl: ttl
|
||||
ttl: ttl,
|
||||
});
|
||||
|
||||
const domain = await domainParser.parse(bean.recordValue);
|
||||
@@ -377,7 +366,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
fullRecord: fullRecord,
|
||||
hostRecord: hostRecord,
|
||||
type: "TXT",
|
||||
value: testRecordValue
|
||||
value: testRecordValue,
|
||||
};
|
||||
|
||||
const dnsProvider = await buildDnsProvider();
|
||||
@@ -418,8 +407,6 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
|
||||
async checkRepeatAcmeChallengeRecords(acmeRecordDomain: string, targetCnameDomain: string) {
|
||||
|
||||
|
||||
let dnsResolver = null;
|
||||
try {
|
||||
dnsResolver = await getAuthoritativeDnsResolver(acmeRecordDomain);
|
||||
@@ -443,7 +430,6 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
throw new Error(`${acmeRecordDomain}存在多个CNAME记录,请删除多余的CNAME记录:${cnameRecord}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 确保权威服务器里面没有纯粹的TXT记录
|
||||
@@ -465,7 +451,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return;
|
||||
}
|
||||
|
||||
const {walkTxtRecord} = createChallengeFn({logger});
|
||||
const { walkTxtRecord } = createChallengeFn({ logger });
|
||||
|
||||
if (cnameRecords.length > 0) {
|
||||
// 从cname记录中获取txt记录
|
||||
@@ -479,7 +465,6 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async resetStatus(id: number) {
|
||||
@@ -489,27 +474,32 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
await this.getRepository().update(id, { status: "cname", mainDomain: "" });
|
||||
}
|
||||
|
||||
async doImport(req:{ userId: number; projectId: number; domainList: string; cnameProviderId: any }) {
|
||||
const {userId,projectId,cnameProviderId,domainList} = req;
|
||||
const domains = domainList.split("\n").map(item => item.trim()).filter(item => item.length > 0);
|
||||
async doImport(req: { userId: number; projectId: number; domainList: string; cnameProviderId: any }) {
|
||||
const { userId, projectId, cnameProviderId, domainList } = req;
|
||||
const domains = domainList
|
||||
.split("\n")
|
||||
.map(item => item.trim())
|
||||
.filter(item => item.length > 0);
|
||||
if (domains.length === 0) {
|
||||
throw new ValidateException("域名列表不能为空");
|
||||
}
|
||||
if (!req.cnameProviderId) {
|
||||
throw new ValidateException("CNAME服务提供商不能为空");
|
||||
}
|
||||
|
||||
taskExecutor.start(new BackTask({
|
||||
type:"cnameImport",
|
||||
key: "user_"+userId,
|
||||
title: "导入CNAME记录",
|
||||
run: async (task) => {
|
||||
await this._import({ userId,projectId, domains, cnameProviderId },task);
|
||||
}
|
||||
}));
|
||||
|
||||
taskExecutor.start(
|
||||
new BackTask({
|
||||
type: "cnameImport",
|
||||
key: "user_" + userId,
|
||||
title: "导入CNAME记录",
|
||||
run: async task => {
|
||||
await this._import({ userId, projectId, domains, cnameProviderId }, task);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async _import(req :{ userId: number; projectId: number; domains: string[]; cnameProviderId: any },task:BackTask) {
|
||||
async _import(req: { userId: number; projectId: number; domains: string[]; cnameProviderId: any }, task: BackTask) {
|
||||
const userId = req.userId;
|
||||
for (const domain of req.domains) {
|
||||
const old = await this.getRepository().findOne({
|
||||
@@ -523,17 +513,16 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
logger.warn(`域名${domain}已存在,跳过`);
|
||||
}
|
||||
//开始导入
|
||||
try{
|
||||
await this.add({
|
||||
userId,
|
||||
domain: domain,
|
||||
projectId: req.projectId,
|
||||
cnameProviderId: req.cnameProviderId,
|
||||
});
|
||||
}catch(e){
|
||||
try {
|
||||
await this.add({
|
||||
userId,
|
||||
domain: domain,
|
||||
projectId: req.projectId,
|
||||
cnameProviderId: req.cnameProviderId,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(`导入域名${domain}失败:${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {CreateRecordOptions, DnsProviderContext, DomainRecord, IDnsProvider, RemoveRecordOptions} from '@certd/plugin-cert';
|
||||
import {PlusService} from '@certd/lib-server';
|
||||
import punycode from 'punycode.js'
|
||||
import { Pager, PageRes } from '@certd/pipeline';
|
||||
import { CreateRecordOptions, DnsProviderContext, DomainRecord, IDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
|
||||
import { PlusService } from "@certd/lib-server";
|
||||
import punycode from "punycode.js";
|
||||
import { Pager, PageRes } from "@certd/pipeline";
|
||||
export type CommonCnameProvider = {
|
||||
id: number;
|
||||
domain: string;
|
||||
@@ -10,8 +10,8 @@ export type CommonCnameProvider = {
|
||||
export const CommonProviders = [
|
||||
{
|
||||
id: -1,
|
||||
domain: 'cname.certd.com.cn',
|
||||
title: '公共CNAME服务',
|
||||
domain: "cname.certd.com.cn",
|
||||
title: "公共CNAME服务",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -25,7 +25,7 @@ export class CommonDnsProvider implements IDnsProvider {
|
||||
this.plusService = opts.plusService;
|
||||
}
|
||||
getDomainListPage(pager: Pager): Promise<PageRes<DomainRecord>> {
|
||||
throw new Error('公共CNAME服务不支持获取域名列表');
|
||||
throw new Error("公共CNAME服务不支持获取域名列表");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,24 +44,22 @@ export class CommonDnsProvider implements IDnsProvider {
|
||||
return punycode.decode(domain);
|
||||
}
|
||||
|
||||
|
||||
usePunyCode(): boolean {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
async onInstance() {
|
||||
}
|
||||
async onInstance() {}
|
||||
|
||||
async createRecord(options: CreateRecordOptions) {
|
||||
if (!this.config.domain.endsWith(options.domain)) {
|
||||
throw new Error('cname服务域名不匹配');
|
||||
throw new Error("cname服务域名不匹配");
|
||||
}
|
||||
|
||||
await this.plusService.register();
|
||||
|
||||
const res = await this.plusService.requestWithToken({
|
||||
url: '/activation/certd/cname/recordCreate',
|
||||
method: 'post',
|
||||
url: "/activation/certd/cname/recordCreate",
|
||||
method: "post",
|
||||
data: {
|
||||
subjectId: await this.plusService.getSubjectId(),
|
||||
domain: options.domain,
|
||||
@@ -75,8 +73,8 @@ export class CommonDnsProvider implements IDnsProvider {
|
||||
|
||||
async removeRecord(options: RemoveRecordOptions<any>) {
|
||||
const res = await this.plusService.requestWithToken({
|
||||
url: '/activation/certd/cname/recordRemove',
|
||||
method: 'post',
|
||||
url: "/activation/certd/cname/recordRemove",
|
||||
method: "post",
|
||||
data: {
|
||||
subjectId: await this.plusService.getSubjectId(),
|
||||
domain: options.recordReq.domain,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { logger } from '@certd/basic';
|
||||
import { Config, Configuration, IMidwayContainer } from '@midwayjs/core';
|
||||
import { Cron } from './cron.js';
|
||||
import { logger } from "@certd/basic";
|
||||
import { Config, Configuration, IMidwayContainer } from "@midwayjs/core";
|
||||
import { Cron } from "./cron.js";
|
||||
|
||||
// ... (see below) ...
|
||||
@Configuration({
|
||||
namespace: 'cron',
|
||||
namespace: "cron",
|
||||
//importConfigs: [join(__dirname, './config')],
|
||||
})
|
||||
export class CronConfiguration {
|
||||
@@ -12,13 +12,13 @@ export class CronConfiguration {
|
||||
config;
|
||||
cron: Cron;
|
||||
async onReady(container: IMidwayContainer) {
|
||||
logger.info('cron start');
|
||||
logger.info("cron start");
|
||||
this.cron = new Cron({
|
||||
logger: logger,
|
||||
...this.config,
|
||||
});
|
||||
container.registerObject('cron', this.cron);
|
||||
container.registerObject("cron", this.cron);
|
||||
this.cron.start();
|
||||
logger.info('cron started');
|
||||
logger.info("cron started");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import parser from 'cron-parser';
|
||||
import { ILogger, logger } from '@certd/basic';
|
||||
import parser from "cron-parser";
|
||||
import { ILogger, logger } from "@certd/basic";
|
||||
|
||||
export type CronTaskReq = {
|
||||
/**
|
||||
@@ -57,7 +57,7 @@ export class Cron {
|
||||
}
|
||||
|
||||
start() {
|
||||
this.logger.info('[cron] start');
|
||||
this.logger.info("[cron] start");
|
||||
this.queue.forEach(task => {
|
||||
task.genNextTime();
|
||||
});
|
||||
@@ -85,11 +85,11 @@ export class Cron {
|
||||
}
|
||||
this.logger.info(`[cron] register cron : [${req.name}] ,${req.cron}`);
|
||||
|
||||
this.remove(req.name)
|
||||
this.remove(req.name);
|
||||
|
||||
const task = new CronTask(req, this.logger);
|
||||
this.queue.push(task);
|
||||
this.logger.info('当前定时任务数量:', this.getTaskSize());
|
||||
this.logger.info("当前定时任务数量:", this.getTaskSize());
|
||||
}
|
||||
|
||||
remove(taskName: string) {
|
||||
@@ -99,7 +99,7 @@ export class Cron {
|
||||
this.queue[index].stop();
|
||||
this.queue.splice(index, 1);
|
||||
}
|
||||
this.logger.info('当前定时任务数量:', this.getTaskSize());
|
||||
this.logger.info("当前定时任务数量:", this.getTaskSize());
|
||||
}
|
||||
|
||||
getTaskSize() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/index.ts
|
||||
export { CronConfiguration as Configuration } from './configuration.js';
|
||||
export { CronConfiguration as Configuration } from "./configuration.js";
|
||||
// export * from './controller/user';
|
||||
// export * from './controller/api';
|
||||
// export * from './service/user';
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { SqliteAdapter } from './sqlite.js';
|
||||
import { PostgresqlAdapter } from './postgresql.js';
|
||||
import { Config, Init, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { SqlAdapter } from './d.js';
|
||||
import { MysqlAdapter } from './mysql.js';
|
||||
import { SqliteAdapter } from "./sqlite.js";
|
||||
import { PostgresqlAdapter } from "./postgresql.js";
|
||||
import { Config, Init, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { SqlAdapter } from "./d.js";
|
||||
import { MysqlAdapter } from "./mysql.js";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class DbAdapter implements SqlAdapter {
|
||||
adapter: SqlAdapter;
|
||||
@Config('typeorm.dataSource.default.type')
|
||||
@Config("typeorm.dataSource.default.type")
|
||||
dbType: string;
|
||||
|
||||
@Init()
|
||||
@@ -25,13 +25,13 @@ export class DbAdapter implements SqlAdapter {
|
||||
}
|
||||
|
||||
isSqlite() {
|
||||
return this.dbType === 'better-sqlite3';
|
||||
return this.dbType === "better-sqlite3";
|
||||
}
|
||||
isPostgresql() {
|
||||
return this.dbType === 'postgres';
|
||||
return this.dbType === "postgres";
|
||||
}
|
||||
isMysql() {
|
||||
return this.dbType === 'mysql' || this.dbType === 'mariadb';
|
||||
return this.dbType === "mysql" || this.dbType === "mariadb";
|
||||
}
|
||||
|
||||
date(columnName: string) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SqlAdapter } from './d.js';
|
||||
import { SqlAdapter } from "./d.js";
|
||||
|
||||
export class MysqlAdapter implements SqlAdapter {
|
||||
date(columnName: string) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SqlAdapter } from './d.js';
|
||||
import { SqlAdapter } from "./d.js";
|
||||
|
||||
export class PostgresqlAdapter implements SqlAdapter {
|
||||
date(columnName: string) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SqlAdapter } from './d.js';
|
||||
import { SqlAdapter } from "./d.js";
|
||||
|
||||
export class SqliteAdapter implements SqlAdapter {
|
||||
date(columnName: string) {
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('cd_oauth_bound')
|
||||
@Entity("cd_oauth_bound")
|
||||
export class OauthBoundEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'type', comment: '第三方类型' })
|
||||
@Column({ name: "type", comment: "第三方类型" })
|
||||
type: string; // oidc, wechat, github, gitee , qq , alipay
|
||||
|
||||
@Column({ name: 'open_id', comment: '第三方openid' })
|
||||
@Column({ name: "open_id", comment: "第三方openid" })
|
||||
openId: string;
|
||||
|
||||
@Column({ name: 'create_time',comment: '创建时间', default: () => 'CURRENT_TIMESTAMP',})
|
||||
@Column({ name: "create_time", comment: "创建时间", default: () => "CURRENT_TIMESTAMP" })
|
||||
createTime: Date;
|
||||
|
||||
@Column({ name: 'update_time', comment: '修改时间',default: () => 'CURRENT_TIMESTAMP',})
|
||||
@Column({ name: "update_time", comment: "修改时间", default: () => "CURRENT_TIMESTAMP" })
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('sys_passkey')
|
||||
@Entity("sys_passkey")
|
||||
export class PasskeyEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'device_name', comment: '设备名称' })
|
||||
@Column({ name: "device_name", comment: "设备名称" })
|
||||
deviceName: string;
|
||||
|
||||
@Column({ name: 'passkey_id', comment: 'passkey_id' })
|
||||
@Column({ name: "passkey_id", comment: "passkey_id" })
|
||||
passkeyId: string;
|
||||
|
||||
@Column({ name: 'public_key', comment: '公钥', type: 'text' })
|
||||
@Column({ name: "public_key", comment: "公钥", type: "text" })
|
||||
publicKey: string;
|
||||
|
||||
@Column({ name: 'counter', comment: '计数器' })
|
||||
@Column({ name: "counter", comment: "计数器" })
|
||||
counter: number;
|
||||
|
||||
@Column({ name: 'transports', comment: '传输方式', type: 'text', nullable: true })
|
||||
@Column({ name: "transports", comment: "传输方式", type: "text", nullable: true })
|
||||
transports: string;
|
||||
|
||||
@Column({ name: 'registered_at', comment: '注册时间' })
|
||||
@Column({ name: "registered_at", comment: "注册时间" })
|
||||
registeredAt: number;
|
||||
|
||||
@Column({ name: 'create_time', comment: '创建时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||
@Column({ name: "create_time", comment: "创建时间", default: () => "CURRENT_TIMESTAMP" })
|
||||
createTime: Date;
|
||||
|
||||
@Column({ name: 'update_time', comment: '修改时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||
@Column({ name: "update_time", comment: "修改时间", default: () => "CURRENT_TIMESTAMP" })
|
||||
updateTime: Date;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { UserService } from "../../sys/authority/service/user-service.js";
|
||||
import jwt from "jsonwebtoken";
|
||||
import {
|
||||
AuthException,
|
||||
CommonException,
|
||||
Need2FAException,
|
||||
SysPrivateSettings,
|
||||
SysSettingsService
|
||||
} from "@certd/lib-server";
|
||||
import { AuthException, CommonException, Need2FAException, SysPrivateSettings, SysSettingsService } from "@certd/lib-server";
|
||||
import { RoleService } from "../../sys/authority/service/role-service.js";
|
||||
import { UserEntity } from "../../sys/authority/entity/user.js";
|
||||
import { cache, utils } from "@certd/basic";
|
||||
@@ -24,7 +18,7 @@ import { InviteService } from "@certd/commercial-core";
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class LoginService {
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
@@ -33,7 +27,7 @@ export class LoginService {
|
||||
|
||||
@Inject()
|
||||
codeService: CodeService;
|
||||
@Config('auth.jwt')
|
||||
@Config("auth.jwt")
|
||||
private jwt: any;
|
||||
|
||||
@Inject()
|
||||
@@ -57,7 +51,7 @@ export class LoginService {
|
||||
const blockDurationKey = `login_block_duration:${username}`;
|
||||
const value = cache.get(blockDurationKey);
|
||||
if (value) {
|
||||
const ttl = cache.getRemainingTTL(blockDurationKey)
|
||||
const ttl = cache.getRemainingTTL(blockDurationKey);
|
||||
const leftMin = Math.ceil(ttl / 1000 / 60);
|
||||
throw new CommonException(`账号被锁定,请${leftMin}分钟后重试`);
|
||||
}
|
||||
@@ -102,7 +96,7 @@ export class LoginService {
|
||||
const leftMin = Math.ceil(ttl / 1000 / 60);
|
||||
cache.set(blockDurationKey, 1, {
|
||||
ttl: ttl,
|
||||
})
|
||||
});
|
||||
// 清除error次数
|
||||
cache.delete(errorTimesKey);
|
||||
throw new LoginErrorException(`登录失败次数过多,请${leftMin}分钟后重试`, 0);
|
||||
@@ -114,10 +108,8 @@ export class LoginService {
|
||||
throw new LoginErrorException(errorMessage, leftTimes);
|
||||
}
|
||||
|
||||
|
||||
async loginBySmsCode(req: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; inviteCode?: string }) {
|
||||
|
||||
this.checkIsBlocked(req.mobile)
|
||||
this.checkIsBlocked(req.mobile);
|
||||
|
||||
const smsChecked = await this.codeService.checkSmsCode({
|
||||
mobile: req.mobile,
|
||||
@@ -126,19 +118,19 @@ export class LoginService {
|
||||
throwError: false,
|
||||
});
|
||||
|
||||
const {mobile, phoneCode} = req;
|
||||
const { mobile, phoneCode } = req;
|
||||
if (!smsChecked) {
|
||||
this.addErrorTimes(mobile, '手机验证码错误');
|
||||
this.addErrorTimes(mobile, "手机验证码错误");
|
||||
}
|
||||
let info = await this.userService.findOne({phoneCode, mobile: mobile});
|
||||
let info = await this.userService.findOne({ phoneCode, mobile: mobile });
|
||||
if (info == null) {
|
||||
//用户不存在,注册
|
||||
const registerUser = {
|
||||
phoneCode,
|
||||
mobile,
|
||||
password: '',
|
||||
password: "",
|
||||
} as any;
|
||||
info = await this.userService.register('mobile', registerUser, async txManager => {
|
||||
info = await this.userService.register("mobile", registerUser, async txManager => {
|
||||
await this.inviteService.bindInvitee({ manager: txManager }, { inviteeUserId: registerUser.id, inviteCode: req.inviteCode });
|
||||
});
|
||||
}
|
||||
@@ -147,18 +139,22 @@ export class LoginService {
|
||||
}
|
||||
|
||||
async loginByPassword(req: { username: string; password: string; phoneCode: string }) {
|
||||
this.checkIsBlocked(req.username)
|
||||
const {username, password, phoneCode} = req;
|
||||
const info = await this.userService.findOne([{username: username}, {email: username}, {
|
||||
phoneCode,
|
||||
mobile: username
|
||||
}]);
|
||||
this.checkIsBlocked(req.username);
|
||||
const { username, password, phoneCode } = req;
|
||||
const info = await this.userService.findOne([
|
||||
{ username: username },
|
||||
{ email: username },
|
||||
{
|
||||
phoneCode,
|
||||
mobile: username,
|
||||
},
|
||||
]);
|
||||
if (info == null) {
|
||||
throw new CommonException('用户名或密码错误');
|
||||
throw new CommonException("用户名或密码错误");
|
||||
}
|
||||
const right = await this.userService.checkPassword(password, info.password, info.passwordVersion);
|
||||
if (!right) {
|
||||
this.addErrorTimes(username, '用户名或密码错误');
|
||||
this.addErrorTimes(username, "用户名或密码错误");
|
||||
}
|
||||
this.clearCacheOnSuccess(username);
|
||||
return this.onLoginSuccess(info);
|
||||
@@ -167,56 +163,54 @@ export class LoginService {
|
||||
async checkTwoFactorEnabled(userId: number) {
|
||||
//检查是否开启多重认证
|
||||
if (!isPlus()) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
const twoFactorSetting = await this.twoFactorService.getSetting(userId)
|
||||
const twoFactorSetting = await this.twoFactorService.getSetting(userId);
|
||||
|
||||
const authenticatorSetting = twoFactorSetting.authenticator
|
||||
const authenticatorSetting = twoFactorSetting.authenticator;
|
||||
if (authenticatorSetting.enabled) {
|
||||
//要检查
|
||||
const randomKey = utils.id.simpleNanoId(12)
|
||||
const randomKey = utils.id.simpleNanoId(12);
|
||||
cache.set(`login_2fa_code:${randomKey}`, userId, {
|
||||
ttl: 60 * 1000 * 2,
|
||||
})
|
||||
throw new Need2FAException('已开启多重认证,请在2分钟内输入OPT验证码',randomKey)
|
||||
});
|
||||
throw new Need2FAException("已开启多重认证,请在2分钟内输入OPT验证码", randomKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async loginByTwoFactor(req: { loginId: string; verifyCode: string }) {
|
||||
//检查是否开启多重认证
|
||||
if (!isPlus()) {
|
||||
throw new Error('本功能需要开通Certd专业版')
|
||||
throw new Error("本功能需要开通Certd专业版");
|
||||
}
|
||||
const userId = cache.get(`login_2fa_code:${req.loginId}`)
|
||||
const userId = cache.get(`login_2fa_code:${req.loginId}`);
|
||||
if (!userId) {
|
||||
throw new AuthException('已超时,请返回重新登录')
|
||||
throw new AuthException("已超时,请返回重新登录");
|
||||
}
|
||||
await this.twoFactorService.verifyAuthenticatorCode(userId, req.verifyCode)
|
||||
await this.twoFactorService.verifyAuthenticatorCode(userId, req.verifyCode);
|
||||
|
||||
const user = await this.userService.info(userId);
|
||||
if (!user) {
|
||||
throw new AuthException('用户不存在')
|
||||
throw new AuthException("用户不存在");
|
||||
}
|
||||
return this.generateToken(user)
|
||||
return this.generateToken(user);
|
||||
}
|
||||
|
||||
private async onLoginSuccess(info: UserEntity) {
|
||||
if (info.status === 0) {
|
||||
throw new CommonException('用户已被禁用');
|
||||
throw new CommonException("用户已被禁用");
|
||||
}
|
||||
await this.checkTwoFactorEnabled(info.id)
|
||||
await this.checkTwoFactorEnabled(info.id);
|
||||
return this.generateToken(info);
|
||||
}
|
||||
|
||||
writeTokenCookie(ctx:any,token: { expire: any; token: any }) {
|
||||
writeTokenCookie(ctx: any, token: { expire: any; token: any }) {
|
||||
ctx.cookies.set("certd_token", token.token, {
|
||||
maxAge: 1000 * token.expire
|
||||
maxAge: 1000 * token.expire,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成token
|
||||
* @param user 用户对象
|
||||
@@ -224,7 +218,7 @@ export class LoginService {
|
||||
*/
|
||||
async generateToken(user: UserEntity) {
|
||||
if (user.status === 0) {
|
||||
throw new CommonException('用户已被禁用');
|
||||
throw new CommonException("用户已被禁用");
|
||||
}
|
||||
|
||||
const roleIds = await this.roleService.getRoleIdsByUserId(user.id);
|
||||
@@ -248,26 +242,26 @@ export class LoginService {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async loginByOpenId(req: { openId: string, type:string }) {
|
||||
const {openId, type} = req;
|
||||
async loginByOpenId(req: { openId: string; type: string }) {
|
||||
const { openId, type } = req;
|
||||
const oauthBound = await this.oauthBoundService.findOne({
|
||||
where:{openId, type}
|
||||
where: { openId, type },
|
||||
});
|
||||
if (oauthBound == null) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
const info = await this.userService.findOne({id: oauthBound.userId});
|
||||
const info = await this.userService.findOne({ id: oauthBound.userId });
|
||||
if (info == null) {
|
||||
// 用户已被删除,删除此oauth绑定
|
||||
await this.oauthBoundService.delete([oauthBound.id]);
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
return this.generateToken(info);
|
||||
}
|
||||
|
||||
async loginByPasskey(req: { credential: any; challenge: string }, ctx: any) {
|
||||
const {credential, challenge} = req;
|
||||
const { credential, challenge } = req;
|
||||
const user = await this.passkeyService.loginByPasskey(credential, challenge, ctx);
|
||||
return this.generateToken(user);
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,27 +4,24 @@ import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { OauthBoundEntity } from "../entity/oauth-bound.js";
|
||||
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class OauthBoundService extends BaseService<OauthBoundEntity> {
|
||||
|
||||
@InjectEntityModel(OauthBoundEntity)
|
||||
repository: Repository<OauthBoundEntity>;
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
async unbind(req: { userId: any; type: any; }) {
|
||||
async unbind(req: { userId: any; type: any }) {
|
||||
const { userId, type } = req;
|
||||
if (!userId || !type) {
|
||||
throw new Error('参数错误');
|
||||
throw new Error("参数错误");
|
||||
}
|
||||
|
||||
await this.repository.delete({
|
||||
@@ -33,10 +30,10 @@ export class OauthBoundService extends BaseService<OauthBoundEntity> {
|
||||
});
|
||||
}
|
||||
|
||||
async bind(req: { userId: any; type: any; openId: any; }) {
|
||||
async bind(req: { userId: any; type: any; openId: any }) {
|
||||
const { userId, type, openId } = req;
|
||||
if (!userId || !type || !openId) {
|
||||
throw new Error('参数错误');
|
||||
throw new Error("参数错误");
|
||||
}
|
||||
const exist = await this.repository.findOne({
|
||||
where: {
|
||||
@@ -44,11 +41,11 @@ export class OauthBoundService extends BaseService<OauthBoundEntity> {
|
||||
type,
|
||||
},
|
||||
});
|
||||
if (exist ) {
|
||||
if(exist.userId === userId){
|
||||
if (exist) {
|
||||
if (exist.userId === userId) {
|
||||
return;
|
||||
}
|
||||
throw new Error('该第三方账号已绑定其他用户');
|
||||
throw new Error("该第三方账号已绑定其他用户");
|
||||
}
|
||||
|
||||
const exist2 = await this.repository.findOne({
|
||||
@@ -65,7 +62,7 @@ export class OauthBoundService extends BaseService<OauthBoundEntity> {
|
||||
openId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
//新增
|
||||
await this.add({
|
||||
userId,
|
||||
@@ -73,5 +70,4 @@ export class OauthBoundService extends BaseService<OauthBoundEntity> {
|
||||
openId,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { PasskeyEntity } from "../entity/passkey.js";
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
|
||||
@@ -25,7 +24,7 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
}
|
||||
|
||||
async getRpInfo() {
|
||||
let rpName = "Certd"
|
||||
let rpName = "Certd";
|
||||
if (isComm()) {
|
||||
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
||||
rpName = siteInfo.title || rpName;
|
||||
@@ -42,7 +41,7 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
rpName,
|
||||
rpId,
|
||||
origin,
|
||||
}
|
||||
};
|
||||
}
|
||||
async generateRegistrationOptions(userId: number, username: string, remoteIp: string, ctx: any) {
|
||||
const { generateRegistrationOptions } = await import("@simplewebauthn/server");
|
||||
@@ -50,7 +49,6 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
|
||||
const { rpName, rpId } = await this.getRpInfo();
|
||||
|
||||
|
||||
const options = await generateRegistrationOptions({
|
||||
rpName: rpName,
|
||||
rpID: rpId,
|
||||
@@ -60,15 +58,15 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
timeout: 60000,
|
||||
attestationType: "none",
|
||||
excludeCredentials: [],
|
||||
preferredAuthenticatorType: 'localDevice',
|
||||
preferredAuthenticatorType: "localDevice",
|
||||
authenticatorSelection: {
|
||||
authenticatorAttachment: "cross-platform",
|
||||
userVerification: "preferred",
|
||||
authenticatorAttachment: "cross-platform",
|
||||
userVerification: "preferred",
|
||||
residentKey: "preferred",
|
||||
requireResidentKey: false
|
||||
requireResidentKey: false,
|
||||
},
|
||||
});
|
||||
logger.info('[passkey] 注册选项:', JSON.stringify(options));
|
||||
logger.info("[passkey] 注册选项:", JSON.stringify(options));
|
||||
cache.set(`passkey:registration:${options.challenge}`, userId, {
|
||||
ttl: 5 * 60 * 1000,
|
||||
});
|
||||
@@ -78,12 +76,7 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
};
|
||||
}
|
||||
|
||||
async verifyRegistrationResponse(
|
||||
userId: number,
|
||||
response: any,
|
||||
challenge: string,
|
||||
ctx: any
|
||||
) {
|
||||
async verifyRegistrationResponse(userId: number, response: any, challenge: string, ctx: any) {
|
||||
const { verifyRegistrationResponse } = await import("@simplewebauthn/server");
|
||||
|
||||
const storedUserId = cache.get(`passkey:registration:${challenge}`);
|
||||
@@ -105,13 +98,12 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
verification = await verifyRegistrationResponse(verifyReq);
|
||||
} catch (error) {
|
||||
// 后端验证时
|
||||
logger.error('[passkey] 注册验证失败:', JSON.stringify(verifyReq));
|
||||
logger.error("[passkey] 注册验证失败:", JSON.stringify(verifyReq));
|
||||
throw new AuthException(`注册验证失败:${error.message || error}`);
|
||||
}
|
||||
if (!verification.verified) {
|
||||
throw new AuthException("注册验证失败");
|
||||
}
|
||||
|
||||
|
||||
cache.delete(`passkey:registration:${challenge}`);
|
||||
|
||||
@@ -129,7 +121,7 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
rpID: rpId,
|
||||
timeout: 60000,
|
||||
allowCredentials: [],
|
||||
userVerification: 'preferred' //'required' | 'preferred' | 'discouraged';
|
||||
userVerification: "preferred", //'required' | 'preferred' | 'discouraged';
|
||||
});
|
||||
|
||||
// cache.set(`passkey:authentication:${options.challenge}`, userId, {
|
||||
@@ -141,11 +133,7 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
};
|
||||
}
|
||||
|
||||
async verifyAuthenticationResponse(
|
||||
credential: any,
|
||||
challenge: string,
|
||||
ctx: any
|
||||
) {
|
||||
async verifyAuthenticationResponse(credential: any, challenge: string, ctx: any) {
|
||||
const { verifyAuthenticationResponse } = await import("@simplewebauthn/server");
|
||||
|
||||
const passkey = await this.repository.findOne({
|
||||
@@ -168,7 +156,7 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
requireUserVerification: false,
|
||||
credential: {
|
||||
id: passkey.passkeyId,
|
||||
publicKey: new Uint8Array(Buffer.from(passkey.publicKey, 'base64')),
|
||||
publicKey: new Uint8Array(Buffer.from(passkey.publicKey, "base64")),
|
||||
counter: passkey.counter,
|
||||
transports: passkey.transports as any,
|
||||
},
|
||||
@@ -187,24 +175,13 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
};
|
||||
}
|
||||
|
||||
async registerPasskey(
|
||||
userId: number,
|
||||
response: any,
|
||||
challenge: string,
|
||||
deviceName: string,
|
||||
ctx: any
|
||||
) {
|
||||
const verification = await this.verifyRegistrationResponse(
|
||||
userId,
|
||||
response,
|
||||
challenge,
|
||||
ctx
|
||||
);
|
||||
async registerPasskey(userId: number, response: any, challenge: string, deviceName: string, ctx: any) {
|
||||
const verification = await this.verifyRegistrationResponse(userId, response, challenge, ctx);
|
||||
|
||||
await this.add({
|
||||
userId,
|
||||
passkeyId: verification.credentialId,
|
||||
publicKey: Buffer.from(verification.credentialPublicKey).toString('base64'),
|
||||
publicKey: Buffer.from(verification.credentialPublicKey).toString("base64"),
|
||||
counter: verification.counter,
|
||||
deviceName,
|
||||
registeredAt: Date.now(),
|
||||
@@ -214,11 +191,7 @@ export class PasskeyService extends BaseService<PasskeyEntity> {
|
||||
}
|
||||
|
||||
async loginByPasskey(credential: any, challenge: string, ctx: any) {
|
||||
const verification = await this.verifyAuthenticationResponse(
|
||||
credential,
|
||||
challenge,
|
||||
ctx
|
||||
);
|
||||
const verification = await this.verifyAuthenticationResponse(credential, challenge, ctx);
|
||||
|
||||
const passkey = await this.repository.findOne({
|
||||
where: {
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
/**
|
||||
*/
|
||||
@Entity('user_settings')
|
||||
@Entity("user_settings")
|
||||
export class UserSettingsEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
@Column({ comment: 'key', length: 100 })
|
||||
@Column({ comment: "key", length: 100 })
|
||||
key: string;
|
||||
@Column({ comment: '名称', length: 100 })
|
||||
@Column({ comment: "名称", length: 100 })
|
||||
title: string;
|
||||
@Column({ name: 'setting', comment: '设置', length: 1024, nullable: true })
|
||||
@Column({ name: "setting", comment: "设置", length: 1024, nullable: true })
|
||||
setting: 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;
|
||||
|
||||
}
|
||||
|
||||
@@ -4,65 +4,57 @@ export type TwoFactorAuthenticator = {
|
||||
enabled: boolean;
|
||||
secret?: string;
|
||||
type?: string;
|
||||
verified?:boolean;
|
||||
}
|
||||
verified?: boolean;
|
||||
};
|
||||
|
||||
export class UserTwoFactorSetting extends BaseSettings {
|
||||
static __title__ = "用户多重认证设置";
|
||||
static __key__ = "user.two.factor";
|
||||
|
||||
authenticator: TwoFactorAuthenticator = {
|
||||
enabled:false,
|
||||
verified:false,
|
||||
enabled: false,
|
||||
verified: false,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class UserSiteMonitorSetting extends BaseSettings {
|
||||
static __title__ = "站点监控设置";
|
||||
static __key__ = "user.site.monitor";
|
||||
|
||||
notificationId?:number= 0;
|
||||
cron?:string = undefined;
|
||||
retryTimes?:number = 3;
|
||||
dnsServer?:string[] = undefined;
|
||||
certValidDays?:number = 14;
|
||||
notificationId?: number = 0;
|
||||
cron?: string = undefined;
|
||||
retryTimes?: number = 3;
|
||||
dnsServer?: string[] = undefined;
|
||||
certValidDays?: number = 14;
|
||||
}
|
||||
|
||||
|
||||
export class UserDomainMonitorSetting extends BaseSettings {
|
||||
static __title__ = "域名到期监控设置";
|
||||
static __key__ = "user.domain.monitor";
|
||||
|
||||
enabled?:boolean = false;
|
||||
notificationId?:number= 0;
|
||||
cron?:string = undefined;
|
||||
willExpireDays?:number = 30;
|
||||
enabled?: boolean = false;
|
||||
notificationId?: number = 0;
|
||||
cron?: string = undefined;
|
||||
willExpireDays?: number = 30;
|
||||
}
|
||||
|
||||
|
||||
export class UserEmailSetting extends BaseSettings {
|
||||
static __title__ = "用户邮箱设置";
|
||||
static __key__ = "user.email";
|
||||
|
||||
list:string[] = [];
|
||||
list: string[] = [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class UserGrantSetting extends BaseSettings {
|
||||
static __title__ = "用户授权设置";
|
||||
static __key__ = "user.grant";
|
||||
|
||||
allowAdminViewCerts:boolean = false;
|
||||
allowAdminViewCerts = false;
|
||||
}
|
||||
|
||||
|
||||
export class UserDomainImportSetting extends BaseSettings {
|
||||
static __title__ = "用户域名导入设置";
|
||||
static __key__ = "user.domain.import";
|
||||
|
||||
domainImportList:{dnsProviderType:string,dnsProviderAccessId:number,key:string,title:string,icon?:string}[];
|
||||
domainImportList: { dnsProviderType: string; dnsProviderAccessId: number; key: string; title: string; icon?: string }[];
|
||||
}
|
||||
|
||||
@@ -14,15 +14,14 @@ export class TwoFactorService {
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
|
||||
|
||||
async getAuthenticatorQrCode(userId: any) {
|
||||
const setting = await this.getSetting(userId)
|
||||
const setting = await this.getSetting(userId);
|
||||
|
||||
const authenticatorSetting = setting.authenticator;
|
||||
if (!authenticatorSetting.secret) {
|
||||
const { authenticator } = await import("otplib");
|
||||
const { authenticator } = await import("otplib");
|
||||
|
||||
authenticatorSetting.secret = authenticator.generateSecret()
|
||||
authenticatorSetting.secret = authenticator.generateSecret();
|
||||
await this.userSettingsService.saveSetting(userId, null, setting);
|
||||
}
|
||||
|
||||
@@ -34,14 +33,13 @@ export class TwoFactorService {
|
||||
//生成qrcode base64
|
||||
const qrcode = await import("qrcode");
|
||||
const qrcodeBase64 = await qrcode.toDataURL(qrcodeContent);
|
||||
return {qrcode:qrcodeBase64,link:qrcodeContent,secret}
|
||||
|
||||
return { qrcode: qrcodeBase64, link: qrcodeContent, secret };
|
||||
}
|
||||
|
||||
async saveAuthenticator(req: { userId: any; verifyCode: any }) {
|
||||
const userId = req.userId;
|
||||
const { authenticator } = await import("otplib");
|
||||
const setting = await this.getSetting(userId)
|
||||
const setting = await this.getSetting(userId);
|
||||
|
||||
const authenticatorSetting = setting.authenticator;
|
||||
if (!authenticatorSetting.secret) {
|
||||
@@ -62,26 +60,25 @@ export class TwoFactorService {
|
||||
await this.userSettingsService.saveSetting(userId, null, setting);
|
||||
}
|
||||
|
||||
async offAuthenticator(userId:number) {
|
||||
async offAuthenticator(userId: number) {
|
||||
if (!userId || userId <= 0) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
|
||||
const setting = await this.getSetting(userId)
|
||||
const setting = await this.getSetting(userId);
|
||||
setting.authenticator.enabled = false;
|
||||
setting.authenticator.verified = false;
|
||||
setting.authenticator.secret = '';
|
||||
setting.authenticator.secret = "";
|
||||
await this.userSettingsService.saveSetting(userId, null, setting);
|
||||
}
|
||||
|
||||
async getSetting(userId:number) {
|
||||
async getSetting(userId: number) {
|
||||
return await this.userSettingsService.getSetting<UserTwoFactorSetting>(userId, null, UserTwoFactorSetting);
|
||||
|
||||
}
|
||||
|
||||
async verifyAuthenticatorCode(userId: any, verifyCode: string) {
|
||||
const { authenticator } = await import("otplib");
|
||||
const setting = await this.getSetting(userId)
|
||||
const setting = await this.getSetting(userId);
|
||||
if (!setting.authenticator.enabled) {
|
||||
throw new Error("authenticator 未开启");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Repository } from "typeorm";
|
||||
import { BaseService, BaseSettings } from "@certd/lib-server";
|
||||
import { UserSettingsEntity } from "../entity/user-settings.js";
|
||||
import { LocalCache, mergeUtils } from "@certd/basic";
|
||||
const {merge} = mergeUtils
|
||||
const { merge } = mergeUtils;
|
||||
|
||||
const UserSettingCache = new LocalCache({
|
||||
clearInterval: 5 * 60 * 1000,
|
||||
@@ -33,13 +33,13 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
|
||||
const setting = JSON.parse(entity.setting);
|
||||
return {
|
||||
id: entity.id,
|
||||
...setting
|
||||
...setting,
|
||||
};
|
||||
}
|
||||
|
||||
async getByKey(key: string, userId: number, projectId: number): Promise<UserSettingsEntity | null> {
|
||||
if(userId == null){
|
||||
throw new Error('userId is required');
|
||||
if (userId == null) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
if (!key) {
|
||||
return null;
|
||||
@@ -48,14 +48,14 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
|
||||
where: {
|
||||
key,
|
||||
userId,
|
||||
projectId
|
||||
}
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getSettingByKey(key: string, userId: number, projectId: number): Promise<any | null> {
|
||||
if(userId == null){
|
||||
throw new Error('userId is required');
|
||||
if (userId == null) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
const entity = await this.getByKey(key, userId, projectId);
|
||||
if (!entity) {
|
||||
@@ -69,8 +69,8 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
|
||||
where: {
|
||||
key: bean.key,
|
||||
userId: bean.userId,
|
||||
projectId: bean.projectId
|
||||
}
|
||||
projectId: bean.projectId,
|
||||
},
|
||||
});
|
||||
if (entity) {
|
||||
entity.setting = bean.setting;
|
||||
@@ -81,15 +81,14 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getSetting<T>( userId: number, projectId: number,type: any, cache:boolean = false): Promise<T> {
|
||||
if(userId==null){
|
||||
throw new Error('userId is required');
|
||||
async getSetting<T>(userId: number, projectId: number, type: any, cache = false): Promise<T> {
|
||||
if (userId == null) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
const key = type.__key__;
|
||||
let cacheKey = key + '_' + userId ;
|
||||
let cacheKey = key + "_" + userId;
|
||||
if (projectId) {
|
||||
cacheKey += '_' + projectId;
|
||||
cacheKey += "_" + projectId;
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
@@ -109,23 +108,23 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
|
||||
return newSetting;
|
||||
}
|
||||
|
||||
async saveSetting<T extends BaseSettings>(userId:number, projectId: number,bean: T) {
|
||||
if(userId == null){
|
||||
throw new Error('userId is required');
|
||||
async saveSetting<T extends BaseSettings>(userId: number, projectId: number, bean: T) {
|
||||
if (userId == null) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
const old = await this.getSetting(userId, projectId,bean.constructor)
|
||||
bean = merge(old,bean)
|
||||
const old = await this.getSetting(userId, projectId, bean.constructor);
|
||||
bean = merge(old, bean);
|
||||
|
||||
const type: any = bean.constructor;
|
||||
const key = type.__key__;
|
||||
if(!key){
|
||||
if (!key) {
|
||||
throw new Error(`${type.name} must have __key__`);
|
||||
}
|
||||
const entity = await this.getByKey(key,userId, projectId);
|
||||
const entity = await this.getByKey(key, userId, projectId);
|
||||
const newEntity = new UserSettingsEntity();
|
||||
if (entity) {
|
||||
newEntity.id = entity.id;
|
||||
}else{
|
||||
} else {
|
||||
newEntity.key = key;
|
||||
newEntity.title = type.__title__;
|
||||
newEntity.userId = userId;
|
||||
@@ -134,5 +133,4 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
|
||||
newEntity.setting = JSON.stringify(bean);
|
||||
await this.repository.save(newEntity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { PipelineEntity } from '../../pipeline/entity/pipeline.js';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { PipelineEntity } from "../../pipeline/entity/pipeline.js";
|
||||
|
||||
@Entity('cd_cert_info')
|
||||
@Entity("cd_cert_info")
|
||||
export class CertInfoEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'domain', comment: '主域名' })
|
||||
@Column({ name: "domain", comment: "主域名" })
|
||||
domain: string;
|
||||
|
||||
@Column({ name: 'domains', comment: '域名' })
|
||||
@Column({ name: "domains", comment: "域名" })
|
||||
domains: string;
|
||||
|
||||
@Column({ name: 'domain_count', comment: '域名数量' })
|
||||
@Column({ name: "domain_count", comment: "域名数量" })
|
||||
domainCount: number;
|
||||
|
||||
@Column({ name: 'wildcard_domain_count', comment: '泛域名数量', default: 0 })
|
||||
@Column({ name: "wildcard_domain_count", comment: "泛域名数量", default: 0 })
|
||||
wildcardDomainCount: number;
|
||||
|
||||
@Column({ name: 'pipeline_id', comment: '关联流水线id' })
|
||||
@Column({ name: "pipeline_id", comment: "关联流水线id" })
|
||||
pipelineId: number;
|
||||
|
||||
@Column({ name: 'apply_time', comment: '申请时间' })
|
||||
@Column({ name: "apply_time", comment: "申请时间" })
|
||||
applyTime: number;
|
||||
|
||||
@Column({ name: 'from_type', comment: '来源' })
|
||||
@Column({ name: "from_type", comment: "来源" })
|
||||
fromType: string;
|
||||
|
||||
@Column({ name: 'cert_provider', comment: '证书颁发机构' })
|
||||
@Column({ name: "cert_provider", comment: "证书颁发机构" })
|
||||
certProvider: string;
|
||||
|
||||
@Column({ name: 'effective_time', comment: '生效时间' })
|
||||
@Column({ name: "effective_time", comment: "生效时间" })
|
||||
effectiveTime: number;
|
||||
|
||||
@Column({ name: 'expires_time', comment: '过期时间' })
|
||||
@Column({ name: "expires_time", comment: "过期时间" })
|
||||
expiresTime: number;
|
||||
|
||||
@Column({ name: 'cert_info', comment: '证书详情' })
|
||||
@Column({ name: "cert_info", comment: "证书详情" })
|
||||
certInfo: string;
|
||||
|
||||
@Column({ name: 'cert_file', comment: '证书下载' })
|
||||
@Column({ name: "cert_file", comment: "证书下载" })
|
||||
certFile: 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;
|
||||
|
||||
|
||||
@@ -1,50 +1,49 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { PipelineEntity } from '../../pipeline/entity/pipeline.js';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { PipelineEntity } from "../../pipeline/entity/pipeline.js";
|
||||
|
||||
@Entity('cd_job_history')
|
||||
@Entity("cd_job_history")
|
||||
export class JobHistoryEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'project_id', comment: '项目id' })
|
||||
@Column({ name: "project_id", comment: "项目id" })
|
||||
projectId: number;
|
||||
|
||||
|
||||
@Column({ name: 'type', comment: '类型' })
|
||||
@Column({ name: "type", comment: "类型" })
|
||||
type: string;
|
||||
|
||||
@Column({ name: 'title', comment: '标题' })
|
||||
@Column({ name: "title", comment: "标题" })
|
||||
title: string;
|
||||
|
||||
@Column({ name: 'content', comment: '内容' })
|
||||
@Column({ name: "content", comment: "内容" })
|
||||
content: string;
|
||||
|
||||
@Column({ name: 'related_id', comment: '关联id' })
|
||||
@Column({ name: "related_id", comment: "关联id" })
|
||||
relatedId: string;
|
||||
|
||||
@Column({ name: 'result', comment: '结果' })
|
||||
@Column({ name: "result", comment: "结果" })
|
||||
result: string;
|
||||
|
||||
@Column({ name: 'start_at', comment: '开始时间' })
|
||||
@Column({ name: "start_at", comment: "开始时间" })
|
||||
startAt: number;
|
||||
|
||||
@Column({ name: 'end_at', comment: '结束时间' })
|
||||
@Column({ name: "end_at", comment: "结束时间" })
|
||||
endAt: 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;
|
||||
|
||||
|
||||
@@ -1,84 +1,82 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
/**
|
||||
*/
|
||||
@Entity('cd_site_info')
|
||||
@Entity("cd_site_info")
|
||||
export class SiteInfoEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
@Column({ name: 'name', comment: '站点名称', length: 100 })
|
||||
@Column({ name: "name", comment: "站点名称", length: 100 })
|
||||
name: string;
|
||||
@Column({ name: 'domain', comment: '域名', length: 100 })
|
||||
@Column({ name: "domain", comment: "域名", length: 100 })
|
||||
domain: string;
|
||||
|
||||
@Column({ name: 'https_port', comment: '端口' })
|
||||
@Column({ name: "https_port", comment: "端口" })
|
||||
httpsPort: number;
|
||||
|
||||
@Column({ name: 'cert_domains', comment: '证书域名', length: 4096 })
|
||||
@Column({ name: "cert_domains", comment: "证书域名", length: 4096 })
|
||||
certDomains: string;
|
||||
@Column({ name: 'cert_info', comment: '证书详情', length: 4096 })
|
||||
@Column({ name: "cert_info", comment: "证书详情", length: 4096 })
|
||||
certInfo: string;
|
||||
@Column({ name: 'cert_status', comment: '证书状态', length: 100 })
|
||||
@Column({ name: "cert_status", comment: "证书状态", length: 100 })
|
||||
certStatus: string;
|
||||
|
||||
@Column({ name: 'cert_provider', comment: '证书颁发机构', length: 100 })
|
||||
@Column({ name: "cert_provider", comment: "证书颁发机构", length: 100 })
|
||||
certProvider: string;
|
||||
|
||||
@Column({ name: 'cert_effective_time', comment: '证书生效时间' })
|
||||
@Column({ name: "cert_effective_time", comment: "证书生效时间" })
|
||||
certEffectiveTime: number;
|
||||
@Column({ name: 'cert_expires_time', comment: '证书到期时间' })
|
||||
@Column({ name: "cert_expires_time", comment: "证书到期时间" })
|
||||
certExpiresTime: number;
|
||||
@Column({ name: 'last_check_time', comment: '上次检查时间' })
|
||||
@Column({ name: "last_check_time", comment: "上次检查时间" })
|
||||
lastCheckTime: number;
|
||||
@Column({ name: 'check_status', comment: '检查状态' })
|
||||
@Column({ name: "check_status", comment: "检查状态" })
|
||||
checkStatus: string;
|
||||
@Column({ name: 'error', comment: '错误信息' })
|
||||
@Column({ name: "error", comment: "错误信息" })
|
||||
error: string;
|
||||
@Column({ name: 'pipeline_id', comment: '关联流水线id' })
|
||||
@Column({ name: "pipeline_id", comment: "关联流水线id" })
|
||||
pipelineId: number;
|
||||
|
||||
@Column({ name: 'cert_info_id', comment: '证书id' })
|
||||
@Column({ name: "cert_info_id", comment: "证书id" })
|
||||
certInfoId: number;
|
||||
|
||||
|
||||
@Column({ name: 'ip_check', comment: '是否检查IP' })
|
||||
@Column({ name: "ip_check", comment: "是否检查IP" })
|
||||
ipCheck: boolean;
|
||||
|
||||
@Column({ name: 'ip_sync_auto', comment: '是否自动同步IP' })
|
||||
@Column({ name: "ip_sync_auto", comment: "是否自动同步IP" })
|
||||
ipSyncAuto: boolean;
|
||||
|
||||
@Column({ name: 'ip_sync_mode', comment: 'IP同步模式' })
|
||||
@Column({ name: "ip_sync_mode", comment: "IP同步模式" })
|
||||
ipSyncMode: string;
|
||||
|
||||
@Column({ name: 'ip_ignore_coherence', comment: '忽略证书一致性' })
|
||||
@Column({ name: "ip_ignore_coherence", comment: "忽略证书一致性" })
|
||||
ipIgnoreCoherence: boolean;
|
||||
|
||||
@Column({ name: 'ip_count', comment: 'ip数量' })
|
||||
ipCount: number
|
||||
@Column({ name: "ip_count", comment: "ip数量" })
|
||||
ipCount: number;
|
||||
|
||||
@Column({ name: 'ip_error_count', comment: 'ip异常数量' })
|
||||
ipErrorCount: number
|
||||
@Column({ name: "ip_error_count", comment: "ip异常数量" })
|
||||
ipErrorCount: number;
|
||||
|
||||
|
||||
@Column({ name: 'disabled', comment: '禁用启用' })
|
||||
@Column({ name: "disabled", comment: "禁用启用" })
|
||||
disabled: boolean;
|
||||
|
||||
@Column({ name: 'remark', comment: '备注', length: 512 })
|
||||
@Column({ name: "remark", comment: "备注", length: 512 })
|
||||
remark: string;
|
||||
|
||||
@Column({ name: 'group_id', comment: '分组id' })
|
||||
@Column({ name: "group_id", comment: "分组id" })
|
||||
groupId: number;
|
||||
|
||||
@Column({ name: 'ip_address', comment: 'IP地址', length: 128 })
|
||||
@Column({ name: "ip_address", comment: "IP地址", length: 128 })
|
||||
ipAddress: string;
|
||||
|
||||
@Column({ name: 'project_id', comment: '项目id' })
|
||||
@Column({ name: "project_id", comment: "项目id" })
|
||||
projectId: number;
|
||||
|
||||
@Column({ name: 'create_time', comment: '创建时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||
@Column({ name: "create_time", comment: "创建时间", default: () => "CURRENT_TIMESTAMP" })
|
||||
createTime: Date;
|
||||
@Column({ name: 'update_time', comment: '修改时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||
@Column({ name: "update_time", comment: "修改时间", default: () => "CURRENT_TIMESTAMP" })
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
/**
|
||||
*/
|
||||
@Entity('cd_site_ip')
|
||||
@Entity("cd_site_ip")
|
||||
export class SiteIpEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
@Column({ name: 'site_id', comment: '站点id' })
|
||||
@Column({ name: "site_id", comment: "站点id" })
|
||||
siteId: number;
|
||||
@Column({ name: 'ip_address', comment: 'IP', length: 100 })
|
||||
@Column({ name: "ip_address", comment: "IP", length: 100 })
|
||||
ipAddress: string;
|
||||
|
||||
@Column({ name: 'cert_domains', comment: '证书域名', length: 4096 })
|
||||
@Column({ name: "cert_domains", comment: "证书域名", length: 4096 })
|
||||
certDomains: string;
|
||||
@Column({ name: 'cert_status', comment: '证书状态', length: 100 })
|
||||
@Column({ name: "cert_status", comment: "证书状态", length: 100 })
|
||||
certStatus: string;
|
||||
@Column({ name: 'cert_provider', comment: '证书颁发机构', length: 100 })
|
||||
@Column({ name: "cert_provider", comment: "证书颁发机构", length: 100 })
|
||||
certProvider: string;
|
||||
@Column({ name: 'cert_expires_time', comment: '证书到期时间' })
|
||||
@Column({ name: "cert_expires_time", comment: "证书到期时间" })
|
||||
certExpiresTime: number;
|
||||
@Column({ name: 'last_check_time', comment: '上次检查时间' })
|
||||
@Column({ name: "last_check_time", comment: "上次检查时间" })
|
||||
lastCheckTime: number;
|
||||
@Column({ name: 'check_status', comment: '检查状态' })
|
||||
@Column({ name: "check_status", comment: "检查状态" })
|
||||
checkStatus: string;
|
||||
@Column({ name: 'error', comment: '错误信息' })
|
||||
@Column({ name: "error", comment: "错误信息" })
|
||||
error: string;
|
||||
@Column({ name: 'from', comment: '来源' })
|
||||
from: string
|
||||
@Column({ name: 'remark', comment: '备注' })
|
||||
@Column({ name: "from", comment: "来源" })
|
||||
from: string;
|
||||
@Column({ name: "remark", comment: "备注" })
|
||||
remark: string;
|
||||
@Column({ name: "disabled", comment: "禁用启用" })
|
||||
disabled: boolean;
|
||||
@Column({ name: 'project_id', comment: '项目id' })
|
||||
@Column({ name: "project_id", comment: "项目id" })
|
||||
projectId: number;
|
||||
|
||||
@Column({ name: 'create_time', comment: '创建时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||
@Column({ name: "create_time", comment: "创建时间", default: () => "CURRENT_TIMESTAMP" })
|
||||
createTime: Date;
|
||||
@Column({ name: 'update_time', comment: '修改时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||
@Column({ name: "update_time", comment: "修改时间", default: () => "CURRENT_TIMESTAMP" })
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
@@ -10,25 +10,23 @@ import { CertInfoService } from "../service/cert-info-service.js";
|
||||
import { DomainService } from "../../cert/service/domain-service.js";
|
||||
import { DomainVerifierGetter } from "../../pipeline/service/getter/domain-verifier-getter.js";
|
||||
|
||||
|
||||
@Provide("CertInfoFacade")
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class CertInfoFacade {
|
||||
|
||||
export class CertInfoFacade {
|
||||
@Inject()
|
||||
pipelineService: PipelineService;
|
||||
|
||||
@Inject()
|
||||
certInfoService: CertInfoService ;
|
||||
certInfoService: CertInfoService;
|
||||
|
||||
@Inject()
|
||||
domainService: DomainService
|
||||
domainService: DomainService;
|
||||
|
||||
@Inject()
|
||||
userSettingsService : UserSettingsService
|
||||
userSettingsService: UserSettingsService;
|
||||
|
||||
async getCertInfo(req: { domains?: string; certId?: number; userId: number, projectId:number, autoApply?:boolean,format?:string }) {
|
||||
const { domains, certId, userId,projectId } = req;
|
||||
async getCertInfo(req: { domains?: string; certId?: number; userId: number; projectId: number; autoApply?: boolean; format?: string }) {
|
||||
const { domains, certId, userId, projectId } = req;
|
||||
if (certId) {
|
||||
return await this.certInfoService.getCertInfoById({ id: certId, userId, projectId });
|
||||
}
|
||||
@@ -38,41 +36,38 @@ export class CertInfoFacade {
|
||||
message: "参数错误,certId和domains必须传一个",
|
||||
});
|
||||
}
|
||||
const domainArr = domains.split(',');
|
||||
const domainArr = domains.split(",");
|
||||
|
||||
const matchedList = await this.certInfoService.getMatchCertList({domains:domainArr,userId,projectId})
|
||||
const matchedList = await this.certInfoService.getMatchCertList({ domains: domainArr, userId, projectId });
|
||||
|
||||
if (matchedList.length === 0 ) {
|
||||
if(req.autoApply === true){
|
||||
if (matchedList.length === 0) {
|
||||
if (req.autoApply === true) {
|
||||
//自动申请,先创建自动申请流水线
|
||||
const pipeline:PipelineEntity = await this.createAutoPipeline({domains:domainArr,userId,projectId})
|
||||
await this.triggerApplyPipeline({pipelineId:pipeline.id})
|
||||
}else{
|
||||
const pipeline: PipelineEntity = await this.createAutoPipeline({ domains: domainArr, userId, projectId });
|
||||
await this.triggerApplyPipeline({ pipelineId: pipeline.id });
|
||||
} else {
|
||||
throw new CodeException({
|
||||
...Constants.res.openCertNotFound,
|
||||
message:"在证书仓库中没有找到匹配域名的证书,请先创建证书流水线,或传入autoApply参数,自动创建"
|
||||
message: "在证书仓库中没有找到匹配域名的证书,请先创建证书流水线,或传入autoApply参数,自动创建",
|
||||
});
|
||||
}
|
||||
}
|
||||
let matched = this.getMinixMatched(matchedList);
|
||||
const matched = this.getMinixMatched(matchedList);
|
||||
if (!matched) {
|
||||
if(req.autoApply === true){
|
||||
if (req.autoApply === true) {
|
||||
//如果没有找到有效期内的证书,则自动触发一次申请
|
||||
const first = matchedList[0]
|
||||
await this.triggerApplyPipeline({pipelineId:first.pipelineId})
|
||||
return
|
||||
}else{
|
||||
const first = matchedList[0];
|
||||
await this.triggerApplyPipeline({ pipelineId: first.pipelineId });
|
||||
return;
|
||||
} else {
|
||||
throw new CodeException({
|
||||
...Constants.res.openCertNotFound,
|
||||
message:"证书已过期,请触发流水线申请,或者传入autoApply参数,自动触发"
|
||||
message: "证书已过期,请触发流水线申请,或者传入autoApply参数,自动触发",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return await this.certInfoService.getCertInfoById({ id: matched.id, userId: userId,projectId,format:req.format });
|
||||
|
||||
|
||||
|
||||
return await this.certInfoService.getCertInfoById({ id: matched.id, userId: userId, projectId, format: req.format });
|
||||
}
|
||||
|
||||
public getMinixMatched(matchedList: CertInfoEntity[]) {
|
||||
@@ -103,59 +98,55 @@ export class CertInfoFacade {
|
||||
return matched;
|
||||
}
|
||||
|
||||
async createAutoPipeline(req:{domains:string[],userId:number,projectId:number}){
|
||||
async createAutoPipeline(req: { domains: string[]; userId: number; projectId: number }) {
|
||||
const verifierGetter = new DomainVerifierGetter(req.userId, req.projectId, this.domainService);
|
||||
|
||||
const verifierGetter = new DomainVerifierGetter(req.userId, req.projectId, this.domainService)
|
||||
|
||||
const allDomains = []
|
||||
const allDomains = [];
|
||||
for (const item of req.domains) {
|
||||
allDomains.push(item.replaceAll("*.",""))
|
||||
allDomains.push(item.replaceAll("*.", ""));
|
||||
}
|
||||
const verifiers = await verifierGetter.getVerifiers(allDomains)
|
||||
const verifiers = await verifierGetter.getVerifiers(allDomains);
|
||||
for (const item of allDomains) {
|
||||
if (!verifiers[item]){
|
||||
if (!verifiers[item]) {
|
||||
throw new CodeException({
|
||||
...Constants.res.openDomainNoVerifier,
|
||||
message:`域名${item}没有配置校验方式,请先在域名管理页面配置`,
|
||||
data:{
|
||||
domain:item
|
||||
}
|
||||
message: `域名${item}没有配置校验方式,请先在域名管理页面配置`,
|
||||
data: {
|
||||
domain: item,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const userEmailSetting = await this.userSettingsService.getSetting<UserEmailSetting>(req.userId,null, UserEmailSetting)
|
||||
if(!userEmailSetting.list){
|
||||
throw new CodeException(Constants.res.openEmailNotFound)
|
||||
const userEmailSetting = await this.userSettingsService.getSetting<UserEmailSetting>(req.userId, null, UserEmailSetting);
|
||||
if (!userEmailSetting.list) {
|
||||
throw new CodeException(Constants.res.openEmailNotFound);
|
||||
}
|
||||
const email = userEmailSetting.list[0]
|
||||
const email = userEmailSetting.list[0];
|
||||
|
||||
return await this.pipelineService.createAutoPipeline({
|
||||
domains: req.domains,
|
||||
email,
|
||||
projectId: req.projectId,
|
||||
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)
|
||||
}
|
||||
const certInfo = await this.certInfoService.getByPipelineId(req.pipelineId)
|
||||
throw new CodeException({
|
||||
...Constants.res.openCertApplying,
|
||||
data:{
|
||||
pipelineId:req.pipelineId,
|
||||
certId:certInfo?.id
|
||||
}
|
||||
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);
|
||||
}
|
||||
const certInfo = await this.certInfoService.getByPipelineId(req.pipelineId);
|
||||
throw new CodeException({
|
||||
...Constants.res.openCertApplying,
|
||||
data: {
|
||||
pipelineId: req.pipelineId,
|
||||
certId: certInfo?.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './entity/site-info.js';
|
||||
export * from './entity/cert-info.js';
|
||||
export * from "./entity/site-info.js";
|
||||
export * from "./entity/cert-info.js";
|
||||
|
||||
export * from './service/cert-info-service.js';
|
||||
export * from './service/site-info-service.js';
|
||||
export * from "./service/cert-info-service.js";
|
||||
export * from "./service/site-info-service.js";
|
||||
|
||||
@@ -12,10 +12,9 @@ export type UploadCertReq = {
|
||||
fromType?: string;
|
||||
userId?: number;
|
||||
projectId?: number;
|
||||
file?:any
|
||||
file?: any;
|
||||
};
|
||||
|
||||
|
||||
@Provide("CertInfoService")
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
@@ -32,19 +31,19 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
}
|
||||
|
||||
async getUserDomainCount(userId: number) {
|
||||
if (userId==null) {
|
||||
throw new Error('userId is required');
|
||||
if (userId == null) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
return await this.repository.sum('domainCount', {
|
||||
return await this.repository.sum("domainCount", {
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
async getUserWildcardDomainCount(userId: number) {
|
||||
if (userId == null) {
|
||||
throw new Error('userId is required');
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
return await this.repository.sum('wildcardDomainCount', {
|
||||
return await this.repository.sum("wildcardDomainCount", {
|
||||
userId,
|
||||
});
|
||||
}
|
||||
@@ -56,7 +55,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
return domains.filter(domain => domain?.trim().toLowerCase().startsWith("*.")).length;
|
||||
}
|
||||
|
||||
async updateDomains(pipelineId: number, userId: number, projectId: number, domains: string[],fromType?:string) {
|
||||
async updateDomains(pipelineId: number, userId: number, projectId: number, domains: string[], fromType?: string) {
|
||||
const found = await this.repository.findOne({
|
||||
where: {
|
||||
pipelineId,
|
||||
@@ -73,26 +72,26 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
bean.pipelineId = pipelineId;
|
||||
bean.userId = userId;
|
||||
bean.projectId = projectId;
|
||||
bean.fromType = fromType
|
||||
bean.fromType = fromType;
|
||||
if (!domains || domains.length === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!domains || domains.length === 0) {
|
||||
bean.domain = '';
|
||||
bean.domains = '';
|
||||
bean.domain = "";
|
||||
bean.domains = "";
|
||||
bean.domainCount = 0;
|
||||
bean.wildcardDomainCount = 0;
|
||||
} else {
|
||||
bean.domain = domains[0];
|
||||
bean.domains = domains.join(',');
|
||||
bean.domains = domains.join(",");
|
||||
bean.domainCount = domains.length;
|
||||
bean.wildcardDomainCount = this.countWildcardDomains(domains);
|
||||
}
|
||||
|
||||
await this.addOrUpdate(bean);
|
||||
return bean.id
|
||||
return bean.id;
|
||||
}
|
||||
|
||||
async deleteByPipelineId(id: number) {
|
||||
@@ -104,12 +103,12 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
});
|
||||
}
|
||||
|
||||
async getMatchCertList(params: { domains: string[]; userId: number,projectId?:number }) {
|
||||
const { domains, userId,projectId } = params;
|
||||
async getMatchCertList(params: { domains: string[]; userId: number; projectId?: number }) {
|
||||
const { domains, userId, projectId } = params;
|
||||
if (!domains) {
|
||||
throw new CodeException({
|
||||
...Constants.res.openCertNotFound,
|
||||
message:"域名不能为空"
|
||||
message: "域名不能为空",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,27 +116,27 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
select: {
|
||||
id: true,
|
||||
domains: true,
|
||||
expiresTime:true,
|
||||
pipelineId:true,
|
||||
expiresTime: true,
|
||||
pipelineId: true,
|
||||
},
|
||||
where: {
|
||||
userId,
|
||||
projectId,
|
||||
},
|
||||
order: {
|
||||
id: 'DESC',
|
||||
id: "DESC",
|
||||
},
|
||||
});
|
||||
//遍历查找
|
||||
return list.filter(item => {
|
||||
const itemDomains = item.domains.split(',');
|
||||
const itemDomains = item.domains.split(",");
|
||||
return utils.domain.match(domains, itemDomains);
|
||||
});
|
||||
}
|
||||
|
||||
async getCertInfoById(req: { id: number; userId: number,projectId:number,format?:string }) {
|
||||
async getCertInfoById(req: { id: number; userId: number; projectId: number; format?: string }) {
|
||||
const entity = await this.info(req.id);
|
||||
if (!entity || entity.userId !== req.userId ) {
|
||||
if (!entity || entity.userId !== req.userId) {
|
||||
throw new CodeException(Constants.res.openCertNotFound);
|
||||
}
|
||||
if (req.projectId && entity.projectId !== req.projectId) {
|
||||
@@ -153,13 +152,13 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
...certReader.toCertInfo(req.format),
|
||||
detail: {
|
||||
id: entity.id,
|
||||
domains: entity.domains.split(','),
|
||||
domains: entity.domains.split(","),
|
||||
notAfter: certReader.expires,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async updateCertByPipelineId(pipelineId: number, cert: CertInfo,file?:string,fromType = 'pipeline') {
|
||||
async updateCertByPipelineId(pipelineId: number, cert: CertInfo, file?: string, fromType = "pipeline") {
|
||||
const found = await this.repository.findOne({
|
||||
where: {
|
||||
pipelineId,
|
||||
@@ -169,14 +168,14 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
id: found?.id,
|
||||
certReader: new CertReader(cert),
|
||||
fromType,
|
||||
file
|
||||
file,
|
||||
});
|
||||
return bean;
|
||||
}
|
||||
|
||||
private async updateCert(req: UploadCertReq) {
|
||||
const bean = new CertInfoEntity();
|
||||
const { id, fromType,userId, certReader } = req;
|
||||
const { id, fromType, userId, certReader } = req;
|
||||
if (id) {
|
||||
bean.id = id;
|
||||
} else {
|
||||
@@ -186,7 +185,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
bean.certInfo = JSON.stringify(certInfo);
|
||||
bean.applyTime = new Date().getTime();
|
||||
const domains = certReader.detail.domains.altNames;
|
||||
bean.domains = domains.join(',');
|
||||
bean.domains = domains.join(",");
|
||||
bean.domain = domains[0];
|
||||
bean.domainCount = domains.length;
|
||||
bean.wildcardDomainCount = this.countWildcardDomains(domains);
|
||||
@@ -195,8 +194,8 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
bean.certProvider = certReader.detail.issuer.commonName;
|
||||
bean.userId = userId;
|
||||
bean.projectId = req.projectId;
|
||||
if(req.file){
|
||||
bean.certFile = req.file
|
||||
if (req.file) {
|
||||
bean.certFile = req.file;
|
||||
}
|
||||
await this.addOrUpdate(bean);
|
||||
return bean;
|
||||
@@ -210,7 +209,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
|
||||
});
|
||||
}
|
||||
|
||||
async count({ userId,projectId }: { userId: number,projectId?:number }) {
|
||||
async count({ userId, projectId }: { userId: number; projectId?: number }) {
|
||||
const total = await this.repository.count({
|
||||
where: {
|
||||
userId,
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import {LocalCache, logger} from '@certd/basic';
|
||||
import dnsSdk, {AnyRecord} from 'dns'
|
||||
import {LookupAddress} from "node:dns";
|
||||
import { LocalCache, logger } from "@certd/basic";
|
||||
import dnsSdk, { AnyRecord } from "dns";
|
||||
import { LookupAddress } from "node:dns";
|
||||
|
||||
const dns = dnsSdk.promises
|
||||
const dns = dnsSdk.promises;
|
||||
|
||||
export class DnsCustom{
|
||||
export class DnsCustom {
|
||||
private resolver: dnsSdk.promises.Resolver;
|
||||
// private cache = new LRUCache<string, any>({
|
||||
// max: 1000,
|
||||
// ttl: 1000 * 60 * 5,
|
||||
// });
|
||||
|
||||
constructor(dnsServers:string[]) {
|
||||
constructor(dnsServers: string[]) {
|
||||
const resolver = new dns.Resolver();
|
||||
resolver.setServers(dnsServers);
|
||||
this.resolver = resolver;
|
||||
@@ -27,103 +27,100 @@ export class DnsCustom{
|
||||
// this.cache.set(cacheKey,res)
|
||||
// return res
|
||||
// }
|
||||
async lookup(hostname:string,options?:{ family: any, hints: number, all: boolean }):Promise<LookupAddress[]>{
|
||||
async lookup(hostname: string, options?: { family: any; hints: number; all: boolean }): Promise<LookupAddress[]> {
|
||||
// { family: undefined, hints: 0, all: true }
|
||||
let v4:LookupAddress[] = []
|
||||
let v6:LookupAddress[] = []
|
||||
let errors = []
|
||||
const queryV6 = async ()=>{
|
||||
try{
|
||||
const list = await this.resolver.resolve6(hostname)
|
||||
let v4: LookupAddress[] = [];
|
||||
let v6: LookupAddress[] = [];
|
||||
const errors = [];
|
||||
const queryV6 = async () => {
|
||||
try {
|
||||
const list = await this.resolver.resolve6(hostname);
|
||||
if (list && list.length > 0) {
|
||||
v6 = list.map(item=>{
|
||||
v6 = list.map(item => {
|
||||
return {
|
||||
address: item,
|
||||
family: 6
|
||||
}
|
||||
})
|
||||
family: 6,
|
||||
};
|
||||
});
|
||||
}
|
||||
}catch (e) {
|
||||
logger.warn("query v6 error",e)
|
||||
errors.push(e)
|
||||
} catch (e) {
|
||||
logger.warn("query v6 error", e);
|
||||
errors.push(e);
|
||||
}
|
||||
}
|
||||
const queryV4 = async ()=>{
|
||||
try{
|
||||
const list =await this.resolver.resolve4(hostname)
|
||||
};
|
||||
const queryV4 = async () => {
|
||||
try {
|
||||
const list = await this.resolver.resolve4(hostname);
|
||||
if (list && list.length > 0) {
|
||||
v4 = list.map(item=>{
|
||||
v4 = list.map(item => {
|
||||
return {
|
||||
address: item,
|
||||
family: 4
|
||||
}
|
||||
})
|
||||
family: 4,
|
||||
};
|
||||
});
|
||||
}
|
||||
}catch (e) {
|
||||
logger.warn("query v4 error",e)
|
||||
errors.push(e)
|
||||
} catch (e) {
|
||||
logger.warn("query v4 error", e);
|
||||
errors.push(e);
|
||||
}
|
||||
};
|
||||
|
||||
const queries: Promise<any>[] = [];
|
||||
|
||||
const { family, all } = options;
|
||||
if (all) {
|
||||
queries.push(queryV6());
|
||||
queries.push(queryV4());
|
||||
} else {
|
||||
if (family === 6) {
|
||||
queries.push(queryV6());
|
||||
}
|
||||
if (family === 4) {
|
||||
queries.push(queryV4());
|
||||
}
|
||||
}
|
||||
|
||||
const queries:Promise<any>[] = []
|
||||
|
||||
|
||||
const {family, all} = options
|
||||
if (all){
|
||||
queries.push(queryV6())
|
||||
queries.push(queryV4())
|
||||
}else{
|
||||
if(family === 6 ){
|
||||
queries.push(queryV6())
|
||||
}
|
||||
if(family === 4 ){
|
||||
queries.push(queryV4())
|
||||
}
|
||||
}
|
||||
await Promise.all(queries)
|
||||
const res = [...v4,...v6]
|
||||
if(res.length === 0){
|
||||
if (errors.length > 0){
|
||||
const e = new Error(errors[0])
|
||||
await Promise.all(queries);
|
||||
const res = [...v4, ...v6];
|
||||
if (res.length === 0) {
|
||||
if (errors.length > 0) {
|
||||
const e = new Error(errors[0]);
|
||||
// @ts-ignore
|
||||
e.errors = errors
|
||||
throw e
|
||||
e.errors = errors;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return res
|
||||
return res;
|
||||
}
|
||||
|
||||
async resolve4(hostname:string):Promise<string[]>{
|
||||
return await this.resolver.resolve4(hostname)
|
||||
async resolve4(hostname: string): Promise<string[]> {
|
||||
return await this.resolver.resolve4(hostname);
|
||||
}
|
||||
async resolve6(hostname:string):Promise<string[]>{
|
||||
return await this.resolver.resolve6(hostname)
|
||||
async resolve6(hostname: string): Promise<string[]> {
|
||||
return await this.resolver.resolve6(hostname);
|
||||
}
|
||||
async resolveAny(hostname:string):Promise<AnyRecord[]>{
|
||||
return await this.resolver.resolveAny(hostname)
|
||||
async resolveAny(hostname: string): Promise<AnyRecord[]> {
|
||||
return await this.resolver.resolveAny(hostname);
|
||||
}
|
||||
|
||||
async resolveCname(hostname:string):Promise<string[]>{
|
||||
return await this.resolver.resolveCname(hostname)
|
||||
async resolveCname(hostname: string): Promise<string[]> {
|
||||
return await this.resolver.resolveCname(hostname);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export class DnsContainer{
|
||||
bucket: LocalCache<DnsCustom> = new LocalCache()
|
||||
export class DnsContainer {
|
||||
bucket: LocalCache<DnsCustom> = new LocalCache();
|
||||
|
||||
constructor() {}
|
||||
getDns(server:string[]){
|
||||
const key = server.join(',')
|
||||
let dns = this.bucket.get(key)
|
||||
if (dns){
|
||||
return dns
|
||||
getDns(server: string[]) {
|
||||
const key = server.join(",");
|
||||
let dns = this.bucket.get(key);
|
||||
if (dns) {
|
||||
return dns;
|
||||
}
|
||||
dns = new DnsCustom(server)
|
||||
this.bucket.set(key,dns)
|
||||
return dns
|
||||
dns = new DnsCustom(server);
|
||||
this.bucket.set(key, dns);
|
||||
return dns;
|
||||
}
|
||||
}
|
||||
|
||||
export const dnsContainer = new DnsContainer()
|
||||
export const dnsContainer = new DnsContainer();
|
||||
|
||||
@@ -5,14 +5,12 @@ import { Repository } from "typeorm";
|
||||
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
|
||||
import { JobHistoryEntity } from "../entity/job-history.js";
|
||||
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class JobHistoryService extends BaseService<JobHistoryEntity> {
|
||||
@InjectEntityModel(JobHistoryEntity)
|
||||
repository: Repository<JobHistoryEntity>;
|
||||
|
||||
|
||||
@Inject()
|
||||
userSettingsService: UserSettingsService;
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
|
||||
import {BaseService, Constants, isEnterprise, NeedSuiteException, NeedVIPException, SysSettingsService} from "@certd/lib-server";
|
||||
import {InjectEntityModel} from "@midwayjs/typeorm";
|
||||
import {In, Repository} from "typeorm";
|
||||
import {SiteInfoEntity} from "../entity/site-info.js";
|
||||
import {siteTester} from "./site-tester.js";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { BaseService, Constants, isEnterprise, NeedSuiteException, NeedVIPException, SysSettingsService } from "@certd/lib-server";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { SiteInfoEntity } from "../entity/site-info.js";
|
||||
import { siteTester } from "./site-tester.js";
|
||||
import dayjs from "dayjs";
|
||||
import {logger, utils} from "@certd/basic";
|
||||
import {PeerCertificate} from "tls";
|
||||
import {NotificationService} from "../../pipeline/service/notification-service.js";
|
||||
import {isComm, isPlus} from "@certd/plus-core";
|
||||
import {UserSuiteService} from "@certd/commercial-core";
|
||||
import {UserSettingsService} from "../../mine/service/user-settings-service.js";
|
||||
import {UserSiteMonitorSetting} from "../../mine/service/models.js";
|
||||
import {SiteIpService} from "./site-ip-service.js";
|
||||
import {SiteIpEntity} from "../entity/site-ip.js";
|
||||
import {Cron} from "../../cron/cron.js";
|
||||
import { logger, utils } from "@certd/basic";
|
||||
import { PeerCertificate } from "tls";
|
||||
import { NotificationService } from "../../pipeline/service/notification-service.js";
|
||||
import { isComm, isPlus } from "@certd/plus-core";
|
||||
import { UserSuiteService } from "@certd/commercial-core";
|
||||
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
|
||||
import { UserSiteMonitorSetting } from "../../mine/service/models.js";
|
||||
import { SiteIpService } from "./site-ip-service.js";
|
||||
import { SiteIpEntity } from "../entity/site-ip.js";
|
||||
import { Cron } from "../../cron/cron.js";
|
||||
import { dnsContainer } from "./dns-custom.js";
|
||||
import { merge } from "lodash-es";
|
||||
import { JobHistoryService } from "./job-history-service.js";
|
||||
@@ -23,7 +23,7 @@ import { UserService } from "../../sys/authority/service/user-service.js";
|
||||
import { ProjectService } from "../../sys/enterprise/service/project-service.js";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
@InjectEntityModel(SiteInfoEntity)
|
||||
repository: Repository<SiteInfoEntity>;
|
||||
@@ -87,18 +87,18 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
}
|
||||
|
||||
await this.checkMonitorLimit(data.userId);
|
||||
|
||||
|
||||
data.disabled = false;
|
||||
|
||||
const found = await this.repository.findOne({
|
||||
where: {
|
||||
domain: data.domain,
|
||||
userId: data.userId,
|
||||
httpsPort: data.httpsPort || 443
|
||||
}
|
||||
httpsPort: data.httpsPort || 443,
|
||||
},
|
||||
});
|
||||
if (found) {
|
||||
return {id: found.id};
|
||||
return { id: found.id };
|
||||
}
|
||||
|
||||
return await super.add(data);
|
||||
@@ -113,11 +113,11 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
}
|
||||
|
||||
async getUserMonitorCount(userId: number) {
|
||||
if (userId==null) {
|
||||
if (userId == null) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
return await this.repository.count({
|
||||
where: {userId}
|
||||
where: { userId },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -132,18 +132,18 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
throw new Error("站点域名不能为空");
|
||||
}
|
||||
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId,site.projectId, UserSiteMonitorSetting);
|
||||
const dnsServer = setting.dnsServer
|
||||
let customDns = null
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, site.projectId, UserSiteMonitorSetting);
|
||||
const dnsServer = setting.dnsServer;
|
||||
let customDns = null;
|
||||
if (dnsServer && dnsServer.length > 0) {
|
||||
customDns = dnsContainer.getDns(dnsServer) as any
|
||||
customDns = dnsContainer.getDns(dnsServer) as any;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.update({
|
||||
id: site.id,
|
||||
checkStatus: "checking",
|
||||
lastCheckTime: dayjs().valueOf()
|
||||
lastCheckTime: dayjs().valueOf(),
|
||||
});
|
||||
const res = await siteTester.test({
|
||||
host: site.domain,
|
||||
@@ -177,56 +177,56 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
certExpiresTime: dayjs(expires).valueOf(),
|
||||
lastCheckTime: dayjs().valueOf(),
|
||||
error: null,
|
||||
checkStatus: "ok"
|
||||
checkStatus: "ok",
|
||||
};
|
||||
logger.info(`测试站点成功:id=${updateData.id},site=${site.name},certEffectiveTime=${updateData.certEffectiveTime},expiresTime=${updateData.certExpiresTime}`)
|
||||
logger.info(`测试站点成功:id=${updateData.id},site=${site.name},certEffectiveTime=${updateData.certEffectiveTime},expiresTime=${updateData.certExpiresTime}`);
|
||||
if (site.ipCheck) {
|
||||
delete updateData.checkStatus
|
||||
delete updateData.checkStatus;
|
||||
}
|
||||
await this.update(updateData);
|
||||
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId,site.projectId, UserSiteMonitorSetting)
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, site.projectId, UserSiteMonitorSetting);
|
||||
|
||||
merge(site,updateData)
|
||||
merge(site, updateData);
|
||||
//检查ip
|
||||
await this.checkAllIp(site,retryTimes,setting);
|
||||
await this.checkAllIp(site, retryTimes, setting);
|
||||
|
||||
if (!notify) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.sendExpiresNotify(site.id,setting);
|
||||
await this.sendExpiresNotify(site.id, setting);
|
||||
} catch (e) {
|
||||
logger.error("send notify error", e);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("check site error", e);
|
||||
let message = e.message
|
||||
if (!message){
|
||||
message = e.code
|
||||
let message = e.message;
|
||||
if (!message) {
|
||||
message = e.code;
|
||||
}
|
||||
if (e.errors &&e.errors.length > 0){
|
||||
message += "\n"+e.errors.map((item:any)=>item.message).join("\n")
|
||||
if (e.errors && e.errors.length > 0) {
|
||||
message += "\n" + e.errors.map((item: any) => item.message).join("\n");
|
||||
}
|
||||
await this.update({
|
||||
id: site.id,
|
||||
checkStatus: "error",
|
||||
lastCheckTime: dayjs().valueOf(),
|
||||
error: message
|
||||
error: message,
|
||||
});
|
||||
if (!notify) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.sendCheckErrorNotify(site.id,false,setting);
|
||||
await this.sendCheckErrorNotify(site.id, false, setting);
|
||||
} catch (e) {
|
||||
logger.error("send notify error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkAllIp(site: SiteInfoEntity,retryTimes = null,setting: UserSiteMonitorSetting) {
|
||||
async checkAllIp(site: SiteInfoEntity, retryTimes = null, setting: UserSiteMonitorSetting) {
|
||||
if (!site.ipCheck) {
|
||||
return;
|
||||
}
|
||||
@@ -248,11 +248,11 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
errorMessage += `${item.ipAddress}:${item.error}; \n`;
|
||||
} else if (item.certExpiresTime !== certExpiresTime && !site.ipIgnoreCoherence) {
|
||||
errorMessage += `${item.ipAddress}:与主站证书过期时间不一致(主站:${dayjs(certExpiresTime).format("YYYY-MM-DD")},IP:${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}); \n`;
|
||||
} else if (isExpired){
|
||||
} else if (isExpired) {
|
||||
errorMessage += `${item.ipAddress}:证书已过期(过期时间:${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}); \n`;
|
||||
}else if (isWillExpired){
|
||||
} else if (isWillExpired) {
|
||||
errorMessage += `${item.ipAddress}:证书将过期(过期时间:${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}); \n`;
|
||||
}else {
|
||||
} else {
|
||||
errorCount--;
|
||||
}
|
||||
}
|
||||
@@ -262,7 +262,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
id: site.id,
|
||||
checkStatus: "ok",
|
||||
error: "",
|
||||
ipErrorCount: 0
|
||||
ipErrorCount: 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -270,20 +270,19 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
id: site.id,
|
||||
checkStatus: "error",
|
||||
error: errorMessage,
|
||||
ipErrorCount: errorCount
|
||||
ipErrorCount: errorCount,
|
||||
});
|
||||
try {
|
||||
await this.sendCheckErrorNotify(site.id, true,setting);
|
||||
await this.sendCheckErrorNotify(site.id, true, setting);
|
||||
} catch (e) {
|
||||
logger.error("send notify error", e);
|
||||
}
|
||||
};
|
||||
if (site.ipSyncAuto === false) {
|
||||
await this.siteIpService.checkAll(site, retryTimes,onFinished);
|
||||
}else{
|
||||
await this.siteIpService.syncAndCheck(site, retryTimes,onFinished);
|
||||
await this.siteIpService.checkAll(site, retryTimes, onFinished);
|
||||
} else {
|
||||
await this.siteIpService.syncAndCheck(site, retryTimes, onFinished);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,13 +297,13 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
throw new Error("站点不存在");
|
||||
}
|
||||
|
||||
this.doCheck(site, notify, retryTimes).catch((err) => {
|
||||
this.doCheck(site, notify, retryTimes).catch(err => {
|
||||
logger.error("check site error", err);
|
||||
});
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
async sendCheckErrorNotify(siteId: number, fromIpCheck = false,setting: UserSiteMonitorSetting) {
|
||||
async sendCheckErrorNotify(siteId: number, fromIpCheck = false, setting: UserSiteMonitorSetting) {
|
||||
const site = await this.info(siteId);
|
||||
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
|
||||
// 发邮件
|
||||
@@ -319,13 +318,13 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
|
||||
errorMessage: site.error,
|
||||
notificationType: "siteCheckError",
|
||||
}
|
||||
},
|
||||
},
|
||||
site.userId
|
||||
);
|
||||
}
|
||||
|
||||
async sendExpiresNotify(siteId: number,setting: UserSiteMonitorSetting) {
|
||||
async sendExpiresNotify(siteId: number, setting: UserSiteMonitorSetting) {
|
||||
const tipDays = setting?.certValidDays || 10;
|
||||
const site = await this.info(siteId);
|
||||
const expires = site.certExpiresTime;
|
||||
@@ -345,7 +344,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
url,
|
||||
errorMessage: "站点证书即将过期",
|
||||
notificationType: "siteCertExpireRemind",
|
||||
}
|
||||
},
|
||||
},
|
||||
site.userId
|
||||
);
|
||||
@@ -362,7 +361,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
url,
|
||||
errorMessage: "站点证书已过期",
|
||||
notificationType: "siteCertExpireRemind",
|
||||
}
|
||||
},
|
||||
},
|
||||
site.userId
|
||||
);
|
||||
@@ -370,45 +369,44 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
}
|
||||
|
||||
async checkList(sites: SiteInfoEntity[]) {
|
||||
const cache = {}
|
||||
const getFromCache = async (userId: number,projectId?: number) =>{
|
||||
const key = `${userId}_${projectId??""}`
|
||||
const cache = {};
|
||||
const getFromCache = async (userId: number, projectId?: number) => {
|
||||
const key = `${userId}_${projectId ?? ""}`;
|
||||
if (cache[key]) {
|
||||
return cache[key];
|
||||
}
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId, UserSiteMonitorSetting)
|
||||
cache[key] = setting
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, projectId, UserSiteMonitorSetting);
|
||||
cache[key] = setting;
|
||||
return setting;
|
||||
}
|
||||
};
|
||||
for (const site of sites) {
|
||||
const setting = await getFromCache(site.userId,site.projectId)
|
||||
let retryTimes = setting?.retryTimes
|
||||
this.doCheck(site,true,retryTimes).catch(e => {
|
||||
const setting = await getFromCache(site.userId, site.projectId);
|
||||
const retryTimes = setting?.retryTimes;
|
||||
this.doCheck(site, true, retryTimes).catch(e => {
|
||||
logger.error(`检查站点证书失败,${site.domain}`, e.message);
|
||||
});
|
||||
await utils.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
async getSetting(userId: number,projectId?: number) {
|
||||
return await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId, UserSiteMonitorSetting);
|
||||
async getSetting(userId: number, projectId?: number) {
|
||||
return await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, projectId, UserSiteMonitorSetting);
|
||||
}
|
||||
|
||||
async saveSetting(userId: number,projectId: number, bean: UserSiteMonitorSetting) {
|
||||
await this.userSettingsService.saveSetting(userId,projectId, bean);
|
||||
if(bean.cron){
|
||||
async saveSetting(userId: number, projectId: number, bean: UserSiteMonitorSetting) {
|
||||
await this.userSettingsService.saveSetting(userId, projectId, bean);
|
||||
if (bean.cron) {
|
||||
//注册job
|
||||
await this.registerSiteMonitorJob(userId,projectId);
|
||||
}else{
|
||||
this.clearSiteMonitorJob(userId,projectId);
|
||||
await this.registerSiteMonitorJob(userId, projectId);
|
||||
} else {
|
||||
this.clearSiteMonitorJob(userId, projectId);
|
||||
}
|
||||
}
|
||||
|
||||
async ipCheckChange(req: { id: any; ipCheck: any }) {
|
||||
|
||||
await this.update({
|
||||
id: req.id,
|
||||
ipCheck: req.ipCheck
|
||||
ipCheck: req.ipCheck,
|
||||
});
|
||||
if (req.ipCheck) {
|
||||
const site = await this.info(req.id);
|
||||
@@ -419,7 +417,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
async disabledChange(req: { disabled: any; id: any }) {
|
||||
await this.update({
|
||||
id: req.id,
|
||||
disabled: req.disabled
|
||||
disabled: req.disabled,
|
||||
});
|
||||
if (!req.disabled) {
|
||||
const site = await this.info(req.id);
|
||||
@@ -427,7 +425,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
async doImport(req: { text: string; userId: number,groupId?:number,projectId?:number }) {
|
||||
async doImport(req: { text: string; userId: number; groupId?: number; projectId?: number }) {
|
||||
if (!req.text) {
|
||||
throw new Error("text is required");
|
||||
}
|
||||
@@ -459,7 +457,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
if (arr.length > 2) {
|
||||
name = arr[2] || domain;
|
||||
}
|
||||
let remark:string = "";
|
||||
let remark = "";
|
||||
if (arr.length > 3) {
|
||||
remark = arr[3] || "";
|
||||
}
|
||||
@@ -471,7 +469,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
userId: req.userId,
|
||||
remark,
|
||||
groupId: req.groupId,
|
||||
projectId: req.projectId
|
||||
projectId: req.projectId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -485,77 +483,77 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
await batchAdd(list);
|
||||
}
|
||||
|
||||
clearSiteMonitorJob(userId: number,projectId?: number) {
|
||||
this.cron.remove(`siteMonitor_${userId}_${projectId||""}`);
|
||||
clearSiteMonitorJob(userId: number, projectId?: number) {
|
||||
this.cron.remove(`siteMonitor_${userId}_${projectId || ""}`);
|
||||
}
|
||||
|
||||
async registerSiteMonitorJob(userId?: number,projectId?: number) {
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId, UserSiteMonitorSetting);
|
||||
async registerSiteMonitorJob(userId?: number, projectId?: number) {
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, projectId, UserSiteMonitorSetting);
|
||||
if (!setting.cron) {
|
||||
return;
|
||||
}
|
||||
//注册个人的 或项目的
|
||||
this.cron.register({
|
||||
name: `siteMonitor_${userId}_${projectId||""}`,
|
||||
name: `siteMonitor_${userId}_${projectId || ""}`,
|
||||
cron: setting.cron,
|
||||
job: () => this.triggerJobOnce(userId,projectId),
|
||||
job: () => this.triggerJobOnce(userId, projectId),
|
||||
});
|
||||
}
|
||||
|
||||
async triggerCommonJob(){
|
||||
async triggerCommonJob() {
|
||||
//遍历用户
|
||||
const userIds = await this.userService.getAllUserIds()
|
||||
const userIds = await this.userService.getAllUserIds();
|
||||
for (const userId of userIds) {
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,null,UserSiteMonitorSetting)
|
||||
if(setting && setting.cron){
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, null, UserSiteMonitorSetting);
|
||||
if (setting && setting.cron) {
|
||||
//该用户有自定义检查时间,跳过公共job
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
await this.triggerJobOnce(userId)
|
||||
await this.triggerJobOnce(userId);
|
||||
}
|
||||
|
||||
//遍历项目
|
||||
const projectIds = await this.projectService.getAllProjectIds()
|
||||
const projectIds = await this.projectService.getAllProjectIds();
|
||||
for (const projectId of projectIds) {
|
||||
const userId = Constants.enterpriseUserId
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId,UserSiteMonitorSetting)
|
||||
if(setting && setting.cron){
|
||||
const userId = Constants.enterpriseUserId;
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, projectId, UserSiteMonitorSetting);
|
||||
if (setting && setting.cron) {
|
||||
//该项目有自定义检查时间,跳过公共job
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
await this.triggerJobOnce(userId,projectId)
|
||||
await this.triggerJobOnce(userId, projectId);
|
||||
}
|
||||
}
|
||||
|
||||
async triggerJobOnce(userId?:number,projectId?:number) {
|
||||
if(userId==null){
|
||||
async triggerJobOnce(userId?: number, projectId?: number) {
|
||||
if (userId == null) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
const query:any = { disabled: false };
|
||||
const query: any = { disabled: false };
|
||||
query.userId = userId;
|
||||
if(projectId){
|
||||
if (projectId) {
|
||||
query.projectId = projectId;
|
||||
}
|
||||
const siteCount = await this.repository.count({
|
||||
where: query,
|
||||
});
|
||||
if (siteCount === 0) {
|
||||
logger.info(`用户/项目[${userId}_${projectId||""}]没有站点证书需要检查`)
|
||||
logger.info(`用户/项目[${userId}_${projectId || ""}]没有站点证书需要检查`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`站点证书检查开始执行[${userId}_${projectId||""}]`);
|
||||
|
||||
let jobEntity :Partial<JobHistoryEntity> = null;
|
||||
|
||||
jobEntity = {
|
||||
logger.info(`站点证书检查开始执行[${userId}_${projectId || ""}]`);
|
||||
|
||||
let jobEntity: Partial<JobHistoryEntity> = null;
|
||||
|
||||
jobEntity = {
|
||||
userId,
|
||||
projectId,
|
||||
type:"siteCertMonitor",
|
||||
title: '站点证书检查',
|
||||
result:"start",
|
||||
startAt:new Date().getTime(),
|
||||
}
|
||||
type: "siteCertMonitor",
|
||||
title: "站点证书检查",
|
||||
result: "start",
|
||||
startAt: new Date().getTime(),
|
||||
};
|
||||
await this.jobHistoryService.add(jobEntity);
|
||||
let offset = 0;
|
||||
const limit = 50;
|
||||
@@ -575,17 +573,17 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
await this.checkList(records);
|
||||
}
|
||||
|
||||
logger.info(`站点证书检查完成[${userId}_${projectId||""}]`);
|
||||
logger.info(`站点证书检查完成[${userId}_${projectId || ""}]`);
|
||||
await this.jobHistoryService.update({
|
||||
id: jobEntity.id,
|
||||
result: "done",
|
||||
content:`共检查${count}个站点`,
|
||||
endAt:new Date().getTime(),
|
||||
updateTime:new Date(),
|
||||
content: `共检查${count}个站点`,
|
||||
endAt: new Date().getTime(),
|
||||
updateTime: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
async batchDelete(ids: number[], userId: number,projectId?:number): Promise<void> {
|
||||
async batchDelete(ids: number[], userId: number, projectId?: number): Promise<void> {
|
||||
await this.repository.delete({
|
||||
id: In(ids),
|
||||
userId,
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
|
||||
import {BaseService, SysSettingsService} from "@certd/lib-server";
|
||||
import {InjectEntityModel} from "@midwayjs/typeorm";
|
||||
import {Repository} from "typeorm";
|
||||
import {SiteInfoEntity} from "../entity/site-info.js";
|
||||
import {NotificationService} from "../../pipeline/service/notification-service.js";
|
||||
import {UserSuiteService} from "@certd/commercial-core";
|
||||
import {UserSettingsService} from "../../mine/service/user-settings-service.js";
|
||||
import {SiteIpEntity} from "../entity/site-ip.js";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { BaseService, SysSettingsService } from "@certd/lib-server";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { SiteInfoEntity } from "../entity/site-info.js";
|
||||
import { NotificationService } from "../../pipeline/service/notification-service.js";
|
||||
import { UserSuiteService } from "@certd/commercial-core";
|
||||
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
|
||||
import { SiteIpEntity } from "../entity/site-ip.js";
|
||||
import dnsSdk from "dns";
|
||||
import {logger} from "@certd/basic";
|
||||
import { logger } from "@certd/basic";
|
||||
import dayjs from "dayjs";
|
||||
import {siteTester} from "./site-tester.js";
|
||||
import {PeerCertificate} from "tls";
|
||||
import { siteTester } from "./site-tester.js";
|
||||
import { PeerCertificate } from "tls";
|
||||
import { UserSiteMonitorSetting } from "../../mine/service/models.js";
|
||||
import { dnsContainer } from "./dns-custom.js";
|
||||
|
||||
const dns = dnsSdk.promises;
|
||||
const dns = dnsSdk.promises;
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -47,12 +47,11 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
data.disabled = false;
|
||||
const res= await super.add(data);
|
||||
await this.updateIpCount(data.siteId)
|
||||
return res
|
||||
const res = await super.add(data);
|
||||
await this.updateIpCount(data.siteId);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
async update(data: any) {
|
||||
if (!data.id) {
|
||||
throw new Error("id is required");
|
||||
@@ -61,52 +60,52 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
await super.update(data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async sync(entity: SiteInfoEntity,check:boolean = true) {
|
||||
|
||||
async sync(entity: SiteInfoEntity, check = true) {
|
||||
const domain = entity.domain;
|
||||
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(entity.userId,entity.projectId, UserSiteMonitorSetting);
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(entity.userId, entity.projectId, UserSiteMonitorSetting);
|
||||
|
||||
const dnsServer = setting.dnsServer
|
||||
let resolver = dns
|
||||
const dnsServer = setting.dnsServer;
|
||||
let resolver = dns;
|
||||
if (dnsServer && dnsServer.length > 0) {
|
||||
resolver = dnsContainer.getDns(dnsServer) as any
|
||||
resolver = dnsContainer.getDns(dnsServer) as any;
|
||||
}
|
||||
|
||||
//从域名解析中获取所有ip
|
||||
const ips = await this.getAllIpsFromDomain(domain,resolver,entity.ipSyncMode);
|
||||
if (ips.length === 0 ) {
|
||||
logger.warn(`没有发现${domain}的IP`)
|
||||
return
|
||||
const ips = await this.getAllIpsFromDomain(domain, resolver, entity.ipSyncMode);
|
||||
if (ips.length === 0) {
|
||||
logger.warn(`没有发现${domain}的IP`);
|
||||
return;
|
||||
}
|
||||
|
||||
const oldIps = await this.repository.find({
|
||||
where:{
|
||||
siteId: entity.id,
|
||||
from:"sync"
|
||||
}
|
||||
})
|
||||
where: {
|
||||
siteId: entity.id,
|
||||
from: "sync",
|
||||
},
|
||||
});
|
||||
|
||||
let hasChanged = true
|
||||
if (oldIps.length === ips.length ){
|
||||
let hasChanged = true;
|
||||
if (oldIps.length === ips.length) {
|
||||
//检查是否有变化
|
||||
const oldIpList = oldIps.map(ip=>ip.ipAddress).sort().join(",")
|
||||
const newIpList = ips.sort().join(",")
|
||||
if(oldIpList === newIpList){
|
||||
//无变化
|
||||
hasChanged = false
|
||||
}
|
||||
const oldIpList = oldIps
|
||||
.map(ip => ip.ipAddress)
|
||||
.sort()
|
||||
.join(",");
|
||||
const newIpList = ips.sort().join(",");
|
||||
if (oldIpList === newIpList) {
|
||||
//无变化
|
||||
hasChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(hasChanged){
|
||||
logger.info(`发现${domain}的IP变化,需要更新,旧IP:${oldIps.map(ip=>ip.ipAddress).join(",")},新IP:${ips.join(",")}`)
|
||||
if (hasChanged) {
|
||||
logger.info(`发现${domain}的IP变化,需要更新,旧IP:${oldIps.map(ip => ip.ipAddress).join(",")},新IP:${ips.join(",")}`);
|
||||
//有变化需要更新
|
||||
//删除所有的ip
|
||||
await this.repository.delete({
|
||||
siteId: entity.id,
|
||||
from: "sync"
|
||||
from: "sync",
|
||||
});
|
||||
|
||||
//添加新的ip
|
||||
@@ -116,36 +115,36 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
userId: entity.userId,
|
||||
siteId: entity.id,
|
||||
from: "sync",
|
||||
disabled:false,
|
||||
disabled: false,
|
||||
});
|
||||
await this.updateIpCount(entity.id)
|
||||
await this.updateIpCount(entity.id);
|
||||
}
|
||||
}
|
||||
if (check){
|
||||
if (check) {
|
||||
await this.checkAll(entity);
|
||||
}
|
||||
}
|
||||
|
||||
async check(ipId: number, domain: string, port: number,retryTimes = null ,tipDays = 10) {
|
||||
if(!ipId){
|
||||
return
|
||||
async check(ipId: number, domain: string, port: number, retryTimes = null, tipDays = 10) {
|
||||
if (!ipId) {
|
||||
return;
|
||||
}
|
||||
const entity = await this.info(ipId);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
logger.info(`开始测试站点ip: id=${entity.id},ip=${entity.ipAddress}`)
|
||||
logger.info(`开始测试站点ip: id=${entity.id},ip=${entity.ipAddress}`);
|
||||
try {
|
||||
await this.update({
|
||||
id: entity.id,
|
||||
checkStatus: "checking",
|
||||
lastCheckTime: dayjs().valueOf()
|
||||
lastCheckTime: dayjs().valueOf(),
|
||||
});
|
||||
const res = await siteTester.test({
|
||||
host: domain,
|
||||
port: port,
|
||||
retryTimes : retryTimes??3,
|
||||
ipAddress: entity.ipAddress
|
||||
retryTimes: retryTimes ?? 3,
|
||||
ipAddress: entity.ipAddress,
|
||||
});
|
||||
|
||||
const certi: PeerCertificate = res.certificate;
|
||||
@@ -170,39 +169,38 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
certExpiresTime: dayjs(expires).valueOf(),
|
||||
lastCheckTime: dayjs().valueOf(),
|
||||
error: null,
|
||||
checkStatus: "ok"
|
||||
checkStatus: "ok",
|
||||
};
|
||||
|
||||
await this.update(updateData);
|
||||
logger.info(`测试站点ip成功: id=${updateData.id},ip=${entity.ipAddress},expiresTime=${updateData.certExpiresTime}`)
|
||||
logger.info(`测试站点ip成功: id=${updateData.id},ip=${entity.ipAddress},expiresTime=${updateData.certExpiresTime}`);
|
||||
|
||||
return {
|
||||
...updateData,
|
||||
ipAddress: entity.ipAddress,
|
||||
}
|
||||
|
||||
};
|
||||
} catch (e) {
|
||||
logger.error("check site ip error", e);
|
||||
await this.update({
|
||||
id: entity.id,
|
||||
checkStatus: "error",
|
||||
lastCheckTime: dayjs().valueOf(),
|
||||
error: e.message
|
||||
error: e.message,
|
||||
});
|
||||
return {
|
||||
id: entity.id,
|
||||
ipAddress: entity.ipAddress,
|
||||
error: e.message
|
||||
}
|
||||
error: e.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async checkAll(siteInfo: SiteInfoEntity,retryTimes = null,onFinish?: (e: any) => void) {
|
||||
async checkAll(siteInfo: SiteInfoEntity, retryTimes = null, onFinish?: (e: any) => void) {
|
||||
const siteId = siteInfo.id;
|
||||
const ips = await this.repository.find({
|
||||
where: {
|
||||
siteId: siteId
|
||||
}
|
||||
siteId: siteId,
|
||||
},
|
||||
});
|
||||
const domain = siteInfo.domain;
|
||||
const port = siteInfo.httpsPort;
|
||||
@@ -210,44 +208,44 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
for (const item of ips) {
|
||||
const func = async () => {
|
||||
try {
|
||||
return await this.check(item.id, domain, port,retryTimes);
|
||||
return await this.check(item.id, domain, port, retryTimes);
|
||||
} catch (e) {
|
||||
logger.error("check site item error", e);
|
||||
return {
|
||||
...item,
|
||||
error:e.message
|
||||
}
|
||||
error: e.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
promiseList.push(func());
|
||||
}
|
||||
Promise.all(promiseList).then((res)=>{
|
||||
const finished = res.filter(item=>{
|
||||
return item!=null
|
||||
})
|
||||
Promise.all(promiseList).then(res => {
|
||||
const finished = res.filter(item => {
|
||||
return item != null;
|
||||
});
|
||||
if (onFinish) {
|
||||
onFinish && onFinish(finished)
|
||||
onFinish && onFinish(finished);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async getAllIpsFromDomain(domain: string,resolver:any = dns,ipSyncMode:string = "all"):Promise<string[]> {
|
||||
const getFromV4 = async ():Promise<string[]> => {
|
||||
try{
|
||||
async getAllIpsFromDomain(domain: string, resolver: any = dns, ipSyncMode = "all"): Promise<string[]> {
|
||||
const getFromV4 = async (): Promise<string[]> => {
|
||||
try {
|
||||
return await resolver.resolve4(domain);
|
||||
}catch (err) {
|
||||
logger.warn(`[${domain}] resolve4 error`, err)
|
||||
return []
|
||||
} catch (err) {
|
||||
logger.warn(`[${domain}] resolve4 error`, err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
const getFromV6 = async ():Promise<string[]> => {
|
||||
try{
|
||||
};
|
||||
const getFromV6 = async (): Promise<string[]> => {
|
||||
try {
|
||||
return await resolver.resolve6(domain);
|
||||
}catch (err) {
|
||||
logger.warn(`[${domain}] resolve6 error`, err)
|
||||
return []
|
||||
} catch (err) {
|
||||
logger.warn(`[${domain}] resolve6 error`, err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (ipSyncMode === "ipv4") {
|
||||
return await getFromV4();
|
||||
@@ -261,24 +259,25 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async updateIpCount(siteId:number){
|
||||
async updateIpCount(siteId: number) {
|
||||
const count = await this.repository.count({
|
||||
where:{
|
||||
siteId
|
||||
}
|
||||
})
|
||||
await this.siteInfoRepository.update({
|
||||
//where
|
||||
id:siteId,
|
||||
},
|
||||
where: {
|
||||
siteId,
|
||||
},
|
||||
});
|
||||
await this.siteInfoRepository.update(
|
||||
{
|
||||
//where
|
||||
id: siteId,
|
||||
},
|
||||
{
|
||||
//update
|
||||
ipCount:count
|
||||
})
|
||||
ipCount: count,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async doImport(req: { text: string; userId:number, siteId:number,projectId?:number }) {
|
||||
async doImport(req: { text: string; userId: number; siteId: number; projectId?: number }) {
|
||||
if (!req.text) {
|
||||
throw new Error("text is required");
|
||||
}
|
||||
@@ -289,9 +288,9 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
const siteEntity = await this.siteInfoRepository.findOne({
|
||||
where: {
|
||||
id: req.siteId,
|
||||
userId:req.userId,
|
||||
projectId:req.projectId
|
||||
}
|
||||
userId: req.userId,
|
||||
projectId: req.projectId,
|
||||
},
|
||||
});
|
||||
if (!siteEntity) {
|
||||
throw new Error(`站点${req.siteId}不存在`);
|
||||
@@ -307,11 +306,11 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
continue;
|
||||
}
|
||||
list.push({
|
||||
ipAddress:item,
|
||||
ipAddress: item,
|
||||
userId: userId,
|
||||
siteId: req.siteId,
|
||||
from: "import",
|
||||
disabled:false,
|
||||
disabled: false,
|
||||
projectId: req.projectId,
|
||||
});
|
||||
}
|
||||
@@ -324,8 +323,8 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
await batchAdd(list);
|
||||
}
|
||||
|
||||
async syncAndCheck(siteEntity:SiteInfoEntity,retryTimes = null,onFinish?: (e: any) => void){
|
||||
await this.sync(siteEntity,false);
|
||||
await this.checkAll(siteEntity,retryTimes,onFinish);
|
||||
async syncAndCheck(siteEntity: SiteInfoEntity, retryTimes = null, onFinish?: (e: any) => void) {
|
||||
await this.sync(siteEntity, false);
|
||||
await this.checkAll(siteEntity, retryTimes, onFinish);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { logger, safePromise, utils } from "@certd/basic";
|
||||
import { merge } from "lodash-es";
|
||||
import https from "https";
|
||||
import { PeerCertificate } from "tls";
|
||||
import {DnsCustom} from "./dns-custom.js";
|
||||
import { DnsCustom } from "./dns-custom.js";
|
||||
|
||||
export type SiteTestReq = {
|
||||
host: string; // 只用域名部分
|
||||
@@ -20,8 +20,8 @@ export type SiteTestRes = {
|
||||
|
||||
export class SiteTester {
|
||||
async test(req: SiteTestReq): Promise<SiteTestRes> {
|
||||
const req_ = {...req}
|
||||
delete req_.customDns
|
||||
const req_ = { ...req };
|
||||
delete req_.customDns;
|
||||
logger.info("测试站点:", JSON.stringify(req_));
|
||||
const maxRetryTimes = req.retryTimes == null ? 3 : req.retryTimes;
|
||||
let tryCount = 0;
|
||||
@@ -49,40 +49,40 @@ export class SiteTester {
|
||||
{
|
||||
port: 443,
|
||||
method: "GET",
|
||||
rejectUnauthorized: false
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
req
|
||||
);
|
||||
|
||||
let customLookup = null
|
||||
let customLookup = null;
|
||||
if (req.ipAddress) {
|
||||
//使用固定的ip
|
||||
const ipAddress = req.ipAddress;
|
||||
options.headers = {
|
||||
host: options.host,
|
||||
//sni
|
||||
servername: options.host
|
||||
servername: options.host,
|
||||
};
|
||||
options.host = ipAddress;
|
||||
}else if (req.customDns ) {
|
||||
} else if (req.customDns) {
|
||||
// 非ip address 请求时
|
||||
const customDns = req.customDns
|
||||
customLookup = async (hostname:string, options:any, callback)=> {
|
||||
const customDns = req.customDns;
|
||||
customLookup = async (hostname: string, options: any, callback) => {
|
||||
console.log(hostname, options);
|
||||
|
||||
// { family: undefined, hints: 0, all: true }
|
||||
const res = await customDns.lookup(hostname, options)
|
||||
console.log("custom lookup res:",res)
|
||||
const res = await customDns.lookup(hostname, options);
|
||||
console.log("custom lookup res:", res);
|
||||
if (!res || res.length === 0) {
|
||||
callback(new Error("没有解析到IP"));
|
||||
}
|
||||
callback(null, res);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const agentOptions:any = { keepAlive: false };
|
||||
const agentOptions: any = { keepAlive: false };
|
||||
if (customLookup) {
|
||||
agentOptions.lookup = customLookup
|
||||
agentOptions.lookup = customLookup;
|
||||
}
|
||||
options.agent = new https.Agent(agentOptions);
|
||||
|
||||
@@ -95,19 +95,19 @@ export class SiteTester {
|
||||
});
|
||||
|
||||
// ✅ 关键:在 'socket' 事件中获取证书(握手完成后立即执行)
|
||||
req.on('socket', (socket:any) => {
|
||||
socket.on('secureConnect', () => {
|
||||
req.on("socket", (socket: any) => {
|
||||
socket.on("secureConnect", () => {
|
||||
// TLS握手完成,证书已经可用
|
||||
const certificate = socket.getPeerCertificate();
|
||||
if (certificate.subject) {
|
||||
logger.info('证书获取成功', certificate.subject);
|
||||
logger.info("证书获取成功", certificate.subject);
|
||||
resolve({
|
||||
certificate
|
||||
certificate,
|
||||
});
|
||||
}else{
|
||||
} else {
|
||||
logger.warn("证书信息为空");
|
||||
resolve({
|
||||
certificate: null
|
||||
certificate: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('cd_open_key')
|
||||
@Entity("cd_open_key")
|
||||
export class OpenKeyEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'key_id', comment: 'keyId' })
|
||||
@Column({ name: "key_id", comment: "keyId" })
|
||||
keyId: string;
|
||||
|
||||
@Column({ name: 'key_secret', comment: 'keySecret' })
|
||||
@Column({ name: "key_secret", comment: "keySecret" })
|
||||
keySecret: string;
|
||||
|
||||
@Column({ name: 'scope', comment: '权限范围' })
|
||||
@Column({ name: "scope", comment: "权限范围" })
|
||||
scope: string; // open 仅开放接口、 user 用户所有权限
|
||||
|
||||
@Column({ name: 'project_id', comment: '项目id' })
|
||||
@Column({ name: "project_id", comment: "项目id" })
|
||||
projectId: number;
|
||||
|
||||
@Column({ name: 'create_time', comment: '创建时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||
@Column({ name: "create_time", comment: "创建时间", default: () => "CURRENT_TIMESTAMP" })
|
||||
createTime: Date;
|
||||
|
||||
@Column({ name: 'update_time', comment: '修改时间', default: () => 'CURRENT_TIMESTAMP' })
|
||||
@Column({ name: "update_time", comment: "修改时间", default: () => "CURRENT_TIMESTAMP" })
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { BaseService, Constants, CodeException, PageReq } from '@certd/lib-server';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { OpenKeyEntity } from '../entity/open-key.js';
|
||||
import { utils } from '@certd/basic';
|
||||
import crypto from 'crypto';
|
||||
import dayjs from 'dayjs';
|
||||
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { BaseService, Constants, CodeException, PageReq } from "@certd/lib-server";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { OpenKeyEntity } from "../entity/open-key.js";
|
||||
import { utils } from "@certd/basic";
|
||||
import crypto from "crypto";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export type OpenKey = {
|
||||
userId: number;
|
||||
@@ -31,34 +31,34 @@ export class OpenKeyService extends BaseService<OpenKeyEntity> {
|
||||
}
|
||||
|
||||
async add(bean: OpenKeyEntity) {
|
||||
return await this.generate(bean.userId,bean.projectId, bean.scope);
|
||||
return await this.generate(bean.userId, bean.projectId, bean.scope);
|
||||
}
|
||||
|
||||
async generate(userId: number, projectId?: number, scope: string = 'open') {
|
||||
const keyId = utils.id.simpleNanoId(18) + '_key';
|
||||
async generate(userId: number, projectId?: number, scope = "open") {
|
||||
const keyId = utils.id.simpleNanoId(18) + "_key";
|
||||
const secretKey = crypto.randomBytes(32);
|
||||
const keySecret = Buffer.from(secretKey).toString('hex');
|
||||
const keySecret = Buffer.from(secretKey).toString("hex");
|
||||
const entity = new OpenKeyEntity();
|
||||
entity.userId = userId;
|
||||
entity.projectId = projectId;
|
||||
entity.keyId = keyId;
|
||||
entity.keySecret = keySecret;
|
||||
entity.scope = scope ?? 'open';
|
||||
entity.scope = scope ?? "open";
|
||||
await this.repository.save(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
async getByKeyId(keyId: string) {
|
||||
if (!keyId) {
|
||||
throw new Error('keyId不能为空');
|
||||
throw new Error("keyId不能为空");
|
||||
}
|
||||
return this.repository.findOne({ where: { keyId } });
|
||||
}
|
||||
|
||||
async verifyOpenKey(openKey: string): Promise<OpenKey> {
|
||||
// openkey 组成,content = base64({keyId,t,encrypt,signType}) ,sign = md5({keyId,t,encrypt,signType}secret) , key = content.sign
|
||||
const [content, sign] = openKey.split('.');
|
||||
const contentJson = Buffer.from(content, 'base64').toString();
|
||||
const [content, sign] = openKey.split(".");
|
||||
const contentJson = Buffer.from(content, "base64").toString();
|
||||
const { keyId, t, encrypt, signType } = JSON.parse(contentJson);
|
||||
// 正负不超过3分钟 ,timestamps单位为秒
|
||||
if (Math.abs(Number(t) - Math.floor(Date.now() / 1000)) > 180) {
|
||||
@@ -67,22 +67,22 @@ export class OpenKeyService extends BaseService<OpenKeyEntity> {
|
||||
|
||||
const entity = await this.getByKeyId(keyId);
|
||||
if (!entity) {
|
||||
throw new Error('openKey不存在');
|
||||
throw new Error("openKey不存在");
|
||||
}
|
||||
const secret = entity.keySecret;
|
||||
let computedSign = '';
|
||||
if (signType === 'md5') {
|
||||
let computedSign = "";
|
||||
if (signType === "md5") {
|
||||
computedSign = utils.hash.md5(contentJson + secret);
|
||||
} else if (signType === 'sha256') {
|
||||
} else if (signType === "sha256") {
|
||||
computedSign = utils.hash.sha256(contentJson + secret);
|
||||
} else {
|
||||
throw new CodeException(Constants.res.openKeySignTypeError);
|
||||
}
|
||||
if (Buffer.from(computedSign).toString('base64') !== sign) {
|
||||
if (Buffer.from(computedSign).toString("base64") !== sign) {
|
||||
throw new CodeException(Constants.res.openKeySignError);
|
||||
}
|
||||
|
||||
if (entity.userId==null) {
|
||||
if (entity.userId == null) {
|
||||
throw new CodeException(Constants.res.openKeyError);
|
||||
}
|
||||
|
||||
@@ -98,21 +98,21 @@ export class OpenKeyService extends BaseService<OpenKeyEntity> {
|
||||
|
||||
async getApiToken(id: number) {
|
||||
if (!id) {
|
||||
throw new Error('id不能为空');
|
||||
throw new Error("id不能为空");
|
||||
}
|
||||
const entity = await this.repository.findOne({ where: { id } });
|
||||
if (!entity) {
|
||||
throw new Error('id不存在');
|
||||
throw new Error("id不存在");
|
||||
}
|
||||
const { keyId, keySecret } = entity;
|
||||
const openKey = {
|
||||
keyId,
|
||||
t: dayjs().unix(),
|
||||
encrypt: false,
|
||||
signType: 'md5',
|
||||
signType: "md5",
|
||||
};
|
||||
const content = JSON.stringify(openKey);
|
||||
const sign = utils.hash.md5(content + keySecret);
|
||||
return Buffer.from(content).toString('base64') + '.' + Buffer.from(sign).toString('base64');
|
||||
return Buffer.from(content).toString("base64") + "." + Buffer.from(sign).toString("base64");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('pi_history_log')
|
||||
@Entity("pi_history_log")
|
||||
export class HistoryLogEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'pipeline_id', comment: '流水线' })
|
||||
@Column({ name: "pipeline_id", comment: "流水线" })
|
||||
pipelineId: number;
|
||||
|
||||
@Column({ name: 'history_id', comment: '历史id' })
|
||||
@Column({ name: "history_id", comment: "历史id" })
|
||||
historyId: number;
|
||||
|
||||
@Column({
|
||||
name: 'node_id',
|
||||
comment: '任务节点id',
|
||||
name: "node_id",
|
||||
comment: "任务节点id",
|
||||
length: 100,
|
||||
nullable: true,
|
||||
})
|
||||
nodeId: string;
|
||||
|
||||
@Column({ comment: '日志内容', length: 40960, nullable: true })
|
||||
@Column({ comment: "日志内容", length: 40960, nullable: true })
|
||||
logs: 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;
|
||||
}
|
||||
|
||||
@@ -1,47 +1,46 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('pi_history')
|
||||
@Entity("pi_history")
|
||||
export class HistoryEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'pipeline_id', comment: '流水线' })
|
||||
@Column({ name: "pipeline_id", comment: "流水线" })
|
||||
pipelineId: number;
|
||||
@Column({ comment: '运行状态', length: 40960, nullable: true })
|
||||
@Column({ comment: "运行状态", length: 40960, nullable: true })
|
||||
pipeline: string;
|
||||
|
||||
@Column({ comment: '结果状态', length: 20, nullable: true })
|
||||
@Column({ comment: "结果状态", length: 20, nullable: true })
|
||||
status: string;
|
||||
|
||||
@Column({ name: 'trigger_type',comment: '触发类型', length: 20, nullable: true })
|
||||
@Column({ name: "trigger_type", comment: "触发类型", length: 20, nullable: true })
|
||||
triggerType: string;
|
||||
|
||||
@Column({
|
||||
name: 'end_time',
|
||||
comment: '结束时间',
|
||||
name: "end_time",
|
||||
comment: "结束时间",
|
||||
nullable: true,
|
||||
})
|
||||
endTime: Date;
|
||||
|
||||
@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;
|
||||
|
||||
|
||||
pipelineTitle: string;
|
||||
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('pi_notification')
|
||||
@Entity("pi_notification")
|
||||
export class NotificationEntity {
|
||||
@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: 'UserId' })
|
||||
@Column({ name: "user_id", comment: "UserId" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'type', comment: '通知类型' })
|
||||
@Column({ name: "type", comment: "通知类型" })
|
||||
type: string;
|
||||
|
||||
@Column({ name: 'name', comment: '名称' })
|
||||
@Column({ name: "name", comment: "名称" })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'setting', comment: '通知配置', length: 10240 })
|
||||
@Column({ name: "setting", comment: "通知配置", length: 10240 })
|
||||
setting: string;
|
||||
|
||||
@Column({ name: 'is_default', comment: '是否默认' })
|
||||
@Column({ name: "is_default", comment: "是否默认" })
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('pi_pipeline_group')
|
||||
@Entity("pi_pipeline_group")
|
||||
export class PipelineGroupEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'name', comment: '分组名称' })
|
||||
@Column({ name: "name", comment: "分组名称" })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'icon', comment: '图标' })
|
||||
@Column({ name: "icon", comment: "图标" })
|
||||
icon: string;
|
||||
|
||||
@Column({ name: 'favorite', comment: '收藏' })
|
||||
@Column({ name: "favorite", comment: "收藏" })
|
||||
favorite: 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;
|
||||
}
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('pi_pipeline')
|
||||
@Entity("pi_pipeline")
|
||||
export class PipelineEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'title', comment: '标题' })
|
||||
@Column({ name: "title", comment: "标题" })
|
||||
title: string;
|
||||
|
||||
@Column({ comment: '配置', length: 40960 })
|
||||
@Column({ comment: "配置", length: 40960 })
|
||||
content: string;
|
||||
|
||||
@Column({ name: 'keep_history_count', comment: '历史记录保持数量', nullable: true })
|
||||
@Column({ name: "keep_history_count", comment: "历史记录保持数量", nullable: true })
|
||||
keepHistoryCount: number;
|
||||
|
||||
@Column({ name: 'group_id', comment: '分组id', nullable: true })
|
||||
@Column({ name: "group_id", comment: "分组id", nullable: true })
|
||||
groupId: number;
|
||||
|
||||
@Column({ comment: '备注', length: 100, nullable: true })
|
||||
@Column({ comment: "备注", length: 100, nullable: true })
|
||||
remark: string;
|
||||
|
||||
@Column({ comment: '状态', length: 100, nullable: true })
|
||||
@Column({ comment: "状态", length: 100, nullable: true })
|
||||
status: string;
|
||||
|
||||
@Column({ comment: '启用/禁用', nullable: true, default: false })
|
||||
@Column({ comment: "启用/禁用", nullable: true, default: false })
|
||||
disabled: boolean;
|
||||
|
||||
// cert_apply: 证书申请;cert_upload: 证书上传; backup: 备份; custom:自定义; template: 模板
|
||||
@Column({ comment: '类型', nullable: true, default: 'cert' })
|
||||
@Column({ comment: "类型", nullable: true, default: "cert" })
|
||||
type: string;
|
||||
|
||||
@Column({ name: 'webhook_key', comment: 'webhookkey', length: 100, nullable: true })
|
||||
@Column({ name: "webhook_key", comment: "webhookkey", length: 100, nullable: true })
|
||||
webhookKey: string;
|
||||
|
||||
@Column({ name: 'trigger_count', comment: '触发器数量', nullable: true, default: 0 })
|
||||
@Column({ name: "trigger_count", comment: "触发器数量", nullable: true, default: 0 })
|
||||
triggerCount: number;
|
||||
|
||||
// custom: 自定义; monitor: 监控;
|
||||
@Column({ comment: '来源', nullable: true, default: '' })
|
||||
@Column({ comment: "来源", nullable: true, default: "" })
|
||||
from: string;
|
||||
|
||||
@Column({ name:"template_id", comment: '关联模版id', nullable: true, default: 0 })
|
||||
@Column({ name: "template_id", comment: "关联模版id", nullable: true, default: 0 })
|
||||
templateId: number;
|
||||
|
||||
@Column({ name:"is_template", comment: '是否模版', nullable: true, default: false })
|
||||
@Column({ name: "is_template", comment: "是否模版", nullable: true, default: false })
|
||||
isTemplate: boolean;
|
||||
|
||||
@Column({name: 'last_history_time',comment: '最后一次执行时间',nullable: true,})
|
||||
@Column({ name: "last_history_time", comment: "最后一次执行时间", nullable: true })
|
||||
lastHistoryTime: number;
|
||||
|
||||
@Column({name: 'valid_time',comment: '到期时间',nullable: true,default: 0})
|
||||
@Column({ name: "valid_time", comment: "到期时间", nullable: true, default: 0 })
|
||||
validTime: number;
|
||||
|
||||
// 变量
|
||||
@@ -60,19 +60,18 @@ export class PipelineEntity {
|
||||
|
||||
nextRunTime: number;
|
||||
|
||||
@Column({name: 'order', comment: '排序', nullable: true,})
|
||||
@Column({ name: "order", comment: "排序", nullable: true })
|
||||
order: number;
|
||||
|
||||
@Column({ name: 'project_id', comment: '项目id' })
|
||||
@Column({ name: "project_id", comment: "项目id" })
|
||||
projectId: number;
|
||||
|
||||
@Column({name: 'create_time',comment: '创建时间', default: () => 'CURRENT_TIMESTAMP',})
|
||||
@Column({ 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;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('pi_storage')
|
||||
@Entity("pi_storage")
|
||||
export class StorageEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'scope', comment: '范围' })
|
||||
@Column({ name: "scope", comment: "范围" })
|
||||
scope: string;
|
||||
|
||||
@Column({ name: 'namespace', comment: '命名空间' })
|
||||
@Column({ name: "namespace", comment: "命名空间" })
|
||||
namespace: string;
|
||||
|
||||
@Column({ comment: 'version', length: 100, nullable: true })
|
||||
@Column({ comment: "version", length: 100, nullable: true })
|
||||
version: string;
|
||||
|
||||
@Column({ comment: 'key', length: 100, nullable: true })
|
||||
@Column({ comment: "key", length: 100, nullable: true })
|
||||
key: string;
|
||||
|
||||
@Column({ comment: 'value', length: 40960, nullable: true })
|
||||
@Column({ comment: "value", length: 40960, nullable: true })
|
||||
value: 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;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
/**
|
||||
* 子域名托管
|
||||
*/
|
||||
@Entity('pi_sub_domain')
|
||||
@Entity("pi_sub_domain")
|
||||
export class SubDomainEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: 'UserId' })
|
||||
@Column({ name: "user_id", comment: "UserId" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'domain', comment: '子域名' })
|
||||
@Column({ name: "domain", comment: "子域名" })
|
||||
domain: string;
|
||||
|
||||
@Column({ name: 'disabled', comment: '禁用' })
|
||||
@Column({ name: "disabled", comment: "禁用" })
|
||||
disabled: 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;
|
||||
}
|
||||
|
||||
@@ -1,57 +1,55 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
export type PipelineTemplateType = {
|
||||
input: {
|
||||
[key: string]: {
|
||||
value: string;
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@Entity('pi_template')
|
||||
@Entity("pi_template")
|
||||
export class TemplateEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'pipeline_id', comment: '流水线id' })
|
||||
@Column({ name: "pipeline_id", comment: "流水线id" })
|
||||
pipelineId: number;
|
||||
|
||||
@Column({ name: 'title', comment: '标题' })
|
||||
@Column({ name: "title", comment: "标题" })
|
||||
title: string;
|
||||
@Column({ name: 'desc', comment: '说明' })
|
||||
@Column({ name: "desc", comment: "说明" })
|
||||
desc: string;
|
||||
|
||||
@Column({ comment: '配置', length: 40960 })
|
||||
@Column({ comment: "配置", length: 40960 })
|
||||
content: string;
|
||||
|
||||
@Column({ comment: '启用/禁用', nullable: true, default: false })
|
||||
@Column({ comment: "启用/禁用", nullable: true, default: false })
|
||||
disabled: boolean;
|
||||
|
||||
@Column({
|
||||
name: 'order',
|
||||
comment: '排序',
|
||||
name: "order",
|
||||
comment: "排序",
|
||||
nullable: true,
|
||||
})
|
||||
order: number;
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { HistoryEntity } from '../history.js';
|
||||
import { HistoryLogEntity } from '../history-log.js';
|
||||
import { HistoryEntity } from "../history.js";
|
||||
import { HistoryLogEntity } from "../history-log.js";
|
||||
|
||||
export class HistoryDetail {
|
||||
history: HistoryEntity;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PipelineEntity } from '../pipeline.js';
|
||||
import { HistoryEntity } from '../history.js';
|
||||
import { HistoryLogEntity } from '../history-log.js';
|
||||
import { PipelineEntity } from "../pipeline.js";
|
||||
import { HistoryEntity } from "../history.js";
|
||||
import { HistoryLogEntity } from "../history-log.js";
|
||||
|
||||
export class PipelineDetail {
|
||||
pipeline: PipelineEntity;
|
||||
|
||||
@@ -8,15 +8,13 @@ import { AddonService, newAddon, PermissionException, ValidateException } from "
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AddonGetterService {
|
||||
|
||||
export class AddonGetterService {
|
||||
@Inject()
|
||||
taskServiceBuilder: TaskServiceBuilder;
|
||||
@Inject()
|
||||
addonService: AddonService;
|
||||
|
||||
|
||||
async getAddonById(id: any, checkUserId: boolean, userId?: number, projectId?: number, defaultAddon?:{type:string,name:string} ): Promise<any> {
|
||||
async getAddonById(id: any, checkUserId: boolean, userId?: number, projectId?: number, defaultAddon?: { type: string; name: string }): Promise<any> {
|
||||
const serviceGetter = this.taskServiceBuilder.create({
|
||||
userId,
|
||||
projectId,
|
||||
@@ -25,8 +23,8 @@ export class AddonGetterService {
|
||||
http,
|
||||
logger,
|
||||
utils,
|
||||
serviceGetter
|
||||
}
|
||||
serviceGetter,
|
||||
};
|
||||
|
||||
if (!id) {
|
||||
if (!defaultAddon) {
|
||||
@@ -53,7 +51,7 @@ export class AddonGetterService {
|
||||
const setting = JSON.parse(entity.setting ?? "{}");
|
||||
const input = {
|
||||
id: entity.id,
|
||||
...setting
|
||||
...setting,
|
||||
};
|
||||
|
||||
return await newAddon(entity.addonType, entity.type, input, ctx);
|
||||
@@ -63,11 +61,10 @@ export class AddonGetterService {
|
||||
return await this.getAddonById(id, true, userId, projectId);
|
||||
}
|
||||
|
||||
|
||||
async getBlank(addonType:string,subType:string,projectId?: number){
|
||||
return await this.getAddonById(null,false,0,projectId,{
|
||||
type: addonType, name:subType
|
||||
})
|
||||
async getBlank(addonType: string, subType: string, projectId?: number) {
|
||||
return await this.getAddonById(null, false, 0, projectId, {
|
||||
type: addonType,
|
||||
name: subType,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { pluginGroups, pluginRegistry } from '@certd/pipeline';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { pluginGroups, pluginRegistry } from "@certd/pipeline";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -14,23 +14,23 @@ export class BuiltInPluginService {
|
||||
continue;
|
||||
}
|
||||
//@ts-ignore
|
||||
if(Plugin.define?.type && Plugin.define?.type.toLowerCase() !== 'builtin'){
|
||||
if (Plugin.define?.type && Plugin.define?.type.toLowerCase() !== "builtin") {
|
||||
continue;
|
||||
}
|
||||
list.push({ ...Plugin.define, key });
|
||||
}
|
||||
list = list.sort((a, b) => {
|
||||
return (a.order ?? 10 )- (b.order ?? 10);
|
||||
return (a.order ?? 10) - (b.order ?? 10);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
getGroups() {
|
||||
const groups:any = cloneDeep(pluginGroups);
|
||||
const groups: any = cloneDeep(pluginGroups);
|
||||
for (const key in groups) {
|
||||
const group = groups[key];
|
||||
group.plugins = group.plugins.sort((a, b) => {
|
||||
return (a.order ?? 10 )- (b.order ?? 10);
|
||||
return (a.order ?? 10) - (b.order ?? 10);
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IStorage } from '@certd/pipeline';
|
||||
import { StorageService } from './storage-service.js';
|
||||
import { IStorage } from "@certd/pipeline";
|
||||
import { StorageService } from "./storage-service.js";
|
||||
|
||||
export class DbStorage implements IStorage {
|
||||
/**
|
||||
@@ -13,7 +13,7 @@ export class DbStorage implements IStorage {
|
||||
}
|
||||
|
||||
async remove(scope: string, namespace: string, version: string, key: string): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
async get(scope: string, namespace: string, version: string, key: string): Promise<string | null> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Provide } from '@midwayjs/core';
|
||||
import { dnsProviderRegistry } from '@certd/plugin-cert';
|
||||
import { Provide } from "@midwayjs/core";
|
||||
import { dnsProviderRegistry } from "@certd/plugin-cert";
|
||||
|
||||
@Provide()
|
||||
export class DnsProviderService {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CertInfo, CertReader, ICertInfoGetter } from '@certd/plugin-lib';
|
||||
import { CertInfoService } from '../../../monitor/index.js';
|
||||
import { CertInfo, CertReader, ICertInfoGetter } from "@certd/plugin-lib";
|
||||
import { CertInfoService } from "../../../monitor/index.js";
|
||||
|
||||
export class CertInfoGetter implements ICertInfoGetter {
|
||||
export class CertInfoGetter implements ICertInfoGetter {
|
||||
userId: number;
|
||||
projectId: number;
|
||||
certInfoService: CertInfoService;
|
||||
@@ -12,20 +12,20 @@ export class CertInfoGetter implements ICertInfoGetter {
|
||||
}
|
||||
async getByPipelineId(pipelineId: number): Promise<CertInfo> {
|
||||
if (!pipelineId) {
|
||||
throw new Error(`流水线id不能为空`)
|
||||
}
|
||||
const query :any= {
|
||||
pipelineId,
|
||||
userId: this.userId,
|
||||
throw new Error(`流水线id不能为空`);
|
||||
}
|
||||
const query: any = {
|
||||
pipelineId,
|
||||
userId: this.userId,
|
||||
};
|
||||
if (this.projectId) {
|
||||
query.projectId = this.projectId
|
||||
query.projectId = this.projectId;
|
||||
}
|
||||
const entity = await this.certInfoService.findOne({
|
||||
where:query
|
||||
})
|
||||
const entity = await this.certInfoService.findOne({
|
||||
where: query,
|
||||
});
|
||||
if (!entity || !entity.certInfo) {
|
||||
throw new Error(`流水线(${pipelineId})还未生成证书,请先运行一次流水线`)
|
||||
throw new Error(`流水线(${pipelineId})还未生成证书,请先运行一次流水线`);
|
||||
}
|
||||
return new CertReader(JSON.parse(entity.certInfo)).cert;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CnameRecord, ICnameProxyService } from '@certd/pipeline';
|
||||
import { CnameRecord, ICnameProxyService } from "@certd/pipeline";
|
||||
|
||||
export class CnameProxyService implements ICnameProxyService {
|
||||
userId: number;
|
||||
|
||||
+4
-5
@@ -1,5 +1,5 @@
|
||||
import {DomainVerifiers, IDomainVerifierGetter} from "@certd/plugin-cert";
|
||||
import {DomainService} from "../../../cert/service/domain-service.js";
|
||||
import { DomainVerifiers, IDomainVerifierGetter } from "@certd/plugin-cert";
|
||||
import { DomainService } from "../../../cert/service/domain-service.js";
|
||||
|
||||
export class DomainVerifierGetter implements IDomainVerifierGetter {
|
||||
private userId: number;
|
||||
@@ -12,8 +12,7 @@ export class DomainVerifierGetter implements IDomainVerifierGetter {
|
||||
this.domainService = domainService;
|
||||
}
|
||||
|
||||
async getVerifiers(domains: string[]): Promise<DomainVerifiers>{
|
||||
return await this.domainService.getDomainVerifiers(this.userId,this.projectId,domains);
|
||||
async getVerifiers(domains: string[]): Promise<DomainVerifiers> {
|
||||
return await this.domainService.getDomainVerifiers(this.userId, this.projectId, domains);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { INotificationService, NotificationSendReq } from '@certd/pipeline';
|
||||
import {NotificationService} from "../notification-service.js";
|
||||
import { INotificationService, NotificationSendReq } from "@certd/pipeline";
|
||||
import { NotificationService } from "../notification-service.js";
|
||||
|
||||
export class NotificationGetter implements INotificationService {
|
||||
userId: number;
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { SysSettingsService, SysInstallInfo } from "@certd/lib-server";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { SiteInfo ,ISiteInfoGetter} from "@certd/plugin-lib";
|
||||
import { SiteInfo, ISiteInfoGetter } from "@certd/plugin-lib";
|
||||
|
||||
@Provide("siteInfoGetter")
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class SiteInfoGetter implements ISiteInfoGetter{
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
export class SiteInfoGetter implements ISiteInfoGetter {
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
async getSiteInfo(): Promise<SiteInfo> {
|
||||
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||
|
||||
async getSiteInfo(): Promise<SiteInfo> {
|
||||
|
||||
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||
|
||||
return {
|
||||
siteUrl: installInfo?.bindUrl || "",
|
||||
}
|
||||
}
|
||||
return {
|
||||
siteUrl: installInfo?.bindUrl || "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export class SubDomainsGetter implements ISubDomainsGetter {
|
||||
}
|
||||
|
||||
async getSubDomains() {
|
||||
const projectSubDomains = await this.subDomainService.getListByUserId(this.userId, this.projectId) || [];
|
||||
const projectSubDomains = (await this.subDomainService.getListByUserId(this.userId, this.projectId)) || [];
|
||||
const cnameProviderSubDomains = await this.cnameProviderService.getSubDomains();
|
||||
return [...projectSubDomains, ...cnameProviderSubDomains]
|
||||
.map(item => item?.trim())
|
||||
@@ -28,8 +28,8 @@ export class SubDomainsGetter implements ISubDomainsGetter {
|
||||
}
|
||||
|
||||
async hasSubDomain(fullDomain: string) {
|
||||
let arr = fullDomain.split(".")
|
||||
const subDomains = await this.getSubDomains()
|
||||
let arr = fullDomain.split(".");
|
||||
const subDomains = await this.getSubDomains();
|
||||
if (subDomains && subDomains.length > 0) {
|
||||
const fullDomainDot = "." + fullDomain;
|
||||
for (const subDomain of subDomains) {
|
||||
@@ -41,40 +41,38 @@ export class SubDomainsGetter implements ISubDomainsGetter {
|
||||
if (subDomain.startsWith("*.")) {
|
||||
//如果子域名配置的是泛域名,说明这一层及以下的子域名都是托管的
|
||||
//以fullDomain在这一层的子域名作为返回值
|
||||
const nonStarDomain = subDomain.slice(1)
|
||||
const nonStarDomain = subDomain.slice(1);
|
||||
if (fullDomainDot.endsWith(nonStarDomain)) {
|
||||
//提取fullDomain在这一层的子域名
|
||||
const fullArr = arr.reverse()
|
||||
const subArr = subDomain.split(".").reverse()
|
||||
let strBuilder = ""
|
||||
for (let i =0 ;i<subArr.length;i++) {
|
||||
const fullArr = arr.reverse();
|
||||
const subArr = subDomain.split(".").reverse();
|
||||
let strBuilder = "";
|
||||
for (let i = 0; i < subArr.length; i++) {
|
||||
if (strBuilder) {
|
||||
strBuilder = fullArr[i] + "." + strBuilder
|
||||
strBuilder = fullArr[i] + "." + strBuilder;
|
||||
} else {
|
||||
strBuilder = fullArr[i]
|
||||
strBuilder = fullArr[i];
|
||||
}
|
||||
}
|
||||
return strBuilder
|
||||
return strBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
while (arr.length > 0) {
|
||||
const subDomain = arr.join(".")
|
||||
const subDomain = arr.join(".");
|
||||
const domain = await this.domainService.findOne({
|
||||
where: {
|
||||
userId: this.userId,
|
||||
domain: subDomain,
|
||||
challengeType: "dns",
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
if (domain) {
|
||||
return subDomain
|
||||
return subDomain;
|
||||
}
|
||||
arr = arr.slice(1)
|
||||
arr = arr.slice(1);
|
||||
}
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+38
-44
@@ -14,93 +14,87 @@ import { CertInfoService } from "../../../monitor/index.js";
|
||||
import { ICertInfoGetter } from "@certd/plugin-lib";
|
||||
import { CnameProviderService } from "../../../cname/service/cname-provider-service.js";
|
||||
|
||||
const serviceNames = [
|
||||
'ocrService',
|
||||
]
|
||||
export class TaskServiceGetter implements IServiceGetter{
|
||||
const serviceNames = ["ocrService"];
|
||||
export class TaskServiceGetter implements IServiceGetter {
|
||||
private userId: number;
|
||||
private projectId: number;
|
||||
private appCtx : IMidwayContainer;
|
||||
constructor(userId:number,projectId:number,appCtx:IMidwayContainer) {
|
||||
private appCtx: IMidwayContainer;
|
||||
constructor(userId: number, projectId: number, appCtx: IMidwayContainer) {
|
||||
this.userId = userId;
|
||||
this.projectId = projectId;
|
||||
this.appCtx = appCtx
|
||||
this.appCtx = appCtx;
|
||||
}
|
||||
async get<T>(serviceName: string): Promise<T> {
|
||||
|
||||
if(serviceName === 'subDomainsGetter'){
|
||||
return await this.getSubDomainsGetter() as T
|
||||
} if (serviceName === 'accessService') {
|
||||
return await this.getAccessService() as T
|
||||
} else if (serviceName === 'cnameProxyService') {
|
||||
return await this.getCnameProxyService() as T
|
||||
} else if (serviceName === 'notificationService') {
|
||||
return await this.getNotificationService() as T
|
||||
} else if (serviceName === 'domainVerifierGetter') {
|
||||
return await this.getDomainVerifierGetter() as T
|
||||
} else if (serviceName === 'certInfoGetter') {
|
||||
return await this.getCertInfoGetter() as T
|
||||
}else{
|
||||
if(!serviceNames.includes(serviceName)){
|
||||
throw new Error(`${serviceName} not in whitelist`)
|
||||
if (serviceName === "subDomainsGetter") {
|
||||
return (await this.getSubDomainsGetter()) as T;
|
||||
}
|
||||
if (serviceName === "accessService") {
|
||||
return (await this.getAccessService()) as T;
|
||||
} else if (serviceName === "cnameProxyService") {
|
||||
return (await this.getCnameProxyService()) as T;
|
||||
} else if (serviceName === "notificationService") {
|
||||
return (await this.getNotificationService()) as T;
|
||||
} else if (serviceName === "domainVerifierGetter") {
|
||||
return (await this.getDomainVerifierGetter()) as T;
|
||||
} else if (serviceName === "certInfoGetter") {
|
||||
return (await this.getCertInfoGetter()) as T;
|
||||
} else {
|
||||
if (!serviceNames.includes(serviceName)) {
|
||||
throw new Error(`${serviceName} not in whitelist`);
|
||||
}
|
||||
const service = await this.appCtx.getAsync(serviceName)
|
||||
if (! service){
|
||||
throw new Error(`${serviceName} not found`)
|
||||
const service = await this.appCtx.getAsync(serviceName);
|
||||
if (!service) {
|
||||
throw new Error(`${serviceName} not found`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getSubDomainsGetter(): Promise<SubDomainsGetter> {
|
||||
const subDomainsService:SubDomainService = await this.appCtx.getAsync("subDomainService")
|
||||
const domainService:DomainService = await this.appCtx.getAsync("domainService")
|
||||
const cnameProviderService:CnameProviderService = await this.appCtx.getAsync("cnameProviderService")
|
||||
return new SubDomainsGetter(this.userId,this.projectId, subDomainsService,domainService,cnameProviderService)
|
||||
const subDomainsService: SubDomainService = await this.appCtx.getAsync("subDomainService");
|
||||
const domainService: DomainService = await this.appCtx.getAsync("domainService");
|
||||
const cnameProviderService: CnameProviderService = await this.appCtx.getAsync("cnameProviderService");
|
||||
return new SubDomainsGetter(this.userId, this.projectId, subDomainsService, domainService, cnameProviderService);
|
||||
}
|
||||
|
||||
async getCertInfoGetter(): Promise<ICertInfoGetter> {
|
||||
const certInfoService:CertInfoService = await this.appCtx.getAsync("certInfoService")
|
||||
return new CertInfoGetter(this.userId, this.projectId, certInfoService)
|
||||
const certInfoService: CertInfoService = await this.appCtx.getAsync("certInfoService");
|
||||
return new CertInfoGetter(this.userId, this.projectId, certInfoService);
|
||||
}
|
||||
|
||||
async getAccessService(): Promise<AccessGetter> {
|
||||
const accessService:AccessService = await this.appCtx.getAsync("accessService")
|
||||
const accessService: AccessService = await this.appCtx.getAsync("accessService");
|
||||
return new AccessGetter(this.userId, this.projectId, accessService.getById.bind(accessService));
|
||||
}
|
||||
|
||||
|
||||
async getCnameProxyService(): Promise<CnameProxyService> {
|
||||
const cnameRecordService:CnameRecordService = await this.appCtx.getAsync("cnameRecordService")
|
||||
const cnameRecordService: CnameRecordService = await this.appCtx.getAsync("cnameRecordService");
|
||||
return new CnameProxyService(this.userId, this.projectId, cnameRecordService.getWithAccessByDomain.bind(cnameRecordService));
|
||||
}
|
||||
|
||||
async getNotificationService(): Promise<NotificationGetter> {
|
||||
const notificationService:NotificationService = await this.appCtx.getAsync("notificationService")
|
||||
const notificationService: NotificationService = await this.appCtx.getAsync("notificationService");
|
||||
return new NotificationGetter(this.userId, this.projectId, notificationService);
|
||||
}
|
||||
|
||||
async getDomainVerifierGetter(): Promise<DomainVerifierGetter> {
|
||||
const domainService:DomainService = await this.appCtx.getAsync("domainService")
|
||||
const domainService: DomainService = await this.appCtx.getAsync("domainService");
|
||||
return new DomainVerifierGetter(this.userId, this.projectId, domainService);
|
||||
}
|
||||
}
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class TaskServiceBuilder {
|
||||
export class TaskServiceBuilder {
|
||||
@ApplicationContext()
|
||||
appCtx: IMidwayContainer;
|
||||
|
||||
create(req:TaskServiceCreateReq){
|
||||
create(req: TaskServiceCreateReq) {
|
||||
const userId = req.userId;
|
||||
const projectId = req.projectId;
|
||||
return new TaskServiceGetter(userId,projectId,this.appCtx)
|
||||
return new TaskServiceGetter(userId, projectId, this.appCtx);
|
||||
}
|
||||
}
|
||||
|
||||
export type TaskServiceCreateReq = {
|
||||
userId: number;
|
||||
projectId?: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { BaseService } from '@certd/lib-server';
|
||||
import { HistoryLogEntity } from '../entity/history-log.js';
|
||||
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { BaseService } from "@certd/lib-server";
|
||||
import { HistoryLogEntity } from "../entity/history-log.js";
|
||||
|
||||
/**
|
||||
* 证书申请
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { In, MoreThan, Repository } from 'typeorm';
|
||||
import { BaseService, PageReq } from '@certd/lib-server';
|
||||
import { HistoryEntity } from '../entity/history.js';
|
||||
import { PipelineEntity } from '../entity/pipeline.js';
|
||||
import { HistoryDetail } from '../entity/vo/history-detail.js';
|
||||
import { HistoryLogService } from './history-log-service.js';
|
||||
import { FileItem, FileStore, Pipeline, RunnableCollection } from '@certd/pipeline';
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { In, MoreThan, Repository } from "typeorm";
|
||||
import { BaseService, PageReq } from "@certd/lib-server";
|
||||
import { HistoryEntity } from "../entity/history.js";
|
||||
import { PipelineEntity } from "../entity/pipeline.js";
|
||||
import { HistoryDetail } from "../entity/vo/history-detail.js";
|
||||
import { HistoryLogService } from "./history-log-service.js";
|
||||
import { FileItem, FileStore, Pipeline, RunnableCollection } from "@certd/pipeline";
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { DbAdapter } from '../../db/index.js';
|
||||
import { logger } from '@certd/basic';
|
||||
import dayjs from "dayjs";
|
||||
import { DbAdapter } from "../../db/index.js";
|
||||
import { logger } from "@certd/basic";
|
||||
|
||||
/**
|
||||
* 证书申请
|
||||
@@ -29,7 +29,7 @@ export class HistoryService extends BaseService<HistoryEntity> {
|
||||
@Inject()
|
||||
dbAdapter: DbAdapter;
|
||||
|
||||
@Config('certd')
|
||||
@Config("certd")
|
||||
private certdConfig: any;
|
||||
|
||||
//@ts-ignore
|
||||
@@ -60,12 +60,12 @@ export class HistoryService extends BaseService<HistoryEntity> {
|
||||
return new HistoryDetail(entity, log);
|
||||
}
|
||||
|
||||
async start(pipeline: PipelineEntity,triggerType:string) {
|
||||
async start(pipeline: PipelineEntity, triggerType: string) {
|
||||
const bean = {
|
||||
userId: pipeline.userId,
|
||||
pipelineId: pipeline.id,
|
||||
title: pipeline.title,
|
||||
status: 'start',
|
||||
status: "start",
|
||||
triggerType,
|
||||
projectId: pipeline.projectId,
|
||||
};
|
||||
@@ -104,7 +104,7 @@ export class HistoryService extends BaseService<HistoryEntity> {
|
||||
pipelineId,
|
||||
},
|
||||
order: {
|
||||
id: 'ASC',
|
||||
id: "ASC",
|
||||
},
|
||||
skip: 0,
|
||||
take: deleteCountBatch,
|
||||
@@ -130,7 +130,7 @@ export class HistoryService extends BaseService<HistoryEntity> {
|
||||
pipelineId,
|
||||
},
|
||||
order: {
|
||||
id: 'DESC',
|
||||
id: "DESC",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -139,7 +139,7 @@ export class HistoryService extends BaseService<HistoryEntity> {
|
||||
const status: Pipeline = JSON.parse(history.pipeline);
|
||||
const files: FileItem[] = [];
|
||||
RunnableCollection.each([status], runnable => {
|
||||
if (runnable.runnableType !== 'step') {
|
||||
if (runnable.runnableType !== "step") {
|
||||
return;
|
||||
}
|
||||
if (runnable.status?.files != null) {
|
||||
@@ -174,32 +174,32 @@ export class HistoryService extends BaseService<HistoryEntity> {
|
||||
try {
|
||||
const fileStore = new FileStore({
|
||||
rootDir: this.certdConfig.fileRootDir,
|
||||
scope: id + '',
|
||||
parent: '0',
|
||||
scope: id + "",
|
||||
parent: "0",
|
||||
});
|
||||
fileStore.deleteByParent(id + '', '');
|
||||
fileStore.deleteByParent(id + "", "");
|
||||
} catch (e) {
|
||||
logger.error('删除文件失败', e);
|
||||
logger.error("删除文件失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
async countPerDay(param: { days: number; userId?: any,projectId?:number }) {
|
||||
const todayEnd = dayjs().endOf('day');
|
||||
async countPerDay(param: { days: number; userId?: any; projectId?: number }) {
|
||||
const todayEnd = dayjs().endOf("day");
|
||||
const where: any = {
|
||||
createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()),
|
||||
createTime: MoreThan(todayEnd.add(-param.days, "day").toDate()),
|
||||
};
|
||||
|
||||
|
||||
if (param.projectId > 0) {
|
||||
where.projectId = param.projectId;
|
||||
}else if (param.userId > 0) {
|
||||
} else if (param.userId > 0) {
|
||||
where.userId = param.userId;
|
||||
}
|
||||
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(where)
|
||||
.groupBy('date')
|
||||
.groupBy("date")
|
||||
.getRawMany();
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import {
|
||||
BaseService,
|
||||
NeedVIPException,
|
||||
SysInstallInfo,
|
||||
SysSettingsService,
|
||||
SysSiteInfo,
|
||||
ValidateException
|
||||
} from "@certd/lib-server";
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { NotificationEntity } from '../entity/notification.js';
|
||||
import { NotificationInstanceConfig, notificationRegistry, NotificationSendReq, sendNotification } from '@certd/pipeline';
|
||||
import { http, utils } from '@certd/basic';
|
||||
import { EmailService } from '../../basic/service/email-service.js';
|
||||
import { isComm, isPlus } from '@certd/plus-core';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { BaseService, NeedVIPException, SysInstallInfo, SysSettingsService, SysSiteInfo, ValidateException } from "@certd/lib-server";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { NotificationEntity } from "../entity/notification.js";
|
||||
import { NotificationInstanceConfig, notificationRegistry, NotificationSendReq, sendNotification } from "@certd/pipeline";
|
||||
import { http, utils } from "@certd/basic";
|
||||
import { EmailService } from "../../basic/service/email-service.js";
|
||||
import { isComm, isPlus } from "@certd/plus-core";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -35,7 +28,7 @@ export class NotificationService extends BaseService<NotificationEntity> {
|
||||
async getSimpleInfo(id: number) {
|
||||
const entity = await this.info(id);
|
||||
if (entity == null) {
|
||||
throw new ValidateException('该通知配置不存在,请确认是否已被删除');
|
||||
throw new ValidateException("该通知配置不存在,请确认是否已被删除");
|
||||
}
|
||||
return {
|
||||
id: entity.id,
|
||||
@@ -56,32 +49,31 @@ export class NotificationService extends BaseService<NotificationEntity> {
|
||||
async add(bean: NotificationEntity) {
|
||||
this.checkNeedPlus(bean.type);
|
||||
const res = await super.add(bean);
|
||||
if(bean.isDefault){
|
||||
if (bean.isDefault) {
|
||||
await this.setDefault(res.id, bean.userId);
|
||||
}
|
||||
bean.keyId = "nt_" + utils.id.simpleNanoId();
|
||||
return res
|
||||
return res;
|
||||
}
|
||||
|
||||
async update(bean: NotificationEntity) {
|
||||
|
||||
const old = await this.info(bean.id);
|
||||
this.checkNeedPlus(old.type);
|
||||
|
||||
delete bean.userId;
|
||||
delete bean.type
|
||||
delete bean.keyId
|
||||
delete bean.type;
|
||||
delete bean.keyId;
|
||||
const res = await super.update(bean);
|
||||
if(bean.isDefault){
|
||||
if (bean.isDefault) {
|
||||
await this.setDefault(bean.id, old.userId);
|
||||
}
|
||||
|
||||
return res
|
||||
return res;
|
||||
}
|
||||
|
||||
checkNeedPlus(type: string){
|
||||
const define = this.getDefineByType(type)
|
||||
//@ts-ignore
|
||||
checkNeedPlus(type: string) {
|
||||
const define = this.getDefineByType(type);
|
||||
//@ts-ignore
|
||||
if (define.needPlus && !isPlus()) {
|
||||
throw new NeedVIPException("此通知类型为Certd专业版功能,请升级到专业版或以上级别");
|
||||
}
|
||||
@@ -89,10 +81,10 @@ export class NotificationService extends BaseService<NotificationEntity> {
|
||||
|
||||
async getById(id: number, userId: number, projectId?: number): Promise<NotificationInstanceConfig> {
|
||||
if (!id) {
|
||||
throw new ValidateException('id不能为空');
|
||||
throw new ValidateException("id不能为空");
|
||||
}
|
||||
if (userId==null) {
|
||||
throw new ValidateException('userId不能为空');
|
||||
if (userId == null) {
|
||||
throw new ValidateException("userId不能为空");
|
||||
}
|
||||
const res = await this.repository.findOne({
|
||||
where: {
|
||||
@@ -125,7 +117,7 @@ export class NotificationService extends BaseService<NotificationEntity> {
|
||||
projectId,
|
||||
},
|
||||
order: {
|
||||
isDefault: 'DESC',
|
||||
isDefault: "DESC",
|
||||
},
|
||||
});
|
||||
if (!res) {
|
||||
@@ -136,30 +128,24 @@ export class NotificationService extends BaseService<NotificationEntity> {
|
||||
|
||||
async setDefault(id: number, userId: number, projectId?: number) {
|
||||
if (!id) {
|
||||
throw new ValidateException('id不能为空');
|
||||
throw new ValidateException("id不能为空");
|
||||
}
|
||||
if (userId==null) {
|
||||
throw new ValidateException('userId不能为空');
|
||||
if (userId == null) {
|
||||
throw new ValidateException("userId不能为空");
|
||||
}
|
||||
const query:any = {
|
||||
userId,
|
||||
const query: any = {
|
||||
userId,
|
||||
};
|
||||
if (projectId) {
|
||||
query.projectId = projectId;
|
||||
}
|
||||
if (projectId){
|
||||
query.projectId = projectId
|
||||
}
|
||||
await this.repository.update(
|
||||
query,
|
||||
{
|
||||
isDefault: false,
|
||||
}
|
||||
);
|
||||
query.id = id
|
||||
await this.repository.update(
|
||||
query,
|
||||
{
|
||||
isDefault: true,
|
||||
}
|
||||
);
|
||||
await this.repository.update(query, {
|
||||
isDefault: false,
|
||||
});
|
||||
query.id = id;
|
||||
await this.repository.update(query, {
|
||||
isDefault: true,
|
||||
});
|
||||
}
|
||||
|
||||
async getOrCreateDefault(email: string, userId: any, projectId?: number) {
|
||||
@@ -172,8 +158,8 @@ export class NotificationService extends BaseService<NotificationEntity> {
|
||||
};
|
||||
const res = await this.repository.save({
|
||||
userId,
|
||||
type: 'email',
|
||||
name: '邮件通知',
|
||||
type: "email",
|
||||
name: "邮件通知",
|
||||
setting: JSON.stringify(setting),
|
||||
isDefault: true,
|
||||
projectId,
|
||||
@@ -199,11 +185,11 @@ export class NotificationService extends BaseService<NotificationEntity> {
|
||||
|
||||
if (notifyConfig) {
|
||||
//发送通知
|
||||
logger.info('发送通知, 使用通知渠道:' + notifyConfig.name);
|
||||
logger.info("发送通知, 使用通知渠道:" + notifyConfig.name);
|
||||
|
||||
if (notifyConfig.type != 'email') {
|
||||
if (notifyConfig.type != "email") {
|
||||
//非邮件通知,需要加上站点名称
|
||||
let siteTitle = 'Certd';
|
||||
let siteTitle = "Certd";
|
||||
if (isComm()) {
|
||||
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
||||
siteTitle = siteInfo?.title || siteTitle;
|
||||
@@ -223,7 +209,7 @@ export class NotificationService extends BaseService<NotificationEntity> {
|
||||
});
|
||||
} else {
|
||||
if (req.useEmail && req.emailAddress) {
|
||||
logger.info('使用邮件通知');
|
||||
logger.info("使用邮件通知");
|
||||
await this.emailService.send({
|
||||
receivers: [req.emailAddress],
|
||||
subject: req.body.title,
|
||||
@@ -235,7 +221,7 @@ export class NotificationService extends BaseService<NotificationEntity> {
|
||||
|
||||
async getBindUrl(path: string) {
|
||||
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||
const bindUrl = installInfo.bindUrl || 'http://127.0.0.1:7001';
|
||||
const bindUrl = installInfo.bindUrl || "http://127.0.0.1:7001";
|
||||
return bindUrl + path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,10 @@ describe("pipeline batch update", () => {
|
||||
},
|
||||
};
|
||||
|
||||
assert.equal(updateCertApplyStepInputs(pipeline, {}, stepType => inputDefines[stepType]), 0);
|
||||
assert.equal(
|
||||
updateCertApplyStepInputs(pipeline, {}, stepType => inputDefines[stepType]),
|
||||
0
|
||||
);
|
||||
assert.equal(
|
||||
updateCertApplyStepInputs(
|
||||
pipeline,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { BaseService } from '@certd/lib-server';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { PipelineGroupEntity } from '../entity/pipeline-group.js';
|
||||
import { merge } from 'lodash-es';
|
||||
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { BaseService } from "@certd/lib-server";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { PipelineGroupEntity } from "../entity/pipeline-group.js";
|
||||
import { merge } from "lodash-es";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
|
||||
@@ -1,32 +1,10 @@
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum, sleep } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { In, MoreThan, Repository } from "typeorm";
|
||||
import {
|
||||
AccessService,
|
||||
BaseService,
|
||||
isEnterprise,
|
||||
NeedSuiteException,
|
||||
NeedVIPException,
|
||||
PageReq,
|
||||
SysPublicSettings,
|
||||
SysSettingsService,
|
||||
SysSiteInfo
|
||||
} from "@certd/lib-server";
|
||||
import { AccessService, BaseService, isEnterprise, NeedSuiteException, NeedVIPException, PageReq, SysPublicSettings, SysSettingsService, SysSiteInfo } from "@certd/lib-server";
|
||||
import { PipelineEntity } from "../entity/pipeline.js";
|
||||
import { PipelineDetail } from "../entity/vo/pipeline-detail.js";
|
||||
import {
|
||||
Executor,
|
||||
IAccessService,
|
||||
ICnameProxyService,
|
||||
INotificationService, Notification,
|
||||
Pipeline,
|
||||
pluginRegistry,
|
||||
ResultType,
|
||||
RunHistory,
|
||||
RunnableCollection,
|
||||
SysInfo,
|
||||
UserInfo
|
||||
} from "@certd/pipeline";
|
||||
import { Executor, IAccessService, ICnameProxyService, INotificationService, Notification, Pipeline, pluginRegistry, ResultType, RunHistory, RunnableCollection, SysInfo, UserInfo } from "@certd/pipeline";
|
||||
import { DbStorage } from "./db-storage.js";
|
||||
import { StorageService } from "./storage-service.js";
|
||||
import { Cron } from "../../cron/cron.js";
|
||||
@@ -56,7 +34,6 @@ import { CertApplyStepInputPatch, updateCertApplyStepInputs } from "./pipeline-b
|
||||
import { calcNextSuiteCountUsed } from "./pipeline-suite-limit.js";
|
||||
const runningTasks: Map<string | number, Executor> = new Map();
|
||||
|
||||
|
||||
/**
|
||||
* 证书申请
|
||||
*/
|
||||
@@ -154,9 +131,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
|
||||
//获取下次执行时间
|
||||
if (pipeline.triggers?.length > 0) {
|
||||
const triggers = pipeline.triggers.filter((item) => item.type === 'timer');
|
||||
const triggers = pipeline.triggers.filter(item => item.type === "timer");
|
||||
if (triggers && triggers.length > 0) {
|
||||
let nextTimes: any = [];
|
||||
const nextTimes: any = [];
|
||||
for (const item of triggers) {
|
||||
if (!item.props?.cron) {
|
||||
continue;
|
||||
@@ -164,9 +141,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
const ret = this.getCronNextTimes(item.props?.cron, 1);
|
||||
nextTimes.push(...ret);
|
||||
}
|
||||
item.nextRunTime = nextTimes[0]
|
||||
item.nextRunTime = nextTimes[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delete item.content;
|
||||
@@ -175,7 +151,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
return result;
|
||||
}
|
||||
|
||||
getCronNextTimes(cron: string, count: number = 1) {
|
||||
getCronNextTimes(cron: string, count = 1) {
|
||||
if (cron == null) {
|
||||
return [];
|
||||
}
|
||||
@@ -188,7 +164,6 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
return nextTimes;
|
||||
}
|
||||
|
||||
|
||||
private async fillLastVars(records: PipelineEntity[]) {
|
||||
const pipelineIds: number[] = [];
|
||||
const recordMap = {};
|
||||
@@ -235,8 +210,6 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
* @param id
|
||||
@@ -270,7 +243,6 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
|
||||
const isUpdate = bean.id > 0 && old != null;
|
||||
|
||||
|
||||
const pipeline = JSON.parse(bean.content || "{}");
|
||||
RunnableCollection.initPipelineRunnableType(pipeline);
|
||||
pipeline.userId = bean.userId;
|
||||
@@ -334,12 +306,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
pipeline.version++;
|
||||
|
||||
bean.triggerCount = pipeline.triggers?.filter((trigger) => trigger.type === "timer").length || 0;
|
||||
bean.triggerCount = pipeline.triggers?.filter(trigger => trigger.type === "timer").length || 0;
|
||||
|
||||
bean.content = JSON.stringify(pipeline);
|
||||
await this.addOrUpdate(bean);
|
||||
await this.registerTrigger(bean);
|
||||
return bean
|
||||
return bean;
|
||||
}
|
||||
|
||||
private async checkMaxPipelineCount(bean: PipelineEntity, pipeline: Pipeline, domains: string[], old?: PipelineEntity) {
|
||||
@@ -351,7 +323,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
// }
|
||||
if (isEnterprise()) {
|
||||
//企业模式不限制
|
||||
checkPlus()
|
||||
checkPlus();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -397,18 +369,17 @@ 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,
|
||||
isTemplate: false
|
||||
}
|
||||
isTemplate: false,
|
||||
},
|
||||
});
|
||||
const ids = idEntityList.map(item => {
|
||||
return item.id;
|
||||
@@ -428,7 +399,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) {
|
||||
@@ -478,12 +449,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async trigger(id: any, stepId?: string, doCheck = false) {
|
||||
const entity: PipelineEntity = await this.info(id);
|
||||
if (doCheck) {
|
||||
@@ -499,7 +467,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
} catch (e) {
|
||||
logger.error("手动job执行失败:", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -511,7 +479,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;
|
||||
@@ -595,22 +563,21 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
} catch (e) {
|
||||
logger.error("定时job执行失败:", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
logger.info("当前定时器数量:", this.cron.getTaskSize());
|
||||
}
|
||||
|
||||
|
||||
async isPipelineValidTimeEnabled(entity: PipelineEntity) {
|
||||
const settings = await this.sysSettingsService.getPublicSettings();
|
||||
if (isPlus() && settings.pipelineValidTimeEnabled) {
|
||||
if (entity.validTime > 0 && entity.validTime < Date.now()) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -629,13 +596,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
|
||||
async beforeCheck(entity: PipelineEntity) {
|
||||
|
||||
if (isEnterprise()) {
|
||||
checkPlus()
|
||||
return {}
|
||||
checkPlus();
|
||||
return {};
|
||||
}
|
||||
|
||||
const validTimeEnabled = await this.isPipelineValidTimeEnabled(entity)
|
||||
const validTimeEnabled = await this.isPipelineValidTimeEnabled(entity);
|
||||
if (!validTimeEnabled) {
|
||||
throw new Error(`流水线${entity.id}已过期,不予执行`);
|
||||
}
|
||||
@@ -647,16 +613,15 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
await this.checkUserStatus(entity.userId);
|
||||
|
||||
return {
|
||||
suite
|
||||
}
|
||||
suite,
|
||||
};
|
||||
}
|
||||
|
||||
async doRun(entity: PipelineEntity, triggerId: string, stepId?: string) {
|
||||
|
||||
let suite: any = null
|
||||
let suite: any = null;
|
||||
try {
|
||||
const res = await this.beforeCheck(entity);
|
||||
suite = res.suite
|
||||
suite = res.suite;
|
||||
} catch (e) {
|
||||
logger.error(`流水线${entity.id}触发失败(${triggerId}):${e.message}`);
|
||||
return;
|
||||
@@ -668,7 +633,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
pipeline.id = id;
|
||||
}
|
||||
|
||||
if(entity.userId !=null){
|
||||
if (entity.userId != null) {
|
||||
pipeline.userId = entity.userId;
|
||||
pipeline.projectId = entity.projectId;
|
||||
}
|
||||
@@ -688,7 +653,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const doSaveHistory = async (history: RunHistory) => {
|
||||
//保存执行历史
|
||||
try {
|
||||
@@ -707,8 +672,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
class HistorySaver {
|
||||
latest: RunHistory = null;
|
||||
interval: any = null;
|
||||
started: boolean = false;
|
||||
async save(){
|
||||
started = false;
|
||||
async save() {
|
||||
const latest = this.latest;
|
||||
this.latest = null;
|
||||
if (latest == null) {
|
||||
@@ -716,43 +681,43 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
await doSaveHistory(latest);
|
||||
}
|
||||
async start(){
|
||||
this.started = true
|
||||
async start() {
|
||||
this.started = true;
|
||||
//先存一次,确保有数据
|
||||
await this.save();
|
||||
setTimeout(()=>{
|
||||
setTimeout(() => {
|
||||
//2秒后保存一次,尽快显示第一个任务的状态
|
||||
this.save();
|
||||
this.save();
|
||||
}, 1000 * 2);
|
||||
this.interval = setInterval(()=>{
|
||||
this.interval = setInterval(() => {
|
||||
//之后每5秒保存一次
|
||||
this.save();
|
||||
}, 1000 * 5);
|
||||
}
|
||||
async push(history: RunHistory){
|
||||
async push(history: RunHistory) {
|
||||
this.latest = history;
|
||||
if(!this.started){
|
||||
await this.start();
|
||||
if (!this.started) {
|
||||
await this.start();
|
||||
}
|
||||
}
|
||||
async done(){
|
||||
async done() {
|
||||
clearInterval(this.interval);
|
||||
await this.save();
|
||||
}
|
||||
}
|
||||
|
||||
const historySaver = new HistorySaver();
|
||||
const onChanged = async (history: RunHistory)=>{
|
||||
const onChanged = async (history: RunHistory) => {
|
||||
await historySaver.push(history);
|
||||
}
|
||||
const onFinished = async (history: RunHistory)=>{
|
||||
};
|
||||
const onFinished = async (history: RunHistory) => {
|
||||
await onChanged(history);
|
||||
await historySaver.done();
|
||||
}
|
||||
};
|
||||
|
||||
const userId = entity.userId;
|
||||
const projectId = entity.projectId;
|
||||
let userIsAdmin = false
|
||||
let userIsAdmin = false;
|
||||
|
||||
if (projectId && projectId > 0) {
|
||||
userIsAdmin = await this.projectService.isAdmin(projectId);
|
||||
@@ -761,7 +726,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
const user: UserInfo = {
|
||||
id: userId,
|
||||
role: userIsAdmin ? "admin" : "user"
|
||||
role: userIsAdmin ? "admin" : "user",
|
||||
};
|
||||
|
||||
const historyId = await this.historyService.start(entity, triggerType);
|
||||
@@ -773,7 +738,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
|
||||
const taskServiceGetter = this.taskServiceBuilder.create({
|
||||
userId,
|
||||
projectId
|
||||
projectId,
|
||||
});
|
||||
const accessGetter = await taskServiceGetter.get<IAccessService>("accessService");
|
||||
const notificationGetter = await taskServiceGetter.get<INotificationService>("notificationService");
|
||||
@@ -792,7 +757,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
notificationService: notificationGetter,
|
||||
fileRootDir: this.certdConfig.fileRootDir,
|
||||
sysInfo,
|
||||
serviceGetter: taskServiceGetter
|
||||
serviceGetter: taskServiceGetter,
|
||||
});
|
||||
try {
|
||||
runningTasks.set(historyId, executor);
|
||||
@@ -875,13 +840,13 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
},
|
||||
});
|
||||
if (!pipelineEntity) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
return pipelineEntity.projectId;
|
||||
}
|
||||
private async saveHistory(history: RunHistory) {
|
||||
//修改pipeline状态
|
||||
let pipelineEntity = new PipelineEntity();
|
||||
const pipelineEntity = new PipelineEntity();
|
||||
pipelineEntity.id = parseInt(history.pipeline.id);
|
||||
pipelineEntity.status = history.pipeline.status.result + "";
|
||||
pipelineEntity.lastHistoryTime = history.pipeline.status.startTime;
|
||||
@@ -909,18 +874,18 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
await this.historyLogService.addOrUpdate(logEntity);
|
||||
}
|
||||
|
||||
async count(param: { userId?: any, projectId?: number }) {
|
||||
async count(param: { userId?: any; projectId?: number }) {
|
||||
const count = await this.repository.count({
|
||||
where: {
|
||||
userId: param.userId,
|
||||
projectId: param.projectId,
|
||||
isTemplate: false
|
||||
}
|
||||
isTemplate: false,
|
||||
},
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
async statusCount(param: { userId?: any, projectId?: number } = {}) {
|
||||
async statusCount(param: { userId?: any; projectId?: number } = {}) {
|
||||
const statusCount = await this.repository
|
||||
.createQueryBuilder()
|
||||
.select("status")
|
||||
@@ -928,14 +893,14 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
.where({
|
||||
userId: param.userId,
|
||||
projectId: param.projectId,
|
||||
isTemplate: false
|
||||
isTemplate: false,
|
||||
})
|
||||
.groupBy("status")
|
||||
.getRawMany();
|
||||
return statusCount;
|
||||
}
|
||||
|
||||
async enableCount(param: { userId?: any, projectId?: number } = {}) {
|
||||
async enableCount(param: { userId?: any; projectId?: number } = {}) {
|
||||
const statusCount = await this.repository
|
||||
.createQueryBuilder()
|
||||
.select("disabled")
|
||||
@@ -943,7 +908,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
.where({
|
||||
userId: param.userId,
|
||||
projectId: param.projectId,
|
||||
isTemplate: false
|
||||
isTemplate: false,
|
||||
})
|
||||
.groupBy("disabled")
|
||||
.getRawMany();
|
||||
@@ -962,14 +927,14 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
status: true
|
||||
status: true,
|
||||
},
|
||||
where: {
|
||||
userId,
|
||||
disabled: false,
|
||||
projectId,
|
||||
isTemplate: false
|
||||
}
|
||||
isTemplate: false,
|
||||
},
|
||||
});
|
||||
await this.fillLastVars(list);
|
||||
list = list.filter(item => {
|
||||
@@ -991,7 +956,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
.where({
|
||||
// 0点
|
||||
createTime: MoreThan(todayEnd.add(-param.days, "day").toDate()),
|
||||
isTemplate: false
|
||||
isTemplate: false,
|
||||
})
|
||||
.groupBy("date")
|
||||
.getRawMany();
|
||||
@@ -1008,7 +973,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
await this.checkUserId(id, userId);
|
||||
}
|
||||
if (projectId) {
|
||||
await this.checkUserId(id, projectId, "projectId")
|
||||
await this.checkUserId(id, projectId, "projectId");
|
||||
}
|
||||
await this.delete(id);
|
||||
}
|
||||
@@ -1018,7 +983,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
if (!isPlus()) {
|
||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||
}
|
||||
const query: any = {}
|
||||
const query: any = {};
|
||||
if (userId && userId > 0) {
|
||||
query.userId = userId;
|
||||
}
|
||||
@@ -1028,15 +993,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
await this.repository.update(
|
||||
{
|
||||
id: In(ids),
|
||||
...query
|
||||
...query,
|
||||
},
|
||||
{ groupId }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 批量转移到其他项目
|
||||
*/
|
||||
@@ -1050,7 +1012,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
if (!projectId || projectId <= 0) {
|
||||
throw new Error("projectId不能为空");
|
||||
}
|
||||
const userId = -1 // 强制为-1
|
||||
const userId = -1; // 强制为-1
|
||||
|
||||
async function eachSteps(pipeline, callback) {
|
||||
for (const stage of pipeline.stages) {
|
||||
@@ -1062,7 +1024,6 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (const id of ids) {
|
||||
const pipelineEntity = await this.info(id);
|
||||
if (!pipelineEntity) {
|
||||
@@ -1080,16 +1041,16 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
userId: userId,
|
||||
projectId: projectId,
|
||||
groupId: null,
|
||||
}
|
||||
};
|
||||
|
||||
const pipeline = JSON.parse(pipelineEntity.content);
|
||||
pipeline.userId = userId;
|
||||
pipeline.projectId = projectId;
|
||||
|
||||
//转移和修改access 和 Notification
|
||||
await eachSteps(pipeline, async (step) => {
|
||||
await eachSteps(pipeline, async step => {
|
||||
const type = step.type;
|
||||
//plugin
|
||||
//plugin
|
||||
const pluginDefine: any = pluginRegistry.getDefine(type);
|
||||
if (pluginDefine) {
|
||||
for (const key in step.input) {
|
||||
@@ -1097,48 +1058,40 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
if (!value || value <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!pluginDefine.input[key]){
|
||||
if (!pluginDefine.input[key]) {
|
||||
continue;
|
||||
}
|
||||
const componentName = pluginDefine.input[key].component?.name;
|
||||
if (componentName === "access-selector" || componentName === "AccessSelector") {
|
||||
//这是一个授权ID属性,检查是否需要转移授权
|
||||
const newAccessId = await this.accessService.copyTo(value,projectId);
|
||||
const newAccessId = await this.accessService.copyTo(value, projectId);
|
||||
step.input[key] = newAccessId;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
pipeline.notifications = [
|
||||
});
|
||||
(pipeline.notifications = [
|
||||
{
|
||||
"type": "custom",
|
||||
"when": [
|
||||
"error",
|
||||
"turnToSuccess"
|
||||
],
|
||||
"notificationId": 0,
|
||||
"title": "使用默认通知",
|
||||
"id": nanoid()
|
||||
}
|
||||
],
|
||||
|
||||
entity.content = JSON.stringify(pipeline);
|
||||
type: "custom",
|
||||
when: ["error", "turnToSuccess"],
|
||||
notificationId: 0,
|
||||
title: "使用默认通知",
|
||||
id: nanoid(),
|
||||
},
|
||||
]),
|
||||
(entity.content = JSON.stringify(pipeline));
|
||||
await this.unregisterTriggers(entity.id);
|
||||
await this.repository.save(entity);
|
||||
await this.save(entity)
|
||||
await this.save(entity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
async batchUpdateTrigger(ids: number[], trigger: any, userId: any, projectId?: number) {
|
||||
if (!isPlus()) {
|
||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||
}
|
||||
//允许管理员修改,userId=null
|
||||
const query: any = {}
|
||||
const query: any = {};
|
||||
if (userId && userId > 0) {
|
||||
query.userId = userId;
|
||||
}
|
||||
@@ -1148,15 +1101,15 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
const list = await this.find({
|
||||
where: {
|
||||
id: In(ids),
|
||||
...query
|
||||
}
|
||||
...query,
|
||||
},
|
||||
});
|
||||
|
||||
for (const item of list) {
|
||||
const pipeline = JSON.parse(item.content);
|
||||
if (trigger.props === false) {
|
||||
//清除trigger
|
||||
pipeline.triggers = []
|
||||
pipeline.triggers = [];
|
||||
} else {
|
||||
if (trigger.random === true) {
|
||||
//随机时间
|
||||
@@ -1170,20 +1123,21 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
const endTime = dayjs(end).valueOf();
|
||||
const randomTime = Math.floor(Math.random() * (endTime - startTime)) + startTime;
|
||||
const time = dayjs(randomTime).format(" ss:mm:HH").replaceAll(":", " ").replaceAll(" 0", " ").trim();
|
||||
set(trigger, "props.cron", `${time} * * *`)
|
||||
set(trigger, "props.cron", `${time} * * *`);
|
||||
}
|
||||
delete trigger.random
|
||||
delete trigger.random;
|
||||
delete trigger.randomRange;
|
||||
pipeline.triggers = [{
|
||||
id: nanoid(),
|
||||
title: "定时触发",
|
||||
...trigger
|
||||
}];
|
||||
pipeline.triggers = [
|
||||
{
|
||||
id: nanoid(),
|
||||
title: "定时触发",
|
||||
...trigger,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
await this.doUpdatePipelineJson(item, pipeline);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async batchUpdateNotifications(ids: number[], notification: Notification, userId: any, projectId?: number) {
|
||||
@@ -1191,7 +1145,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||
}
|
||||
//允许管理员修改,userId=null
|
||||
const query: any = {}
|
||||
const query: any = {};
|
||||
if (userId && userId > 0) {
|
||||
query.userId = userId;
|
||||
}
|
||||
@@ -1201,26 +1155,28 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
const list = await this.find({
|
||||
where: {
|
||||
id: In(ids),
|
||||
...query
|
||||
}
|
||||
...query,
|
||||
},
|
||||
});
|
||||
|
||||
for (const item of list) {
|
||||
const pipeline = JSON.parse(item.content);
|
||||
pipeline.notifications = [{
|
||||
id: nanoid(),
|
||||
title: "通知",
|
||||
/**
|
||||
* type: NotificationType;
|
||||
* when: NotificationWhen[];
|
||||
* options: EmailOptions;
|
||||
* notificationId: number;
|
||||
* title: string;
|
||||
* subType: string;
|
||||
*/
|
||||
type: "other",
|
||||
...notification
|
||||
}];
|
||||
pipeline.notifications = [
|
||||
{
|
||||
id: nanoid(),
|
||||
title: "通知",
|
||||
/**
|
||||
* type: NotificationType;
|
||||
* when: NotificationWhen[];
|
||||
* options: EmailOptions;
|
||||
* notificationId: number;
|
||||
* title: string;
|
||||
* subType: string;
|
||||
*/
|
||||
type: "other",
|
||||
...notification,
|
||||
},
|
||||
];
|
||||
await this.doUpdatePipelineJson(item, pipeline);
|
||||
}
|
||||
}
|
||||
@@ -1229,7 +1185,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
if (!isPlus()) {
|
||||
throw new NeedVIPException("此功能需要升级Certd专业版");
|
||||
}
|
||||
const query: any = {}
|
||||
const query: any = {};
|
||||
if (userId && userId > 0) {
|
||||
query.userId = userId;
|
||||
}
|
||||
@@ -1239,8 +1195,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
const list = await this.find({
|
||||
where: {
|
||||
id: In(ids),
|
||||
...query
|
||||
}
|
||||
...query,
|
||||
},
|
||||
});
|
||||
|
||||
for (const item of list) {
|
||||
@@ -1267,15 +1223,15 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
const where: any = {
|
||||
id: In(ids),
|
||||
userId,
|
||||
}
|
||||
};
|
||||
if (projectId) {
|
||||
where.projectId = projectId
|
||||
where.projectId = projectId;
|
||||
}
|
||||
const list = await this.repository.find({
|
||||
select: {
|
||||
id: true
|
||||
id: true,
|
||||
},
|
||||
where: where
|
||||
where: where,
|
||||
});
|
||||
|
||||
ids = list.map(item => item.id);
|
||||
@@ -1293,12 +1249,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
} else {
|
||||
await this.run(id, null);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getUserPipelineCount(userId) {
|
||||
return await this.repository.count({ where: { userId } });
|
||||
}
|
||||
@@ -1307,21 +1262,20 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
return await this.repository.find({
|
||||
select: {
|
||||
id: true,
|
||||
title: true
|
||||
title: true,
|
||||
},
|
||||
where: {
|
||||
id: In(pipelineIds),
|
||||
userId,
|
||||
projectId
|
||||
}
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private async checkUserStatus(userId: number) {
|
||||
if (isEnterprise()) {
|
||||
//企业模式不检查用户状态,都允许运行流水线
|
||||
return
|
||||
return;
|
||||
}
|
||||
const userEntity = await this.userService.info(userId);
|
||||
if (userEntity == null) {
|
||||
@@ -1344,12 +1298,12 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
async createAutoPipeline(req: { domains: string[]; email: string; userId: number, projectId?: number, from: string }) {
|
||||
async createAutoPipeline(req: { domains: string[]; email: string; userId: number; projectId?: 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 = {
|
||||
const pipeline: any = {
|
||||
title: req.domains[0] + `证书自动申请【${req.from ?? "OpenAPI"}】`,
|
||||
runnableType: "pipeline",
|
||||
triggers: [
|
||||
@@ -1359,8 +1313,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
props: {
|
||||
cron: randomCron,
|
||||
},
|
||||
type: "timer"
|
||||
}
|
||||
type: "timer",
|
||||
},
|
||||
],
|
||||
notifications: [
|
||||
{
|
||||
@@ -1369,7 +1323,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
when: ["error", "turnToSuccess", "success"],
|
||||
notificationId: 0,
|
||||
title: "默认通知",
|
||||
}
|
||||
},
|
||||
],
|
||||
stages: [
|
||||
{
|
||||
@@ -1391,28 +1345,28 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
renewDays: 20,
|
||||
domains: req.domains,
|
||||
email: req.email,
|
||||
"challengeType": "auto",
|
||||
"sslProvider": "letsencrypt",
|
||||
"privateKeyType": "rsa_2048",
|
||||
"certProfile": "classic",
|
||||
"preferredChain": "ISRG Root X1",
|
||||
"useProxy": false,
|
||||
"skipLocalVerify": false,
|
||||
"maxCheckRetryCount": 20,
|
||||
"waitDnsDiffuseTime": 30,
|
||||
"pfxArgs": "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
|
||||
"successNotify": true
|
||||
challengeType: "auto",
|
||||
sslProvider: "letsencrypt",
|
||||
privateKeyType: "rsa_2048",
|
||||
certProfile: "classic",
|
||||
preferredChain: "ISRG Root X1",
|
||||
useProxy: false,
|
||||
skipLocalVerify: false,
|
||||
maxCheckRetryCount: 20,
|
||||
waitDnsDiffuseTime: 30,
|
||||
pfxArgs: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
|
||||
successNotify: true,
|
||||
},
|
||||
strategy: {
|
||||
runStrategy: 0 // 正常执行
|
||||
runStrategy: 0, // 正常执行
|
||||
},
|
||||
type: "CertApply"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
type: "CertApply",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const bean = new PipelineEntity();
|
||||
@@ -1421,11 +1375,10 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
bean.userId = req.userId;
|
||||
bean.status = "none";
|
||||
bean.type = "cert_auto";
|
||||
bean.disabled = false
|
||||
bean.keepHistoryCount = 30
|
||||
bean.projectId = req.projectId
|
||||
await this.save(bean)
|
||||
|
||||
bean.disabled = false;
|
||||
bean.keepHistoryCount = 30;
|
||||
bean.projectId = req.projectId;
|
||||
await this.save(bean);
|
||||
|
||||
return bean;
|
||||
}
|
||||
@@ -1433,11 +1386,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
async getStatus(pipelineId: number) {
|
||||
const res = await this.repository.findOne({
|
||||
select: {
|
||||
status: true
|
||||
status: true,
|
||||
},
|
||||
where: {
|
||||
id: pipelineId
|
||||
}
|
||||
id: pipelineId,
|
||||
},
|
||||
});
|
||||
return res?.status;
|
||||
}
|
||||
@@ -1445,11 +1398,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
async getPipelineUserId(pipelineId: number) {
|
||||
const res = await this.repository.findOne({
|
||||
select: {
|
||||
userId: true
|
||||
userId: true,
|
||||
},
|
||||
where: {
|
||||
id: pipelineId
|
||||
}
|
||||
id: pipelineId,
|
||||
},
|
||||
});
|
||||
return res?.userId;
|
||||
}
|
||||
@@ -1474,9 +1427,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
content: true,
|
||||
},
|
||||
where: {
|
||||
webhookKey
|
||||
}
|
||||
})
|
||||
webhookKey,
|
||||
},
|
||||
});
|
||||
if (!pipelineEntity) {
|
||||
throw new Error("webhookKey不存在");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { BaseService } from '@certd/lib-server';
|
||||
import { StorageEntity } from '../entity/storage.js';
|
||||
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { BaseService } from "@certd/lib-server";
|
||||
import { StorageEntity } from "../entity/storage.js";
|
||||
|
||||
/**
|
||||
*/
|
||||
@@ -19,7 +19,7 @@ export class StorageService extends BaseService<StorageEntity> {
|
||||
|
||||
async get(where: { scope: any; namespace: any; userId: number; version: string; key: string }) {
|
||||
if (where.userId == null) {
|
||||
throw new Error('userId 不能为空');
|
||||
throw new Error("userId 不能为空");
|
||||
}
|
||||
return await this.repository.findOne({
|
||||
where,
|
||||
@@ -34,7 +34,7 @@ export class StorageService extends BaseService<StorageEntity> {
|
||||
if (ret != null) {
|
||||
entity.id = ret.id;
|
||||
if (ret.userId !== entity.userId) {
|
||||
throw new Error('您没有权限修改此数据');
|
||||
throw new Error("您没有权限修改此数据");
|
||||
}
|
||||
await this.repository.save(entity);
|
||||
} else {
|
||||
@@ -49,9 +49,9 @@ export class StorageService extends BaseService<StorageEntity> {
|
||||
}
|
||||
return await this.repository.find({
|
||||
where: {
|
||||
scope: 'pipeline',
|
||||
scope: "pipeline",
|
||||
namespace: In(pipelineIds),
|
||||
key: 'vars',
|
||||
key: "vars",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -62,9 +62,9 @@ export class StorageService extends BaseService<StorageEntity> {
|
||||
}
|
||||
const res = await this.repository.findOne({
|
||||
where: {
|
||||
scope: 'pipeline',
|
||||
namespace: pipelineId + '',
|
||||
key: 'privateVars',
|
||||
scope: "pipeline",
|
||||
namespace: pipelineId + "",
|
||||
key: "privateVars",
|
||||
},
|
||||
});
|
||||
if (!res) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {BaseService, SysSettingsService} from '@certd/lib-server';
|
||||
import {InjectEntityModel} from '@midwayjs/typeorm';
|
||||
import {Repository} from 'typeorm';
|
||||
import {SubDomainEntity} from '../entity/sub-domain.js';
|
||||
import {EmailService} from '../../basic/service/email-service.js';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { BaseService, SysSettingsService } from "@certd/lib-server";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { SubDomainEntity } from "../entity/sub-domain.js";
|
||||
import { EmailService } from "../../basic/service/email-service.js";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -22,8 +22,8 @@ export class SubDomainService extends BaseService<SubDomainEntity> {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
async getListByUserId(userId:number, projectId?: number):Promise<string[]>{
|
||||
if (userId==null) {
|
||||
async getListByUserId(userId: number, projectId?: number): Promise<string[]> {
|
||||
if (userId == null) {
|
||||
return [];
|
||||
}
|
||||
const list = await this.find({
|
||||
@@ -34,16 +34,16 @@ export class SubDomainService extends BaseService<SubDomainEntity> {
|
||||
},
|
||||
});
|
||||
|
||||
return list.map(item=>item.domain);
|
||||
return list.map(item => item.domain);
|
||||
}
|
||||
|
||||
async add(bean: SubDomainEntity) {
|
||||
const {domain, userId, projectId} = bean;
|
||||
const { domain, userId, projectId } = bean;
|
||||
if (!domain) {
|
||||
throw new Error('域名不能为空');
|
||||
throw new Error("域名不能为空");
|
||||
}
|
||||
if (userId==null) {
|
||||
throw new Error('用户ID不能为空');
|
||||
if (userId == null) {
|
||||
throw new Error("用户ID不能为空");
|
||||
}
|
||||
const exist = await this.repository.findOne({
|
||||
where: {
|
||||
@@ -52,10 +52,9 @@ export class SubDomainService extends BaseService<SubDomainEntity> {
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
if (exist) {
|
||||
throw new Error('域名已存在');
|
||||
}
|
||||
return await super.add(bean)
|
||||
if (exist) {
|
||||
throw new Error("域名已存在");
|
||||
}
|
||||
return await super.add(bean);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {BaseService, SysSettingsService} from '@certd/lib-server';
|
||||
import {InjectEntityModel} from '@midwayjs/typeorm';
|
||||
import {In, Repository} from 'typeorm';
|
||||
import {TemplateEntity} from '../entity/template.js';
|
||||
import {PipelineService} from './pipeline-service.js';
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {PipelineEntity} from "../entity/pipeline.js";
|
||||
import {Pipeline} from "@certd/pipeline";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { BaseService, SysSettingsService } from "@certd/lib-server";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { TemplateEntity } from "../entity/template.js";
|
||||
import { PipelineService } from "./pipeline-service.js";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { PipelineEntity } from "../entity/pipeline.js";
|
||||
import { Pipeline } from "@certd/pipeline";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class TemplateService extends BaseService<TemplateEntity> {
|
||||
@InjectEntityModel(TemplateEntity)
|
||||
repository: Repository<TemplateEntity>;
|
||||
@@ -31,82 +31,77 @@ export class TemplateService extends BaseService<TemplateEntity> {
|
||||
|
||||
const pipelineEntity = await this.pipelineService.info(pipelineId);
|
||||
if (!pipelineEntity) {
|
||||
throw new Error('pipeline not found');
|
||||
throw new Error("pipeline not found");
|
||||
}
|
||||
if (pipelineEntity.userId !== param.userId) {
|
||||
throw new Error('permission denied');
|
||||
throw new Error("permission denied");
|
||||
}
|
||||
|
||||
|
||||
let template = null
|
||||
let template = null;
|
||||
await this.transaction(async (tx: any) => {
|
||||
|
||||
template = await tx.getRepository(TemplateEntity).save(param);
|
||||
let newPipeline = cloneDeep(pipelineEntity)
|
||||
let newPipeline = cloneDeep(pipelineEntity);
|
||||
//创建pipeline模版
|
||||
newPipeline.id = undefined;
|
||||
newPipeline.title = template.title + "模版流水线"
|
||||
newPipeline.templateId = template.id
|
||||
newPipeline.isTemplate = true
|
||||
newPipeline.userId = template.userId
|
||||
newPipeline.title = template.title + "模版流水线";
|
||||
newPipeline.templateId = template.id;
|
||||
newPipeline.isTemplate = true;
|
||||
newPipeline.userId = template.userId;
|
||||
|
||||
const pipelineJson: Pipeline = JSON.parse(newPipeline.content)
|
||||
delete pipelineJson.triggers
|
||||
pipelineJson.userId = template.userId
|
||||
pipelineJson.title = newPipeline.title
|
||||
newPipeline.content = JSON.stringify(pipelineJson)
|
||||
newPipeline = await tx.getRepository(PipelineEntity).save(newPipeline)
|
||||
const pipelineJson: Pipeline = JSON.parse(newPipeline.content);
|
||||
delete pipelineJson.triggers;
|
||||
pipelineJson.userId = template.userId;
|
||||
pipelineJson.title = newPipeline.title;
|
||||
newPipeline.content = JSON.stringify(pipelineJson);
|
||||
newPipeline = await tx.getRepository(PipelineEntity).save(newPipeline);
|
||||
|
||||
const update: any = {}
|
||||
update.id = template.id
|
||||
update.pipelineId = newPipeline.id
|
||||
await tx.getRepository(TemplateEntity).save(update)
|
||||
})
|
||||
|
||||
return template
|
||||
const update: any = {};
|
||||
update.id = template.id;
|
||||
update.pipelineId = newPipeline.id;
|
||||
await tx.getRepository(TemplateEntity).save(update);
|
||||
});
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
async detail(id: number, userId: number,projectId?:number) {
|
||||
const info = await this.info(id)
|
||||
async detail(id: number, userId: number, projectId?: number) {
|
||||
const info = await this.info(id);
|
||||
if (!info) {
|
||||
throw new Error('模板不存在');
|
||||
throw new Error("模板不存在");
|
||||
}
|
||||
if (info.userId !== userId) {
|
||||
throw new Error('无权限');
|
||||
throw new Error("无权限");
|
||||
}
|
||||
if (projectId && info.projectId !== projectId) {
|
||||
throw new Error('无权限');
|
||||
throw new Error("无权限");
|
||||
}
|
||||
let pipeline = null
|
||||
let pipeline = null;
|
||||
if (info.pipelineId) {
|
||||
const pipelineEntity = await this.pipelineService.info(info.pipelineId);
|
||||
pipeline = JSON.parse(pipelineEntity.content)
|
||||
pipeline = JSON.parse(pipelineEntity.content);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
template: info,
|
||||
pipeline,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async batchDelete(ids: number[], userId: number,projectId?:number) {
|
||||
|
||||
async batchDelete(ids: number[], userId: number, projectId?: number) {
|
||||
const where: any = {
|
||||
id: In(ids),
|
||||
}
|
||||
};
|
||||
if (userId != null) {
|
||||
where.userId = userId
|
||||
where.userId = userId;
|
||||
}
|
||||
if (projectId) {
|
||||
where.projectId = projectId
|
||||
where.projectId = projectId;
|
||||
}
|
||||
const list = await this.getRepository().find({where})
|
||||
ids = list.map(item => item.id)
|
||||
const pipelineIds = list.map(item => item.pipelineId)
|
||||
const list = await this.getRepository().find({ where });
|
||||
ids = list.map(item => item.id);
|
||||
const pipelineIds = list.map(item => item.pipelineId);
|
||||
await this.delete(ids);
|
||||
await this.pipelineService.batchDelete(pipelineIds, userId, projectId)
|
||||
await this.pipelineService.batchDelete(pipelineIds, userId, projectId);
|
||||
}
|
||||
|
||||
async createPipelineByTemplate(body: PipelineEntity) {
|
||||
@@ -114,21 +109,20 @@ export class TemplateService extends BaseService<TemplateEntity> {
|
||||
const template = await this.info(templateId);
|
||||
|
||||
if (!template && template.userId !== body.userId) {
|
||||
throw new Error('模板不存在');
|
||||
throw new Error("模板不存在");
|
||||
}
|
||||
|
||||
const tempPipeline = await this.pipelineService.info(template.pipelineId)
|
||||
const tempPipeline = await this.pipelineService.info(template.pipelineId);
|
||||
|
||||
const newPipeline = {
|
||||
type: tempPipeline.type,
|
||||
from : "template",
|
||||
from: "template",
|
||||
keepHistoryCount: tempPipeline.keepHistoryCount,
|
||||
... body,
|
||||
}
|
||||
...body,
|
||||
};
|
||||
|
||||
await this.pipelineService.save(newPipeline)
|
||||
await this.pipelineService.save(newPipeline);
|
||||
|
||||
return newPipeline
|
||||
return newPipeline;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IUrlService } from '@certd/pipeline';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { SysInstallInfo, SysSettingsService } from '@certd/lib-server';
|
||||
import { IUrlService } from "@certd/pipeline";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { SysInstallInfo, SysSettingsService } from "@certd/lib-server";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -10,7 +10,7 @@ export class UrlService implements IUrlService {
|
||||
|
||||
async getPipelineDetailUrl(pipelineId: number, historyId: number): Promise<string> {
|
||||
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||
let baseUrl = 'http://127.0.0.1:7001';
|
||||
let baseUrl = "http://127.0.0.1:7001";
|
||||
if (installInfo.bindUrl) {
|
||||
baseUrl = installInfo.bindUrl;
|
||||
}
|
||||
|
||||
@@ -1,66 +1,65 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('pi_plugin')
|
||||
@Entity("pi_plugin")
|
||||
export class PluginEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'name', comment: 'Key' })
|
||||
@Column({ name: "name", comment: "Key" })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'icon', comment: '图标' })
|
||||
@Column({ name: "icon", comment: "图标" })
|
||||
icon: string;
|
||||
|
||||
@Column({ name: 'title', comment: '标题' })
|
||||
@Column({ name: "title", comment: "标题" })
|
||||
title: string;
|
||||
|
||||
@Column({ name: 'group', comment: '分组' })
|
||||
@Column({ name: "group", comment: "分组" })
|
||||
group: string;
|
||||
|
||||
@Column({ name: 'desc', comment: '描述' })
|
||||
@Column({ name: "desc", comment: "描述" })
|
||||
desc: string;
|
||||
|
||||
@Column({ comment: '配置', length: 40960 })
|
||||
@Column({ comment: "配置", length: 40960 })
|
||||
setting: string;
|
||||
|
||||
@Column({ name: 'sys_setting', comment: '系统配置', length: 40960 })
|
||||
@Column({ name: "sys_setting", comment: "系统配置", length: 40960 })
|
||||
sysSetting: string;
|
||||
|
||||
@Column({ comment: '脚本', length: 40960 })
|
||||
@Column({ comment: "脚本", length: 40960 })
|
||||
content: string;
|
||||
|
||||
@Column({ comment: '类型', length: 100, nullable: true })
|
||||
@Column({ comment: "类型", length: 100, nullable: true })
|
||||
type: string; // builtIn | local | download
|
||||
|
||||
@Column({ comment: '启用/禁用', default: false })
|
||||
@Column({ comment: "启用/禁用", default: false })
|
||||
disabled: boolean;
|
||||
|
||||
@Column({ comment: '版本', length: 100, nullable: true })
|
||||
@Column({ comment: "版本", length: 100, nullable: true })
|
||||
version: string;
|
||||
|
||||
@Column({ comment: '插件类型', length: 100, nullable: true })
|
||||
@Column({ comment: "插件类型", length: 100, nullable: true })
|
||||
pluginType: string;
|
||||
|
||||
@Column({ comment: '元数据', length: 40960, nullable: true })
|
||||
@Column({ comment: "元数据", length: 40960, nullable: true })
|
||||
metadata: string;
|
||||
|
||||
@Column({ comment: '额外配置', length: 40960, nullable: true })
|
||||
@Column({ comment: "额外配置", length: 40960, nullable: true })
|
||||
extra: string;
|
||||
|
||||
@Column({ comment: '作者', length: 100, nullable: true })
|
||||
@Column({ comment: "作者", length: 100, nullable: true })
|
||||
author: string;
|
||||
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CertApplyPluginNames } from "@certd/plugin-cert";
|
||||
|
||||
|
||||
export function getDefaultAccessPlugin() {
|
||||
const metadata = `
|
||||
input:
|
||||
@@ -18,7 +17,7 @@ input:
|
||||
component:
|
||||
name: a-input
|
||||
allowClear: true
|
||||
`
|
||||
`;
|
||||
|
||||
const script = `
|
||||
// 必须使用 await import 来引入模块
|
||||
@@ -31,19 +30,18 @@ return class DemoAccess extends BaseAccess {
|
||||
}
|
||||
`;
|
||||
return {
|
||||
metadata:metadata,
|
||||
content: script
|
||||
metadata: metadata,
|
||||
content: script,
|
||||
};
|
||||
}
|
||||
|
||||
export function getDefaultDeployPlugin() {
|
||||
|
||||
let certApplyNames = ''
|
||||
let certApplyNames = "";
|
||||
for (const name of CertApplyPluginNames) {
|
||||
certApplyNames += `
|
||||
- "${name}"`
|
||||
- "${name}"`;
|
||||
}
|
||||
const metadata =`
|
||||
const metadata = `
|
||||
input: # 插件的输入参数
|
||||
cert:
|
||||
title: 前置任务证书
|
||||
@@ -90,8 +88,7 @@ input: # 插件的输入参数
|
||||
#output: # 输出参数,一般插件都不需要配置此项
|
||||
# outputName:
|
||||
#
|
||||
`
|
||||
|
||||
`;
|
||||
|
||||
const script = `
|
||||
// 要用await来import模块
|
||||
@@ -129,10 +126,10 @@ return class DemoTask extends AbstractTaskPlugin {
|
||||
// this.outputName = xxxx //设置输出参数,可以被其他插件选择使用
|
||||
}
|
||||
}
|
||||
`
|
||||
`;
|
||||
return {
|
||||
metadata: metadata,
|
||||
content: script
|
||||
content: script,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -144,7 +141,7 @@ accessType: aliyun # 授权类型名称
|
||||
#dependLibs: # 依赖的插件,应用商店安装时会先安装依赖插件
|
||||
# aliyun: *
|
||||
|
||||
`
|
||||
`;
|
||||
|
||||
const script = `
|
||||
const { AbstractDnsProvider } = await import("@certd/pipeline")
|
||||
@@ -186,10 +183,10 @@ return class DemoDnsProvider extends AbstractDnsProvider {
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
`;
|
||||
|
||||
return {
|
||||
metadata: metadata,
|
||||
content: script
|
||||
}
|
||||
content: script,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { IPluginConfigService, PluginConfig } from '@certd/pipeline';
|
||||
import { PluginConfigService } from './plugin-config-service.js';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { IPluginConfigService, PluginConfig } from "@certd/pipeline";
|
||||
import { PluginConfigService } from "./plugin-config-service.js";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -11,7 +11,7 @@ export class PluginConfigGetter implements IPluginConfigService {
|
||||
async getPluginConfig(pluginName: string): Promise<PluginConfig> {
|
||||
const res = await this.pluginConfigService.getPluginConfig({
|
||||
name: pluginName,
|
||||
type: 'builtIn',
|
||||
type: "builtIn",
|
||||
});
|
||||
return {
|
||||
name: res.name,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { PluginService } from './plugin-service.js';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { PluginService } from "./plugin-service.js";
|
||||
|
||||
export type PluginConfig = {
|
||||
name: string;
|
||||
@@ -20,7 +20,6 @@ export type PluginFindReq = {
|
||||
type: string;
|
||||
};
|
||||
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class PluginConfigService {
|
||||
@@ -31,18 +30,18 @@ export class PluginConfigService {
|
||||
const configs: CommPluginConfig = {};
|
||||
|
||||
configs.CertApply = await this.getPluginConfig({
|
||||
name: 'CertApply',
|
||||
type: 'builtIn',
|
||||
name: "CertApply",
|
||||
type: "builtIn",
|
||||
});
|
||||
return configs;
|
||||
}
|
||||
|
||||
async saveCommPluginConfig(config: CommPluginConfig) {
|
||||
config.CertApply.name = 'CertApply';
|
||||
config.CertApply.name = "CertApply";
|
||||
await this.savePluginConfig(config.CertApply);
|
||||
}
|
||||
|
||||
async savePluginConfig( config: PluginConfig) {
|
||||
async savePluginConfig(config: PluginConfig) {
|
||||
const name = config.name;
|
||||
const sysSetting = config?.sysSetting;
|
||||
if (!sysSetting) {
|
||||
@@ -55,12 +54,12 @@ export class PluginConfigService {
|
||||
await this.pluginService.add({
|
||||
name,
|
||||
sysSetting: JSON.stringify(sysSetting),
|
||||
type: 'builtIn',
|
||||
type: "builtIn",
|
||||
disabled: false,
|
||||
author: "certd",
|
||||
});
|
||||
} else {
|
||||
let setting = JSON.parse(pluginEntity.sysSetting || "{}");
|
||||
const setting = JSON.parse(pluginEntity.sysSetting || "{}");
|
||||
if (sysSetting.metadata) {
|
||||
setting.metadata = sysSetting.metadata;
|
||||
}
|
||||
@@ -73,7 +72,7 @@ export class PluginConfigService {
|
||||
|
||||
async get(req: PluginFindReq) {
|
||||
if (!req.name && !req.id) {
|
||||
throw new Error('plugin s name or id is required');
|
||||
throw new Error("plugin s name or id is required");
|
||||
}
|
||||
return await this.pluginService.getRepository().findOne({
|
||||
where: {
|
||||
|
||||
@@ -15,21 +15,21 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
export type PluginImportReq = {
|
||||
content: string,
|
||||
content: string;
|
||||
override?: boolean;
|
||||
};
|
||||
|
||||
async function importer(modulePath: string) {
|
||||
async function importer(modulePath: string) {
|
||||
if (!modulePath) {
|
||||
throw new Error("modules path 不能为空")
|
||||
throw new Error("modules path 不能为空");
|
||||
}
|
||||
if (!modulePath.startsWith("/@/")) {
|
||||
return await import(modulePath)
|
||||
return await import(modulePath);
|
||||
}
|
||||
modulePath = modulePath.replace("/@/", "")
|
||||
modulePath = modulePath.replace("/@/", "");
|
||||
//替换@为相对地址
|
||||
modulePath = `../../../${modulePath}`
|
||||
return await import(modulePath)
|
||||
modulePath = `../../../${modulePath}`;
|
||||
return await import(modulePath);
|
||||
}
|
||||
|
||||
@Provide()
|
||||
@@ -48,7 +48,6 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async page(pageReq: PageReq<PluginEntity>) {
|
||||
|
||||
if (pageReq.query.type && pageReq.query.type !== "builtIn") {
|
||||
return await super.page(pageReq);
|
||||
}
|
||||
@@ -56,7 +55,6 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
const offset = pageReq.page.offset;
|
||||
const limit = pageReq.page.limit;
|
||||
|
||||
|
||||
const builtInList = await this.getBuiltInEntityList();
|
||||
|
||||
//获取分页数据
|
||||
@@ -66,11 +64,11 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
records: data,
|
||||
total: builtInList.length,
|
||||
offset: offset,
|
||||
limit: limit
|
||||
limit: limit,
|
||||
};
|
||||
}
|
||||
|
||||
async getEnabledBuildInGroup(opts?: { isSimple?: boolean, withSetting?: boolean }) {
|
||||
async getEnabledBuildInGroup(opts?: { isSimple?: boolean; withSetting?: boolean }) {
|
||||
const groups = this.builtInPluginService.getGroups();
|
||||
if (opts?.isSimple) {
|
||||
for (const key in groups) {
|
||||
@@ -81,7 +79,6 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!isComm()) {
|
||||
return groups;
|
||||
}
|
||||
@@ -91,14 +88,14 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sysSetting: true
|
||||
sysSetting: true,
|
||||
},
|
||||
where: {
|
||||
sysSetting: Not(IsNull())
|
||||
}
|
||||
})
|
||||
sysSetting: Not(IsNull()),
|
||||
},
|
||||
});
|
||||
//合并插件配置
|
||||
const pluginSettingMap: any = {}
|
||||
const pluginSettingMap: any = {};
|
||||
for (const item of settingPlugins) {
|
||||
if (!item.sysSetting) {
|
||||
continue;
|
||||
@@ -113,17 +110,16 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
for (const item of group.plugins) {
|
||||
const pluginSetting = pluginSettingMap[item.name];
|
||||
if (pluginSetting) {
|
||||
item.sysSetting = pluginSetting
|
||||
item.sysSetting = pluginSetting;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//排除禁用的
|
||||
const list = await this.list({
|
||||
query: {
|
||||
disabled: true
|
||||
}
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
const disabledNames = list.map(it => it.name);
|
||||
for (const key in groups) {
|
||||
@@ -145,8 +141,8 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
const list = await this.list({
|
||||
query: {
|
||||
type: "builtIn",
|
||||
disabled: true
|
||||
}
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
const disabledNames = list.map(it => it.name);
|
||||
|
||||
@@ -159,8 +155,8 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
const builtInList = this.builtInPluginService.getList();
|
||||
const list = await this.list({
|
||||
query: {
|
||||
type: "builtIn"
|
||||
}
|
||||
type: "builtIn",
|
||||
},
|
||||
});
|
||||
|
||||
const records: PluginEntity[] = [];
|
||||
@@ -177,7 +173,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
type: "builtIn",
|
||||
icon: item.icon,
|
||||
desc: item.desc,
|
||||
group: item.group
|
||||
group: item.group,
|
||||
});
|
||||
records.push(record);
|
||||
}
|
||||
@@ -215,12 +211,11 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
* @param param 数据
|
||||
*/
|
||||
async add(param: any) {
|
||||
|
||||
const old = await this.repository.findOne({
|
||||
where: {
|
||||
name: param.name,
|
||||
author: param.author
|
||||
}
|
||||
author: param.author,
|
||||
},
|
||||
});
|
||||
|
||||
if (old) {
|
||||
@@ -248,11 +243,11 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
|
||||
const res = await super.add({
|
||||
...param,
|
||||
...plugin
|
||||
...plugin,
|
||||
});
|
||||
|
||||
await this.registerById(res.id);
|
||||
return res
|
||||
return res;
|
||||
}
|
||||
|
||||
async registerById(id: any) {
|
||||
@@ -276,20 +271,20 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
}
|
||||
let name = item.name;
|
||||
if (item.author && !item.name.startsWith(`${item.author}/`)) {
|
||||
name = `${item.author}/${item.name}`
|
||||
name = `${item.author}/${item.name}`;
|
||||
}
|
||||
if (item.pluginType === "access") {
|
||||
accessRegistry.unRegister(name)
|
||||
accessRegistry.unRegister(name);
|
||||
} else if (item.pluginType === "deploy") {
|
||||
pluginRegistry.unRegister(name)
|
||||
pluginRegistry.unRegister(name);
|
||||
} else if (item.pluginType === "dnsProvider") {
|
||||
dnsProviderRegistry.unRegister(name)
|
||||
dnsProviderRegistry.unRegister(name);
|
||||
} else if (item.pluginType === "notification") {
|
||||
notificationRegistry.unRegister(name)
|
||||
}else if (item.pluginType === "addon") {
|
||||
addonRegistry.unRegister(name)
|
||||
notificationRegistry.unRegister(name);
|
||||
} else if (item.pluginType === "addon") {
|
||||
addonRegistry.unRegister(name);
|
||||
} else {
|
||||
logger.warn(`不支持的插件类型:${item.pluginType}`)
|
||||
logger.warn(`不支持的插件类型:${item.pluginType}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,8 +292,8 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
const old = await this.repository.findOne({
|
||||
where: {
|
||||
name: param.name,
|
||||
author: param.author
|
||||
}
|
||||
author: param.author,
|
||||
},
|
||||
});
|
||||
|
||||
if (old && old.id !== param.id) {
|
||||
@@ -309,22 +304,21 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
const res = await super.update(param);
|
||||
|
||||
await this.registerById(param.id);
|
||||
return res
|
||||
return res;
|
||||
}
|
||||
|
||||
async compile(code: string) {
|
||||
const ts = await import("typescript");
|
||||
return ts.transpileModule(code, {
|
||||
compilerOptions: { module: ts.ModuleKind.ESNext }
|
||||
compilerOptions: { module: ts.ModuleKind.ESNext },
|
||||
}).outputText;
|
||||
}
|
||||
|
||||
|
||||
private async getPluginClassFromFile(item: any) {
|
||||
const scriptFilePath = item.scriptFilePath;
|
||||
const res = await import((`../../..${scriptFilePath}`))
|
||||
const classNames = Object.keys(res)
|
||||
return res[classNames[classNames.length - 1]]
|
||||
const res = await import(`../../..${scriptFilePath}`);
|
||||
const classNames = Object.keys(res);
|
||||
return res[classNames[classNames.length - 1]];
|
||||
}
|
||||
|
||||
async getPluginClassFromDb(pluginName: string) {
|
||||
@@ -341,14 +335,13 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
const info = await this.find({
|
||||
where: {
|
||||
name: name,
|
||||
author: author
|
||||
}
|
||||
author: author,
|
||||
},
|
||||
});
|
||||
if (info && info.length > 0) {
|
||||
const plugin = info[0];
|
||||
try {
|
||||
const AsyncFunction = Object.getPrototypeOf(async () => {
|
||||
}).constructor;
|
||||
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
|
||||
// const script = await this.compile(plugin.content);
|
||||
const script = plugin.content;
|
||||
const getPluginClass = new AsyncFunction("_ctx", script);
|
||||
@@ -357,7 +350,6 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
logger.error("编译插件失败:", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
throw new Error(`插件${pluginName}不存在`);
|
||||
}
|
||||
@@ -367,11 +359,11 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
*/
|
||||
async registerFromDb() {
|
||||
const res = await this.list({
|
||||
buildQuery: ((bq) => {
|
||||
buildQuery: bq => {
|
||||
bq.andWhere("type != :type", {
|
||||
type: "builtIn"
|
||||
type: "builtIn",
|
||||
});
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
for (const item of res) {
|
||||
@@ -382,7 +374,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
async registerFromLocal(localDir: string) {
|
||||
//scan path
|
||||
const files = fs.readdirSync(localDir);
|
||||
let list = []
|
||||
let list = [];
|
||||
for (const file of files) {
|
||||
if (!file.endsWith(".yaml")) {
|
||||
continue;
|
||||
@@ -390,7 +382,6 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
const item = yaml.load(fs.readFileSync(path.join(localDir, file), "utf8"));
|
||||
|
||||
list.push(item);
|
||||
|
||||
}
|
||||
//排序
|
||||
list = list.sort((a, b) => {
|
||||
@@ -408,7 +399,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
const item = {
|
||||
...plugin,
|
||||
...metadata,
|
||||
...extra
|
||||
...extra,
|
||||
};
|
||||
delete item.metadata;
|
||||
delete item.content;
|
||||
@@ -416,7 +407,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
if (item.author && !item.name.startsWith(`${item.author}/`)) {
|
||||
item.name = item.author + "/" + item.name;
|
||||
}
|
||||
let name = item.name
|
||||
let name = item.name;
|
||||
if (item.addonType) {
|
||||
name = item.addonType + ":" + name;
|
||||
}
|
||||
@@ -444,7 +435,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
} else {
|
||||
return await this.getPluginClassFromDb(name);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -466,35 +457,34 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
...info,
|
||||
...metadata,
|
||||
...extra,
|
||||
content
|
||||
content,
|
||||
};
|
||||
|
||||
return yaml.dump(plugin) as string;
|
||||
}
|
||||
|
||||
async importPlugin(req: PluginImportReq) {
|
||||
|
||||
const loaded = yaml.load(req.content);
|
||||
if (!loaded) {
|
||||
throw new Error("插件内容不能为空");
|
||||
}
|
||||
delete loaded.id
|
||||
delete loaded.id;
|
||||
|
||||
const old = await this.repository.findOne({
|
||||
where: {
|
||||
name: loaded.name,
|
||||
author: loaded.author
|
||||
}
|
||||
author: loaded.author,
|
||||
},
|
||||
});
|
||||
|
||||
const metadata = {
|
||||
input: loaded.input,
|
||||
output: loaded.output
|
||||
output: loaded.output,
|
||||
};
|
||||
const extra = {
|
||||
dependPlugins: loaded.dependPlugins,
|
||||
default: loaded.default,
|
||||
showRunStrategy: loaded.showRunStrategy
|
||||
showRunStrategy: loaded.showRunStrategy,
|
||||
};
|
||||
|
||||
const pluginEntity = {
|
||||
@@ -502,7 +492,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
metadata: yaml.dump(metadata),
|
||||
extra: yaml.dump(extra),
|
||||
content: loaded.content,
|
||||
disabled: false
|
||||
disabled: false,
|
||||
};
|
||||
if (!pluginEntity.pluginType) {
|
||||
throw new Error(`插件类型不能为空`);
|
||||
@@ -521,7 +511,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
//update
|
||||
await this.update(pluginEntity);
|
||||
return {
|
||||
id: pluginEntity.id
|
||||
id: pluginEntity.id,
|
||||
};
|
||||
}
|
||||
async deleteByIds(ids: any[]) {
|
||||
@@ -531,5 +521,4 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
await this.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user