mirror of
https://github.com/certd/certd.git
synced 2026-04-24 12:27:25 +08:00
perf: 支持邮件模版设置
This commit is contained in:
@@ -423,31 +423,46 @@ export class Executor {
|
|||||||
let subject = "";
|
let subject = "";
|
||||||
let content = "";
|
let content = "";
|
||||||
const errorMessage = error?.message;
|
const errorMessage = error?.message;
|
||||||
|
const templateData: any = {
|
||||||
|
pipelineId: this.pipeline.id,
|
||||||
|
historyId: this.runtime.id,
|
||||||
|
pipelineTitle: this.pipeline.title,
|
||||||
|
};
|
||||||
|
let pipelineResult = "";
|
||||||
|
let errors = "";
|
||||||
if (when === "start") {
|
if (when === "start") {
|
||||||
subject = `开始执行,${this.pipeline.title}【${this.pipeline.id}】`;
|
pipelineResult = "开始执行";
|
||||||
|
subject = `${pipelineResult},${this.pipeline.title}【${this.pipeline.id}】`;
|
||||||
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
|
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
|
||||||
} else if (when === "success") {
|
} else if (when === "success") {
|
||||||
subject = `执行成功,${this.pipeline.title}【${this.pipeline.id}】`;
|
pipelineResult = "执行成功";
|
||||||
|
subject = `${pipelineResult},${this.pipeline.title}【${this.pipeline.id}】`;
|
||||||
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
|
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
|
||||||
} else if (when === "turnToSuccess") {
|
} else if (when === "turnToSuccess") {
|
||||||
subject = `执行成功(失败转成功),${this.pipeline.title}【${this.pipeline.id}】`;
|
pipelineResult = "执行成功(失败转成功)";
|
||||||
|
subject = `${pipelineResult},${this.pipeline.title}【${this.pipeline.id}】`;
|
||||||
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
|
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`;
|
||||||
} else if (when === "error") {
|
} else if (when === "error") {
|
||||||
subject = `执行失败,${this.pipeline.title}【${this.pipeline.id}】`;
|
pipelineResult = "执行失败";
|
||||||
|
subject = `${pipelineResult},${this.pipeline.title}【${this.pipeline.id}】`;
|
||||||
if (error instanceof RunnableError) {
|
if (error instanceof RunnableError) {
|
||||||
const runnableError = error as RunnableError;
|
const runnableError = error as RunnableError;
|
||||||
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n`;
|
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n`;
|
||||||
for (const re of runnableError.errors) {
|
for (const re of runnableError.errors) {
|
||||||
content += ` - ${re.runnable.title} 执行失败,错误详情:${re.e?.message || re.e?.error?.message}\n\n`;
|
errors += ` - ${re.runnable.title} 执行失败,错误详情:${re.e?.message || re.e?.error?.message}\n\n`;
|
||||||
}
|
}
|
||||||
|
content += errors;
|
||||||
} else {
|
} else {
|
||||||
|
errors = error.message;
|
||||||
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n${this.currentStatusMap?.currentStep?.title} 执行失败\n\n错误详情:${error.message}`;
|
content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n${this.currentStatusMap?.currentStep?.title} 执行失败\n\n错误详情:${error.message}`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templateData.errors = errors;
|
||||||
|
templateData.pipelineResult = pipelineResult;
|
||||||
|
|
||||||
for (const notification of this.pipeline.notifications) {
|
for (const notification of this.pipeline.notifications) {
|
||||||
if (!notification.when.includes(when)) {
|
if (!notification.when.includes(when)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -455,10 +470,12 @@ export class Executor {
|
|||||||
|
|
||||||
if (notification.type === "email" && notification.options?.receivers) {
|
if (notification.type === "email" && notification.options?.receivers) {
|
||||||
try {
|
try {
|
||||||
await this.options.emailService?.send({
|
await this.options.emailService?.sendByTemplate({
|
||||||
subject,
|
type: "pipelineResult",
|
||||||
content,
|
data: templateData,
|
||||||
receivers: notification.options?.receivers,
|
email: {
|
||||||
|
receivers: notification.options?.receivers,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("send email error", e);
|
logger.error("send email error", e);
|
||||||
@@ -472,15 +489,15 @@ export class Executor {
|
|||||||
useEmail: false,
|
useEmail: false,
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
body: {
|
body: {
|
||||||
|
notificationType: "pipelineResult",
|
||||||
title: subject,
|
title: subject,
|
||||||
content,
|
content,
|
||||||
userId: this.pipeline.userId,
|
userId: this.pipeline.userId,
|
||||||
pipeline: this.pipeline,
|
pipeline: this.pipeline,
|
||||||
result: this.lastRuntime?.pipeline?.status,
|
result: this.lastRuntime?.pipeline?.status,
|
||||||
pipelineId: this.pipeline.id,
|
|
||||||
historyId: this.runtime.id,
|
|
||||||
errorMessage,
|
errorMessage,
|
||||||
url,
|
url,
|
||||||
|
...templateData,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ export type NotificationBody = {
|
|||||||
historyId?: number;
|
historyId?: number;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
notificationType?: string;
|
||||||
|
attachments?: any[];
|
||||||
|
pipelineResult?: string;
|
||||||
|
pipelineTitle?: string;
|
||||||
|
errors?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotificationRequestHandleReqInput<T = any> = {
|
export type NotificationRequestHandleReqInput<T = any> = {
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ export type EmailSend = {
|
|||||||
html?: string;
|
html?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EmailSendByTemplateReq = {
|
||||||
|
type: string;
|
||||||
|
data: any;
|
||||||
|
email: { receivers: string[]; attachments?: any[] };
|
||||||
|
};
|
||||||
|
|
||||||
export interface IEmailService {
|
export interface IEmailService {
|
||||||
send(email: EmailSend): Promise<void>;
|
send(email: EmailSend): Promise<void>;
|
||||||
|
sendByTemplate(req: EmailSendByTemplateReq): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,11 @@ export class SysLicenseInfo extends BaseSettings {
|
|||||||
license?: string;
|
license?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type EmailTemplate = {
|
||||||
|
addonId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class SysEmailConf extends BaseSettings {
|
export class SysEmailConf extends BaseSettings {
|
||||||
static __title__ = '邮箱配置';
|
static __title__ = '邮箱配置';
|
||||||
static __key__ = 'sys.email';
|
static __key__ = 'sys.email';
|
||||||
@@ -126,6 +131,16 @@ export class SysEmailConf extends BaseSettings {
|
|||||||
};
|
};
|
||||||
sender: string;
|
sender: string;
|
||||||
usePlus?: boolean;
|
usePlus?: boolean;
|
||||||
|
|
||||||
|
templates:{
|
||||||
|
registerCode?: EmailTemplate,
|
||||||
|
forgotPasswordCode?: EmailTemplate,
|
||||||
|
certSuccessNotify?: EmailTemplate,
|
||||||
|
certSend?: EmailTemplate,
|
||||||
|
pipelineNotify?: EmailTemplate,
|
||||||
|
test?: EmailTemplate,
|
||||||
|
siteMonitorNotify?: EmailTemplate,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SysSiteInfo extends BaseSettings {
|
export class SysSiteInfo extends BaseSettings {
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
|||||||
title: `证书申请成功【${this.pipeline.title}】`,
|
title: `证书申请成功【${this.pipeline.title}】`,
|
||||||
content: `域名:${this.domains.join(",")}`,
|
content: `域名:${this.domains.join(",")}`,
|
||||||
url: url,
|
url: url,
|
||||||
|
notificationType: "certApplySuccess",
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await this.ctx.notificationService.send({
|
await this.ctx.notificationService.send({
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div class="params-show">
|
||||||
|
<div v-for="item of params" :key="item.value" class="item">
|
||||||
|
<span class="label">{{ item.label }}:</span>
|
||||||
|
<fs-copyable>{{ item.value }}</fs-copyable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({
|
||||||
|
name: "ParamsShow",
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
params: { value: string; label: string }[];
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.params-show {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 200px;
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-copyable {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,6 +11,7 @@ import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
|
|||||||
import InputPassword from "./common/input-password.vue";
|
import InputPassword from "./common/input-password.vue";
|
||||||
import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue";
|
import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue";
|
||||||
import ApiTest from "./common/api-test.vue";
|
import ApiTest from "./common/api-test.vue";
|
||||||
|
import ParamsShow from "./common/params-show.vue";
|
||||||
export * from "./cert/index.js";
|
export * from "./cert/index.js";
|
||||||
export default {
|
export default {
|
||||||
install(app: any) {
|
install(app: any) {
|
||||||
@@ -29,5 +30,6 @@ export default {
|
|||||||
app.component("RemoteInput", RemoteInput);
|
app.component("RemoteInput", RemoteInput);
|
||||||
app.component("CertDomainsGetter", CertDomainsGetter);
|
app.component("CertDomainsGetter", CertDomainsGetter);
|
||||||
app.component("InputPassword", InputPassword);
|
app.component("InputPassword", InputPassword);
|
||||||
|
app.component("ParamsShow", ParamsShow);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -780,6 +780,18 @@ export default {
|
|||||||
oauthAutoRedirectHelper: "Whether to auto redirect to OAuth2 login when login (using the first enabled OAuth2 login type)",
|
oauthAutoRedirectHelper: "Whether to auto redirect to OAuth2 login when login (using the first enabled OAuth2 login type)",
|
||||||
oauthOnly: "OAuth2 Login Only",
|
oauthOnly: "OAuth2 Login Only",
|
||||||
oauthOnlyHelper: "Whether to only allow OAuth2 login, disable password login",
|
oauthOnlyHelper: "Whether to only allow OAuth2 login, disable password login",
|
||||||
|
|
||||||
|
email: {
|
||||||
|
templates: "Email Templates",
|
||||||
|
templateType: "Template Type",
|
||||||
|
templateProvider: "Template Config",
|
||||||
|
|
||||||
|
templateSetting: "Email Template Setting",
|
||||||
|
serverSetting: "Email Server Setting",
|
||||||
|
sendTest: "Send Test",
|
||||||
|
|
||||||
|
templateProviderSelectorPlaceholder: "Not Configured",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modal: {
|
modal: {
|
||||||
|
|||||||
@@ -780,6 +780,18 @@ export default {
|
|||||||
oauthAutoRedirectHelper: "是否自动跳转第三方登录(使用第一个已启用的第三方登录类型)",
|
oauthAutoRedirectHelper: "是否自动跳转第三方登录(使用第一个已启用的第三方登录类型)",
|
||||||
oauthOnly: "仅使用第三方登录",
|
oauthOnly: "仅使用第三方登录",
|
||||||
oauthOnlyHelper: "是否仅使用第三方登录,关闭密码登录(注意:请务必在测试第三方登录功能正常后再开启)",
|
oauthOnlyHelper: "是否仅使用第三方登录,关闭密码登录(注意:请务必在测试第三方登录功能正常后再开启)",
|
||||||
|
|
||||||
|
email: {
|
||||||
|
templates: "邮件模板",
|
||||||
|
templateType: "模板类型",
|
||||||
|
templateProvider: "模板配置",
|
||||||
|
|
||||||
|
templateSetting: "邮件模板设置",
|
||||||
|
serverSetting: "邮件服务器设置",
|
||||||
|
sendTest: "发送测试",
|
||||||
|
|
||||||
|
templateProviderSelectorPlaceholder: "未配置",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modal: {
|
modal: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { request } from "/src/api/service";
|
import { request } from "/src/api/service";
|
||||||
const apiPrefix = "/mine/email";
|
const apiPrefix = "/mine/email";
|
||||||
|
const apiSettingPrefix = "/sys/settings";
|
||||||
|
|
||||||
export async function TestSend(receiver: string) {
|
export async function TestSend(receiver: string) {
|
||||||
await request({
|
await request({
|
||||||
@@ -10,3 +11,10 @@ export async function TestSend(receiver: string) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function GetEmailTemplates() {
|
||||||
|
return await request({
|
||||||
|
url: apiSettingPrefix + "/getEmailTemplates",
|
||||||
|
method: "post",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,53 +7,93 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="flex-o">
|
<div class="email-form">
|
||||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" class="email-form-box" @finish="onFinish" @finish-failed="onFinishFailed">
|
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" class="" @finish="onFinish">
|
||||||
<div v-if="!formState.usePlus" class="email-form">
|
<h2>{{ t("certd.sys.setting.email.serverSetting") }}</h2>
|
||||||
<a-form-item :label="t('certd.useCustomEmailServer')"> </a-form-item>
|
<a-tabs v-model:active-key="activeKey" @change="onChangeActiveKey">
|
||||||
<a-form-item :label="t('certd.smtpDomain')" name="host" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpDomain') }]">
|
<a-tab-pane key="custom" :tab="t('certd.useCustomEmailServer')">
|
||||||
<a-input v-model:value="formState.host" />
|
<div>
|
||||||
</a-form-item>
|
<a-form-item :label="t('certd.smtpDomain')" name="host" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpDomain') }]">
|
||||||
|
<a-input v-model:value="formState.host" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="t('certd.smtpPort')" name="port" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpPort') }]">
|
<a-form-item :label="t('certd.smtpPort')" name="port" :rules="[{ required: true, message: t('certd.pleaseEnterSmtpPort') }]">
|
||||||
<a-input v-model:value="formState.port" />
|
<a-input v-model:value="formState.port" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="t('certd.username')" :name="['auth', 'user']" :rules="[{ required: true, message: t('certd.pleaseEnterUsername') }]">
|
<a-form-item :label="t('certd.username')" :name="['auth', 'user']" :rules="[{ required: true, message: t('certd.pleaseEnterUsername') }]">
|
||||||
<a-input v-model:value="formState.auth.user" />
|
<a-input v-model:value="formState.auth.user" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('certd.password')" :name="['auth', 'pass']" :rules="[{ required: true, message: t('certd.pleaseEnterPassword') }]">
|
<a-form-item :label="t('certd.password')" :name="['auth', 'pass']" :rules="[{ required: true, message: t('certd.pleaseEnterPassword') }]">
|
||||||
<a-input-password v-model:value="formState.auth.pass" />
|
<a-input-password v-model:value="formState.auth.pass" />
|
||||||
<div class="helper">{{ t("certd.qqEmailAuthCodeHelper") }}</div>
|
<div class="helper">{{ t("certd.qqEmailAuthCodeHelper") }}</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('certd.senderEmail')" name="sender" :rules="[{ required: true, message: t('certd.pleaseEnterSenderEmail') }]">
|
<a-form-item :label="t('certd.senderEmail')" name="sender" :rules="[{ required: true, message: t('certd.pleaseEnterSenderEmail') }]">
|
||||||
<a-input v-model:value="formState.sender" />
|
<a-input v-model:value="formState.sender" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('certd.useSsl')" name="secure">
|
<a-form-item :label="t('certd.useSsl')" name="secure">
|
||||||
<a-switch v-model:checked="formState.secure" />
|
<a-switch v-model:checked="formState.secure" />
|
||||||
<div class="helper">{{ t("certd.sslPortNote") }}</div>
|
<div class="helper">{{ t("certd.sslPortNote") }}</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('certd.ignoreCertValidation')" :name="['tls', 'rejectUnauthorized']">
|
<a-form-item :label="t('certd.ignoreCertValidation')" :name="['tls', 'rejectUnauthorized']">
|
||||||
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
|
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
|
||||||
<a-button type="primary" html-type="submit">{{ t("certd.save") }}</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
</div>
|
|
||||||
<div class="email-form">
|
|
||||||
<a-form-item :label="t('certd.useOfficialEmailServer')" name="usePlus">
|
|
||||||
<div class="flex-o">
|
|
||||||
<a-switch v-model:checked="formState.usePlus" :disabled="!settingStore.isPlus" @change="onUsePlusChanged" />
|
|
||||||
<vip-button class="ml-5" mode="button"></vip-button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="helper">{{ t("certd.useOfficialEmailServerHelper") }}</div>
|
</a-tab-pane>
|
||||||
</a-form-item>
|
<a-tab-pane key="plus" class="plus" :disabled="!settingStore.isPlus">
|
||||||
</div>
|
<template #tab>
|
||||||
|
<span class="flex items-center">
|
||||||
|
{{ t("certd.useOfficialEmailServer") }}
|
||||||
|
<vip-button class="ml-5" mode="button"></vip-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-form-item :label="t('certd.useOfficialEmailServer')" name="usePlus">
|
||||||
|
<div class="flex-o">
|
||||||
|
<a-switch v-model:checked="formState.usePlus" :disabled="!settingStore.isPlus" @change="onUsePlusChanged" />
|
||||||
|
</div>
|
||||||
|
<div class="helper">{{ t("certd.useOfficialEmailServerHelper") }}</div>
|
||||||
|
</a-form-item>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||||
|
<a-button type="primary" html-type="submit">{{ t("certd.save") }}</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<h2>{{ t("certd.sys.setting.email.templateSetting") }}</h2>
|
||||||
|
<a-form-item :label="t('certd.sys.setting.email.templates')" :name="['templates']">
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<table class="w-full table-auto border-collapse border border-gray-400">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="border border-gray-300 px-4 py-2 w-1/3">{{ t("certd.sys.setting.email.templateType") }}</th>
|
||||||
|
<th class="border border-gray-300 px-4 py-2 w-1/3">{{ t("certd.sys.setting.email.templateProvider") }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(item, key) of emailTemplates" :key="key">
|
||||||
|
<td class="border border-gray-300 px-4 py-2">
|
||||||
|
<div class="flex items-center" :title="item.desc">
|
||||||
|
<fs-icon :icon="item.icon" class="mr-2 text-blue-600" />
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="border border-gray-300 px-4 py-2">
|
||||||
|
<AddonSelector v-model:model-value="item.addonId" addon-type="emailTemplate" from="sys" :type="item.name" :placeholder="t('certd.sys.setting.email.templateProviderSelectorPlaceholder')" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||||
|
<a-button type="primary" html-type="submit">{{ t("certd.save") }}</a-button>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="email-form">
|
<div class="email-form">
|
||||||
<a-form :model="testFormState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onTestSend">
|
<a-form :model="testFormState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onTestSend">
|
||||||
|
<h2>{{ t("certd.sys.setting.email.sendTest") }}</h2>
|
||||||
<a-form-item :label="t('certd.testReceiverEmail')" name="receiver" :rules="[{ required: true, message: t('certd.pleaseEnterTestReceiverEmail') }]">
|
<a-form-item :label="t('certd.testReceiverEmail')" name="receiver" :rules="[{ required: true, message: t('certd.pleaseEnterTestReceiverEmail') }]">
|
||||||
<a-input v-model:value="testFormState.receiver" />
|
<a-input v-model:value="testFormState.receiver" />
|
||||||
<div class="helper">{{ t("certd.saveBeforeTest") }}</div>
|
<div class="helper">{{ t("certd.saveBeforeTest") }}</div>
|
||||||
@@ -71,14 +111,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive } from "vue";
|
import { onMounted, reactive, ref } from "vue";
|
||||||
import * as api from "../api";
|
import * as api from "../api";
|
||||||
import * as emailApi from "./api.email";
|
import * as emailApi from "./api.email";
|
||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
import { useSettingStore } from "/src/store/settings";
|
import { useSettingStore } from "/src/store/settings";
|
||||||
import * as _ from "lodash-es";
|
import * as _ from "lodash-es";
|
||||||
import { useI18n } from "/src/locales";
|
import { useI18n } from "/src/locales";
|
||||||
|
import AddonSelector from "../../../certd/addon/addon-selector/index.vue";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "EmailSetting",
|
name: "EmailSetting",
|
||||||
@@ -98,8 +138,14 @@ interface FormState {
|
|||||||
};
|
};
|
||||||
sender: string;
|
sender: string;
|
||||||
usePlus: boolean;
|
usePlus: boolean;
|
||||||
|
templates: {
|
||||||
|
pipelineResult: any;
|
||||||
|
registerCode: any;
|
||||||
|
forgotPassword: any;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeKey = ref("custom");
|
||||||
const formState = reactive<Partial<FormState>>({
|
const formState = reactive<Partial<FormState>>({
|
||||||
auth: {
|
auth: {
|
||||||
user: "",
|
user: "",
|
||||||
@@ -109,27 +155,65 @@ const formState = reactive<Partial<FormState>>({
|
|||||||
usePlus: false,
|
usePlus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emailTemplates = ref([]);
|
||||||
|
async function loadEmailTemplates() {
|
||||||
|
emailTemplates.value = await emailApi.GetEmailTemplates();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillEmailTemplates(form: any) {
|
||||||
|
const providers: any = {};
|
||||||
|
for (const item of emailTemplates.value) {
|
||||||
|
const type = item.name;
|
||||||
|
providers[type] = {
|
||||||
|
type: type,
|
||||||
|
title: item.title,
|
||||||
|
icon: item.icon,
|
||||||
|
addonId: item.addonId || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
form.templates = providers;
|
||||||
|
return providers;
|
||||||
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
const data: any = await api.EmailSettingsGet();
|
const data: any = await api.EmailSettingsGet();
|
||||||
_.merge(formState, data);
|
_.merge(formState, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
load();
|
onMounted(async () => {
|
||||||
|
await load();
|
||||||
|
refreshActiveKeyByUsePlus();
|
||||||
|
await loadEmailTemplates();
|
||||||
|
});
|
||||||
|
|
||||||
const onFinish = async (form: any) => {
|
const onFinish = async (form: any) => {
|
||||||
console.log("Success:", form);
|
fillEmailTemplates(form);
|
||||||
await api.EmailSettingsSave(form);
|
await api.EmailSettingsSave(form);
|
||||||
notification.success({
|
notification.success({
|
||||||
message: t("certd.saveSuccess"),
|
message: t("certd.saveSuccess"),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFinishFailed = (errorInfo: any) => {
|
|
||||||
// console.log("Failed:", errorInfo);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function onUsePlusChanged() {
|
async function onUsePlusChanged() {
|
||||||
await api.EmailSettingsSave(formState);
|
refreshActiveKeyByUsePlus();
|
||||||
|
await onFinish(formState);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshActiveKeyByUsePlus() {
|
||||||
|
if (formState.usePlus) {
|
||||||
|
activeKey.value = "plus";
|
||||||
|
} else {
|
||||||
|
activeKey.value = "custom";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function onChangeActiveKey(key: string) {
|
||||||
|
activeKey.value = key;
|
||||||
|
if (key === "plus") {
|
||||||
|
formState.usePlus = true;
|
||||||
|
} else {
|
||||||
|
formState.usePlus = false;
|
||||||
|
}
|
||||||
|
await onFinish(formState);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TestFormState {
|
interface TestFormState {
|
||||||
@@ -157,12 +241,21 @@ const settingStore = useSettingStore();
|
|||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.page-setting-email {
|
.page-setting-email {
|
||||||
|
.ant-tabs-nav {
|
||||||
|
margin-left: 80px;
|
||||||
|
}
|
||||||
.email-form-box {
|
.email-form-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 20px 0px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.email-form {
|
.email-form {
|
||||||
width: 500px;
|
width: 700px;
|
||||||
|
max-width: 100%;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,5 +265,10 @@ const settingStore = useSettingStore();
|
|||||||
color: #999;
|
color: #999;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
.addon-selector {
|
||||||
|
.inner {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Rule, RuleType } from "@midwayjs/validate";
|
|
||||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
|
||||||
import { BaseController, Constants, SysSettingsService } from "@certd/lib-server";
|
import { BaseController, Constants, SysSettingsService } from "@certd/lib-server";
|
||||||
|
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
||||||
|
import { Rule, RuleType } from "@midwayjs/validate";
|
||||||
|
import { CaptchaService } from "../../modules/basic/service/captcha-service.js";
|
||||||
import { CodeService } from "../../modules/basic/service/code-service.js";
|
import { CodeService } from "../../modules/basic/service/code-service.js";
|
||||||
import { EmailService } from "../../modules/basic/service/email-service.js";
|
import { EmailService } from "../../modules/basic/service/email-service.js";
|
||||||
import { CaptchaService } from "../../modules/basic/service/captcha-service.js";
|
import { AddonGetterService } from "../../modules/pipeline/service/addon-getter-service.js";
|
||||||
|
|
||||||
export class SmsCodeReq {
|
export class SmsCodeReq {
|
||||||
@Rule(RuleType.string().required())
|
@Rule(RuleType.string().required())
|
||||||
@@ -49,6 +50,9 @@ export class BasicController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
captchaService: CaptchaService;
|
captchaService: CaptchaService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
addonGetterService: AddonGetterService;
|
||||||
|
|
||||||
@Post('/captcha/get', { summary: Constants.per.guest })
|
@Post('/captcha/get', { summary: Constants.per.guest })
|
||||||
async getCaptcha(@Query("captchaAddonId") captchaAddonId:number) {
|
async getCaptcha(@Query("captchaAddonId") captchaAddonId:number) {
|
||||||
const form = await this.captchaService.getCaptcha(captchaAddonId)
|
const form = await this.captchaService.getCaptcha(captchaAddonId)
|
||||||
@@ -83,17 +87,18 @@ export class BasicController extends BaseController {
|
|||||||
const opts = {
|
const opts = {
|
||||||
verificationType: body.verificationType,
|
verificationType: body.verificationType,
|
||||||
verificationCodeLength: undefined,
|
verificationCodeLength: undefined,
|
||||||
title: undefined,
|
|
||||||
content: undefined,
|
|
||||||
duration: undefined,
|
duration: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if(body?.verificationType === 'forgotPassword') {
|
if(body?.verificationType === 'forgotPassword') {
|
||||||
opts.title = '找回密码';
|
|
||||||
opts.content = '验证码:${code}。您正在找回密码,请输入验证码并完成操作。如非本人操作请忽略';
|
|
||||||
opts.duration = FORGOT_PASSWORD_CODE_DURATION;
|
opts.duration = FORGOT_PASSWORD_CODE_DURATION;
|
||||||
opts.verificationCodeLength = 6;
|
opts.verificationCodeLength = 6;
|
||||||
|
}else{
|
||||||
|
opts.duration = 10;
|
||||||
|
opts.verificationCodeLength = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await this.codeService.checkCaptcha(body.captcha);
|
await this.codeService.checkCaptcha(body.captcha);
|
||||||
await this.codeService.sendEmailCode(body.email, opts);
|
await this.codeService.sendEmailCode(body.email, opts);
|
||||||
// 设置缓存内容
|
// 设置缓存内容
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
||||||
import {
|
import {
|
||||||
addonRegistry,
|
addonRegistry,
|
||||||
|
AddonService,
|
||||||
CrudController,
|
CrudController,
|
||||||
SysPrivateSettings,
|
SysPrivateSettings,
|
||||||
SysPublicSettings,
|
SysPublicSettings,
|
||||||
@@ -30,6 +31,8 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
|||||||
pipelineService: PipelineService;
|
pipelineService: PipelineService;
|
||||||
@Inject()
|
@Inject()
|
||||||
codeService: CodeService;
|
codeService: CodeService;
|
||||||
|
@Inject()
|
||||||
|
addonService: AddonService;
|
||||||
|
|
||||||
getService() {
|
getService() {
|
||||||
return this.service;
|
return this.service;
|
||||||
@@ -86,6 +89,25 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
|||||||
return this.ok(conf);
|
return this.ok(conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/getEmailTemplates', { summary: 'sys:settings:view' })
|
||||||
|
async getEmailTemplates(@Body(ALL) body) {
|
||||||
|
const conf = await getEmailSettings(this.service, this.userSettingsService);
|
||||||
|
const templates = conf.templates || {}
|
||||||
|
|
||||||
|
const emailTemplateProviders = await this.addonService.getDefineList("emailTemplate")
|
||||||
|
|
||||||
|
const proviers = []
|
||||||
|
for (const item of emailTemplateProviders) {
|
||||||
|
const templateConf = templates[item.name] || {}
|
||||||
|
proviers.push({
|
||||||
|
name: item.name,
|
||||||
|
title: item.title,
|
||||||
|
addonId : templateConf.addonId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return this.ok(proviers);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/saveEmailSettings', { summary: 'sys:settings:edit' })
|
@Post('/saveEmailSettings', { summary: 'sys:settings:edit' })
|
||||||
async saveEmailSettings(@Body(ALL) body) {
|
async saveEmailSettings(@Body(ALL) body) {
|
||||||
const conf = await getEmailSettings(this.service, this.userSettingsService);
|
const conf = await getEmailSettings(this.service, this.userSettingsService);
|
||||||
|
|||||||
@@ -18,24 +18,24 @@ export class EmailController extends BaseController {
|
|||||||
return this.ok({});
|
return this.ok({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/list', { summary: Constants.per.authOnly })
|
// @Post('/list', { summary: Constants.per.authOnly })
|
||||||
public async list() {
|
// public async list() {
|
||||||
const userId = super.getUserId();
|
// const userId = super.getUserId();
|
||||||
const res = await this.emailService.list(userId);
|
// const res = await this.emailService.list(userId);
|
||||||
return this.ok(res);
|
// return this.ok(res);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Post('/add', { summary: Constants.per.authOnly })
|
// @Post('/add', { summary: Constants.per.authOnly })
|
||||||
public async add(@Body('email') email) {
|
// public async add(@Body('email') email) {
|
||||||
const userId = super.getUserId();
|
// const userId = super.getUserId();
|
||||||
await this.emailService.add(userId, email);
|
// await this.emailService.add(userId, email);
|
||||||
return this.ok({});
|
// return this.ok({});
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Post('/delete', { summary: Constants.per.authOnly })
|
// @Post('/delete', { summary: Constants.per.authOnly })
|
||||||
public async delete(@Body('email') email) {
|
// public async delete(@Body('email') email) {
|
||||||
const userId = super.getUserId();
|
// const userId = super.getUserId();
|
||||||
await this.emailService.delete(userId, email);
|
// await this.emailService.delete(userId, email);
|
||||||
return this.ok({});
|
// return this.ok({});
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,8 @@ export class AutoCRegisterCron {
|
|||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
errorMessage:title,
|
errorMessage:title,
|
||||||
url
|
url,
|
||||||
|
notificationType: "vipExpireRemind",
|
||||||
}
|
}
|
||||||
},adminUser.id)
|
},adminUser.id)
|
||||||
}
|
}
|
||||||
@@ -182,7 +183,8 @@ export class AutoCRegisterCron {
|
|||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
errorMessage: title,
|
errorMessage: title,
|
||||||
url
|
url,
|
||||||
|
notificationType: "userExpireRemind",
|
||||||
}
|
}
|
||||||
}, user.id)
|
}, user.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
|
||||||
import { cache, isDev, randomNumber, simpleNanoId } from '@certd/basic';
|
import { cache, isDev, randomNumber, simpleNanoId } from '@certd/basic';
|
||||||
import { SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
import { AccessService, AccessSysGetter, CodeErrorException, SysSettingsService } from '@certd/lib-server';
|
||||||
import { SmsServiceFactory } from '../sms/factory.js';
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import { ISmsService } from '../sms/api.js';
|
import { ISmsService } from '../sms/api.js';
|
||||||
import { CodeErrorException } from '@certd/lib-server';
|
import { SmsServiceFactory } from '../sms/factory.js';
|
||||||
import { EmailService } from './email-service.js';
|
|
||||||
import { AccessService } from '@certd/lib-server';
|
|
||||||
import { AccessSysGetter } from '@certd/lib-server';
|
|
||||||
import { isComm } from '@certd/plus-core';
|
|
||||||
import { CaptchaService } from "./captcha-service.js";
|
import { CaptchaService } from "./captcha-service.js";
|
||||||
|
import { EmailService } from './email-service.js';
|
||||||
|
|
||||||
// {data: '<svg.../svg>', text: 'abcd'}
|
// {data: '<svg.../svg>', text: 'abcd'}
|
||||||
/**
|
/**
|
||||||
@@ -84,8 +80,6 @@ export class CodeService {
|
|||||||
async sendEmailCode(
|
async sendEmailCode(
|
||||||
email: string,
|
email: string,
|
||||||
opts?: {
|
opts?: {
|
||||||
title?: string,
|
|
||||||
content?: string,
|
|
||||||
duration?: number,
|
duration?: number,
|
||||||
verificationType?: string,
|
verificationType?: string,
|
||||||
verificationCodeLength?: number,
|
verificationCodeLength?: number,
|
||||||
@@ -96,32 +90,27 @@ export class CodeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let siteTitle = 'Certd';
|
|
||||||
if (isComm()) {
|
|
||||||
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
|
||||||
if (siteInfo) {
|
|
||||||
siteTitle = siteInfo.title || siteTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 duration = Math.floor(Math.max(Math.min(opts?.duration || 5, 15), 1));
|
||||||
|
|
||||||
const code = randomNumber(verificationCodeLength);
|
const code = randomNumber(verificationCodeLength);
|
||||||
|
|
||||||
const templateData = {
|
const templateData = {
|
||||||
code, duration, siteTitle
|
code, duration,
|
||||||
|
title: "验证码",
|
||||||
|
content:`您的验证码是${code},请勿泄露`,
|
||||||
|
notificationType: "registerCode"
|
||||||
}
|
}
|
||||||
|
if (opts?.verificationType === 'forgotPassword') {
|
||||||
const titleTemplate = opts?.title?
|
templateData.title = '找回密码';
|
||||||
|
templateData.notificationType = "forgotPassword"
|
||||||
const title = `【${siteTitle}】${!!opts?.title ? opts.title : '验证码'}`;
|
}
|
||||||
const content = !!opts.content ? this.compile(opts.content)(templateData) : `您的验证码是${code},请勿泄露`;
|
await this.emailService.sendByTemplate({
|
||||||
|
type: templateData.notificationType,
|
||||||
await this.emailService.send({
|
data: templateData,
|
||||||
subject: title,
|
email:{
|
||||||
content: content,
|
receivers: [email],
|
||||||
receivers: [email],
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const key = this.buildEmailCodeKey(email,opts?.verificationType);
|
const key = this.buildEmailCodeKey(email,opts?.verificationType);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import type { EmailSend } from '@certd/pipeline';
|
import type { EmailSend, EmailSendByTemplateReq } from '@certd/pipeline';
|
||||||
import { IEmailService } from '@certd/pipeline';
|
import { IEmailService } from '@certd/pipeline';
|
||||||
|
|
||||||
import { logger } from '@certd/basic';
|
import { logger } from '@certd/basic';
|
||||||
@@ -8,9 +8,11 @@ import { isComm, isPlus } from '@certd/plus-core';
|
|||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import { SendMailOptions } from 'nodemailer';
|
import { SendMailOptions } from 'nodemailer';
|
||||||
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
|
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
|
||||||
import { PlusService, SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
import { AddonService, PlusService, SysEmailConf, SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
||||||
import { getEmailSettings } from '../../sys/settings/fix.js';
|
import { getEmailSettings } from '../../sys/settings/fix.js';
|
||||||
import { UserEmailSetting } from "../../mine/service/models.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';
|
||||||
|
|
||||||
export type EmailConfig = {
|
export type EmailConfig = {
|
||||||
host: string;
|
host: string;
|
||||||
@@ -38,6 +40,12 @@ export class EmailService implements IEmailService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
plusService: PlusService;
|
plusService: PlusService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
addonGetterService: AddonGetterService;
|
||||||
|
@Inject()
|
||||||
|
addonService: AddonService
|
||||||
|
|
||||||
|
|
||||||
async sendByPlus(email: EmailSend) {
|
async sendByPlus(email: EmailSend) {
|
||||||
if (!isPlus()) {
|
if (!isPlus()) {
|
||||||
throw new Error('plus not enabled');
|
throw new Error('plus not enabled');
|
||||||
@@ -49,7 +57,6 @@ export class EmailService implements IEmailService {
|
|||||||
* content: string;
|
* content: string;
|
||||||
* receivers: string[];
|
* receivers: string[];
|
||||||
*/
|
*/
|
||||||
|
|
||||||
await this.plusService.sendEmail(email);
|
await this.plusService.sendEmail(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,26 +69,6 @@ export class EmailService implements IEmailService {
|
|||||||
throw new Error('收件人不能为空');
|
throw new Error('收件人不能为空');
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailConf = await getEmailSettings(this.sysSettingsService, this.settingsService);
|
|
||||||
|
|
||||||
if (!emailConf.host && emailConf.usePlus == null) {
|
|
||||||
if (isPlus()) {
|
|
||||||
//自动使用plus发邮件
|
|
||||||
return await this.sendByPlus(email);
|
|
||||||
}
|
|
||||||
throw new Error('邮件服务器还未设置');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emailConf.usePlus && isPlus()) {
|
|
||||||
return await this.sendByPlus(email);
|
|
||||||
}
|
|
||||||
await this.sendByCustom(emailConf, email);
|
|
||||||
logger.info('sendEmail complete: ', email);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendByCustom(emailConfig: EmailConfig, email: EmailSend) {
|
|
||||||
const transporter = nodemailer.createTransport(emailConfig);
|
|
||||||
|
|
||||||
let sysTitle = 'Certd';
|
let sysTitle = 'Certd';
|
||||||
if (isComm()) {
|
if (isComm()) {
|
||||||
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
||||||
@@ -93,10 +80,34 @@ export class EmailService implements IEmailService {
|
|||||||
if (!subject.includes(`【${sysTitle}】`)) {
|
if (!subject.includes(`【${sysTitle}】`)) {
|
||||||
subject = `【${sysTitle}】${subject}`;
|
subject = `【${sysTitle}】${subject}`;
|
||||||
}
|
}
|
||||||
|
email.subject = subject;
|
||||||
|
|
||||||
|
const emailConf = await getEmailSettings(this.sysSettingsService, this.settingsService);
|
||||||
|
|
||||||
|
if (!emailConf.host && emailConf.usePlus == null) {
|
||||||
|
if (isPlus()) {
|
||||||
|
//自动使用plus发邮件
|
||||||
|
return await this.sendByPlus(email);
|
||||||
|
}
|
||||||
|
throw new Error('邮件服务器还未设置');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (emailConf.usePlus && isPlus()) {
|
||||||
|
return await this.sendByPlus(email);
|
||||||
|
}
|
||||||
|
await this.sendByCustom(emailConf, email, sysTitle);
|
||||||
|
logger.info('sendEmail complete: ', email);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendByCustom(emailConfig: EmailConfig, email: EmailSend, sysTitle: string) {
|
||||||
|
const transporter = nodemailer.createTransport(emailConfig);
|
||||||
|
|
||||||
const mailOptions = {
|
const mailOptions = {
|
||||||
from: `${sysTitle} <${emailConfig.sender}>`,
|
from: `${sysTitle} <${emailConfig.sender}>`,
|
||||||
to: email.receivers.join(', '), // list of receivers
|
to: email.receivers.join(', '), // list of receivers
|
||||||
subject: subject,
|
subject: email.subject,
|
||||||
text: email.content,
|
text: email.content,
|
||||||
html: email.html,
|
html: email.html,
|
||||||
attachments: email.attachments,
|
attachments: email.attachments,
|
||||||
@@ -105,30 +116,72 @@ export class EmailService implements IEmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async test(userId: number, receiver: string) {
|
async test(userId: number, receiver: string) {
|
||||||
await this.send({
|
await this.sendByTemplate({
|
||||||
receivers: [receiver],
|
type:"common",
|
||||||
subject: '测试邮件,from certd',
|
data:{
|
||||||
content: '测试邮件,from certd',
|
title: '测试邮件,from certd',
|
||||||
|
content: '测试邮件,from certd',
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
receivers: [receiver],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async list(userId: any) {
|
async list(userId: any) {
|
||||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,UserEmailSetting)
|
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, UserEmailSetting)
|
||||||
return userEmailSetting.list;
|
return userEmailSetting.list;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(userId: any, email: string) {
|
async delete(userId: any, email: string) {
|
||||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,UserEmailSetting)
|
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, UserEmailSetting)
|
||||||
userEmailSetting.list = userEmailSetting.list.filter(item=>item !== email);
|
userEmailSetting.list = userEmailSetting.list.filter(item => item !== email);
|
||||||
await this.settingsService.saveSetting(userId,userEmailSetting)
|
await this.settingsService.saveSetting(userId, userEmailSetting)
|
||||||
}
|
}
|
||||||
async add(userId: any, email: string) {
|
async add(userId: any, email: string) {
|
||||||
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId,UserEmailSetting)
|
const userEmailSetting = await this.settingsService.getSetting<UserEmailSetting>(userId, UserEmailSetting)
|
||||||
//如果已存在
|
//如果已存在
|
||||||
if(userEmailSetting.list.includes(email)){
|
if (userEmailSetting.list.includes(email)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userEmailSetting.list.unshift(email)
|
userEmailSetting.list.unshift(email)
|
||||||
await this.settingsService.saveSetting(userId,userEmailSetting)
|
await this.settingsService.saveSetting(userId, userEmailSetting)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async sendByTemplate(req: EmailSendByTemplateReq) {
|
||||||
|
const emailConf = await this.sysSettingsService.getSetting<SysEmailConf>(SysEmailConf);
|
||||||
|
|
||||||
|
const template = emailConf?.templates?.[req.type]
|
||||||
|
let content = null
|
||||||
|
if (template && template.addonId) {
|
||||||
|
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getAddonById(template.addonId, true, 0)
|
||||||
|
if (addon) {
|
||||||
|
content = await addon.buildContent({ data: req.data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!content) {
|
||||||
|
//看看有没有通用模版
|
||||||
|
if (emailConf?.templates?.common && emailConf?.templates?.common.addonId) {
|
||||||
|
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getAddonById(emailConf.templates.common.addonId, true, 0)
|
||||||
|
if (addon) {
|
||||||
|
content = await addon.buildContent({ data: req.data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 没有找到模版,使用默认模版
|
||||||
|
if (!content) {
|
||||||
|
try {
|
||||||
|
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getBlank(req.type, "默认")
|
||||||
|
content = await addon.buildDefaultContent({ data: req.data })
|
||||||
|
} catch (e) {
|
||||||
|
const addon: ITemplateProvider<EmailContent> = await this.addonGetterService.getBlank("common", "默认")
|
||||||
|
content = await addon.buildDefaultContent({ data: req.data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await this.send({
|
||||||
|
...req.email,
|
||||||
|
...content
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,7 +268,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
|||||||
url,
|
url,
|
||||||
title: `站点证书${fromIpCheck ? "(IP)" : ""}检查出错<${site.name}>`,
|
title: `站点证书${fromIpCheck ? "(IP)" : ""}检查出错<${site.name}>`,
|
||||||
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
|
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
|
||||||
errorMessage: site.error
|
errorMessage: site.error,
|
||||||
|
notificationType: "siteCheckError",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
site.userId
|
site.userId
|
||||||
@@ -295,7 +296,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
|||||||
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
|
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
|
||||||
content,
|
content,
|
||||||
url,
|
url,
|
||||||
errorMessage: "站点证书即将过期"
|
errorMessage: "站点证书即将过期",
|
||||||
|
notificationType: "siteCertExpireRemind",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
site.userId
|
site.userId
|
||||||
@@ -311,7 +313,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
|||||||
title: `站点证书已过期${-validDays}天<${site.name}>`,
|
title: `站点证书已过期${-validDays}天<${site.name}>`,
|
||||||
content,
|
content,
|
||||||
url,
|
url,
|
||||||
errorMessage: "站点证书已过期"
|
errorMessage: "站点证书已过期",
|
||||||
|
notificationType: "siteCertExpireRemind",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
site.userId
|
site.userId
|
||||||
|
|||||||
@@ -62,4 +62,11 @@ export class AddonGetterService {
|
|||||||
return await this.getAddonById(id, true, userId);
|
return await this.getAddonById(id, true, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getBlank(type:string,name:string){
|
||||||
|
return await this.getAddonById(null,false,0,{
|
||||||
|
type,name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ export * from './plugin-xinnet/index.js'
|
|||||||
export * from './plugin-xinnetconnet/index.js'
|
export * from './plugin-xinnetconnet/index.js'
|
||||||
export * from './plugin-oauth/index.js'
|
export * from './plugin-oauth/index.js'
|
||||||
export * from './plugin-cmcc/index.js'
|
export * from './plugin-cmcc/index.js'
|
||||||
|
export * from './plugin-template/index.js'
|
||||||
@@ -132,6 +132,7 @@ nohup sh -c '$RESTART_CERT' >/dev/null 2>&1 & echo '10秒后重启' && exit
|
|||||||
title: `${this.repoName} 新版本 ${this.lastVersion} 发布`,
|
title: `${this.repoName} 新版本 ${this.lastVersion} 发布`,
|
||||||
content: `${body}\n\n > [Certd](https://certd.docmirror.cn),不止证书自动化,插件解锁无限可能!\n\n`,
|
content: `${body}\n\n > [Certd](https://certd.docmirror.cn),不止证书自动化,插件解锁无限可能!\n\n`,
|
||||||
url: `https://github.com/${this.repoName}/releases/tag/${this.lastVersion}`,
|
url: `https://github.com/${this.repoName}/releases/tag/${this.lastVersion}`,
|
||||||
|
notificationType: "githubReleaseCheck",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,23 @@ export class EmailNotification extends BaseNotification {
|
|||||||
receivers!: string[];
|
receivers!: string[];
|
||||||
|
|
||||||
async send(body: NotificationBody) {
|
async send(body: NotificationBody) {
|
||||||
await this.ctx.emailService.send({
|
|
||||||
subject: body.title,
|
const templateData = {
|
||||||
content: body.content + '\n\n[查看详情](' + body.url + ')',
|
...body,
|
||||||
receivers: this.receivers,
|
}
|
||||||
});
|
await this.ctx.emailService.sendByTemplate({
|
||||||
|
type: body.notificationType,
|
||||||
|
data: templateData,
|
||||||
|
email: {
|
||||||
|
receivers: this.receivers,
|
||||||
|
attachments: body.attachments,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// await this.ctx.emailService.send({
|
||||||
|
// subject: body.title,
|
||||||
|
// content: body.content + '\n\n[查看详情](' + body.url + ')',
|
||||||
|
// receivers: this.receivers,
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,16 @@ export type BuildContentReq = {
|
|||||||
data: any;
|
data: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BuildContentReply = Record<string, string>;
|
|
||||||
|
|
||||||
export interface ITemplateProvider {
|
export interface ITemplateProvider<T=any> {
|
||||||
buildContent: (params: BuildContentReq) => Promise<BuildContentReply>;
|
buildContent: (params: BuildContentReq) => Promise<T>;
|
||||||
|
|
||||||
|
buildDefaultContent:(params: BuildContentReq) => Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type EmailContent = {
|
||||||
|
subject:string,
|
||||||
|
content?:string,
|
||||||
|
html?:string
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './plugin-common.js'
|
||||||
|
export * from './plugin-register-code.js'
|
||||||
|
export * from './plugin-forgot-password.js'
|
||||||
|
export * from './plugin-pipeline-result.js'
|
||||||
@@ -1,52 +1,87 @@
|
|||||||
import { AddonInput, BaseAddon } from "@certd/lib-server";
|
import { AddonInput, BaseAddon } from "@certd/lib-server";
|
||||||
import { BuildContentReply, BuildContentReq, ITemplateProvider } from "../api.js";
|
|
||||||
import { get } from "lodash-es";
|
import { get } from "lodash-es";
|
||||||
|
import { BuildContentReq, EmailContent, ITemplateProvider } from "../api.js";
|
||||||
|
|
||||||
|
export class BaseEmailTemplateProvider extends BaseAddon implements ITemplateProvider<EmailContent> {
|
||||||
export class BaseEmailTemplateProvider extends BaseAddon implements ITemplateProvider {
|
|
||||||
@AddonInput({
|
@AddonInput({
|
||||||
title: "配置说明",
|
title: "配置说明",
|
||||||
component:{
|
component: {
|
||||||
name:"a-alert",
|
name: "a-alert",
|
||||||
props:{
|
props: {
|
||||||
type:"info",
|
type: "info",
|
||||||
message:"在标题和内容模版中,通过${param}引用参数,例如: 感谢注册${siteTitle},您的注册验证码为:${code}",
|
message: "在标题和内容模版中,通过${param}引用参数,例如: 感谢注册${siteTitle},您的注册验证码为:${code}",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
order: 1,
|
order: 1,
|
||||||
col:{span:24},
|
col: { span: 24 },
|
||||||
})
|
})
|
||||||
useIntro = "";
|
useIntro = "";
|
||||||
|
|
||||||
|
|
||||||
|
@AddonInput({
|
||||||
|
title: "邮件格式",
|
||||||
|
component: {
|
||||||
|
name: "a-select",
|
||||||
|
props: {
|
||||||
|
options: [
|
||||||
|
{ label: "HTML", value: "html" },
|
||||||
|
{ label: "TEXT", value: "text" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
order: 1,
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
formatType = "";
|
||||||
|
|
||||||
@AddonInput({
|
@AddonInput({
|
||||||
title: "邮件标题模版",
|
title: "邮件标题模版",
|
||||||
required: true,
|
required: true,
|
||||||
order: 10,
|
order: 10,
|
||||||
|
component: {
|
||||||
|
name: "a-input",
|
||||||
|
props: {
|
||||||
|
placeholder: "邮件标题模版",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
col: { span: 24 },
|
||||||
})
|
})
|
||||||
titleTemplate = "";
|
titleTemplate = "";
|
||||||
|
|
||||||
@AddonInput({
|
@AddonInput({
|
||||||
title: "邮件内容模版",
|
title: "邮件内容模版",
|
||||||
component: {
|
component: {
|
||||||
placeholder: "邮件内容模版",
|
name: "a-textarea",
|
||||||
|
rows: 6,
|
||||||
},
|
},
|
||||||
order: 20,
|
order: 20,
|
||||||
|
col: { span: 24 },
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
contentTemplate = "";
|
contentTemplate = "";
|
||||||
|
|
||||||
|
|
||||||
async buildContent(params: BuildContentReq) : Promise<BuildContentReply>{
|
async buildContent(params: BuildContentReq): Promise<EmailContent> {
|
||||||
const title = this.compile(this.titleTemplate)(params.data)
|
const title = this.compile(this.titleTemplate)(params.data)
|
||||||
const content = this.compile(this.contentTemplate)(params.data)
|
const content = this.compile(this.contentTemplate)(params.data)
|
||||||
return {
|
|
||||||
title,
|
const body: any = {
|
||||||
content,
|
subject: title,
|
||||||
}
|
}
|
||||||
|
if (this.formatType === "html") {
|
||||||
|
body.html = content
|
||||||
|
} else {
|
||||||
|
body.content = content
|
||||||
|
}
|
||||||
|
return body
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async buildDefaultContent(params: BuildContentReq): Promise<EmailContent> {
|
||||||
|
throw new Error("请实现 buildDefaultContent 方法")
|
||||||
|
}
|
||||||
|
|
||||||
compile(templateString: string) {
|
compile(templateString: string) {
|
||||||
return function(data:any):string {
|
return function (data: any): string {
|
||||||
return templateString.replace(/\${(.*?)}/g, (match, key) => {
|
return templateString.replace(/\${(.*?)}/g, (match, key) => {
|
||||||
const value = get(data, key, '');
|
const value = get(data, key, '');
|
||||||
return String(value);
|
return String(value);
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { AddonInput, IsAddon } from "@certd/lib-server";
|
||||||
|
import { BuildContentReq, EmailContent, ITemplateProvider } from "../api.js";
|
||||||
|
import { BaseEmailTemplateProvider } from "./plugin-base.js";
|
||||||
|
|
||||||
|
@IsAddon({
|
||||||
|
addonType: "emailTemplate",
|
||||||
|
name: 'common',
|
||||||
|
title: '通用邮件模版',
|
||||||
|
desc: '通用邮件模版',
|
||||||
|
icon: "simple-icons:email:blue",
|
||||||
|
showTest: false,
|
||||||
|
})
|
||||||
|
export class CommonEmailTemplateProvider extends BaseEmailTemplateProvider implements ITemplateProvider<EmailContent> {
|
||||||
|
@AddonInput({
|
||||||
|
title: "可用参数",
|
||||||
|
component: {
|
||||||
|
name: "ParamsShow",
|
||||||
|
params:[
|
||||||
|
{labele:"标题",value:"title"},
|
||||||
|
{labele:"内容",value:"content"},
|
||||||
|
{labele:"URL",value:"url"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
order: 5,
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
paramIntro = "";
|
||||||
|
|
||||||
|
|
||||||
|
async buildDefaultContent(req:BuildContentReq) {
|
||||||
|
const defaultTemplate = new CommonEmailTemplateProvider()
|
||||||
|
defaultTemplate.titleTemplate = "${title}"
|
||||||
|
defaultTemplate.contentTemplate = "${content} \n\n 查看详情:${url}"
|
||||||
|
defaultTemplate.formatType = "text"
|
||||||
|
return await defaultTemplate.buildContent(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { AddonInput, IsAddon } from "@certd/lib-server";
|
||||||
|
import { BuildContentReq, EmailContent, ITemplateProvider } from "../api.js";
|
||||||
|
import { BaseEmailTemplateProvider } from "./plugin-base.js";
|
||||||
|
|
||||||
|
@IsAddon({
|
||||||
|
addonType: "emailTemplate",
|
||||||
|
name: 'forgotPassword',
|
||||||
|
title: '忘记密码邮件模版',
|
||||||
|
desc: '忘记密码邮件模版',
|
||||||
|
icon: "simple-icons:email:blue",
|
||||||
|
showTest: false,
|
||||||
|
})
|
||||||
|
export class ForgotPasswordEmailTemplateProvider extends BaseEmailTemplateProvider implements ITemplateProvider<EmailContent> {
|
||||||
|
@AddonInput({
|
||||||
|
title: "可用参数",
|
||||||
|
component: {
|
||||||
|
name: "ParamsShow",
|
||||||
|
params:[
|
||||||
|
{labele:"验证码",value:"code"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
order: 5,
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
paramIntro = "";
|
||||||
|
|
||||||
|
|
||||||
|
async buildDefaultContent(req:BuildContentReq) {
|
||||||
|
const defaultTemplate = new ForgotPasswordEmailTemplateProvider()
|
||||||
|
defaultTemplate.titleTemplate = "忘记密码"
|
||||||
|
defaultTemplate.contentTemplate = "您的验证码是:${code},请勿泄露"
|
||||||
|
defaultTemplate.formatType = "text"
|
||||||
|
return await defaultTemplate.buildContent(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { AddonInput, IsAddon } from "@certd/lib-server";
|
||||||
|
import { BuildContentReq, EmailContent, ITemplateProvider } from "../api.js";
|
||||||
|
import { BaseEmailTemplateProvider } from "./plugin-base.js";
|
||||||
|
|
||||||
|
@IsAddon({
|
||||||
|
addonType: "emailTemplate",
|
||||||
|
name: 'pipelineResult',
|
||||||
|
title: '流水线执行结果邮件模版',
|
||||||
|
desc: '流水线执行结果邮件模版',
|
||||||
|
icon: "simple-icons:email:blue",
|
||||||
|
showTest: false,
|
||||||
|
})
|
||||||
|
export class PipelineResultEmailTemplateProvider extends BaseEmailTemplateProvider implements ITemplateProvider<EmailContent> {
|
||||||
|
@AddonInput({
|
||||||
|
title: "可用参数",
|
||||||
|
component: {
|
||||||
|
name: "ParamsShow",
|
||||||
|
params:[
|
||||||
|
{labele:"运行结果",value:"pipelineResult"},
|
||||||
|
{labele:"流水线标题",value:"pipelineTitle"},
|
||||||
|
{labele:"流水线ID",value:"pipelineId"},
|
||||||
|
{labele:"运行Id",value:"historyId"},
|
||||||
|
{labele:"错误信息",value:"errors"},
|
||||||
|
{labele:"URL",value:"url"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
order: 5,
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
paramIntro = "";
|
||||||
|
|
||||||
|
|
||||||
|
async buildDefaultContent(req:BuildContentReq) {
|
||||||
|
const defaultTemplate = new PipelineResultEmailTemplateProvider()
|
||||||
|
|
||||||
|
const subject = "${result},${pipelineTitle}【${pipelineId}】";
|
||||||
|
const content = "流水线ID:${pipelineId},运行ID:${runtimeId} \n\n ${errors}";
|
||||||
|
defaultTemplate.titleTemplate = subject
|
||||||
|
defaultTemplate.contentTemplate = content
|
||||||
|
defaultTemplate.formatType = "text"
|
||||||
|
return await defaultTemplate.buildContent(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { AddonInput, IsAddon } from "@certd/lib-server";
|
||||||
|
import { BuildContentReq, EmailContent, ITemplateProvider } from "../api.js";
|
||||||
|
import { BaseEmailTemplateProvider } from "./plugin-base.js";
|
||||||
|
|
||||||
|
@IsAddon({
|
||||||
|
addonType: "emailTemplate",
|
||||||
|
name: 'registerCode',
|
||||||
|
title: '注册验证码邮件模版',
|
||||||
|
desc: '注册验证码邮件模版',
|
||||||
|
icon: "simple-icons:email:blue",
|
||||||
|
showTest: false,
|
||||||
|
})
|
||||||
|
export class RegisterCodeEmailTemplateProvider extends BaseEmailTemplateProvider implements ITemplateProvider<EmailContent> {
|
||||||
|
@AddonInput({
|
||||||
|
title: "可用参数",
|
||||||
|
component: {
|
||||||
|
name: "ParamsShow",
|
||||||
|
params:[
|
||||||
|
{labele:"验证码",value:"code"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
order: 5,
|
||||||
|
col: { span: 24 },
|
||||||
|
})
|
||||||
|
paramIntro = "";
|
||||||
|
|
||||||
|
|
||||||
|
async buildDefaultContent(req:BuildContentReq) {
|
||||||
|
const defaultTemplate = new RegisterCodeEmailTemplateProvider()
|
||||||
|
defaultTemplate.titleTemplate = "注册验证码"
|
||||||
|
defaultTemplate.contentTemplate = "您的注册验证码是:${code},请勿泄露"
|
||||||
|
defaultTemplate.formatType = "text"
|
||||||
|
return await defaultTemplate.buildContent(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { AddonInput, IsAddon } from "@certd/lib-server";
|
|
||||||
import { BaseEmailTemplateProvider } from "./plugin-base.js";
|
|
||||||
|
|
||||||
@IsAddon({
|
|
||||||
addonType: "emailTemplate",
|
|
||||||
name: 'register',
|
|
||||||
title: '注册邮件模版',
|
|
||||||
desc: '注册邮件模版',
|
|
||||||
icon:"simple-icons:gitee:red",
|
|
||||||
showTest: false,
|
|
||||||
})
|
|
||||||
export class RegisterEmailTemplateProvider extends BaseEmailTemplateProvider {
|
|
||||||
|
|
||||||
@AddonInput({
|
|
||||||
title: "可用参数",
|
|
||||||
component:{
|
|
||||||
name:"a-alert",
|
|
||||||
props:{
|
|
||||||
type:"info",
|
|
||||||
message:"站点名称:${siteTitle};注册验证码:${code};有效期:${duration}分钟",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
order: 5,
|
|
||||||
col:{span:24},
|
|
||||||
})
|
|
||||||
paramIntro = "";
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./email/index.js"
|
||||||
Reference in New Issue
Block a user