mirror of
https://github.com/certd/certd.git
synced 2026-04-15 13:32:37 +08:00
pref: 支持子域名托管的域名证书申请
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { request } from "/src/api/service";
|
||||
|
||||
const apiPrefix = "/cname/record";
|
||||
const subDomainApiPrefix = "/pi/subDomain";
|
||||
|
||||
export type CnameRecord = {
|
||||
id?: number;
|
||||
@@ -18,7 +19,7 @@ export type DomainGroupItem = {
|
||||
export async function GetList() {
|
||||
return await request({
|
||||
url: apiPrefix + "/list",
|
||||
method: "post"
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,8 +29,8 @@ export async function GetByDomain(domain: string) {
|
||||
method: "post",
|
||||
data: {
|
||||
domain,
|
||||
createOnNotFound: true
|
||||
}
|
||||
createOnNotFound: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,7 +39,17 @@ export async function DoVerify(id: number) {
|
||||
url: apiPrefix + "/verify",
|
||||
method: "post",
|
||||
data: {
|
||||
id
|
||||
}
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function ParseDomain(fullDomain: string) {
|
||||
return await request({
|
||||
url: subDomainApiPrefix + "/parseDomain",
|
||||
method: "post",
|
||||
data: {
|
||||
fullDomain,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,26 +32,14 @@
|
||||
<div class="form-item">
|
||||
<span class="label">DNS类型:</span>
|
||||
<span class="input">
|
||||
<fs-dict-select
|
||||
v-model:value="item.dnsProviderType"
|
||||
size="small"
|
||||
:dict="dnsProviderTypeDict"
|
||||
placeholder="DNS提供商"
|
||||
@change="onPlanChanged"
|
||||
></fs-dict-select>
|
||||
<fs-dict-select v-model:value="item.dnsProviderType" size="small" :dict="dnsProviderTypeDict" placeholder="DNS提供商" @change="onPlanChanged"></fs-dict-select>
|
||||
</span>
|
||||
</div>
|
||||
<a-divider type="vertical" />
|
||||
<div class="form-item">
|
||||
<span class="label">DNS授权:</span>
|
||||
<span class="input">
|
||||
<access-selector
|
||||
v-model="item.dnsProviderAccessId"
|
||||
size="small"
|
||||
:type="item.dnsProviderType"
|
||||
placeholder="请选择"
|
||||
@change="onPlanChanged"
|
||||
></access-selector>
|
||||
<access-selector v-model="item.dnsProviderAccessId" size="small" :type="item.dnsProviderType" placeholder="请选择" @change="onPlanChanged"></access-selector>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,28 +68,27 @@ import { dict, FsDictSelect } from "@fast-crud/fast-crud";
|
||||
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
|
||||
import CnameVerifyPlan from "./cname-verify-plan.vue";
|
||||
import HttpVerifyPlan from "./http-verify-plan.vue";
|
||||
//@ts-ignore
|
||||
import psl from "psl";
|
||||
import { Form } from "ant-design-vue";
|
||||
import { DomainsVerifyPlanInput } from "./type";
|
||||
import { CnameRecord, DomainGroupItem } from "./api";
|
||||
import { DomainGroupItem, ParseDomain } from "./api";
|
||||
|
||||
defineOptions({
|
||||
name: "DomainsVerifyPlanEditor"
|
||||
name: "DomainsVerifyPlanEditor",
|
||||
});
|
||||
|
||||
const challengeTypeOptions = ref<any[]>([
|
||||
{
|
||||
label: "DNS验证",
|
||||
value: "dns"
|
||||
value: "dns",
|
||||
},
|
||||
{
|
||||
label: "CNAME验证",
|
||||
value: "cname"
|
||||
value: "cname",
|
||||
},
|
||||
{
|
||||
label: "HTTP验证",
|
||||
value: "http"
|
||||
}
|
||||
value: "http",
|
||||
},
|
||||
]);
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -122,7 +109,7 @@ function fullscreenExit() {
|
||||
}
|
||||
const planRef = ref<DomainsVerifyPlanInput>(props.modelValue || {});
|
||||
const dnsProviderTypeDict = dict({
|
||||
url: "pi/dnsProvider/dnsProviderTypeDict"
|
||||
url: "pi/dnsProvider/dnsProviderTypeDict",
|
||||
});
|
||||
|
||||
const formItemContext = Form.useInjectFormItemContext();
|
||||
@@ -139,7 +126,7 @@ function showError(error: string) {
|
||||
|
||||
type DomainGroup = Record<string, DomainGroupItem>;
|
||||
|
||||
function onDomainsChanged(domains: string[]) {
|
||||
async function onDomainsChanged(domains: string[]) {
|
||||
if (domains == null) {
|
||||
return;
|
||||
}
|
||||
@@ -147,12 +134,7 @@ function onDomainsChanged(domains: string[]) {
|
||||
const domainGroups: DomainGroup = {};
|
||||
for (let domain of domains) {
|
||||
const keyDomain = domain.replace("*.", "");
|
||||
const parsed = psl.parse(keyDomain);
|
||||
if (parsed.error) {
|
||||
showError(`域名${domain}解析失败: ${JSON.stringify(parsed.error)}`);
|
||||
continue;
|
||||
}
|
||||
const mainDomain = parsed.domain;
|
||||
const mainDomain = await ParseDomain(keyDomain);
|
||||
if (mainDomain == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -161,7 +143,7 @@ function onDomainsChanged(domains: string[]) {
|
||||
group = {
|
||||
domain: mainDomain,
|
||||
domains: [],
|
||||
keySubDomains: []
|
||||
keySubDomains: [],
|
||||
} as DomainGroupItem;
|
||||
domainGroups[mainDomain] = group;
|
||||
}
|
||||
@@ -180,7 +162,7 @@ function onDomainsChanged(domains: string[]) {
|
||||
//@ts-ignore
|
||||
cnameVerifyPlan: {},
|
||||
//@ts-ignore
|
||||
httpVerifyPlan: {}
|
||||
httpVerifyPlan: {},
|
||||
};
|
||||
planRef.value[domain] = planItem;
|
||||
}
|
||||
@@ -196,7 +178,7 @@ function onDomainsChanged(domains: string[]) {
|
||||
if (!cnameOrigin[subDomain]) {
|
||||
//@ts-ignore
|
||||
planItem.cnameVerifyPlan[subDomain] = {
|
||||
id: 0
|
||||
id: 0,
|
||||
};
|
||||
} else {
|
||||
planItem.cnameVerifyPlan[subDomain] = cnameOrigin[subDomain];
|
||||
@@ -205,14 +187,14 @@ function onDomainsChanged(domains: string[]) {
|
||||
if (!cnamePlan[subDomain]) {
|
||||
//@ts-ignore
|
||||
cnamePlan[subDomain] = {
|
||||
id: 0
|
||||
id: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (!httpOrigin[subDomain]) {
|
||||
//@ts-ignore
|
||||
planItem.httpVerifyPlan[subDomain] = {
|
||||
domain: subDomain
|
||||
domain: subDomain,
|
||||
};
|
||||
} else {
|
||||
planItem.httpVerifyPlan[subDomain] = httpOrigin[subDomain];
|
||||
@@ -221,7 +203,7 @@ function onDomainsChanged(domains: string[]) {
|
||||
if (!httpPlan[subDomain]) {
|
||||
//@ts-ignore
|
||||
httpPlan[subDomain] = {
|
||||
domain: subDomain
|
||||
domain: subDomain,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -255,7 +237,7 @@ watch(
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -98,6 +98,17 @@ export const certdResources = [
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "子域名托管设置",
|
||||
name: "SubDomain",
|
||||
path: "/certd/pipeline/subDomain",
|
||||
component: "/certd/pipeline/sub-domain/index.vue",
|
||||
meta: {
|
||||
icon: "ion:link-outline",
|
||||
auth: true,
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "流水线分组管理",
|
||||
name: "PipelineGroupManager",
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// @ts-ignore
|
||||
import { request } from "/src/api/service";
|
||||
|
||||
const apiPrefix = "/pi/subDomain";
|
||||
|
||||
export async function GetList(query: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/page",
|
||||
method: "post",
|
||||
data: query,
|
||||
});
|
||||
}
|
||||
|
||||
export async function AddObj(obj: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/add",
|
||||
method: "post",
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export async function UpdateObj(obj: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/update",
|
||||
method: "post",
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export async function DelObj(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/delete",
|
||||
method: "post",
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetObj(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/info",
|
||||
method: "post",
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetDetail(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/detail",
|
||||
method: "post",
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function DeleteBatch(ids: any[]) {
|
||||
return await request({
|
||||
url: apiPrefix + "/batchDelete",
|
||||
method: "post",
|
||||
data: { ids },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import * as api from "./api";
|
||||
import { Ref, ref } from "vue";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
const res = await api.UpdateObj(form);
|
||||
return res;
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
const selectedRowKeys: Ref<any[]> = ref([]);
|
||||
context.selectedRowKeys = selectedRowKeys;
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
settings: {
|
||||
plugins: {
|
||||
//这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并
|
||||
rowSelection: {
|
||||
enabled: true,
|
||||
order: -2,
|
||||
before: true,
|
||||
// handle: (pluginProps,useCrudProps)=>CrudOptions,
|
||||
props: {
|
||||
multiple: true,
|
||||
crossPage: true,
|
||||
selectedRowKeys,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
// tabs: {
|
||||
// name: "status",
|
||||
// show: true,
|
||||
// },
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right",
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 80,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
domain: {
|
||||
title: "子域名",
|
||||
type: "text",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
title: "是否禁用",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: false, label: "启用", color: "green" },
|
||||
{ value: true, label: "禁用", color: "gray" },
|
||||
],
|
||||
}),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
updateTime: {
|
||||
title: "更新时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
子域名托管
|
||||
<span class="sub"> 当你的域名设置了子域名托管,需要在此处创建记录,否则申请证书将失败 </span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #pagination-left>
|
||||
<a-tooltip title="批量删除">
|
||||
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onActivated, onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { DeleteBatch } from "./api";
|
||||
|
||||
defineOptions({
|
||||
name: "CnameRecord",
|
||||
});
|
||||
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
|
||||
|
||||
const selectedRowKeys = context.selectedRowKeys;
|
||||
const handleBatchDelete = () => {
|
||||
if (selectedRowKeys.value?.length > 0) {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
|
||||
async onOk() {
|
||||
await DeleteBatch(selectedRowKeys.value);
|
||||
message.info("删除成功");
|
||||
crudExpose.doRefresh();
|
||||
selectedRowKeys.value = [];
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error("请先勾选记录");
|
||||
}
|
||||
};
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
@@ -1,3 +1,19 @@
|
||||
ALTER TABLE pi_plugin ADD COLUMN "pluginType" varchar(100);
|
||||
ALTER TABLE pi_plugin ADD COLUMN "metadata" varchar(40960);
|
||||
ALTER TABLE pi_plugin ADD COLUMN "author" varchar(100);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE "pi_sub_domain"
|
||||
(
|
||||
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
"user_id" integer,
|
||||
"domain" varchar(100),
|
||||
"disabled" boolean NOT NULL DEFAULT (false),
|
||||
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||
);
|
||||
|
||||
|
||||
CREATE INDEX "index_sub_domain_user_id" ON "pi_sub_domain" ("user_id");
|
||||
CREATE INDEX "index_sub_domain_domain" ON "pi_sub_domain" ("domain");
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {ALL, Body, Controller, Inject, Post, Provide} from '@midwayjs/core';
|
||||
import {AccessGetter, AccessService, BaseController, Constants} from '@certd/lib-server';
|
||||
import {AccessService, BaseController, Constants} from '@certd/lib-server';
|
||||
import {
|
||||
AccessRequestHandleReq,
|
||||
IAccessService,
|
||||
ITaskPlugin,
|
||||
newAccess,
|
||||
newNotification,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
import {EmailService} from '../../../modules/basic/service/email-service.js';
|
||||
import {http, HttpRequestConfig, logger, mergeUtils, utils} from '@certd/basic';
|
||||
import {NotificationService} from '../../../modules/pipeline/service/notification-service.js';
|
||||
import {TaskServiceBuilder} from "../../../modules/pipeline/service/task-service-getter.js";
|
||||
|
||||
@Provide()
|
||||
@Controller('/api/pi/handle')
|
||||
@@ -23,6 +25,8 @@ export class HandleController extends BaseController {
|
||||
@Inject()
|
||||
emailService: EmailService;
|
||||
|
||||
@Inject()
|
||||
taskServiceBuilder: TaskServiceBuilder;
|
||||
|
||||
@Inject()
|
||||
notificationService: NotificationService;
|
||||
@@ -82,8 +86,6 @@ export class HandleController extends BaseController {
|
||||
//@ts-ignore
|
||||
const instance = plugin as ITaskPlugin;
|
||||
|
||||
const accessGetter = new AccessGetter(userId, this.accessService.getById.bind(this.accessService));
|
||||
|
||||
const download = async (config: HttpRequestConfig, savePath: string) => {
|
||||
await utils.download({
|
||||
http,
|
||||
@@ -93,13 +95,9 @@ export class HandleController extends BaseController {
|
||||
});
|
||||
};
|
||||
|
||||
const serviceContainer:any = {
|
||||
}
|
||||
const serviceGetter = {
|
||||
get:(name: string) => {
|
||||
return serviceContainer[name]
|
||||
}
|
||||
}
|
||||
const taskServiceGetter = this.taskServiceBuilder.create({userId})
|
||||
|
||||
const accessGetter = await taskServiceGetter.get<IAccessService>("accessService")
|
||||
//@ts-ignore
|
||||
const taskCtx: TaskInstanceContext = {
|
||||
pipeline: undefined,
|
||||
@@ -125,7 +123,7 @@ export class HandleController extends BaseController {
|
||||
// }),
|
||||
// signal: this.abort.signal,
|
||||
utils,
|
||||
serviceGetter
|
||||
serviceGetter:taskServiceGetter
|
||||
};
|
||||
instance.setCtx(taskCtx);
|
||||
mergeUtils.merge(plugin, body.input);
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import {ALL, Body, Controller, Inject, Post, Provide, Query} from '@midwayjs/core';
|
||||
import {Constants, CrudController} from '@certd/lib-server';
|
||||
import {SubDomainService, SubDomainsGetter} from "../../../modules/pipeline/service/sub-domain-service.js";
|
||||
import {DomainParser} from '@certd/plugin-cert/dist/dns-provider/domain-parser.js';
|
||||
|
||||
/**
|
||||
* 子域名托管
|
||||
*/
|
||||
@Provide()
|
||||
@Controller('/api/pi/subDomain')
|
||||
export class SubDomainController extends CrudController<SubDomainService> {
|
||||
@Inject()
|
||||
service: SubDomainService;
|
||||
|
||||
getService() {
|
||||
return this.service;
|
||||
}
|
||||
|
||||
@Post('/parseDomain', { summary: Constants.per.authOnly })
|
||||
async parseDomain(@Body("fullDomain") fullDomain:string) {
|
||||
const userId = this.getUserId()
|
||||
const subDomainGetter = new SubDomainsGetter(userId, this.service)
|
||||
const domainParser = new DomainParser(subDomainGetter)
|
||||
const domain = await domainParser.parse(fullDomain)
|
||||
return this.ok(domain);
|
||||
}
|
||||
|
||||
|
||||
@Post('/page', { summary: Constants.per.authOnly })
|
||||
async page(@Body(ALL) body) {
|
||||
body.query = body.query ?? {};
|
||||
delete body.query.userId;
|
||||
const buildQuery = qb => {
|
||||
qb.andWhere('user_id = :userId', { userId: this.getUserId() });
|
||||
};
|
||||
const res = await this.service.page({
|
||||
query: body.query,
|
||||
page: body.page,
|
||||
sort: body.sort,
|
||||
buildQuery,
|
||||
});
|
||||
return this.ok(res);
|
||||
}
|
||||
|
||||
@Post('/list', { summary: Constants.per.authOnly })
|
||||
async list(@Body(ALL) body) {
|
||||
body.query = body.query ?? {};
|
||||
body.query.userId = this.getUserId();
|
||||
return super.list(body);
|
||||
}
|
||||
|
||||
@Post('/add', { summary: Constants.per.authOnly })
|
||||
async add(@Body(ALL) bean) {
|
||||
bean.userId = this.getUserId();
|
||||
return super.add(bean);
|
||||
}
|
||||
|
||||
@Post('/update', { summary: Constants.per.authOnly })
|
||||
async update(@Body(ALL) bean) {
|
||||
await this.service.checkUserId(bean.id, this.getUserId());
|
||||
delete bean.userId;
|
||||
return super.update(bean);
|
||||
}
|
||||
@Post('/info', { summary: Constants.per.authOnly })
|
||||
async info(@Query('id') id: number) {
|
||||
await this.service.checkUserId(id, this.getUserId());
|
||||
return super.info(id);
|
||||
}
|
||||
|
||||
@Post('/delete', { summary: Constants.per.authOnly })
|
||||
async delete(@Query('id') id: number) {
|
||||
await this.service.checkUserId(id, this.getUserId());
|
||||
return super.delete(id);
|
||||
}
|
||||
|
||||
@Post('/batchDelete', { summary: Constants.per.authOnly })
|
||||
async batchDelete(@Body('ids') ids: number[]) {
|
||||
await this.service.batchDelete(ids, this.getUserId());
|
||||
return this.ok({});
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BaseService, PlusService, ValidateException } from '@certd/lib-server';
|
||||
import { CnameRecordEntity, CnameRecordStatusType } from '../entity/cname-record.js';
|
||||
import { createDnsProvider, IDnsProvider, parseDomain } from '@certd/plugin-cert';
|
||||
import { CnameProvider, CnameRecord } from '@certd/pipeline';
|
||||
import { cache, http, logger, utils } from '@certd/basic';
|
||||
|
||||
import { AccessService } from '@certd/lib-server';
|
||||
import { isDev } from '@certd/basic';
|
||||
import { walkTxtRecord } from '@certd/acme-client';
|
||||
import { CnameProviderService } from './cname-provider-service.js';
|
||||
import { CnameProviderEntity } from '../entity/cname-provider.js';
|
||||
import { CommonDnsProvider } from './common-provider.js';
|
||||
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {InjectEntityModel} from '@midwayjs/typeorm';
|
||||
import {Repository} from 'typeorm';
|
||||
import {AccessService, BaseService, PlusService, ValidateException} from '@certd/lib-server';
|
||||
import {CnameRecordEntity, CnameRecordStatusType} from '../entity/cname-record.js';
|
||||
import {createDnsProvider, IDnsProvider} from '@certd/plugin-cert';
|
||||
import {CnameProvider, CnameRecord} from '@certd/pipeline';
|
||||
import {cache, http, isDev, logger, utils} from '@certd/basic';
|
||||
import {walkTxtRecord} from '@certd/acme-client';
|
||||
import {CnameProviderService} from './cname-provider-service.js';
|
||||
import {CnameProviderEntity} from '../entity/cname-provider.js';
|
||||
import {CommonDnsProvider} from './common-provider.js';
|
||||
import {SubDomainService, SubDomainsGetter} from "../../pipeline/service/sub-domain-service.js";
|
||||
import {DomainParser} from "@certd/plugin-cert/dist/dns-provider/domain-parser.js";
|
||||
|
||||
type CnameCheckCacheValue = {
|
||||
validating: boolean;
|
||||
@@ -40,6 +39,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
@Inject()
|
||||
plusService: PlusService;
|
||||
|
||||
@Inject()
|
||||
subDomainService: SubDomainService;
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
@@ -74,17 +76,20 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
} else {
|
||||
cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
||||
}
|
||||
this.cnameProviderChanged(param, cnameProvider);
|
||||
await this.cnameProviderChanged(param.userId,param, cnameProvider);
|
||||
|
||||
param.status = 'cname';
|
||||
const { id } = await super.add(param);
|
||||
return await this.info(id);
|
||||
}
|
||||
|
||||
private cnameProviderChanged(param: any, cnameProvider: CnameProviderEntity) {
|
||||
private async cnameProviderChanged(userId:number,param: any, cnameProvider: CnameProviderEntity) {
|
||||
param.cnameProviderId = cnameProvider.id;
|
||||
|
||||
const realDomain = parseDomain(param.domain);
|
||||
const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
const realDomain = await domainParser.parse(param.domain);
|
||||
const prefix = param.domain.replace(realDomain, '');
|
||||
let hostRecord = `_acme-challenge.${prefix}`;
|
||||
if (hostRecord.endsWith('.')) {
|
||||
@@ -111,7 +116,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
if (old.cnameProviderId !== param.cnameProviderId) {
|
||||
const cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
||||
this.cnameProviderChanged(param, cnameProvider);
|
||||
await this.cnameProviderChanged(old.userId,param, cnameProvider);
|
||||
param.status = 'cname';
|
||||
}
|
||||
return await super.update(param);
|
||||
@@ -185,6 +190,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return true;
|
||||
}
|
||||
|
||||
const subDomainGetter = new SubDomainsGetter(bean.userId, this.subDomainService)
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
const cacheKey = `cname.record.verify.${bean.id}`;
|
||||
|
||||
let value: CnameCheckCacheValue = cache.get(cacheKey);
|
||||
@@ -219,7 +227,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
|
||||
const access = await this.accessService.getById(cnameProvider.accessId, cnameProvider.userId);
|
||||
const context = { access, logger, http, utils };
|
||||
const context = { access, logger, http, utils,domainParser };
|
||||
const dnsProvider: IDnsProvider = await createDnsProvider({
|
||||
dnsProviderType: cnameProvider.dnsProviderType,
|
||||
context,
|
||||
@@ -239,7 +247,8 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return false;
|
||||
}
|
||||
|
||||
const originDomain = parseDomain(bean.domain);
|
||||
|
||||
const originDomain = await domainParser.parse(bean.domain);
|
||||
const fullDomain = `${bean.hostRecord}.${originDomain}`;
|
||||
|
||||
logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`);
|
||||
@@ -285,7 +294,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
ttl: ttl,
|
||||
});
|
||||
|
||||
const domain = parseDomain(bean.recordValue);
|
||||
const domain = await domainParser.parse(bean.recordValue);
|
||||
const fullRecord = bean.recordValue;
|
||||
const hostRecord = fullRecord.replace(`.${domain}`, '');
|
||||
const req = {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 子域名托管
|
||||
*/
|
||||
@Entity('pi_sub_domain')
|
||||
export class SubDomainEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'user_id', comment: 'UserId' })
|
||||
userId: number;
|
||||
|
||||
@Column({ name: 'domain', comment: '子域名' })
|
||||
domain: string;
|
||||
|
||||
@Column({ name: 'disabled', comment: '禁用' })
|
||||
disabled: boolean;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
comment: '创建时间',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
comment: '修改时间',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum, sleep } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { In, MoreThan, Repository } from "typeorm";
|
||||
import {Config, Inject, Provide, Scope, ScopeEnum, sleep} from "@midwayjs/core";
|
||||
import {InjectEntityModel} from "@midwayjs/typeorm";
|
||||
import {In, MoreThan, Repository} from "typeorm";
|
||||
import {
|
||||
AccessGetter,
|
||||
AccessService,
|
||||
BaseService,
|
||||
NeedSuiteException,
|
||||
@@ -12,30 +11,40 @@ import {
|
||||
SysSettingsService,
|
||||
SysSiteInfo
|
||||
} from "@certd/lib-server";
|
||||
import { PipelineEntity } from "../entity/pipeline.js";
|
||||
import { PipelineDetail } from "../entity/vo/pipeline-detail.js";
|
||||
import { Executor, Pipeline, ResultType, RunHistory, RunnableCollection, SysInfo, UserInfo } from "@certd/pipeline";
|
||||
import { DbStorage } from "./db-storage.js";
|
||||
import { StorageService } from "./storage-service.js";
|
||||
import { Cron } from "../../cron/cron.js";
|
||||
import { HistoryService } from "./history-service.js";
|
||||
import { HistoryEntity } from "../entity/history.js";
|
||||
import { HistoryLogEntity } from "../entity/history-log.js";
|
||||
import { HistoryLogService } from "./history-log-service.js";
|
||||
import { EmailService } from "../../basic/service/email-service.js";
|
||||
import { UserService } from "../../sys/authority/service/user-service.js";
|
||||
import { CnameRecordService } from "../../cname/service/cname-record-service.js";
|
||||
import { CnameProxyService } from "./cname-proxy-service.js";
|
||||
import { PluginConfigGetter } from "../../plugin/service/plugin-config-getter.js";
|
||||
import {PipelineEntity} from "../entity/pipeline.js";
|
||||
import {PipelineDetail} from "../entity/vo/pipeline-detail.js";
|
||||
import {
|
||||
Executor,
|
||||
IAccessService,
|
||||
ICnameProxyService,
|
||||
INotificationService,
|
||||
Pipeline,
|
||||
ResultType,
|
||||
RunHistory,
|
||||
RunnableCollection,
|
||||
SysInfo,
|
||||
UserInfo
|
||||
} from "@certd/pipeline";
|
||||
import {DbStorage} from "./db-storage.js";
|
||||
import {StorageService} from "./storage-service.js";
|
||||
import {Cron} from "../../cron/cron.js";
|
||||
import {HistoryService} from "./history-service.js";
|
||||
import {HistoryEntity} from "../entity/history.js";
|
||||
import {HistoryLogEntity} from "../entity/history-log.js";
|
||||
import {HistoryLogService} from "./history-log-service.js";
|
||||
import {EmailService} from "../../basic/service/email-service.js";
|
||||
import {UserService} from "../../sys/authority/service/user-service.js";
|
||||
import {CnameRecordService} from "../../cname/service/cname-record-service.js";
|
||||
import {PluginConfigGetter} from "../../plugin/service/plugin-config-getter.js";
|
||||
import dayjs from "dayjs";
|
||||
import { DbAdapter } from "../../db/index.js";
|
||||
import { isComm } from "@certd/plus-core";
|
||||
import { logger } from "@certd/basic";
|
||||
import { UrlService } from "./url-service.js";
|
||||
import { NotificationService } from "./notification-service.js";
|
||||
import { NotificationGetter } from "./notification-getter.js";
|
||||
import { UserSuiteEntity, UserSuiteService } from "@certd/commercial-core";
|
||||
import { CertInfoService } from "../../monitor/service/cert-info-service.js";
|
||||
import {DbAdapter} from "../../db/index.js";
|
||||
import {isComm} from "@certd/plus-core";
|
||||
import {logger} from "@certd/basic";
|
||||
import {UrlService} from "./url-service.js";
|
||||
import {NotificationService} from "./notification-service.js";
|
||||
import {UserSuiteEntity, UserSuiteService} from "@certd/commercial-core";
|
||||
import {CertInfoService} from "../../monitor/service/cert-info-service.js";
|
||||
import {TaskServiceBuilder} from "./task-service-getter.js";
|
||||
|
||||
const runningTasks: Map<string | number, Executor> = new Map();
|
||||
|
||||
@@ -65,6 +74,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
@Inject()
|
||||
pluginConfigGetter: PluginConfigGetter;
|
||||
|
||||
@Inject()
|
||||
taskServiceBuilder: TaskServiceBuilder;
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
@@ -473,20 +485,19 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
role: userIsAdmin ? 'admin' : 'user',
|
||||
};
|
||||
|
||||
const accessGetter = new AccessGetter(userId, this.accessService.getById.bind(this.accessService));
|
||||
const cnameProxyService = new CnameProxyService(userId, this.cnameRecordService.getWithAccessByDomain.bind(this.cnameRecordService));
|
||||
const notificationGetter = new NotificationGetter(userId, this.notificationService);
|
||||
|
||||
const sysInfo: SysInfo = {};
|
||||
if (isComm()) {
|
||||
const siteInfo = await this.sysSettingsService.getSetting<SysSiteInfo>(SysSiteInfo);
|
||||
sysInfo.title = siteInfo.title;
|
||||
}
|
||||
const serviceContainer = {}
|
||||
const serviceGetter = {
|
||||
get:(name: string) => {
|
||||
return serviceContainer[name]
|
||||
}
|
||||
}
|
||||
|
||||
const taskServiceGetter = this.taskServiceBuilder.create({
|
||||
userId,
|
||||
})
|
||||
const accessGetter = await taskServiceGetter.get<IAccessService>("accessService")
|
||||
const notificationGetter =await taskServiceGetter.get<INotificationService>("notificationService")
|
||||
const cnameProxyService =await taskServiceGetter.get<ICnameProxyService>("cnameProxyService")
|
||||
const executor = new Executor({
|
||||
user,
|
||||
pipeline,
|
||||
@@ -500,7 +511,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
notificationService: notificationGetter,
|
||||
fileRootDir: this.certdConfig.fileRootDir,
|
||||
sysInfo,
|
||||
serviceGetter
|
||||
serviceGetter:taskServiceGetter
|
||||
});
|
||||
try {
|
||||
runningTasks.set(historyId, executor);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {BaseService, SysSettingsService} from '@certd/lib-server';
|
||||
import {InjectEntityModel} from '@midwayjs/typeorm';
|
||||
import {Repository} from 'typeorm';
|
||||
import {SubDomainEntity} from '../entity/sub-domain.js';
|
||||
import {EmailService} from '../../basic/service/email-service.js';
|
||||
import {ISubDomainsGetter} from "@certd/plugin-cert";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class SubDomainService extends BaseService<SubDomainEntity> {
|
||||
@InjectEntityModel(SubDomainEntity)
|
||||
repository: Repository<SubDomainEntity>;
|
||||
|
||||
@Inject()
|
||||
emailService: EmailService;
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
async getListByUserId(userId:number):Promise<string[]>{
|
||||
if (!userId) {
|
||||
return [];
|
||||
}
|
||||
const list = await this.find({
|
||||
where: {
|
||||
userId,
|
||||
disabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
return list.map(item=>item.domain);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export class SubDomainsGetter implements ISubDomainsGetter {
|
||||
userId: number;
|
||||
subDomainService: SubDomainService;
|
||||
|
||||
constructor(userId: number, subDomainService: SubDomainService) {
|
||||
this.userId = userId;
|
||||
this.subDomainService = subDomainService;
|
||||
}
|
||||
|
||||
async getSubDomains() {
|
||||
return await this.subDomainService.getListByUserId(this.userId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import {IServiceGetter} from "@certd/pipeline";
|
||||
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
|
||||
import {SubDomainService, SubDomainsGetter} from "./sub-domain-service.js";
|
||||
import {AccessGetter, AccessService} from "@certd/lib-server";
|
||||
import {CnameProxyService} from "./cname-proxy-service.js";
|
||||
import {NotificationGetter} from "./notification-getter.js";
|
||||
import {NotificationService} from "./notification-service.js";
|
||||
import {CnameRecordService} from "../../cname/service/cname-record-service.js";
|
||||
|
||||
export class TaskServiceGetter implements IServiceGetter{
|
||||
serviceContainer:Record<string, any>;
|
||||
constructor(serviceContainer:Record<string, any>) {
|
||||
this.serviceContainer = serviceContainer;
|
||||
}
|
||||
async get<T>(serviceName: string): Promise<T> {
|
||||
const ret = this.serviceContainer[serviceName] as T;
|
||||
if(!ret){
|
||||
throw new Error(`service ${serviceName} not found`)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
export type TaskServiceCreateReq = {
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export type TaskServiceContainer = {
|
||||
subDomainsGetter:SubDomainsGetter;
|
||||
accessService: AccessGetter;
|
||||
cnameProxyService: CnameProxyService;
|
||||
notificationService: NotificationGetter;
|
||||
}
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class TaskServiceBuilder {
|
||||
|
||||
@Inject()
|
||||
subDomainService: SubDomainService;
|
||||
@Inject()
|
||||
accessService: AccessService;
|
||||
@Inject()
|
||||
cnameRecordService: CnameRecordService;
|
||||
@Inject()
|
||||
notificationService: NotificationService;
|
||||
|
||||
|
||||
create(req:TaskServiceCreateReq){
|
||||
|
||||
const userId = req.userId;
|
||||
const accessGetter = new AccessGetter(userId, this.accessService.getById.bind(this.accessService));
|
||||
const cnameProxyService = new CnameProxyService(userId, this.cnameRecordService.getWithAccessByDomain.bind(this.cnameRecordService));
|
||||
const notificationGetter = new NotificationGetter(userId, this.notificationService);
|
||||
|
||||
const serviceContainer:TaskServiceContainer = {
|
||||
subDomainsGetter:new SubDomainsGetter(req.userId, this.subDomainService),
|
||||
accessService: accessGetter,
|
||||
cnameProxyService:cnameProxyService,
|
||||
notificationService:notificationGetter
|
||||
}
|
||||
return new TaskServiceGetter(serviceContainer)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user