perf: 优化用户体验,首次访问时弹出邮箱账号绑定用以初始化账号

This commit is contained in:
xiaojunnuo
2026-07-05 01:14:48 +08:00
parent 396670dc8f
commit 608cc2a81f
10 changed files with 319 additions and 144 deletions
+1 -1
View File
@@ -102,7 +102,7 @@ Certd 是可私有化部署的 SSL/TLS 证书自动化管理平台,提供 Web
- 只有需要事务传播时才定义 `ctx`;普通查询、纯函数和简单私有方法继续使用明确参数。 - 只有需要事务传播时才定义 `ctx`;普通查询、纯函数和简单私有方法继续使用明确参数。
- 需要按事务上下文取 Repository 时,用 `BaseService.getRepo(ctx, EntityClass)` - 需要按事务上下文取 Repository 时,用 `BaseService.getRepo(ctx, EntityClass)`
- 需要“有事务则复用、无事务则开启”时,用 `BaseService.transactionWithCtx(ctx, callback)` - 需要“有事务则复用、无事务则开启”时,用 `BaseService.transactionWithCtx(ctx, callback)`
- 拼接可选 `projectId` 查询条件时,用 `BaseService.buildUserProjectQuery(userId, projectId)`;不要直接写 `{ userId, projectId }` - 拼接可选 `projectId` 查询条件时,**必须**使`BaseService.buildUserProjectQuery(userId, projectId)`,禁止直接写 `{ userId, projectId }`因为 `projectId` 可能为 `null`/`undefined`,直接放入查询会生成错误的 `WHERE projectId = NULL` 条件。
- `ctx` 类型复用 `BaseService` 导出的 `ServiceContext` - `ctx` 类型复用 `BaseService` 导出的 `ServiceContext`
- 新增 service 方法避免与 `BaseService` 方法签名冲突,例如不要用 `delete(id)` 覆盖 `delete(ids, where?)`;改用 `deleteById` 等具体名称。 - 新增 service 方法避免与 `BaseService` 方法签名冲突,例如不要用 `delete(id)` 覆盖 `delete(ids, where?)`;改用 `deleteById` 等具体名称。
@@ -1,9 +1,8 @@
import { ApplicationContext, Inject } from '@midwayjs/core'; import { ApplicationContext, Inject } from "@midwayjs/core";
import type {IMidwayContainer} from '@midwayjs/core'; import type { IMidwayContainer } from "@midwayjs/core";
import * as koa from '@midwayjs/koa'; import * as koa from "@midwayjs/koa";
import { Constants } from './constants.js'; import { Constants } from "./constants.js";
import { isEnterprise } from './mode.js'; import { isEnterprise } from "./mode.js";
export abstract class BaseController { export abstract class BaseController {
@Inject() @Inject()
@@ -41,7 +40,7 @@ export abstract class BaseController {
getUserId() { getUserId() {
const userId = this.ctx.user?.id; const userId = this.ctx.user?.id;
if (userId == null) { if (userId == null) {
throw new Error('Token已过期'); throw new Error("Token已过期");
} }
return userId; return userId;
} }
@@ -49,7 +48,7 @@ export abstract class BaseController {
getLoginUser() { getLoginUser() {
const user = this.ctx.user; const user = this.ctx.user;
if (user == null) { if (user == null) {
throw new Error('Token已过期'); throw new Error("Token已过期");
} }
return user; return user;
} }
@@ -61,73 +60,71 @@ export abstract class BaseController {
} }
} }
async getProjectId(permission:string) { async getProjectId(permission: string) {
if (!isEnterprise()) { if (!isEnterprise()) {
return undefined return undefined;
} }
let projectIdStr = this.ctx.headers["project-id"] as string; let projectIdStr = this.ctx.headers["project-id"] as string;
if (!projectIdStr){ if (!projectIdStr) {
projectIdStr = this.ctx.request.query["projectId"] as string; projectIdStr = this.ctx.request.query["projectId"] as string;
} }
if (!projectIdStr) { if (!projectIdStr) {
//这里必须抛异常,否则可能会有权限问题 //这里必须抛异常,否则可能会有权限问题
throw new Error("projectId 不能为空") throw new Error("projectId 不能为空");
} }
const userId = this.getUserId() const userId = this.getUserId();
const projectId = parseInt(projectIdStr) const projectId = parseInt(projectIdStr);
await this.checkProjectPermission(userId, projectId,permission) await this.checkProjectPermission(userId, projectId, permission);
return projectId; return projectId;
} }
async getProjectUserId(permission:string){ async getProjectUserId(permission: string) {
let userId = this.getUserId() let userId = this.getUserId();
const projectId = await this.getProjectId(permission) const projectId = await this.getProjectId(permission);
if(projectId){ if (projectId) {
userId = -1 // 企业管理模式下,用户id固定-1 userId = -1; // 企业管理模式下,用户id固定-1
} }
return { return {
projectId,userId projectId,
} userId,
};
} }
async getProjectUserIdRead(){ async getProjectUserIdRead() {
return await this.getProjectUserId("read") return await this.getProjectUserId("read");
} }
async getProjectUserIdWrite(){ async getProjectUserIdWrite() {
return await this.getProjectUserId("write") return await this.getProjectUserId("write");
} }
async getProjectUserIdAdmin(){ async getProjectUserIdAdmin() {
return await this.getProjectUserId("admin") return await this.getProjectUserId("admin");
} }
async checkProjectPermission(userId: number, projectId: number,permission:string) { async checkProjectPermission(userId: number, projectId: number, permission: string) {
const projectService:any = await this.applicationContext.getAsync("projectService"); const projectService: any = await this.applicationContext.getAsync("projectService");
await projectService.checkPermission({userId,projectId,permission}) await projectService.checkPermission({ userId, projectId, permission });
} }
/** /**
* *
* @param service 检查记录是否属于某用户或某项目 * @param service 检查记录是否属于某用户或某项目
* @param id * @param id
*/ */
async checkOwner(service:any,id:number,permission:string,allowAdmin:boolean = false){ async checkOwner(service: any, id: number, permission: string, allowAdmin: boolean = false) {
let { projectId,userId } = await this.getProjectUserId(permission) const { projectId, userId } = await this.getProjectUserId(permission);
const authService:any = await this.applicationContext.getAsync("authService"); const authService: any = await this.applicationContext.getAsync("authService");
if (projectId) { if (projectId) {
await authService.checkProjectId(service, id, projectId); await authService.checkProjectId(service, id, projectId);
}else{ } else {
if (userId === Constants.systemUserId) {
if(userId === Constants.systemUserId){
//系统级别,不检查权限 //系统级别,不检查权限
}else{ } else {
if(allowAdmin){ if (allowAdmin) {
await authService.checkUserIdButAllowAdmin(this.ctx, service, id); await authService.checkUserIdButAllowAdmin(this.ctx, service, id);
}else{ } else {
await authService.checkUserId( service, id, userId); await authService.checkUserId(service, id, userId);
} }
} }
} }
return {projectId,userId} return { projectId, userId };
} }
} }
@@ -56,7 +56,7 @@ export abstract class BaseService<T> {
return dataSource.getRepository(entity); return dataSource.getRepository(entity);
} }
protected buildUserProjectQuery(userId: number, projectId?: number) { public buildUserProjectQuery(userId: number, projectId?: number) {
const query: { userId: number; projectId?: number; [key: string]: any } = { const query: { userId: number; projectId?: number; [key: string]: any } = {
userId, userId,
}; };
@@ -1,33 +1,33 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
/** /**
*/ */
@Entity('sys_settings') @Entity("sys_settings")
export class SysSettingsEntity { export class SysSettingsEntity {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;
@Column({ comment: 'key', length: 100 }) @Column({ comment: "key", length: 100 })
key: string; key: string;
@Column({ comment: '名称', length: 100 }) @Column({ comment: "名称", length: 100 })
title: string; title: string;
@Column({ name: 'setting', comment: '设置', length: 1024, nullable: true }) @Column({ name: "setting", comment: "设置", length: 1024, nullable: true })
setting: string; setting: string;
// public 公开读,私有写, private 私有读,私有写 // public 公开读,私有写, private 私有读,私有写
@Column({ name: 'access', comment: '访问权限' }) @Column({ name: "access", comment: "访问权限" })
access: string; access: string;
@Column({ @Column({
name: 'create_time', name: "create_time",
comment: '创建时间', comment: "创建时间",
default: () => 'CURRENT_TIMESTAMP', default: () => "CURRENT_TIMESTAMP",
}) })
createTime: Date; createTime: Date;
@Column({ @Column({
name: 'update_time', name: "update_time",
comment: '修改时间', comment: "修改时间",
default: () => 'CURRENT_TIMESTAMP', default: () => "CURRENT_TIMESTAMP",
}) })
updateTime: Date; updateTime: Date;
} }
@@ -1,19 +1,19 @@
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from "lodash-es";
export class BaseSettings { export class BaseSettings {
static __key__: string; static __key__: string;
static __title__: string; static __title__: string;
static __access__ = 'private'; static __access__ = "private";
static getCacheKey() { static getCacheKey() {
return 'settings.' + this.__key__; return "settings." + this.__key__;
} }
} }
export class SysPublicSettings extends BaseSettings { export class SysPublicSettings extends BaseSettings {
static __key__ = 'sys.public'; static __key__ = "sys.public";
static __title__ = '系统公共设置'; static __title__ = "系统公共设置";
static __access__ = 'public'; static __access__ = "public";
registerEnabled = false; registerEnabled = false;
userValidTimeEnabled?: boolean = false; userValidTimeEnabled?: boolean = false;
@@ -34,19 +34,15 @@ export class SysPublicSettings extends BaseSettings {
aiChatEnabled = true; aiChatEnabled = true;
homePageEnabled = true; homePageEnabled = true;
//验证码是否开启 //验证码是否开启
captchaEnabled = false; captchaEnabled = false;
//验证码类型 //验证码类型
captchaType?: string; captchaType?: string;
captchaAddonId?: number; captchaAddonId?: number;
//流水线是否启用有效期 //流水线是否启用有效期
pipelineValidTimeEnabled?: boolean = false; pipelineValidTimeEnabled?: boolean = false;
//证书域名添加到监控 //证书域名添加到监控
certDomainAddToMonitorEnabled?: boolean = false; certDomainAddToMonitorEnabled?: boolean = false;
@@ -60,12 +56,15 @@ export class SysPublicSettings extends BaseSettings {
// 第三方OAuth配置 // 第三方OAuth配置
oauthEnabled?: boolean = false; oauthEnabled?: boolean = false;
oauthProviders: Record<string, { oauthProviders: Record<
type: string; string,
title: string; {
addonId: number; type: string;
icon?: string; title: string;
}> = {}; addonId: number;
icon?: string;
}
> = {};
notice?: string; notice?: string;
@@ -73,40 +72,37 @@ export class SysPublicSettings extends BaseSettings {
} }
export class SysPrivateSettings extends BaseSettings { export class SysPrivateSettings extends BaseSettings {
static __title__ = '系统私有设置'; static __title__ = "系统私有设置";
static __access__ = 'private'; static __access__ = "private";
static __key__ = 'sys.private'; static __key__ = "sys.private";
jwtKey?: string; jwtKey?: string;
encryptSecret?: string; encryptSecret?: string;
httpsProxy? = ''; httpsProxy? = "";
httpProxy? = ''; httpProxy? = "";
noProxy? = ''; noProxy? = "";
commonHeaders?: string = ''; commonHeaders?: string = "";
reverseProxies?: Record<string, string> = {}; reverseProxies?: Record<string, string> = {};
dnsResultOrder? = ''; dnsResultOrder? = "";
commonCnameEnabled?: boolean = true; commonCnameEnabled?: boolean = true;
httpRequestTimeout?: number = 30; httpRequestTimeout?: number = 30;
pipelineMaxRunningCount?: number; pipelineMaxRunningCount?: number;
environmentVars?: string = "";
environmentVars?: string = '';
acmeWalkFromAuthoritative?: boolean = true; acmeWalkFromAuthoritative?: boolean = true;
sms?: { sms?: {
type?: string; type?: string;
config?: any; config?: any;
} = { } = {
type: 'aliyun', type: "aliyun",
config: {}, config: {},
}; };
removeSecret() { removeSecret() {
const clone = cloneDeep(this); const clone = cloneDeep(this);
@@ -117,9 +113,9 @@ export class SysPrivateSettings extends BaseSettings {
} }
export class SysInstallInfo extends BaseSettings { export class SysInstallInfo extends BaseSettings {
static __title__ = '系统安装信息'; static __title__ = "系统安装信息";
static __key__ = 'sys.install'; static __key__ = "sys.install";
static __access__ = 'private'; static __access__ = "private";
installTime?: number; installTime?: number;
siteId?: string; siteId?: string;
bindUserId?: number; bindUserId?: number;
@@ -130,21 +126,20 @@ export class SysInstallInfo extends BaseSettings {
} }
export class SysLicenseInfo extends BaseSettings { export class SysLicenseInfo extends BaseSettings {
static __title__ = '授权许可信息'; static __title__ = "授权许可信息";
static __key__ = 'sys.license'; static __key__ = "sys.license";
static __access__ = 'private'; static __access__ = "private";
license?: string; license?: string;
} }
export type EmailTemplate = { export type EmailTemplate = {
addonId?: number; addonId?: number;
} };
export class SysEmailConf extends BaseSettings { export class SysEmailConf extends BaseSettings {
static __title__ = '邮箱配置'; static __title__ = "邮箱配置";
static __key__ = 'sys.email'; static __key__ = "sys.email";
static __access__ = 'private'; static __access__ = "private";
host: string; host: string;
port: number; port: number;
@@ -160,18 +155,18 @@ export class SysEmailConf extends BaseSettings {
sender: string; sender: string;
usePlus?: boolean; usePlus?: boolean;
templates:{ templates: {
registerCode?: EmailTemplate, registerCode?: EmailTemplate;
forgotPassword?: EmailTemplate, forgotPassword?: EmailTemplate;
pipelineResult?: EmailTemplate, pipelineResult?: EmailTemplate;
common?: EmailTemplate, common?: EmailTemplate;
} };
} }
export class SysSiteInfo extends BaseSettings { export class SysSiteInfo extends BaseSettings {
static __title__ = '站点信息'; static __title__ = "站点信息";
static __key__ = 'sys.site'; static __key__ = "sys.site";
static __access__ = 'public'; static __access__ = "public";
title?: string; title?: string;
slogan?: string; slogan?: string;
logo?: string; logo?: string;
@@ -179,9 +174,9 @@ export class SysSiteInfo extends BaseSettings {
} }
export class SysSecretBackup extends BaseSettings { export class SysSecretBackup extends BaseSettings {
static __title__ = '密钥信息备份'; static __title__ = "密钥信息备份";
static __key__ = 'sys.secret.backup'; static __key__ = "sys.secret.backup";
static __access__ = 'private'; static __access__ = "private";
siteId?: string; siteId?: string;
encryptSecret?: string; encryptSecret?: string;
} }
@@ -190,9 +185,9 @@ export class SysSecretBackup extends BaseSettings {
* 不要修改 * 不要修改
*/ */
export class SysSecret extends BaseSettings { export class SysSecret extends BaseSettings {
static __title__ = '密钥信息'; static __title__ = "密钥信息";
static __key__ = 'sys.secret'; static __key__ = "sys.secret";
static __access__ = 'private'; static __access__ = "private";
siteId?: string; siteId?: string;
encryptSecret?: string; encryptSecret?: string;
} }
@@ -215,9 +210,9 @@ export type MenuItem = {
children?: MenuItem[]; children?: MenuItem[];
}; };
export class SysHeaderMenus extends BaseSettings { export class SysHeaderMenus extends BaseSettings {
static __title__ = '顶部菜单'; static __title__ = "顶部菜单";
static __key__ = 'sys.header.menus'; static __key__ = "sys.header.menus";
static __access__ = 'public'; static __access__ = "public";
menus: MenuItem[]; menus: MenuItem[];
} }
@@ -228,9 +223,9 @@ export type PaymentItem = {
}; };
export class SysPaymentSetting extends BaseSettings { export class SysPaymentSetting extends BaseSettings {
static __title__ = '支付设置'; static __title__ = "支付设置";
static __key__ = 'sys.payment'; static __key__ = "sys.payment";
static __access__ = 'private'; static __access__ = "private";
yizhifu?: PaymentItem = { enabled: false }; yizhifu?: PaymentItem = { enabled: false };
@@ -240,9 +235,9 @@ export class SysPaymentSetting extends BaseSettings {
} }
export class SysSuiteSetting extends BaseSettings { export class SysSuiteSetting extends BaseSettings {
static __title__ = '套餐设置'; static __title__ = "套餐设置";
static __key__ = 'sys.suite'; static __key__ = "sys.suite";
static __access__ = 'private'; static __access__ = "private";
enabled: boolean = false; enabled: boolean = false;
@@ -257,26 +252,25 @@ export class SysSuiteSetting extends BaseSettings {
} }
export class SysAutoFixSetting extends BaseSettings { export class SysAutoFixSetting extends BaseSettings {
static __title__ = '自动修复记录'; static __title__ = "自动修复记录";
static __key__ = 'sys.auto.fix'; static __key__ = "sys.auto.fix";
static __access__ = 'private'; static __access__ = "private";
fixed: Record<string, boolean> = {}; fixed: Record<string, boolean> = {};
} }
export type SiteHidden = { export type SiteHidden = {
enabled: boolean; enabled: boolean;
openPath?: string; openPath?: string;
//md5 hash 两次后保存 //md5 hash 两次后保存
openPassword?: string; openPassword?: string;
autoHiddenTimes?: number; autoHiddenTimes?: number;
hiddenOpenApi?: boolean hiddenOpenApi?: boolean;
}; };
export class SysSafeSetting extends BaseSettings { export class SysSafeSetting extends BaseSettings {
static __title__ = '站点安全设置'; static __title__ = "站点安全设置";
static __key__ = 'sys.safe'; static __key__ = "sys.safe";
static __access__ = 'private'; static __access__ = "private";
// 站点隐藏 // 站点隐藏
hidden: SiteHidden = { hidden: SiteHidden = {
@@ -31,6 +31,13 @@ export function useFormDialog() {
crudOptions: { crudOptions: {
columns: req.columns, columns: req.columns,
form: { form: {
labelCol: {
// @ts-ignore
span: null,
style: {
width: "100px",
},
},
initialForm: req.initialForm, initialForm: req.initialForm,
wrapper: warpper, wrapper: warpper,
async afterSubmit() {}, async afterSubmit() {},
@@ -44,7 +51,7 @@ export function useFormDialog() {
}; };
} }
const { crudOptions } = createCrudOptions(); const { crudOptions } = createCrudOptions();
await openCrudFormDialog({ crudOptions }); return await openCrudFormDialog({ crudOptions });
} }
return { return {
openFormDialog, openFormDialog,
@@ -18,6 +18,10 @@ defineProps<{
showButton: boolean; showButton: boolean;
}>(); }>();
const emit = defineEmits<{
(e: "close"): void;
}>();
let passwordFormRef = ref(); let passwordFormRef = ref();
type OpenOptions = { type OpenOptions = {
@@ -68,8 +72,8 @@ const passwordFormOptions: CrudOptions = {
}, },
async afterSubmit() { async afterSubmit() {
const formData = passwordFormRef.value?.getFormData?.(); const formData = passwordFormRef.value?.getFormData?.();
const message = formData?.init ? t("authentication.initPasswordSuccessMessage") : t("authentication.successMessage"); const msg = formData?.init ? t("authentication.initPasswordSuccessMessage") : t("authentication.successMessage");
notification.success({ message }); notification.success({ message: msg });
}, },
}, },
columns: { columns: {
@@ -84,6 +88,7 @@ const passwordFormOptions: CrudOptions = {
title: t("authentication.oldPassword"), title: t("authentication.oldPassword"),
type: "password", type: "password",
form: { form: {
//@ts-ignore
show: compute(({ form }) => form.init !== true), show: compute(({ form }) => form.init !== true),
rules: [{ required: true, message: t("authentication.oldPasswordRequired") }], rules: [{ required: true, message: t("authentication.oldPasswordRequired") }],
}, },
@@ -118,16 +123,18 @@ const passwordFormOptions: CrudOptions = {
async function open(opts: OpenOptions = {}) { async function open(opts: OpenOptions = {}) {
const formOptions = buildFormOptions(passwordFormOptions); const formOptions = buildFormOptions(passwordFormOptions);
formOptions.newInstance = true; //新实例打开 formOptions.newInstance = true;
if (opts.init) { if (opts.init) {
formOptions.wrapper.title = t("authentication.initPasswordTitle"); formOptions.wrapper.title = t("authentication.initPasswordTitle");
} }
formOptions.wrapper.onClosed = () => {
emit("close");
};
passwordFormRef.value = await openDialog(formOptions); passwordFormRef.value = await openDialog(formOptions);
passwordFormRef.value.setFormData({ passwordFormRef.value.setFormData({
init: opts.init === true, init: opts.init === true,
password: opts.password || "", password: opts.password || "",
}); });
console.log(passwordFormRef.value);
} }
const scope = ref({ const scope = ref({
@@ -2,22 +2,110 @@
<fs-page class="home—index bg-neutral-100 dark:bg-black"> <fs-page class="home—index bg-neutral-100 dark:bg-black">
<!-- <page-content />--> <!-- <page-content />-->
<dashboard-user /> <dashboard-user />
<change-password-button ref="changePasswordButtonRef" :show-button="false"></change-password-button> <change-password-button ref="changePasswordButtonRef" :show-button="false" @close="checkAndSetupAccount"></change-password-button>
</fs-page> </fs-page>
</template> </template>
<script lang="ts" setup> <script lang="tsx" setup>
import DashboardUser from "./dashboard/index.vue"; import DashboardUser from "./dashboard/index.vue";
import { useUserStore } from "/@/store/user"; import { useUserStore } from "/@/store/user";
import ChangePasswordButton from "/@/views/certd/mine/change-password-button.vue"; import ChangePasswordButton from "/@/views/certd/mine/change-password-button.vue";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { Modal } from "ant-design-vue"; import { Modal, notification } from "ant-design-vue";
import { useI18n } from "/src/locales"; import { useI18n } from "/src/locales";
import { request } from "/@/api/service";
import { useFormDialog } from "/@/use/use-dialog";
const { t } = useI18n(); const { t } = useI18n();
const { openFormDialog } = useFormDialog();
const userStore = useUserStore(); const userStore = useUserStore();
const changePasswordButtonRef = ref(); const changePasswordButtonRef = ref();
const emailFormWrapperRef = ref<any>();
const validateEmailConfirm = async (_rule: any, value: string) => {
if (!value) {
return;
}
const formData = emailFormWrapperRef.value?.getFormData?.();
if (formData && value !== formData.email) {
throw new Error("两次输入的邮箱地址不一致");
}
};
async function checkAndSetupAccount() {
try {
const userInfo = userStore.getUserInfo as any;
if (!userInfo.needInitAccount) {
return;
}
if (userInfo.email) {
await request({
url: "/mine/accountInit",
method: "post",
});
return;
}
emailFormWrapperRef.value = await openFormDialog({
title: "绑定邮箱",
wrapper: {
width: 560,
},
initialForm: { email: "", emailConfirm: "" },
async onSubmit(form: any) {
await request({
url: "/mine/accountInit",
method: "post",
data: { email: form.email },
});
notification.success({
message: "邮箱绑定成功",
});
},
body: () => {
return <a-alert class="mb-4" message="为保证用户体验,请先绑定邮箱,初始化您的账号" type="success" show-icon></a-alert>;
},
columns: {
email: {
title: "邮箱",
type: "text",
form: {
col: { span: 24 },
component: {
placeholder: "请输入邮箱地址",
},
helper: "请输入您的邮箱",
rules: [
{ required: true, message: "请输入邮箱地址" },
{ type: "email", message: "请输入有效的邮箱地址" },
],
},
},
emailConfirm: {
title: "确认邮箱",
type: "text",
form: {
col: { span: 24 },
component: {
placeholder: "请再次输入邮箱地址",
},
helper: "请再次输入邮箱,以确认邮箱地址无误",
rules: [
{ required: true, message: "请再次输入邮箱地址" },
{ type: "email", message: "请输入有效的邮箱地址" },
{ validator: validateEmailConfirm, trigger: "blur" },
],
},
},
},
});
} catch (e) {
console.error("AcmeAccount setup failed:", e);
}
}
onMounted(() => { onMounted(() => {
if (userStore.getUserInfo.isWeak === true) { if (userStore.getUserInfo.isWeak === true) {
Modal.info({ Modal.info({
@@ -30,6 +118,9 @@ onMounted(() => {
}, },
okText: t("authentication.changeNow"), okText: t("authentication.changeNow"),
}); });
} else {
//两个弹框不要同时出现
checkAndSetupAccount();
} }
}); });
</script> </script>
@@ -1,10 +1,14 @@
import { BaseController, Constants, SysSettingsService } from "@certd/lib-server"; import { AccessGetter, AccessService, BaseController, Constants, SysSettingsService } from "@certd/lib-server";
import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core"; import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core";
import { PasskeyService } from "../../../modules/login/service/passkey-service.js"; import { PasskeyService } from "../../../modules/login/service/passkey-service.js";
import { RoleService } from "../../../modules/sys/authority/service/role-service.js"; import { RoleService } from "../../../modules/sys/authority/service/role-service.js";
import { UserService } from "../../../modules/sys/authority/service/user-service.js"; import { UserService } from "../../../modules/sys/authority/service/user-service.js";
import { NotificationService } from "../../../modules/pipeline/service/notification-service.js";
import { newAccess } from "@certd/pipeline";
import { http, logger, utils } from "@certd/basic";
import { ApiTags } from "@midwayjs/swagger"; import { ApiTags } from "@midwayjs/swagger";
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";
/** /**
*/ */
@@ -27,6 +31,15 @@ export class MineController extends BaseController {
@Inject() @Inject()
sysSettingsService: SysSettingsService; sysSettingsService: SysSettingsService;
@Inject()
accessService: AccessService;
@Inject()
notificationService: NotificationService;
@Inject()
emailService: EmailService;
@Post("/info", { description: Constants.per.authOnly, summary: "查询用户信息" }) @Post("/info", { description: Constants.per.authOnly, summary: "查询用户信息" })
public async info() { public async info() {
const userId = this.getUserId(); const userId = this.getUserId();
@@ -41,6 +54,17 @@ export class MineController extends BaseController {
delete user.password; delete user.password;
//@ts-ignore //@ts-ignore
user.needInitPassword = needInitPassword; user.needInitPassword = needInitPassword;
const { projectId } = await this.getProjectUserIdRead();
const userProjectQuery = this.accessService.buildUserProjectQuery(userId, projectId);
const existingAccess = await this.accessService.findOne({
where: { type: "acmeAccount", subtype: "letsencrypt", ...userProjectQuery },
});
if (!existingAccess) {
//@ts-ignore
user.needInitAccount = true;
}
return this.ok(user); return this.ok(user);
} }
@@ -122,4 +146,58 @@ export class MineController extends BaseController {
}); });
return this.ok({}); return this.ok({});
} }
@Post("/accountInit", { description: Constants.per.authOnly, summary: "初始化Let's Encrypt ACME账号和邮件通知" })
public async accountInit(@Body("email") email?: string) {
const { projectId, userId } = await this.getProjectUserIdWrite();
let userEmail = email;
let user: any = null;
if (!userEmail) {
user = await this.userService.info(userId);
userEmail = user.email;
}
if (!userEmail) {
return this.ok({ needEmail: true });
}
if (email) {
if (!user) {
user = await this.userService.info(userId);
}
if (!user.email) {
await this.userService.updateEmail(userId, { email: userEmail });
}
}
await this.emailService.add(userId, userEmail);
await this.notificationService.getOrCreateDefault(userEmail, userId, projectId);
const getAccessById = this.accessService.getById.bind(this.accessService);
const accessGetter = new AccessGetter(userId, projectId, getAccessById);
const accessContext = {
http,
logger,
utils,
accessService: accessGetter,
define: undefined,
} as any;
const access = await newAccess("acmeAccount", { caType: "letsencrypt", email: userEmail }, accessGetter, accessContext);
const accountJson = await access.onGenerateAccount();
await this.accessService.add({
type: "acmeAccount",
name: "Let's Encrypt",
userId,
projectId,
setting: JSON.stringify({
caType: "letsencrypt",
email: userEmail,
account: accountJson,
}),
});
return this.ok({ success: true });
}
} }
@@ -441,7 +441,8 @@ export class RuntimeDepsService {
private getDefineByPluginKey(pluginKey: string, owner?: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine { private getDefineByPluginKey(pluginKey: string, owner?: RuntimeDependencyPluginDefine): RuntimeDependencyPluginDefine {
const parts = pluginKey.split(":"); const parts = pluginKey.split(":");
let [pluginType, subtype, name] = parts; const [pluginType, subtype, rawName] = parts;
let name = rawName;
if (parts.length === 2) { if (parts.length === 2) {
name = subtype; name = subtype;
} else if (parts.length === 3) { } else if (parts.length === 3) {