perf: 同一时间只允许一个套餐生效

pull/330/head
xiaojunnuo 2024-12-24 10:39:54 +08:00
parent 7f596ed315
commit 8ebf95a222
10 changed files with 120 additions and 86 deletions

View File

@ -102,6 +102,7 @@ export class Executor {
await this.notification("success"); await this.notification("success");
} }
} }
return result;
} catch (e: any) { } catch (e: any) {
await this.notification("error", e); await this.notification("error", e);
this.logger.error("pipeline 执行失败", e); this.logger.error("pipeline 执行失败", e);

View File

@ -8,14 +8,21 @@
<a-col :span="24"> <a-col :span="24">
<a-card> <a-card>
<div class="suite-intro-box"> <div class="suite-intro-box">
<div>套餐说明多个套餐内的数量可以叠加</div> <div>说明同一时间只有最新购买的一个套餐生效 可以购买多个加量包加量包立即生效 套餐和加量包内的数量可以叠加</div>
<div v-if="suiteIntro" v-html="suiteIntro"></div> <div v-if="suiteIntro" v-html="suiteIntro"></div>
</div> </div>
</a-card> </a-card>
</a-col> </a-col>
</a-row> </a-row>
<h3>套餐</h3>
<a-row :gutter="8" class="mt-10"> <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 suites" :key="item.id" class="mb-10 suite-card-col">
<product-info :product="item" @order="doOrder" />
</a-col>
</a-row>
<h3>加量包</h3>
<a-row :gutter="8" class="mt-10">
<a-col v-for="item of addons" :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>
</a-row> </a-row>
@ -31,10 +38,13 @@ import * as api from "./api";
import ProductInfo from "/@/views/certd/suite/product-info.vue"; import ProductInfo from "/@/views/certd/suite/product-info.vue";
import OrderModal from "/@/views/certd/suite/order-modal.vue"; import OrderModal from "/@/views/certd/suite/order-modal.vue";
const products = ref([]); const suites = ref([]);
const addons = ref([]);
async function loadProducts() { async function loadProducts() {
products.value = await api.ProductList(); const list = await api.ProductList();
suites.value = list.filter((x: any) => x.type === "suite");
addons.value = list.filter((x: any) => x.type === "addone");
} }
loadProducts(); loadProducts();

View File

@ -1,54 +1,71 @@
import { request } from "/src/api/service"; import { request } from "/src/api/service";
export function createApi() { const apiPrefix = "/mine/suite";
const apiPrefix = "/mine/suite";
return {
async GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query
});
},
async AddObj(obj: any) { export type SuiteValue = {
return await request({ max: number;
url: apiPrefix + "/add", used: number;
method: "post", };
data: obj export type SuiteDetail = {
}); enabled?: boolean;
}, suites?: any[];
expiresTime?: number;
pipelineCount?: SuiteValue;
domainCount?: SuiteValue;
deployCount?: SuiteValue;
monitorCount?: SuiteValue;
};
async UpdateObj(obj: any) { export default {
return await request({ async GetList(query: any) {
url: apiPrefix + "/update", return await request({
method: "post", url: apiPrefix + "/page",
data: obj method: "post",
}); data: query
}, });
},
async DelObj(id: number) { async AddObj(obj: any) {
return await request({ return await request({
url: apiPrefix + "/delete", url: apiPrefix + "/add",
method: "post", method: "post",
params: { id } data: obj
}); });
}, },
async GetObj(id: number) { async UpdateObj(obj: any) {
return await request({ return await request({
url: apiPrefix + "/info", url: apiPrefix + "/update",
method: "post", method: "post",
params: { id } data: obj
}); });
}, },
async ListAll() {
return await request({
url: apiPrefix + "/all",
method: "post"
});
}
};
}
export const pipelineGroupApi = createApi(); 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"
});
},
async SuiteDetailGet() {
return await request({
url: `${apiPrefix}/detail`,
method: "post"
});
}
};

View File

@ -1,5 +1,5 @@
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { pipelineGroupApi } from "./api"; import api from "./api";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import SuiteValueEdit from "/@/views/sys/suite/product/suite-value-edit.vue"; import SuiteValueEdit from "/@/views/sys/suite/product/suite-value-edit.vue";
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue"; import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
@ -7,7 +7,6 @@ import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const api = pipelineGroupApi;
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query); return await api.GetList(query);
}; };
@ -274,6 +273,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
if (now < row.activeTime) { if (now < row.activeTime) {
return <a-tag color="blue"></a-tag>; return <a-tag color="blue"></a-tag>;
} }
//是否是当前套餐
const suites = context.detail.value.suites;
if (suites && suites.length > 0) {
const suite = suites[0];
if (suite.productType === "suite" && suite.id === row.id) {
return <a-tag color="success"></a-tag>;
}
}
// 是否在激活时间和过期时间之间 // 是否在激活时间和过期时间之间
if (now > row.activeTime && (row.expiresTime == -1 || now < row.expiresTime)) { if (now > row.activeTime && (row.expiresTime == -1 || now < row.expiresTime)) {
return <a-tag color="success"></a-tag>; return <a-tag color="success"></a-tag>;

View File

@ -11,18 +11,25 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineComponent, onActivated, onMounted } from "vue"; import { onActivated, onMounted, ref } from "vue";
import { useFs } from "@fast-crud/fast-crud"; import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud"; import createCrudOptions from "./crud";
import { createApi } from "./api"; import api, { SuiteDetail } from "/@/views/certd/suite/mine/api";
defineOptions({ defineOptions({
name: "MySuites" name: "MySuites"
}); });
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} }); const detail = ref<SuiteDetail>({});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { detail } });
async function loadSuiteDetail() {
detail.value = await api.SuiteDetailGet();
}
// //
onMounted(() => { onMounted(async () => {
crudExpose.doRefresh(); await loadSuiteDetail();
await crudExpose.doRefresh();
}); });
onActivated(() => { onActivated(() => {
crudExpose.doRefresh(); crudExpose.doRefresh();

View File

@ -38,38 +38,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue"; import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
import { computed, ref } from "vue"; import { ref } from "vue";
import { request } from "/@/api/service";
import dayjs from "dayjs";
import ExpiresTimeText from "/@/components/expires-time-text.vue"; import ExpiresTimeText from "/@/components/expires-time-text.vue";
import api, { SuiteDetail } from "/@/views/certd/suite/mine/api";
defineOptions({ defineOptions({
name: "SuiteCard" name: "SuiteCard"
}); });
type SuiteValue = {
max: number;
used: number;
};
type SuiteDetail = {
enabled?: boolean;
suites?: any[];
expiresTime?: number;
pipelineCount?: SuiteValue;
domainCount?: SuiteValue;
deployCount?: SuiteValue;
monitorCount?: SuiteValue;
};
const detail = ref<SuiteDetail>({}); const detail = ref<SuiteDetail>({});
const api = {
async SuiteDetailGet() {
return await request({
url: "/mine/suite/detail",
method: "post"
});
}
};
async function loadSuiteDetail() { async function loadSuiteDetail() {
detail.value = await api.SuiteDetailGet(); detail.value = await api.SuiteDetailGet();
} }

View File

@ -108,7 +108,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
form: { form: {
value: "suite", value: "suite",
rules: [{ required: true, message: "此项必填" }], rules: [{ required: true, message: "此项必填" }],
helper: "目前没区别,重复购买可叠加数量" helper: "套餐:同一时间只有最新购买的一个生效\n加量包可购买多个购买后立即生效不影响套餐\n套餐和加量包数量可叠加"
}, },
column: { column: {
width: 80, width: 80,

View File

@ -57,6 +57,7 @@ CREATE TABLE "cd_user_suite"
"deploy_count_used" integer, "deploy_count_used" integer,
"is_present" boolean, "is_present" boolean,
"is_bootstrap" boolean, "is_bootstrap" boolean,
"is_empty" boolean,
"disabled" boolean NOT NULL DEFAULT (false), "disabled" boolean NOT NULL DEFAULT (false),
"active_time" integer, "active_time" integer,
"expires_time" integer, "expires_time" integer,

View File

@ -72,7 +72,7 @@ const development = {
type: 'better-sqlite3', type: 'better-sqlite3',
database: './data/db.sqlite', database: './data/db.sqlite',
synchronize: false, // 如果第一次使用,不存在表,有同步的需求可以写 true synchronize: false, // 如果第一次使用,不存在表,有同步的需求可以写 true
logging: false, logging: true,
highlightSql: false, highlightSql: false,
// 配置实体模型 或者 entities: '/entity', // 配置实体模型 或者 entities: '/entity',

View File

@ -34,7 +34,7 @@ import { logger } from '@certd/basic';
import { UrlService } from './url-service.js'; import { UrlService } from './url-service.js';
import { NotificationService } from './notification-service.js'; import { NotificationService } from './notification-service.js';
import { NotificationGetter } from './notification-getter.js'; import { NotificationGetter } from './notification-getter.js';
import { UserSuiteService } from '@certd/commercial-core'; import { UserSuiteEntity, UserSuiteService } from '@certd/commercial-core';
import { CertInfoService } from '../../monitor/service/cert-info-service.js'; import { CertInfoService } from '../../monitor/service/cert-info-service.js';
const runningTasks: Map<string | number, Executor> = new Map(); const runningTasks: Map<string | number, Executor> = new Map();
@ -391,6 +391,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
async run(id: number, triggerId: string, stepId?: string) { async run(id: number, triggerId: string, stepId?: string) {
const entity: PipelineEntity = await this.info(id); const entity: PipelineEntity = await this.info(id);
let suite: UserSuiteEntity = null;
if (isComm()) {
suite = await this.userSuiteService.checkHasDeployCount(entity.userId);
}
const pipeline = JSON.parse(entity.content); const pipeline = JSON.parse(entity.content);
if (!pipeline.id) { if (!pipeline.id) {
pipeline.id = id; pipeline.id = id;
@ -464,7 +469,14 @@ export class PipelineService extends BaseService<PipelineEntity> {
// 清除该step的状态 // 清除该step的状态
executor.clearLastStatus(stepId); executor.clearLastStatus(stepId);
} }
await executor.run(historyId, triggerType); const result = await executor.run(historyId, triggerType);
if (result === ResultType.success) {
if (isComm()) {
// 消耗成功次数
await this.userSuiteService.consumeDeployCount(suite, 1);
}
}
} catch (e) { } catch (e) {
logger.error('执行失败:', e); logger.error('执行失败:', e);
// throw e; // throw e;