feat: 套餐购买支持易支付、支付宝支付

pull/330/head
xiaojunnuo 2024-12-23 00:24:31 +08:00
parent 9c8c7a7812
commit faa28f88f9
69 changed files with 1073 additions and 407 deletions

View File

@ -144,6 +144,19 @@ export class RunnableCollection {
this.collection = map; this.collection = map;
} }
static initPipelineRunnableType(pipeline: Pipeline) {
pipeline.runnableType = "pipeline";
pipeline.stages.forEach((stage) => {
stage.runnableType = "stage";
stage.tasks.forEach((task) => {
task.runnableType = "task";
task.steps.forEach((step) => {
step.runnableType = "step";
});
});
});
}
static each<T extends Runnable>(list: T[], exec: (item: Runnable) => void) { static each<T extends Runnable>(list: T[], exec: (item: Runnable) => void) {
list.forEach((item) => { list.forEach((item) => {
exec(item); exec(item);

View File

@ -39,4 +39,12 @@ export abstract class BaseController {
} }
return userId; return userId;
} }
getLoginUser() {
const user = this.ctx.user;
if (user == null) {
throw new Error('Token已过期');
}
return user;
}
} }

View File

@ -148,6 +148,7 @@ export abstract class BaseService<T> {
page.limit = 20; page.limit = 20;
} }
const qb = this.buildListQuery(pageReq); const qb = this.buildListQuery(pageReq);
qb.offset(page.offset).limit(page.limit); qb.offset(page.offset).limit(page.limit);
const list = await qb.getMany(); const list = await qb.getMany();
const total = await qb.getCount(); const total = await qb.getCount();

View File

@ -14,7 +14,7 @@ export const uploadTmpFileCacheKey = 'tmpfile_key_';
/** /**
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class FileService { export class FileService {
async saveFile(userId: number, tmpCacheKey: any, permission: 'public' | 'private') { async saveFile(userId: number, tmpCacheKey: any, permission: 'public' | 'private') {
if (tmpCacheKey.startsWith(`/${permission}`)) { if (tmpCacheKey.startsWith(`/${permission}`)) {

View File

@ -5,7 +5,7 @@ import { SysInstallInfo, SysLicenseInfo, SysSettingsService } from '../../settin
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class PlusService { export class PlusService {
@Inject() @Inject()
sysSettingsService: SysSettingsService; sysSettingsService: SysSettingsService;

View File

@ -12,7 +12,7 @@ import * as dns from 'node:dns';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class SysSettingsService extends BaseService<SysSettingsEntity> { export class SysSettingsService extends BaseService<SysSettingsEntity> {
@InjectEntityModel(SysSettingsEntity) @InjectEntityModel(SysSettingsEntity)
repository: Repository<SysSettingsEntity>; repository: Repository<SysSettingsEntity>;

View File

@ -10,7 +10,7 @@ import { EncryptService } from './encrypt-service.js';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AccessService extends BaseService<AccessEntity> { export class AccessService extends BaseService<AccessEntity> {
@InjectEntityModel(AccessEntity) @InjectEntityModel(AccessEntity)
repository: Repository<AccessEntity>; repository: Repository<AccessEntity>;

View File

@ -6,7 +6,7 @@ import { SysPrivateSettings, SysSettingsService } from '../../../system/index.js
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class EncryptService { export class EncryptService {
secretKey: Buffer; secretKey: Buffer;

View File

@ -25,6 +25,10 @@ function createService() {
if (response.config.responseType === "blob") { if (response.config.responseType === "blob") {
return response; return response;
} }
//@ts-ignore
if (response.config.returnResponse) {
return response;
}
// dataAxios 是 axios 返回数据中的 data // dataAxios 是 axios 返回数据中的 data
const dataAxios = response.data; const dataAxios = response.data;

View File

@ -1,11 +1,13 @@
<template> <template>
<span class="cd-expires-time-text"> <span class="cd-expires-time-text">
<template v-if="label != null"> <component :is="wrapperComp" :color="color">
{{ label }} <template v-if="label != null">
</template> {{ label }}
<template v-else> </template>
<FsTimeHumanize :model-value="value" :use-format-greater="1000000000000" :options="{ units: ['d'] }"></FsTimeHumanize> <template v-else>
</template> <FsTimeHumanize :model-value="value" :use-format-greater="1000000000000" :options="{ units: ['d'] }"></FsTimeHumanize>
</template>
</component>
</span> </span>
</template> </template>
@ -19,8 +21,32 @@ defineOptions({
const props = defineProps<{ const props = defineProps<{
value?: number; value?: number;
mode?: "tag" | "text";
}>(); }>();
const wrapperComp = computed(() => {
if (props.mode === "tag") {
return "a-tag";
}
return "span";
});
const color = computed(() => {
if (props.value == null) {
return "";
}
if (props.value === -1) {
return "green";
}
//3
if (dayjs().add(3, "day").valueOf() > props.value) {
return "red";
}
return "blue";
});
const label = computed(() => { const label = computed(() => {
if (props.value == null) { if (props.value == null) {
return ""; return "";

View File

@ -153,7 +153,7 @@ export default defineComponent({
if (item.value instanceof Array) { if (item.value instanceof Array) {
return; return;
} }
keys.push(item.value.index); keys.push(item.value.path);
}); });
} }
}); });

View File

@ -1,6 +1,6 @@
<template> <template>
<a-layout class="fs-framework"> <a-layout class="fs-framework">
<a-layout-sider v-model:collapsed="asideCollapsed" :trigger="null" collapsible> <a-layout-sider v-model:collapsed="asideCollapsed" :trigger="null" collapsible :width="210">
<div class="header-logo"> <div class="header-logo">
<img :src="siteInfo.logo" /> <img :src="siteInfo.logo" />
<span v-if="!asideCollapsed" class="title">{{ siteInfo.title }}</span> <span v-if="!asideCollapsed" class="title">{{ siteInfo.title }}</span>

View File

@ -39,98 +39,146 @@ export const certdResources = [
} }
}, },
{ {
title: "授权管理", title: "证书监控",
name: "AccessManager",
path: "/certd/access",
component: "/certd/access/index.vue",
meta: {
icon: "ion:disc-outline",
auth: true,
cache: true
}
},
{
title: "通知设置",
name: "NotificationManager",
path: "/certd/notification",
component: "/certd/notification/index.vue",
meta: {
icon: "ion:megaphone-outline",
auth: true,
cache: true
}
},
{
title: "CNAME记录管理",
name: "CnameRecord",
path: "/certd/cname/record",
component: "/certd/cname/record/index.vue",
meta: {
icon: "ion:link-outline",
auth: true
}
},
{
title: "流水线分组管理",
name: "PipelineGroupManager",
path: "/certd/pipeline/group",
component: "/certd/pipeline/group/index.vue",
meta: {
icon: "mdi:format-list-group",
auth: true
}
},
{
title: "套餐购买",
name: "SuiteProductBuy",
path: "/certd/suite/buy",
component: "/certd/suite/buy.vue",
meta: {
icon: "mdi:format-list-group",
auth: true
}
},
{
title: "我的订单",
name: "MyTrade",
path: "/certd/trade",
component: "/certd/trade/index.vue",
meta: {
icon: "ion:person-outline",
auth: true
}
},
{
title: "支付返回",
name: "PaymentReturn",
path: "/certd/payment/return/:type",
component: "/certd/payment/return.vue",
meta: {
icon: "ion:person-outline",
auth: false,
isMenu: false
}
},
{
title: "站点证书监控",
name: "SiteCertMonitor", name: "SiteCertMonitor",
path: "/certd/monitor/site", path: "/certd/monitor/site",
component: "/certd/monitor/site/index.vue", component: "/certd/monitor/site/index.vue",
meta: { meta: {
icon: "ion:person-outline", icon: "ion:videocam-outline",
auth: true auth: true
} }
}, },
{ {
title: "证书仓库", title: "设置",
name: "CertStore", name: "MineSetting",
path: "/certd/monitor/cert", path: "/certd/mine",
component: "/certd/monitor/cert/index.vue",
meta: { meta: {
icon: "ion:person-outline", icon: "ion:settings-outline",
auth: true auth: true,
} cache: true
},
children: [
{
title: "CNAME记录管理",
name: "CnameRecord",
path: "/certd/cname/record",
component: "/certd/cname/record/index.vue",
meta: {
icon: "ion:link-outline",
auth: true
}
},
{
title: "流水线分组管理",
name: "PipelineGroupManager",
path: "/certd/pipeline/group",
component: "/certd/pipeline/group/index.vue",
meta: {
icon: "mdi:format-list-group",
auth: true
}
},
{
title: "证书仓库",
name: "CertStore",
path: "/certd/monitor/cert",
component: "/certd/monitor/cert/index.vue",
meta: {
icon: "ion:shield-checkmark-outline",
auth: true,
isMenu: false
}
},
{
title: "授权管理",
name: "AccessManager",
path: "/certd/access",
component: "/certd/access/index.vue",
meta: {
icon: "ion:disc-outline",
auth: true,
cache: true
}
},
{
title: "通知设置",
name: "NotificationManager",
path: "/certd/notification",
component: "/certd/notification/index.vue",
meta: {
icon: "ion:megaphone-outline",
auth: true,
cache: true
}
},
{
title: "账号信息",
name: "UserProfile",
path: "/certd/mine/user-profile",
component: "/certd/mine/user-profile.vue",
meta: {
icon: "ion:person-outline",
auth: true,
isMenu: false
}
}
]
}, },
{
title: "套餐",
name: "SuiteProduct",
path: "/certd/suite",
meta: {
icon: "ion:cart-outline",
auth: true
},
children: [
{
title: "我的套餐",
name: "MySuite",
path: "/certd/suite/mine",
component: "/certd/suite/mine/index.vue",
meta: {
icon: "ion:gift-outline",
auth: true
}
},
{
title: "套餐购买",
name: "SuiteProductBuy",
path: "/certd/suite/buy",
component: "/certd/suite/buy.vue",
meta: {
icon: "ion:cart-outline",
auth: true
}
},
{
title: "我的订单",
name: "MyTrade",
path: "/certd/trade",
component: "/certd/trade/index.vue",
meta: {
icon: "ion:bag-check-outline",
auth: true
}
},
{
title: "支付返回",
name: "PaymentReturn",
path: "/certd/payment/return/:type",
component: "/certd/payment/return.vue",
meta: {
icon: "ant-design:pay-circle-outlined",
auth: false,
isMenu: false
}
}
]
}
// { // {
// title: "邮箱设置", // title: "邮箱设置",
// name: "EmailSetting", // name: "EmailSetting",
@ -141,17 +189,6 @@ export const certdResources = [
// auth: true // auth: true
// } // }
// }, // },
{
title: "账号信息",
name: "UserProfile",
path: "/certd/mine/user-profile",
component: "/certd/mine/user-profile.vue",
meta: {
icon: "ion:person-outline",
auth: true,
isMenu: false
}
}
] ]
} }
]; ];

View File

@ -176,7 +176,7 @@ export const sysResources = [
path: "/sys/suite/setting", path: "/sys/suite/setting",
component: "/sys/suite/setting/index.vue", component: "/sys/suite/setting/index.vue",
meta: { meta: {
icon: "ion:person-outline", icon: "ion:cart",
permission: "sys:settings:edit" permission: "sys:settings:edit"
} }
}, },
@ -186,7 +186,7 @@ export const sysResources = [
path: "/sys/suite/trade", path: "/sys/suite/trade",
component: "/sys/suite/trade/index.vue", component: "/sys/suite/trade/index.vue",
meta: { meta: {
icon: "ion:person-outline", icon: "ion:bag-check",
permission: "sys:settings:edit" permission: "sys:settings:edit"
} }
} }

View File

@ -28,6 +28,7 @@ interface ResourceState {
export const useResourceStore = defineStore({ export const useResourceStore = defineStore({
id: "app.resource", id: "app.resource",
//@ts-ignore
state: (): ResourceState => ({ state: (): ResourceState => ({
// user info // user info
topMenus: [], topMenus: [],

View File

@ -68,3 +68,11 @@
width: 800px; width: 800px;
margin: 20px; margin: 20px;
} }
.fs-crud-table{
.ant-table-body {
height: 1000px !important;
table {
}
}
}

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="access-selector"> <div class="access-selector">
<span v-if="modelValue" class="mr-5 cd-flex-inline"> <span v-if="modelValue" class="mr-5 cd-flex-inline">
<a-tag class="mr-5" color="green">{{ target.name || modelValue }}</a-tag> <a-tag class="mr-5" color="green">{{ target?.name || modelValue }}</a-tag>
<fs-icon class="cd-icon-button" icon="ion:close-circle-outline" @click="clear"></fs-icon> <fs-icon class="cd-icon-button" icon="ion:close-circle-outline" @click="clear"></fs-icon>
</span> </span>
<span v-else class="mlr-5 text-gray">{{ placeholder }}</span> <span v-else class="mlr-5 text-gray">{{ placeholder }}</span>

View File

@ -53,4 +53,6 @@ onMounted(() => {
crudExpose.doRefresh(); crudExpose.doRefresh();
}); });
</script> </script>
<style lang="less"></style> <style lang="less">
</style>

View File

@ -1,7 +1,7 @@
import { request } from "/src/api/service"; import { request } from "/src/api/service";
export function createApi() { export function createApi() {
const apiPrefix = "/pi/pipeline/group"; const apiPrefix = "/monitor/cert";
return { return {
async GetList(query: any) { async GetList(query: any) {
return await request({ return await request({

View File

@ -49,8 +49,16 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 600 width: 600
} }
}, },
actionbar: { show: false },
rowHandle: { rowHandle: {
width: 200 width: 200,
fixed: "right",
buttons: {
view: { show: false },
copy: { show: false },
edit: { show: false },
remove: { show: false }
}
}, },
columns: { columns: {
id: { id: {
@ -80,8 +88,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: false show: false
}, },
column: { column: {
width: 100, width: 180,
sorter: true sorter: true,
component: {
name: "fs-values-format"
}
} }
}, },
domains: { domains: {
@ -94,8 +105,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
rules: [{ required: true, message: "请输入域名" }] rules: [{ required: true, message: "请输入域名" }]
}, },
column: { column: {
width: 300, width: 350,
sorter: true sorter: true,
component: {
name: "fs-values-format"
}
} }
}, },
domainCount: { domainCount: {
@ -106,19 +120,21 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
column: { column: {
width: 120, width: 120,
sorter: true sorter: true,
show: false
} }
}, },
pipelineId: { "pipeline.title": {
title: "已关联流水线", title: "已关联流水线",
search: { show: false }, search: { show: false },
type: "text", type: "link",
form: { form: {
show: false show: false
}, },
column: { column: {
width: 200, width: 250,
sorter: true sorter: true,
component: {}
} }
}, },
applyTime: { applyTime: {

View File

@ -3,7 +3,7 @@
<template #header> <template #header>
<div class="title"> <div class="title">
证书仓库 证书仓库
<span class="sub">管理证书支持手动上传证书并部署</span> <span class="sub">管理证书后续将支持手动上传证书并部署</span>
</div> </div>
</template> </template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud> <fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>

View File

@ -1,7 +1,7 @@
import { request } from "/src/api/service"; import { request } from "/src/api/service";
export function createApi() { export function createApi() {
const apiPrefix = "/pi/pipeline/group"; const apiPrefix = "/monitor/site";
return { return {
async GetList(query: any) { async GetList(query: any) {
return await request({ return await request({

View File

@ -72,6 +72,34 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
} }
}, },
domain: {
title: "网站域名",
search: {
show: true
},
type: "text",
form: {
rules: [{ required: true, message: "请输入域名" }]
},
column: {
width: 200,
sorter: true
}
},
port: {
title: "HTTPS端口",
search: {
show: false
},
type: "number",
form: {
value: 443,
rules: [{ required: true, message: "请输入端口" }]
},
column: {
width: 100
}
},
name: { name: {
title: "站点名称", title: "站点名称",
search: { search: {
@ -82,35 +110,22 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
rules: [{ required: true, message: "请输入站点名称" }] rules: [{ required: true, message: "请输入站点名称" }]
}, },
column: { column: {
width: 100 width: 200
}
},
domain: {
title: "主域名",
search: {
show: true
},
type: "text",
form: {
rules: [{ required: true, message: "请输入域名" }]
},
column: {
width: 100,
sorter: true
} }
}, },
domains: { domains: {
title: "其他域名", title: "其他域名",
search: { search: {
show: true show: false
}, },
type: "text", type: "text",
form: { form: {
rules: [{ required: true, message: "请输入其他域名" }] show: false
}, },
column: { column: {
width: 300, width: 300,
sorter: true sorter: true,
show: false
} }
}, },
certInfo: { certInfo: {
@ -123,7 +138,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: false show: false
}, },
column: { column: {
width: 100 width: 100,
show: false
} }
}, },
certStatus: { certStatus: {
@ -185,6 +201,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
search: { search: {
show: false show: false
}, },
form: { show: false },
type: "number", type: "number",
column: { column: {
width: 200, width: 200,
@ -197,7 +214,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: false show: false
}, },
type: "number", type: "number",
form: {}, form: { show: false },
column: { column: {
width: 100, width: 100,
sorter: true, sorter: true,
@ -212,11 +229,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "dict-switch", type: "dict-switch",
dict: dict({ dict: dict({
data: [ data: [
{ label: "禁用", value: true, color: "red" }, { label: "启用", value: false, color: "green" },
{ label: "启用", value: false, color: "green" } { label: "禁用", value: true, color: "red" }
] ]
}), }),
form: {}, form: {
value: true
},
column: { column: {
width: 100, width: 100,
sorter: true sorter: true

View File

@ -1,11 +1,11 @@
import { request } from "/src/api/service"; import { request } from "/src/api/service";
const apiPrefix = "/pay"; const apiPrefix = "/payment";
export async function Notify(type: string, query: any) { export async function Notify(type: string, query: any) {
return await request({ return await request({
url: apiPrefix + `/notify/${type}`, url: apiPrefix + `/notify/${type}`,
method: "post", method: "post",
data: query, data: query,
returnResponse: true unpack: false
}); });
} }

View File

@ -1,16 +1,18 @@
<template> <template>
<div class="cd-payment-return"> <div class="cd-payment-return w-100">
<a-card title="支付结果" class="mt-10"> <div v-if="payResult == null" class="flex-o m-20 w-100">
<div class="flex-o"> <a-spin />
<div class="flex-1"> </div>
<a-tag v-if="payResult" color="green" class="m-0"></a-tag> <a-result v-else-if="payResult" status="success" title="支付成功">
<a-tag v-else color="red" class="m-0">支付失败</a-tag> <template #extra>
</div> <a-button key="console" type="primary" @click="goHome"></a-button>
<div class="m-10"> </template>
<a-button type="primary" @click="goHome"></a-button> </a-result>
</div> <a-result v-else status="error" title="支付失败">
</div> <template #extra>
</a-card> <a-button key="console" type="primary" @click="goHome"></a-button>
</template>
</a-result>
</div> </div>
</template> </template>
@ -40,6 +42,7 @@ async function check() {
payResult.value = true; payResult.value = true;
} }
} }
check();
const router = useRouter(); const router = useRouter();
function goHome() { function goHome() {

View File

@ -112,16 +112,20 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
} }
let pipeline = { let pipeline = {
title: form.domains[0] + "证书自动化", title: form.domains[0] + "证书自动化",
runnableType: "pipeline",
stages: [ stages: [
{ {
title: "证书申请阶段", title: "证书申请阶段",
maxTaskCount: 1, maxTaskCount: 1,
runnableType: "stage",
tasks: [ tasks: [
{ {
title: "证书申请任务", title: "证书申请任务",
runnableType: "task",
steps: [ steps: [
{ {
title: "申请证书", title: "申请证书",
runnableType: "step",
input: { input: {
renewDays: 35, renewDays: 35,
...form ...form
@ -141,10 +145,18 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
}; };
pipeline = setRunnableIds(pipeline); pipeline = setRunnableIds(pipeline);
/**
* // cert: 证书; backup: 备份; custom:自定义;
* type: string;
* // custom: 自定义; monitor: 监控;
* from: string;
*/
const id = await api.Save({ const id = await api.Save({
title: pipeline.title, title: pipeline.title,
content: JSON.stringify(pipeline), content: JSON.stringify(pipeline),
keepHistoryCount: 30 keepHistoryCount: 30,
type: "cert",
from: "custom"
}); });
message.success("创建成功,请添加证书部署任务"); message.success("创建成功,请添加证书部署任务");
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } }); router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });

View File

@ -4,13 +4,17 @@
<div class="title">套餐购买</div> <div class="title">套餐购买</div>
</template> </template>
<div class="suite-buy-content"> <div class="suite-buy-content">
<div class="mb-10"> <a-row class="w-100" :gutter="8">
<a-card> <a-col :span="24">
<div>套餐说明多个套餐内的数量可以叠加</div> <a-card>
<div v-if="suiteIntro" v-html="suiteIntro"></div> <div class="suite-intro-box">
</a-card> <div>套餐说明多个套餐内的数量可以叠加</div>
</div> <div v-if="suiteIntro" v-html="suiteIntro"></div>
<a-row :gutter="8"> </div>
</a-card>
</a-col>
</a-row>
<a-row :gutter="8" class="mt-10">
<a-col v-for="item of products" :key="item.id" class="mb-10 suite-card-col"> <a-col v-for="item of products" :key="item.id" class="mb-10 suite-card-col">
<product-info :product="item" @order="doOrder" /> <product-info :product="item" @order="doOrder" />
</a-col> </a-col>
@ -57,11 +61,23 @@ loadSuiteIntro();
background: #f0f2f5; background: #f0f2f5;
.suite-buy-content { .suite-buy-content {
padding: 20px; padding: 20px;
display: flex;
flex-direction: column;
align-items: baseline;
.hr { .suite-intro-box {
border-top: 1px solid #cdcdcd; //height: 60px;
margin-top: 5px; //overflow: hidden;
padding-top: 5px; //text-overflow: ellipsis;
}
.suite-list {
display: flex;
align-items: baseline;
}
.my-suites {
width: 360px;
margin-left: 10px;
} }
.price-text { .price-text {

View File

@ -0,0 +1,54 @@
import { request } from "/src/api/service";
export function createApi() {
const apiPrefix = "/mine/suite";
return {
async GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query
});
},
async AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
},
async UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
},
async DelObj(id: number) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
},
async GetObj(id: number) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id }
});
},
async ListAll() {
return await request({
url: apiPrefix + "/all",
method: "post"
});
}
};
}
export const pipelineGroupApi = createApi();

View File

@ -0,0 +1,323 @@
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { pipelineGroupApi } from "./api";
import { useRouter } from "vue-router";
import SuiteValueEdit from "/@/views/sys/suite/product/suite-value-edit.vue";
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
import dayjs from "dayjs";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const api = pipelineGroupApi;
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async (req: EditReq) => {
const { form, row } = req;
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async (req: DelReq) => {
const { row } = req;
return await api.DelObj(row.id);
};
const addRequest = async (req: AddReq) => {
const { form } = req;
const res = await api.AddObj(form);
return res;
};
const router = useRouter();
return {
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
form: {
labelCol: {
//固定label宽度
span: null,
style: {
width: "100px"
}
},
col: {
span: 22
},
wrapper: {
width: 600
}
},
actionbar: {
buttons: {
add: { show: false },
buy: {
text: "购买",
type: "primary",
click() {
router.push({
path: "/certd/suite/buy"
});
}
}
}
},
rowHandle: {
width: 200,
fixed: "right",
buttons: {
view: { show: false },
copy: { show: false },
edit: { show: false },
remove: { show: false }
// continue:{
// text:"续期",
// type:"link",
// click(){
// console.log("续期");
// }
// }
}
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
search: {
show: false
},
column: {
width: 100,
editable: {
disabled: true
}
},
form: {
show: false
}
},
title: {
title: "套餐名称",
type: "text",
search: {
show: true
},
form: {
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 200
}
},
productType: {
title: "类型",
type: "dict-select",
editForm: {
component: {
disabled: true
}
},
dict: dict({
data: [
{ label: "套餐", value: "suite", color: "green" },
{ label: "加量包", value: "addon", color: "blue" }
]
}),
form: {
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 80,
align: "center"
},
valueBuilder: ({ row }) => {
if (row.content) {
row.content = JSON.parse(row.content);
}
},
valueResolve: ({ form }) => {
if (form.content) {
form.content = JSON.stringify(form.content);
}
}
},
"content.maxDomainCount": {
title: "域名数量",
type: "text",
form: {
key: ["content", "maxDomainCount"],
component: {
name: SuiteValueEdit,
vModel: "modelValue",
unit: "个"
},
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 100,
component: {
name: SuiteValue,
vModel: "modelValue",
unit: "个"
},
align: "center"
}
},
"content.maxPipelineCount": {
title: "流水线数量",
type: "text",
form: {
key: ["content", "maxPipelineCount"],
component: {
name: SuiteValueEdit,
vModel: "modelValue",
unit: "条"
},
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 100,
component: {
name: SuiteValue,
vModel: "modelValue",
unit: "条"
},
align: "center"
}
},
"content.maxDeployCount": {
title: "部署次数",
type: "text",
form: {
key: ["content", "maxDeployCount"],
component: {
name: SuiteValueEdit,
vModel: "modelValue",
unit: "次"
},
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 100,
component: {
name: SuiteValue,
vModel: "modelValue",
unit: "次"
},
align: "center"
}
},
"content.maxMonitorCount": {
title: "证书监控数量",
type: "text",
form: {
key: ["content", "maxMonitorCount"],
component: {
name: SuiteValueEdit,
vModel: "modelValue",
unit: "个"
},
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 120,
component: {
name: SuiteValue,
vModel: "modelValue",
unit: "个"
},
align: "center"
}
},
duration: {
title: "时长",
type: "text",
form: {},
column: {
component: {
name: DurationValue,
vModel: "modelValue"
},
width: 100,
align: "center"
}
},
status: {
title: "状态",
type: "text",
form: { show: false },
column: {
width: 100,
align: "center",
conditionalRender: {
match() {
return false;
}
},
cellRender({ row }) {
if (row.activeTime == null) {
return <a-tag color="blue">使</a-tag>;
}
const now = dayjs().valueOf();
//已过期
const isExpired = row.expiresTime != -1 && now > row.expiresTime;
if (isExpired) {
return <a-tag color="error"></a-tag>;
}
//如果在激活时间之前
if (now < row.activeTime) {
return <a-tag color="blue"></a-tag>;
}
// 是否在激活时间和过期时间之间
if (now > row.activeTime && (row.expiresTime == -1 || now < row.expiresTime)) {
return <a-tag color="success"></a-tag>;
}
}
}
},
activeTime: {
title: "激活时间",
type: "date",
column: {
width: 150
}
},
expiresTime: {
title: "过期时间",
type: "date",
column: {
width: 150,
component: {
name: "expires-time-text",
vModel: "value",
mode: "tag"
}
}
},
isPresent: {
title: "是否赠送",
type: "dict-switch",
dict: dict({
data: [
{ label: "是", value: true, color: "success" },
{ label: "否", value: false, color: "blue" }
]
}),
form: {
value: true
},
column: {
width: 100,
align: "center"
}
}
}
}
};
}

View File

@ -0,0 +1,30 @@
<template>
<fs-page>
<template #header>
<div class="title">
我的套餐
<span class="sub">我的所有套餐</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { defineComponent, onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { createApi } from "./api";
defineOptions({
name: "MySuites"
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
</script>

View File

@ -25,14 +25,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from 'vue';
import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from "/@/views/certd/suite/api"; import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from '/@/views/certd/suite/api';
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue"; import SuiteValue from '/@/views/sys/suite/product/suite-value.vue';
import PriceInput from "/@/views/sys/suite/product/price-input.vue"; import PriceInput from '/@/views/sys/suite/product/price-input.vue';
import modal from "/@/views/certd/notification/notification-selector/modal/index.vue"; import { dict } from '@fast-crud/fast-crud';
import { dict } from "@fast-crud/fast-crud"; import { Modal, notification } from 'ant-design-vue';
import { notification } from "ant-design-vue"; import DurationValue from '/@/views/sys/suite/product/duration-value.vue';
import DurationValue from "/@/views/sys/suite/product/duration-value.vue"; import { useRouter } from 'vue-router';
const openRef = ref(false); const openRef = ref(false);
@ -60,6 +60,8 @@ const paymentsDictRef = dict({
} }
}); });
const router = useRouter();
async function orderCreate() { async function orderCreate() {
console.log("orderCreate", formRef.value); console.log("orderCreate", formRef.value);
if (!formRef.value.payType) { if (!formRef.value.payType) {
@ -80,22 +82,41 @@ async function orderCreate() {
// //
if (formRef.value.payType === "yizhifu") { if (formRef.value.payType === "yizhifu") {
doYizhifu(paymentReq); doYizhifu(paymentReq);
} else if (formRef.value.payType === "alipay") {
//
doAlipay(paymentReq);
} else if (formRef.value.payType === "wxpay") {
//
doWxpay(paymentReq);
} else { } else {
// notification.error({
window.open(paymentReq); message: "暂不支持该支付方式"
});
return;
} }
modal.open({ Modal.confirm({
title: "支付", title: "请在新页面完成支付",
content: "请在新页面完成支付", content: "是否确认已完成支付",
onOk: () => { onOk: async () => {
openRef.value = false; openRef.value = false;
router.push({
path: "/"
});
}, },
cancelText: "取消支付", cancelText: "取消支付",
onText: "已完成支付" okText: "已完成支付"
}); });
} }
function doAlipay(paymentReq: any) {
window.open(paymentReq.api);
}
function doWxpay(paymentReq: any) {
window.open(paymentReq.api);
}
function doYizhifu(paymentReq: any) { function doYizhifu(paymentReq: any) {
console.log("doYizhifu", paymentReq); console.log("doYizhifu", paymentReq);
/** /**

View File

@ -58,18 +58,10 @@ export async function DeleteBatch(ids: any[]) {
}); });
} }
export async function SetDefault(id: any) { export async function SyncStatus(id: any) {
return await request({ return await request({
url: apiPrefix + "/setDefault", url: apiPrefix + "/syncStatus",
method: "post", method: "post",
data: { id } data: { id }
}); });
} }
export async function SetDisabled(id: any, disabled: boolean) {
return await request({
url: apiPrefix + "/setDisabled",
method: "post",
data: { id, disabled }
});
}

View File

@ -60,13 +60,38 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
delRequest delRequest
}, },
rowHandle: { rowHandle: {
width: 100, width: 240,
fixed: "right", fixed: "right",
buttons: { buttons: {
edit: { show: false }, edit: { show: false },
copy: { show: false } copy: { show: false },
syncStatus: {
show: compute(({ row }) => {
return row.status === "wait_pay";
}),
text: "同步订单状态",
type: "link",
click: async ({ row }) => {
Modal.confirm({
title: "确认",
content: "确认同步订单状态?",
onOk: async () => {
await api.SyncStatus(row.id);
await crudExpose.doRefresh();
}
});
}
}
} }
}, },
actionbar: {
buttons: {
add: {
show: false
}
}
},
toolbar: { show: false },
tabs: { tabs: {
name: "status", name: "status",
show: true show: true

View File

@ -53,7 +53,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
groups: { groups: {
base: { base: {
header: "基础信息", header: "基础信息",
columns: ["title", "type", "isBootstrap", "disabled", "order", "intro"] columns: ["title", "type", "disabled", "order", "supportBuy", "intro"]
}, },
content: { content: {
header: "套餐内容", header: "套餐内容",
@ -244,12 +244,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "dict-switch", type: "dict-switch",
dict: dict({ dict: dict({
data: [ data: [
{ label: "", value: true, color: "success" }, { label: "支持购买", value: true, color: "success" },
{ label: "", value: false, color: "gray" } { label: "不能购买", value: false, color: "gray" }
] ]
}), }),
form: { form: {
value: false value: true
}, },
column: { column: {
width: 120 width: 120

View File

@ -5,6 +5,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { durationDict } from "/@/views/certd/suite/api"; import { durationDict } from "/@/views/certd/suite/api";
defineOptions({
name: "DurationValue"
});
const props = defineProps<{ const props = defineProps<{
modelValue: number; modelValue: number;
}>(); }>();

View File

@ -1,17 +1,34 @@
<template> <template>
<div class="flex-o price-input"> <div class="flex-o price-input">
<a-input-number v-if="edit" prefix="¥" :value="priceValue" :precision="2" class="ml-5" @update:value="onPriceChange"> </a-input-number> <a-input-number v-if="edit" prefix="¥" :value="priceValue" :precision="2" class="ml-5" @update:value="onPriceChange"> </a-input-number>
<span v-else class="price-text">{{ priceLabel }}</span> <span v-else class="price-text" :style="style">{{ priceLabel }}</span>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue"; import { computed } from "vue";
const props = defineProps<{ const props = withDefaults(
modelValue?: number; defineProps<{
edit?: boolean; modelValue?: number;
}>(); edit?: boolean;
fontSize?: number;
}>(),
{
modelValue: 0,
edit: false,
fontSize: 14
}
);
const style = computed(() => {
if (props.fontSize == null) {
return {};
}
return {
fontSize: props.fontSize + "px"
};
});
const priceValue = computed(() => { const priceValue = computed(() => {
if (props.modelValue == null) { if (props.modelValue == null) {
@ -37,7 +54,6 @@ const onPriceChange = (price: number) => {
<style lang="less"> <style lang="less">
.price-input { .price-input {
.price-text { .price-text {
font-size: 18px;
color: red; color: red;
} }
} }

View File

@ -58,18 +58,18 @@ export async function DeleteBatch(ids: any[]) {
}); });
} }
export async function SetDefault(id: any) { export async function UpdatePaid(id: any) {
return await request({ return await request({
url: apiPrefix + "/setDefault", url: apiPrefix + "/updatePaid",
method: "post", method: "post",
data: { id } data: { id }
}); });
} }
export async function SetDisabled(id: any, disabled: boolean) { export async function SyncStatus(id: any) {
return await request({ return await request({
url: apiPrefix + "/setDisabled", url: apiPrefix + "/syncStatus",
method: "post", method: "post",
data: { id, disabled } data: { id }
}); });
} }

View File

@ -6,6 +6,8 @@ import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq,
import { useUserStore } from "/@/store/modules/user"; import { useUserStore } from "/@/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings"; import { useSettingStore } from "/@/store/modules/settings";
import { Modal } from "ant-design-vue"; import { Modal } from "ant-design-vue";
import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter(); const router = useRouter();
@ -56,9 +58,63 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
editRequest, editRequest,
delRequest delRequest
}, },
actionbar: {
buttons: {
add: {
show: false
}
}
},
toolbar: { show: false },
rowHandle: { rowHandle: {
minWidth: 200, width: 320,
fixed: "right" fixed: "right",
buttons: {
copy: {
show: false
},
edit: {
show: false
},
syncStatus: {
show: compute(({ row }) => {
return row.status === "wait_pay";
}),
text: "同步订单状态",
type: "link",
click: async ({ row }) => {
Modal.confirm({
title: "确认",
content: "确认同步订单状态?",
onOk: async () => {
await api.SyncStatus(row.id);
await crudExpose.doRefresh();
}
});
}
},
updatePaid: {
show: compute(({ row }) => {
return row.status === "wait_pay";
}),
text: "确认已支付",
type: "link",
click({ row }) {
Modal.confirm({
title: "确认",
content: "确认修改订单状态为已支付?",
onOk: async () => {
await api.UpdatePaid(row.id);
await crudExpose.doRefresh();
}
});
}
}
}
},
tabs: {
name: "status",
show: true
}, },
columns: { columns: {
id: { id: {
@ -72,156 +128,88 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: false show: false
} }
}, },
domain: { tradeNo: {
title: "CNAME域名", title: "订单号",
type: "text",
editForm: {
component: {
disabled: true
}
},
search: {
show: true
},
form: {
component: {
placeholder: "cname.handsfree.work"
},
helper: "需要一个右边DNS提供商注册的域名也可以将其他域名的dns服务器转移到这几家来。\nCNAME域名一旦确定不可修改建议使用一级子域名",
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 200
}
},
dnsProviderType: {
title: "DNS提供商",
type: "dict-select",
search: {
show: true
},
dict: dict({
url: "pi/dnsProvider/list",
value: "key",
label: "title"
}),
form: {
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 150,
component: {
color: "auto"
}
}
},
accessId: {
title: "DNS提供商授权",
type: "dict-select",
dict: dict({
url: "/pi/access/list",
value: "id",
label: "name"
}),
form: {
component: {
name: "access-selector",
vModel: "modelValue",
type: compute(({ form }) => {
return form.dnsProviderType;
})
},
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 150,
component: {
color: "auto"
}
}
},
isDefault: {
title: "是否默认",
type: "dict-switch",
dict: dict({
data: [
{ label: "是", value: true, color: "success" },
{ label: "否", value: false, color: "default" }
]
}),
form: {
value: false,
rules: [{ required: true, message: "请选择是否默认" }]
},
column: {
align: "center",
width: 100
}
},
setDefault: {
title: "设置默认",
type: "text", type: "text",
search: { show: true },
form: { form: {
show: false show: false
}, },
column: { column: {
width: 100, width: 250
align: "center",
conditionalRenderDisabled: true,
cellRender: ({ row }) => {
if (row.isDefault) {
return;
}
const onClick = async () => {
Modal.confirm({
title: "提示",
content: `确定要设置为默认吗?`,
onOk: async () => {
await api.SetDefault(row.id);
await crudExpose.doRefresh();
}
});
};
return (
<a-button type={"link"} size={"small"} onClick={onClick}>
</a-button>
);
}
} }
}, },
disabled: { title: {
title: "禁用/启用", title: "商品名称",
type: "dict-switch", type: "text",
dict: dict({ search: { show: true },
data: [ column: {
{ label: "启用", value: false, color: "success" }, width: 150
{ label: "禁用", value: true, color: "error" } }
] },
}), duration: {
form: { title: "时长",
value: false type: "number",
},
column: { column: {
width: 100, width: 100,
component: { component: {
title: "点击可禁用/启用", name: DurationValue,
on: { vModel: "modelValue"
async click({ value, row }) {
Modal.confirm({
title: "提示",
content: `确定要${!value ? "禁用" : "启用"}吗?`,
onOk: async () => {
await api.SetDisabled(row.id, !value);
await crudExpose.doRefresh();
}
});
}
}
} }
} }
}, },
amount: {
title: "金额",
type: "number",
column: {
width: 100,
component: {
name: PriceInput,
vModel: "modelValue",
edit: false
}
}
},
status: {
title: "状态",
search: { show: true },
type: "dict-select",
dict: dict({
data: [
{ label: "待支付", value: "wait_pay", color: "warning" },
{ label: "已支付", value: "paid", color: "success" },
{ label: "已取消", value: "canceled", color: "error" }
]
}),
column: {
width: 100
}
},
payType: {
title: "支付方式",
search: { show: true },
type: "dict-select",
dict: dict({
data: [
{ label: "聚合支付", value: "yizhifu" },
{ label: "支付宝", value: "alipay" },
{ label: "微信", value: "wxpay" }
]
}),
column: {
width: 100,
component: {
color: "auto"
}
}
},
payTime: {
title: "支付时间",
type: "datetime",
column: {
width: 160
}
},
createTime: { createTime: {
title: "创建时间", title: "创建时间",
type: "datetime", type: "datetime",

View File

@ -2,11 +2,8 @@
<fs-page class="page-cert"> <fs-page class="page-cert">
<template #header> <template #header>
<div class="title"> <div class="title">
CNAME服务配置 订单管理
<span class="sub"> <span class="sub"> </span>
此处配置的域名作为其他域名校验的代理当别的域名需要申请证书时通过CNAME映射到此域名上来验证所有权好处是任何域名都可以通过此方式申请证书也无需填写AccessSecret
<a href="https://certd.docmirror.cn/guide/feature/cname/" target="_blank">CNAME功能原理及使用说明</a>
</span>
</div> </div>
</template> </template>
<fs-crud ref="crudRef" v-bind="crudBinding"> <fs-crud ref="crudRef" v-bind="crudBinding">
@ -27,7 +24,7 @@ import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api"; import { DeleteBatch } from "./api";
defineOptions({ defineOptions({
name: "CnameProvider" name: "TradeManager"
}); });
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions }); const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });

View File

@ -1,20 +1,22 @@
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core'; import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
import { Constants, CrudController } from '@certd/lib-server'; import { Constants, CrudController } from '@certd/lib-server';
import { AuthService } from '../../modules/sys/authority/service/auth-service.js'; import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
import { SiteInfoService } from '../../modules/monitor/service/site-info-service.js'; import { CertInfoService } from '../../modules/monitor/index.js';
import { PipelineService } from '../../modules/pipeline/service/pipeline-service.js';
/** /**
*
*/ */
@Provide() @Provide()
@Controller('/api/monitor/site') @Controller('/api/monitor/cert')
export class SiteInfoController extends CrudController<SiteInfoService> { export class CertInfoController extends CrudController<CertInfoService> {
@Inject() @Inject()
service: SiteInfoService; service: CertInfoService;
@Inject() @Inject()
authService: AuthService; authService: AuthService;
@Inject()
pipelineService: PipelineService;
getService(): SiteInfoService { getService(): CertInfoService {
return this.service; return this.service;
} }
@ -22,11 +24,23 @@ export class SiteInfoController extends CrudController<SiteInfoService> {
async page(@Body(ALL) body: any) { async page(@Body(ALL) body: any) {
body.query = body.query ?? {}; body.query = body.query ?? {};
body.query.userId = this.getUserId(); body.query.userId = this.getUserId();
const res = await this.service.page({ const res = await this.service.page({
query: body.query, query: body.query,
page: body.page, page: body.page,
sort: body.sort, sort: body.sort,
}); });
const records = res.records;
const pipelineIds = records.map(r => r.pipelineId);
const pipelines = await this.pipelineService.getSimplePipelines(pipelineIds);
const pMap = new Map();
for (const p of pipelines) {
pMap.set(p.id, p);
}
for (const record of records) {
record.pipeline = pMap.get(record.pipelineId);
}
return this.ok(res); return this.ok(res);
} }

View File

@ -1,20 +1,19 @@
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core'; import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
import { Constants, CrudController } from '@certd/lib-server'; import { Constants, CrudController } from '@certd/lib-server';
import { AuthService } from '../../modules/sys/authority/service/auth-service.js'; import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
import { CertInfoService } from '../../modules/monitor/service/cert-info-service.js'; import { SiteInfoService } from '../../modules/monitor/index.js';
/** /**
*
*/ */
@Provide() @Provide()
@Controller('/api/monitor/cert') @Controller('/api/monitor/site')
export class CertInfoController extends CrudController<CertInfoService> { export class SiteInfoController extends CrudController<SiteInfoService> {
@Inject() @Inject()
service: CertInfoService; service: SiteInfoService;
@Inject() @Inject()
authService: AuthService; authService: AuthService;
getService(): CertInfoService { getService(): SiteInfoService {
return this.service; return this.service;
} }
@ -22,7 +21,6 @@ export class CertInfoController extends CrudController<CertInfoService> {
async page(@Body(ALL) body: any) { async page(@Body(ALL) body: any) {
body.query = body.query ?? {}; body.query = body.query ?? {};
body.query.userId = this.getUserId(); body.query.userId = this.getUserId();
const res = await this.service.page({ const res = await this.service.page({
query: body.query, query: body.query,
page: body.page, page: body.page,

View File

@ -6,7 +6,7 @@ import { nanoid } from 'nanoid';
import crypto from 'crypto'; import crypto from 'crypto';
@Autoload() @Autoload()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoInitSite { export class AutoInitSite {
@Inject() @Inject()
userService: UserService; userService: UserService;

View File

@ -4,7 +4,7 @@ import { logger } from '@certd/basic';
import { SysSettingsService } from '@certd/lib-server'; import { SysSettingsService } from '@certd/lib-server';
@Autoload() @Autoload()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoRegisterCron { export class AutoRegisterCron {
@Inject() @Inject()
pipelineService: PipelineService; pipelineService: PipelineService;

View File

@ -9,7 +9,7 @@ import { Application } from '@midwayjs/koa';
import { httpsServer, HttpsServerOptions } from './https/server.js'; import { httpsServer, HttpsServerOptions } from './https/server.js';
@Autoload() @Autoload()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AutoZPrint { export class AutoZPrint {
@Inject() @Inject()
sysSettingsService: SysSettingsService; sysSettingsService: SysSettingsService;

View File

@ -27,7 +27,7 @@ export type EmailConfig = {
usePlus?: boolean; usePlus?: boolean;
} & SMTPConnection.Options; } & SMTPConnection.Options;
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class EmailService implements IEmailService { export class EmailService implements IEmailService {
@Inject() @Inject()
settingsService: UserSettingsService; settingsService: UserSettingsService;

View File

@ -9,7 +9,7 @@ import { CommonProviders } from './common-provider.js';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class CnameProviderService extends BaseService<CnameProviderEntity> { export class CnameProviderService extends BaseService<CnameProviderEntity> {
@InjectEntityModel(CnameProviderEntity) @InjectEntityModel(CnameProviderEntity)
repository: Repository<CnameProviderEntity>; repository: Repository<CnameProviderEntity>;

View File

@ -26,7 +26,7 @@ type CnameCheckCacheValue = {
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class CnameRecordService extends BaseService<CnameRecordEntity> { export class CnameRecordService extends BaseService<CnameRecordEntity> {
@InjectEntityModel(CnameRecordEntity) @InjectEntityModel(CnameRecordEntity)
repository: Repository<CnameRecordEntity>; repository: Repository<CnameRecordEntity>;

View File

@ -5,7 +5,7 @@ import { SqlAdapter } from './d.js';
import { MysqlAdapter } from './mysql.js'; import { MysqlAdapter } from './mysql.js';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class DbAdapter implements SqlAdapter { export class DbAdapter implements SqlAdapter {
adapter: SqlAdapter; adapter: SqlAdapter;
@Config('typeorm.dataSource.default.type') @Config('typeorm.dataSource.default.type')

View File

@ -8,7 +8,7 @@ import { UserSettingsEntity } from '../entity/user-settings.js';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class UserSettingsService extends BaseService<UserSettingsEntity> { export class UserSettingsService extends BaseService<UserSettingsEntity> {
@InjectEntityModel(UserSettingsEntity) @InjectEntityModel(UserSettingsEntity)
repository: Repository<UserSettingsEntity>; repository: Repository<UserSettingsEntity>;

View File

@ -1,4 +1,5 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { PipelineEntity } from '../../pipeline/entity/pipeline.js';
@Entity('cd_cert_info') @Entity('cd_cert_info')
export class CertInfoEntity { export class CertInfoEntity {
@ -51,4 +52,6 @@ export class CertInfoEntity {
default: () => 'CURRENT_TIMESTAMP', default: () => 'CURRENT_TIMESTAMP',
}) })
updateTime: Date; updateTime: Date;
pipeline?: PipelineEntity;
} }

View File

@ -0,0 +1,5 @@
export * from "./entity/site-info.js";
export * from "./entity/cert-info.js";
export * from "./service/cert-info-service.js";
export * from "./service/site-info-service.js";

View File

@ -1,5 +1,5 @@
import { Provide } from '@midwayjs/core'; import { Provide } from '@midwayjs/core';
import { BaseService } from '@certd/lib-server'; import { BaseService, PageReq } from '@certd/lib-server';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { CertInfoEntity } from '../entity/cert-info.js'; import { CertInfoEntity } from '../entity/cert-info.js';
@ -14,6 +14,10 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
return this.repository; return this.repository;
} }
async page(pageReq: PageReq<CertInfoEntity>) {
return await super.page(pageReq);
}
async getUserDomainCount(userId: number) { async getUserDomainCount(userId: number) {
if (!userId) { if (!userId) {
throw new Error('userId is required'); throw new Error('userId is required');
@ -23,10 +27,11 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
}); });
} }
async updateDomains(pipelineId: number, domains: string[]) { async updateDomains(pipelineId: number, userId: number, domains: string[]) {
const found = await this.repository.findOne({ const found = await this.repository.findOne({
where: { where: {
pipelineId, pipelineId,
userId,
}, },
}); });
const bean = new CertInfoEntity(); const bean = new CertInfoEntity();
@ -36,11 +41,21 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
} else { } else {
//create //create
bean.pipelineId = pipelineId; bean.pipelineId = pipelineId;
bean.userId = userId;
if (!domains || domains.length === 0) {
return;
}
} }
bean.domain = domains[0]; if (!domains || domains.length === 0) {
bean.domains = domains.join(','); bean.domain = '';
bean.domainCount = domains.length; bean.domains = '';
bean.domainCount = 0;
} else {
bean.domain = domains[0];
bean.domains = domains.join(',');
bean.domainCount = domains.length;
}
await this.addOrUpdate(bean); await this.addOrUpdate(bean);
} }

View File

@ -3,7 +3,7 @@ import { pluginGroups, pluginRegistry } from '@certd/pipeline';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class BuiltInPluginService { export class BuiltInPluginService {
getList() { getList() {
const collection = pluginRegistry.storage; const collection = pluginRegistry.storage;

View File

@ -8,7 +8,7 @@ import { HistoryLogEntity } from '../entity/history-log.js';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class HistoryLogService extends BaseService<HistoryLogEntity> { export class HistoryLogService extends BaseService<HistoryLogEntity> {
@InjectEntityModel(HistoryLogEntity) @InjectEntityModel(HistoryLogEntity)
repository: Repository<HistoryLogEntity>; repository: Repository<HistoryLogEntity>;

View File

@ -16,7 +16,7 @@ import { logger } from '@certd/basic';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class HistoryService extends BaseService<HistoryEntity> { export class HistoryService extends BaseService<HistoryEntity> {
@InjectEntityModel(HistoryEntity) @InjectEntityModel(HistoryEntity)
repository: Repository<HistoryEntity>; repository: Repository<HistoryEntity>;

View File

@ -9,7 +9,7 @@ import { EmailService } from '../../basic/service/email-service.js';
import { isComm } from '@certd/plus-core'; import { isComm } from '@certd/plus-core';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class NotificationService extends BaseService<NotificationEntity> { export class NotificationService extends BaseService<NotificationEntity> {
@InjectEntityModel(NotificationEntity) @InjectEntityModel(NotificationEntity)
repository: Repository<NotificationEntity>; repository: Repository<NotificationEntity>;

View File

@ -6,7 +6,7 @@ import { PipelineGroupEntity } from '../entity/pipeline-group.js';
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class PipelineGroupService extends BaseService<PipelineGroupEntity> { export class PipelineGroupService extends BaseService<PipelineGroupEntity> {
@InjectEntityModel(PipelineGroupEntity) @InjectEntityModel(PipelineGroupEntity)
repository: Repository<PipelineGroupEntity>; repository: Repository<PipelineGroupEntity>;

View File

@ -160,14 +160,17 @@ export class PipelineService extends BaseService<PipelineEntity> {
old = await this.info(bean.id); old = await this.info(bean.id);
} }
const pipeline = JSON.parse(bean.content || '{}'); const pipeline = JSON.parse(bean.content || '{}');
RunnableCollection.initPipelineRunnableType(pipeline);
const isUpdate = bean.id > 0 && old != null; const isUpdate = bean.id > 0 && old != null;
let domains = []; let domains = [];
RunnableCollection.each(pipeline.stages, (runnable: any) => { if (pipeline.stages) {
if (runnable.runnableType === 'step' && runnable.type.startsWith('CertApply')) { RunnableCollection.each(pipeline.stages, (runnable: any) => {
domains = runnable.input.domains || []; if (runnable.runnableType === 'step' && runnable.type.startsWith('CertApply')) {
} domains = runnable.input.domains || [];
}); }
});
}
if (!isUpdate) { if (!isUpdate) {
//如果是添加,校验数量 //如果是添加,校验数量
@ -188,7 +191,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
await this.registerTriggerById(bean.id); await this.registerTriggerById(bean.id);
//保存域名信息到certInfo表 //保存域名信息到certInfo表
await this.certInfoService.updateDomains(pipeline.id, domains); await this.certInfoService.updateDomains(pipeline.id, pipeline.userId || bean.userId, domains);
return bean; return bean;
} }
@ -622,4 +625,17 @@ export class PipelineService extends BaseService<PipelineEntity> {
async getUserPipelineCount(userId) { async getUserPipelineCount(userId) {
return await this.repository.count({ where: { userId } }); return await this.repository.count({ where: { userId } });
} }
async getSimplePipelines(pipelineIds: number[], userId?: number) {
return await this.repository.find({
select: {
id: true,
title: true,
},
where: {
id: In(pipelineIds),
userId,
},
});
}
} }

View File

@ -7,7 +7,7 @@ import { StorageEntity } from '../entity/storage.js';
/** /**
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class StorageService extends BaseService<StorageEntity> { export class StorageService extends BaseService<StorageEntity> {
@InjectEntityModel(StorageEntity) @InjectEntityModel(StorageEntity)
repository: Repository<StorageEntity>; repository: Repository<StorageEntity>;

View File

@ -3,7 +3,7 @@ import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { SysInstallInfo, SysSettingsService } from '@certd/lib-server'; import { SysInstallInfo, SysSettingsService } from '@certd/lib-server';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class UrlService implements IUrlService { export class UrlService implements IUrlService {
@Inject() @Inject()
sysSettingsService: SysSettingsService; sysSettingsService: SysSettingsService;

View File

@ -3,7 +3,7 @@ import { IPluginConfigService, PluginConfig } from '@certd/pipeline';
import { PluginConfigService } from './plugin-config-service.js'; import { PluginConfigService } from './plugin-config-service.js';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class PluginConfigGetter implements IPluginConfigService { export class PluginConfigGetter implements IPluginConfigService {
@Inject() @Inject()
pluginConfigService: PluginConfigService; pluginConfigService: PluginConfigService;

View File

@ -19,7 +19,7 @@ export type PluginFindReq = {
type: string; type: string;
}; };
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class PluginConfigService { export class PluginConfigService {
@Inject() @Inject()
pluginService: PluginService; pluginService: PluginService;

View File

@ -8,7 +8,7 @@ import { BuiltInPluginService } from '../../pipeline/service/builtin-plugin-serv
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class PluginService extends BaseService<PluginEntity> { export class PluginService extends BaseService<PluginEntity> {
@InjectEntityModel(PluginEntity) @InjectEntityModel(PluginEntity)
repository: Repository<PluginEntity>; repository: Repository<PluginEntity>;

View File

@ -5,7 +5,7 @@ import { RoleService } from './role-service.js';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AuthService { export class AuthService {
@Inject() @Inject()
roleService: RoleService; roleService: RoleService;

View File

@ -8,7 +8,7 @@ import { PermissionEntity } from '../entity/permission.js';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class PermissionService extends BaseService<PermissionEntity> { export class PermissionService extends BaseService<PermissionEntity> {
@InjectEntityModel(PermissionEntity) @InjectEntityModel(PermissionEntity)
repository: Repository<PermissionEntity>; repository: Repository<PermissionEntity>;

View File

@ -8,7 +8,7 @@ import { RolePermissionEntity } from '../entity/role-permission.js';
* -> * ->
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class RolePermissionService extends BaseService<RolePermissionEntity> { export class RolePermissionService extends BaseService<RolePermissionEntity> {
@InjectEntityModel(RolePermissionEntity) @InjectEntityModel(RolePermissionEntity)
repository: Repository<RolePermissionEntity>; repository: Repository<RolePermissionEntity>;

View File

@ -13,7 +13,7 @@ import { LRUCache } from 'lru-cache';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class RoleService extends BaseService<RoleEntity> { export class RoleService extends BaseService<RoleEntity> {
@InjectEntityModel(RoleEntity) @InjectEntityModel(RoleEntity)
repository: Repository<RoleEntity>; repository: Repository<RoleEntity>;

View File

@ -8,7 +8,7 @@ import { UserRoleEntity } from '../entity/user-role.js';
* -> * ->
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class UserRoleService extends BaseService<UserRoleEntity> { export class UserRoleService extends BaseService<UserRoleEntity> {
@InjectEntityModel(UserRoleEntity) @InjectEntityModel(UserRoleEntity)
repository: Repository<UserRoleEntity>; repository: Repository<UserRoleEntity>;

View File

@ -19,7 +19,7 @@ export type RegisterType = 'username' | 'mobile' | 'email';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class UserService extends BaseService<UserEntity> { export class UserService extends BaseService<UserEntity> {
@InjectEntityModel(UserEntity) @InjectEntityModel(UserEntity)
repository: Repository<UserEntity>; repository: Repository<UserEntity>;