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

v2
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;
}
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) {
list.forEach((item) => {
exec(item);

View File

@ -39,4 +39,12 @@ export abstract class BaseController {
}
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;
}
const qb = this.buildListQuery(pageReq);
qb.offset(page.offset).limit(page.limit);
const list = await qb.getMany();
const total = await qb.getCount();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,13 @@
<template>
<span class="cd-expires-time-text">
<template v-if="label != null">
{{ label }}
</template>
<template v-else>
<FsTimeHumanize :model-value="value" :use-format-greater="1000000000000" :options="{ units: ['d'] }"></FsTimeHumanize>
</template>
<component :is="wrapperComp" :color="color">
<template v-if="label != null">
{{ label }}
</template>
<template v-else>
<FsTimeHumanize :model-value="value" :use-format-greater="1000000000000" :options="{ units: ['d'] }"></FsTimeHumanize>
</template>
</component>
</span>
</template>
@ -19,8 +21,32 @@ defineOptions({
const props = defineProps<{
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(() => {
if (props.value == null) {
return "";

View File

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

View File

@ -1,6 +1,6 @@
<template>
<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">
<img :src="siteInfo.logo" />
<span v-if="!asideCollapsed" class="title">{{ siteInfo.title }}</span>

View File

@ -39,98 +39,146 @@ export const certdResources = [
}
},
{
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: "站点证书监控",
title: "证书监控",
name: "SiteCertMonitor",
path: "/certd/monitor/site",
component: "/certd/monitor/site/index.vue",
meta: {
icon: "ion:person-outline",
icon: "ion:videocam-outline",
auth: true
}
},
{
title: "证书仓库",
name: "CertStore",
path: "/certd/monitor/cert",
component: "/certd/monitor/cert/index.vue",
title: "设置",
name: "MineSetting",
path: "/certd/mine",
meta: {
icon: "ion:person-outline",
auth: true
}
icon: "ion:settings-outline",
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: "邮箱设置",
// name: "EmailSetting",
@ -141,17 +189,6 @@ export const certdResources = [
// 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",
component: "/sys/suite/setting/index.vue",
meta: {
icon: "ion:person-outline",
icon: "ion:cart",
permission: "sys:settings:edit"
}
},
@ -186,7 +186,7 @@ export const sysResources = [
path: "/sys/suite/trade",
component: "/sys/suite/trade/index.vue",
meta: {
icon: "ion:person-outline",
icon: "ion:bag-check",
permission: "sys:settings:edit"
}
}

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<template>
<div class="access-selector">
<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>
</span>
<span v-else class="mlr-5 text-gray">{{ placeholder }}</span>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { request } from "/src/api/service";
export function createApi() {
const apiPrefix = "/pi/pipeline/group";
const apiPrefix = "/monitor/site";
return {
async GetList(query: any) {
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: {
title: "站点名称",
search: {
@ -82,35 +110,22 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
rules: [{ required: true, message: "请输入站点名称" }]
},
column: {
width: 100
}
},
domain: {
title: "主域名",
search: {
show: true
},
type: "text",
form: {
rules: [{ required: true, message: "请输入域名" }]
},
column: {
width: 100,
sorter: true
width: 200
}
},
domains: {
title: "其他域名",
search: {
show: true
show: false
},
type: "text",
form: {
rules: [{ required: true, message: "请输入其他域名" }]
show: false
},
column: {
width: 300,
sorter: true
sorter: true,
show: false
}
},
certInfo: {
@ -123,7 +138,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: false
},
column: {
width: 100
width: 100,
show: false
}
},
certStatus: {
@ -185,6 +201,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
search: {
show: false
},
form: { show: false },
type: "number",
column: {
width: 200,
@ -197,7 +214,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: false
},
type: "number",
form: {},
form: { show: false },
column: {
width: 100,
sorter: true,
@ -212,11 +229,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "dict-switch",
dict: dict({
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: {
width: 100,
sorter: true

View File

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

View File

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

View File

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

View File

@ -4,13 +4,17 @@
<div class="title">套餐购买</div>
</template>
<div class="suite-buy-content">
<div class="mb-10">
<a-card>
<div>套餐说明多个套餐内的数量可以叠加</div>
<div v-if="suiteIntro" v-html="suiteIntro"></div>
</a-card>
</div>
<a-row :gutter="8">
<a-row class="w-100" :gutter="8">
<a-col :span="24">
<a-card>
<div class="suite-intro-box">
<div>套餐说明多个套餐内的数量可以叠加</div>
<div v-if="suiteIntro" v-html="suiteIntro"></div>
</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">
<product-info :product="item" @order="doOrder" />
</a-col>
@ -57,11 +61,23 @@ loadSuiteIntro();
background: #f0f2f5;
.suite-buy-content {
padding: 20px;
display: flex;
flex-direction: column;
align-items: baseline;
.hr {
border-top: 1px solid #cdcdcd;
margin-top: 5px;
padding-top: 5px;
.suite-intro-box {
//height: 60px;
//overflow: hidden;
//text-overflow: ellipsis;
}
.suite-list {
display: flex;
align-items: baseline;
}
.my-suites {
width: 360px;
margin-left: 10px;
}
.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>
<script setup lang="ts">
import { ref } from "vue";
import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from "/@/views/certd/suite/api";
import SuiteValue from "/@/views/sys/suite/product/suite-value.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 { notification } from "ant-design-vue";
import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
import { ref } from 'vue';
import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from '/@/views/certd/suite/api';
import SuiteValue from '/@/views/sys/suite/product/suite-value.vue';
import PriceInput from '/@/views/sys/suite/product/price-input.vue';
import { dict } from '@fast-crud/fast-crud';
import { Modal, notification } from 'ant-design-vue';
import DurationValue from '/@/views/sys/suite/product/duration-value.vue';
import { useRouter } from 'vue-router';
const openRef = ref(false);
@ -60,6 +60,8 @@ const paymentsDictRef = dict({
}
});
const router = useRouter();
async function orderCreate() {
console.log("orderCreate", formRef.value);
if (!formRef.value.payType) {
@ -80,22 +82,41 @@ async function orderCreate() {
//
if (formRef.value.payType === "yizhifu") {
doYizhifu(paymentReq);
} else if (formRef.value.payType === "alipay") {
//
doAlipay(paymentReq);
} else if (formRef.value.payType === "wxpay") {
//
doWxpay(paymentReq);
} else {
//
window.open(paymentReq);
notification.error({
message: "暂不支持该支付方式"
});
return;
}
modal.open({
title: "支付",
content: "请在新页面完成支付",
onOk: () => {
Modal.confirm({
title: "请在新页面完成支付",
content: "是否确认已完成支付",
onOk: async () => {
openRef.value = false;
router.push({
path: "/"
});
},
cancelText: "取消支付",
onText: "已完成支付"
okText: "已完成支付"
});
}
function doAlipay(paymentReq: any) {
window.open(paymentReq.api);
}
function doWxpay(paymentReq: any) {
window.open(paymentReq.api);
}
function doYizhifu(paymentReq: any) {
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({
url: apiPrefix + "/setDefault",
url: apiPrefix + "/syncStatus",
method: "post",
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
},
rowHandle: {
width: 100,
width: 240,
fixed: "right",
buttons: {
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: {
name: "status",
show: true

View File

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

View File

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

View File

@ -1,17 +1,34 @@
<template>
<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>
<span v-else class="price-text">{{ priceLabel }}</span>
<span v-else class="price-text" :style="style">{{ priceLabel }}</span>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
const props = defineProps<{
modelValue?: number;
edit?: boolean;
}>();
const props = withDefaults(
defineProps<{
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(() => {
if (props.modelValue == null) {
@ -37,7 +54,6 @@ const onPriceChange = (price: number) => {
<style lang="less">
.price-input {
.price-text {
font-size: 18px;
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({
url: apiPrefix + "/setDefault",
url: apiPrefix + "/updatePaid",
method: "post",
data: { id }
});
}
export async function SetDisabled(id: any, disabled: boolean) {
export async function SyncStatus(id: any) {
return await request({
url: apiPrefix + "/setDisabled",
url: apiPrefix + "/syncStatus",
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 { useSettingStore } from "/@/store/modules/settings";
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 {
const router = useRouter();
@ -56,9 +58,63 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
editRequest,
delRequest
},
actionbar: {
buttons: {
add: {
show: false
}
}
},
toolbar: { show: false },
rowHandle: {
minWidth: 200,
fixed: "right"
width: 320,
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: {
id: {
@ -72,156 +128,88 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: false
}
},
domain: {
title: "CNAME域名",
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: "设置默认",
tradeNo: {
title: "订单号",
type: "text",
search: { show: true },
form: {
show: false
},
column: {
width: 100,
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>
);
}
width: 250
}
},
disabled: {
title: "禁用/启用",
type: "dict-switch",
dict: dict({
data: [
{ label: "启用", value: false, color: "success" },
{ label: "禁用", value: true, color: "error" }
]
}),
form: {
value: false
},
title: {
title: "商品名称",
type: "text",
search: { show: true },
column: {
width: 150
}
},
duration: {
title: "时长",
type: "number",
column: {
width: 100,
component: {
title: "点击可禁用/启用",
on: {
async click({ value, row }) {
Modal.confirm({
title: "提示",
content: `确定要${!value ? "禁用" : "启用"}吗?`,
onOk: async () => {
await api.SetDisabled(row.id, !value);
await crudExpose.doRefresh();
}
});
}
}
name: DurationValue,
vModel: "modelValue"
}
}
},
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: {
title: "创建时间",
type: "datetime",

View File

@ -2,11 +2,8 @@
<fs-page class="page-cert">
<template #header>
<div class="title">
CNAME服务配置
<span class="sub">
此处配置的域名作为其他域名校验的代理当别的域名需要申请证书时通过CNAME映射到此域名上来验证所有权好处是任何域名都可以通过此方式申请证书也无需填写AccessSecret
<a href="https://certd.docmirror.cn/guide/feature/cname/" target="_blank">CNAME功能原理及使用说明</a>
</span>
订单管理
<span class="sub"> </span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
@ -27,7 +24,7 @@ import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
defineOptions({
name: "CnameProvider"
name: "TradeManager"
});
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 { Constants, CrudController } from '@certd/lib-server';
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()
@Controller('/api/monitor/site')
export class SiteInfoController extends CrudController<SiteInfoService> {
@Controller('/api/monitor/cert')
export class CertInfoController extends CrudController<CertInfoService> {
@Inject()
service: SiteInfoService;
service: CertInfoService;
@Inject()
authService: AuthService;
@Inject()
pipelineService: PipelineService;
getService(): SiteInfoService {
getService(): CertInfoService {
return this.service;
}
@ -22,11 +24,23 @@ export class SiteInfoController extends CrudController<SiteInfoService> {
async page(@Body(ALL) body: any) {
body.query = body.query ?? {};
body.query.userId = this.getUserId();
const res = await this.service.page({
query: body.query,
page: body.page,
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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { PipelineEntity } from '../../pipeline/entity/pipeline.js';
@Entity('cd_cert_info')
export class CertInfoEntity {
@ -51,4 +52,6 @@ export class CertInfoEntity {
default: () => 'CURRENT_TIMESTAMP',
})
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 { BaseService } from '@certd/lib-server';
import { BaseService, PageReq } from '@certd/lib-server';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { CertInfoEntity } from '../entity/cert-info.js';
@ -14,6 +14,10 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
return this.repository;
}
async page(pageReq: PageReq<CertInfoEntity>) {
return await super.page(pageReq);
}
async getUserDomainCount(userId: number) {
if (!userId) {
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({
where: {
pipelineId,
userId,
},
});
const bean = new CertInfoEntity();
@ -36,11 +41,21 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
} else {
//create
bean.pipelineId = pipelineId;
bean.userId = userId;
if (!domains || domains.length === 0) {
return;
}
}
bean.domain = domains[0];
bean.domains = domains.join(',');
bean.domainCount = domains.length;
if (!domains || domains.length === 0) {
bean.domain = '';
bean.domains = '';
bean.domainCount = 0;
} else {
bean.domain = domains[0];
bean.domains = domains.join(',');
bean.domainCount = domains.length;
}
await this.addOrUpdate(bean);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -160,14 +160,17 @@ export class PipelineService extends BaseService<PipelineEntity> {
old = await this.info(bean.id);
}
const pipeline = JSON.parse(bean.content || '{}');
RunnableCollection.initPipelineRunnableType(pipeline);
const isUpdate = bean.id > 0 && old != null;
let domains = [];
RunnableCollection.each(pipeline.stages, (runnable: any) => {
if (runnable.runnableType === 'step' && runnable.type.startsWith('CertApply')) {
domains = runnable.input.domains || [];
}
});
if (pipeline.stages) {
RunnableCollection.each(pipeline.stages, (runnable: any) => {
if (runnable.runnableType === 'step' && runnable.type.startsWith('CertApply')) {
domains = runnable.input.domains || [];
}
});
}
if (!isUpdate) {
//如果是添加,校验数量
@ -188,7 +191,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
await this.registerTriggerById(bean.id);
//保存域名信息到certInfo表
await this.certInfoService.updateDomains(pipeline.id, domains);
await this.certInfoService.updateDomains(pipeline.id, pipeline.userId || bean.userId, domains);
return bean;
}
@ -622,4 +625,17 @@ export class PipelineService extends BaseService<PipelineEntity> {
async getUserPipelineCount(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()
@Scope(ScopeEnum.Singleton)
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class StorageService extends BaseService<StorageEntity> {
@InjectEntityModel(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';
@Provide()
@Scope(ScopeEnum.Singleton)
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class UrlService implements IUrlService {
@Inject()
sysSettingsService: SysSettingsService;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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