mirror of
https://github.com/certd/certd.git
synced 2026-07-16 10:47:31 +08:00
chore: format
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user