perf: 邮箱设置改为系统设置,普通用户无需配置发件邮箱

pull/213/head
xiaojunnuo 2024-10-11 02:54:42 +08:00
parent f23c4af2ad
commit 4244569211
16 changed files with 130 additions and 86 deletions

View File

@ -1,7 +1,7 @@
version: '3.3' # 兼容旧版docker-compose version: '3.3' # 兼容旧版docker-compose
services: services:
certd: certd:
# 镜像 # ↓↓↓↓↓ --- 镜像版本号,建议改成固定版本号 # 镜像 # ↓↓↓↓↓ ---- 镜像版本号,建议改成固定版本号
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
container_name: certd # 容器名 container_name: certd # 容器名
restart: unless-stopped # 自动重启 restart: unless-stopped # 自动重启

View File

@ -45,6 +45,26 @@ export class SysLicenseInfo extends BaseSettings {
license?: string; license?: string;
} }
export class SysEmailConf extends BaseSettings {
static __title__ = '邮箱配置';
static __key__ = 'sys.email';
static __access__ = 'private';
host: string;
port: number;
auth: {
user: string;
pass: string;
};
secure: boolean; // use TLS
tls: {
// do not fail on invalid certs
rejectUnauthorized: boolean;
};
sender: string;
usePlus?: boolean;
}
export class SysSiteInfo extends BaseSettings { export class SysSiteInfo extends BaseSettings {
static __title__ = '站点信息'; static __title__ = '站点信息';
static __key__ = 'sys.site'; static __key__ = 'sys.site';

View File

@ -83,6 +83,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
let newSetting: T = new type(); let newSetting: T = new type();
const savedSettings = await this.getSettingByKey(key); const savedSettings = await this.getSettingByKey(key);
newSetting = _.merge(newSetting, savedSettings); newSetting = _.merge(newSetting, savedSettings);
await this.saveSetting(newSetting);
await this.cache.set(cacheKey, newSetting); await this.cache.set(cacheKey, newSetting);
return newSetting; return newSetting;
} }
@ -95,6 +96,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
const entity = await this.getByKey(key); const entity = await this.getByKey(key);
if (entity) { if (entity) {
entity.setting = JSON.stringify(bean); entity.setting = JSON.stringify(bean);
entity.access = type.__access__;
await this.repository.save(entity); await this.repository.save(entity);
} else { } else {
const newEntity = new SysSettingsEntity(); const newEntity = new SysSettingsEntity();

View File

@ -15,9 +15,9 @@
<div class="fs-bootstrap__loading"></div> <div class="fs-bootstrap__loading"></div>
</div> </div>
<div class="fs-bootstrap__footer"> <div class="fs-bootstrap__footer">
<a href="https://github.com/certd/certd" target="_blank"> <!-- <a href="https://github.com/certd/certd" target="_blank">-->
https://github.com/certd/certd <!-- https://github.com/certd/certd-->
</a> <!-- </a>-->
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="layout-vip isPlus" @click="openUpgrade"> <div v-if="!settingStore.isComm" class="layout-vip isPlus" @click="openUpgrade">
<contextHolder /> <contextHolder />
<fs-icon icon="mingcute:vip-1-line" :title="text.title" /> <fs-icon icon="mingcute:vip-1-line" :title="text.title" />

View File

@ -32,7 +32,7 @@
<!-- Button--> <!-- Button-->
<!-- </button>--> <!-- </button>-->
<fs-menu <fs-menu
v-if="settingStore?.siteEnv?.agent?.enabled === false" v-if="settingStore?.siteEnv?.agent?.enabled === false || !settingStore.isComm"
class="header-menu" class="header-menu"
mode="horizontal" mode="horizontal"
:expand-selected="false" :expand-selected="false"
@ -67,19 +67,22 @@
</a-layout-content> </a-layout-content>
<a-layout-footer class="fs-framework-footer"> <a-layout-footer class="fs-framework-footer">
<div> <div>
<span>Powered by</span> <span v-if="!settingStore.isComm">
<a> handsfree.work </a> <span>Powered by</span>
<a> handsfree.work </a>
</span>
<template v-if="siteInfo.licenseTo">
<a-divider type="vertical" />
<a :href="siteInfo.licenseToUrl || ''">{{ siteInfo.licenseTo }}</a>
</template>
<template v-if="siteInfo.icpNo"> <template v-if="siteInfo.icpNo">
<a-divider type="vertical" /> <a-divider type="vertical" />
<span> <span>
<a href="https://beian.miit.gov.cn/" target="_blank">{{ siteInfo.icpNo }}</a> <a href="https://beian.miit.gov.cn/" target="_blank">{{ siteInfo.icpNo }}</a>
</span> </span>
</template> </template>
<template v-if="siteInfo.licenseTo">
<a-divider type="vertical" />
<a :href="siteInfo.licenseToUrl || ''">{{ siteInfo.licenseTo }}</a>
</template>
</div> </div>
<div>v{{ version }}</div> <div>v{{ version }}</div>

View File

@ -13,26 +13,24 @@
<router-view /> <router-view />
<div class="footer"> <div class="footer">
<!-- <div class="links">-->
<!-- <a href="_self">帮助</a>-->
<!-- <a href="_self">隐私</a>-->
<!-- <a href="_self">条款</a>-->
<!-- </div>-->
<div class="copyright"> <div class="copyright">
<span>Copyright</span> <span v-if="!settingStore.isComm">
<span>&copy;</span> <span>Copyright</span>
<span>{{ envRef.COPYRIGHT_YEAR }}</span> <span>&copy;</span>
<span> <span>{{ envRef.COPYRIGHT_YEAR }}</span>
<a :href="envRef.COPYRIGHT_URL" target="_blank">{{ envRef.COPYRIGHT_NAME }}</a> <span>
<a :href="envRef.COPYRIGHT_URL" target="_blank">{{ envRef.COPYRIGHT_NAME }}</a>
</span>
</span>
<span v-if="siteInfo.licenseTo">
<a-divider type="vertical" />
<a :href="siteInfo.licenseToUrl" target="_blank">{{ siteInfo.licenseTo }}</a>
</span> </span>
<span v-if="siteInfo.icpNo"> <span v-if="siteInfo.icpNo">
<a-divider type="vertical" /> <a-divider type="vertical" />
<a href="https://beian.miit.gov.cn/" target="_blank">{{ siteInfo.icpNo }}</a> <a href="https://beian.miit.gov.cn/" target="_blank">{{ siteInfo.icpNo }}</a>
</span> </span>
<span v-if="siteInfo.licenseTo">
<a-divider type="vertical" />
<a :href="siteInfo.licenseToUrl" target="_blank">{{ siteInfo.licenseTo }}</a>
</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -56,16 +56,16 @@ export const certdResources = [
auth: true auth: true
} }
}, },
{ // {
title: "邮箱设置", // title: "邮箱设置",
name: "EmailSetting", // name: "EmailSetting",
path: "/certd/settings/email", // path: "/certd/settings/email",
component: "/certd/settings/email-setting.vue", // component: "/certd/settings/email-setting.vue",
meta: { // meta: {
icon: "ion:mail-outline", // icon: "ion:mail-outline",
auth: true // auth: true
} // }
}, // },
{ {
title: "账号信息", title: "账号信息",
name: "UserProfile", name: "UserProfile",

View File

@ -77,6 +77,16 @@ export const sysResources = [
permission: "sys:settings:view" permission: "sys:settings:view"
} }
}, },
{
title: "邮箱设置",
name: "EmailSetting",
path: "/sys/settings/email",
component: "/sys/settings/email-setting.vue",
meta: {
icon: "ion:mail-outline",
auth: true
}
},
{ {
title: "站点个性化", title: "站点个性化",
name: "SiteSetting", name: "SiteSetting",
@ -91,6 +101,7 @@ export const sysResources = [
permission: "sys:settings:view" permission: "sys:settings:view"
} }
}, },
// { // {
// title: "商业版设置", // title: "商业版设置",
// name: "SysCommercial", // name: "SysCommercial",

View File

@ -1,26 +0,0 @@
import { request } from "/@/api/service";
const apiPrefix = "/user/settings";
export const SettingKeys = {
Email: "email"
};
export async function SettingsGet(key: string) {
return await request({
url: apiPrefix + "/get",
method: "post",
params: {
key
}
});
}
export async function SettingsSave(key: string, setting: any) {
await request({
url: apiPrefix + "/save",
method: "post",
data: {
key,
setting: JSON.stringify(setting)
}
});
}

View File

@ -1,4 +1,4 @@
import { request } from "/@/api/service"; import { request } from "/src/api/service";
const apiPrefix = "/basic/email"; const apiPrefix = "/basic/email";
export async function TestSend(receiver: string) { export async function TestSend(receiver: string) {

View File

@ -4,7 +4,8 @@ const apiPrefix = "/sys/settings";
export const SettingKeys = { export const SettingKeys = {
SysPublic: "sys.public", SysPublic: "sys.public",
SysPrivate: "sys.private" SysPrivate: "sys.private",
SysEmail: "sys.email"
}; };
export async function SettingsGet(key: string) { export async function SettingsGet(key: string) {
return await request({ return await request({
@ -27,6 +28,13 @@ export async function SettingsSave(key: string, setting: any) {
}); });
} }
export async function EmailSettingsGet() {
await request({
url: apiPrefix + "/getEmailSettings",
method: "post"
});
}
export async function PublicSettingsSave(setting: any) { export async function PublicSettingsSave(setting: any) {
await request({ await request({
url: apiPrefix + "/savePublicSettings", url: apiPrefix + "/savePublicSettings",

View File

@ -82,7 +82,7 @@ import * as api from "./api";
import { SettingKeys } from "./api"; import { SettingKeys } 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 "/@/store/modules/settings"; import { useSettingStore } from "/src/store/modules/settings";
defineOptions({ defineOptions({
name: "EmailSetting" name: "EmailSetting"
@ -114,19 +114,15 @@ const formState = reactive<Partial<FormState>>({
}); });
async function load() { async function load() {
const data: any = await api.SettingsGet(SettingKeys.Email); const data: any = await api.EmailSettingsGet();
if (!data?.setting) { _.merge(formState, data);
return;
}
const setting = JSON.parse(data.setting);
Object.assign(formState, setting);
} }
load(); load();
const onFinish = async (form: any) => { const onFinish = async (form: any) => {
console.log("Success:", form); console.log("Success:", form);
await api.SettingsSave(SettingKeys.Email, form); await api.SettingsSave(SettingKeys.SysEmail, form);
notification.success({ notification.success({
message: "保存成功" message: "保存成功"
}); });
@ -137,7 +133,7 @@ const onFinishFailed = (errorInfo: any) => {
}; };
async function onUsePlusChanged() { async function onUsePlusChanged() {
await api.SettingsSave(SettingKeys.Email, formState); await api.SettingsSave(SettingKeys.SysEmail, formState);
} }
interface TestFormState { interface TestFormState {

View File

@ -21,6 +21,7 @@ typeorm:
dataSource: dataSource:
default: default:
database: './data/db-comm.sqlite' database: './data/db-comm.sqlite'
plus: plus:
server: server:
baseUrls: ['https://api.ai.handsfree.work', 'https://api.ai.docmirror.cn'] baseUrls: ['https://api.ai.handsfree.work', 'https://api.ai.docmirror.cn']
@ -29,7 +30,7 @@ account:
server: server:
baseUrl: 'https://ai.handsfree.work/subject' baseUrl: 'https://ai.handsfree.work/subject'
#
#plus: #plus:
# server: # server:
# baseUrls: ['http://127.0.0.1:11007'] # baseUrls: ['http://127.0.0.1:11007']

View File

@ -1,11 +1,11 @@
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 } from '@certd/pipeline';
import { IEmailService, isPlus } from '@certd/pipeline'; import { IEmailService, isPlus, logger } from '@certd/pipeline';
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import type SMTPConnection from 'nodemailer/lib/smtp-connection'; import type SMTPConnection from 'nodemailer/lib/smtp-connection';
import { logger } from '@certd/pipeline';
import { UserSettingsService } from '../../mine/service/user-settings-service.js'; import { UserSettingsService } from '../../mine/service/user-settings-service.js';
import { PlusService } from '@certd/lib-server'; import { PlusService, SysEmailConf, SysSettingsService } from '@certd/lib-server';
import { merge } from 'lodash-es';
export type EmailConfig = { export type EmailConfig = {
host: string; host: string;
@ -27,6 +27,9 @@ export type EmailConfig = {
export class EmailService implements IEmailService { export class EmailService implements IEmailService {
@Inject() @Inject()
settingsService: UserSettingsService; settingsService: UserSettingsService;
@Inject()
sysSettingsService: SysSettingsService;
@Inject() @Inject()
plusService: PlusService; plusService: PlusService;
@ -55,21 +58,30 @@ export class EmailService implements IEmailService {
/** /**
*/ */
async send(email: EmailSend) { async send(email: EmailSend) {
console.log('sendEmail', email); logger.info('sendEmail', email);
const emailConfigEntity = await this.settingsService.getByKey('email', email.userId); const emailConf = await this.sysSettingsService.getSetting<SysEmailConf>(SysEmailConf);
if (emailConfigEntity == null || !emailConfigEntity.setting) { if (!emailConf.host && emailConf.usePlus != null) {
const emailConfigEntity = await this.settingsService.getByKey('email', 1);
if (emailConfigEntity) {
const emailConfig = JSON.parse(emailConfigEntity.setting) as EmailConfig;
merge(emailConf, emailConfig);
await this.sysSettingsService.saveSetting(emailConf);
}
}
if (!emailConf.host) {
if (isPlus()) { if (isPlus()) {
//自动使用plus发邮件 //自动使用plus发邮件
return await this.sendByPlus(email); return await this.sendByPlus(email);
} }
throw new Error('email settings 未设置'); throw new Error('email settings 未设置');
} }
const emailConfig = JSON.parse(emailConfigEntity.setting) as EmailConfig;
if (emailConfig.usePlus && isPlus()) { if (emailConf.usePlus && isPlus()) {
return await this.sendByPlus(email); return await this.sendByPlus(email);
} }
await this.sendByCustom(emailConfig, email); await this.sendByCustom(emailConf, email);
logger.info('sendEmail complete: ', email); logger.info('sendEmail complete: ', email);
} }

View File

@ -1,10 +1,11 @@
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core'; import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
import { CrudController } from '@certd/lib-server'; import { CrudController, SysEmailConf } from '@certd/lib-server';
import { SysSettingsService } from '@certd/lib-server'; import { SysSettingsService } from '@certd/lib-server';
import { SysSettingsEntity } from '../entity/sys-settings.js'; import { SysSettingsEntity } from '../entity/sys-settings.js';
import { SysPublicSettings } from '@certd/lib-server'; import { SysPublicSettings } from '@certd/lib-server';
import * as _ from 'lodash-es'; import * as _ from 'lodash-es';
import { PipelineService } from '../../../pipeline/service/pipeline-service.js'; import { PipelineService } from '../../../pipeline/service/pipeline-service.js';
import { UserSettingsService } from '../../../mine/service/user-settings-service.js';
/** /**
*/ */
@ -14,6 +15,8 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
@Inject() @Inject()
service: SysSettingsService; service: SysSettingsService;
@Inject() @Inject()
userSettingsService: UserSettingsService;
@Inject()
pipelineService: PipelineService; pipelineService: PipelineService;
getService() { getService() {
@ -68,6 +71,22 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
return this.ok(entity); return this.ok(entity);
} }
// savePublicSettings
@Post('/getEmailSettings', { summary: 'sys:settings:edit' })
async getEmailSettings(@Body(ALL) body) {
let conf = await this.service.getSetting<SysEmailConf>(SysEmailConf);
if (!conf.host && conf.usePlus != null) {
//到userSetting里面去找
const adminEmailSetting = await this.userSettingsService.getByKey('email', 1);
if (adminEmailSetting) {
const setting = JSON.parse(adminEmailSetting.setting);
conf = _.merge(conf, setting);
await this.service.saveSetting(conf);
}
}
return this.ok(conf);
}
// savePublicSettings // savePublicSettings
@Post('/savePublicSettings', { summary: 'sys:settings:edit' }) @Post('/savePublicSettings', { summary: 'sys:settings:edit' })
async savePublicSettings(@Body(ALL) body) { async savePublicSettings(@Body(ALL) body) {