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");
}
}
return result;
} catch (e: any) {
await this.notification("error", e);
this.logger.error("pipeline 执行失败", e);

View File

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

View File

@ -1,54 +1,71 @@
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
});
},
const apiPrefix = "/mine/suite";
async AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
},
export type SuiteValue = {
max: number;
used: number;
};
export type SuiteDetail = {
enabled?: boolean;
suites?: any[];
expiresTime?: number;
pipelineCount?: SuiteValue;
domainCount?: SuiteValue;
deployCount?: SuiteValue;
monitorCount?: SuiteValue;
};
async UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
},
export default {
async GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query
});
},
async DelObj(id: number) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
},
async AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
},
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 UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
},
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 { pipelineGroupApi } from "./api";
import api 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";
@ -7,7 +7,6 @@ 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);
};
@ -274,6 +273,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
if (now < row.activeTime) {
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)) {
return <a-tag color="success"></a-tag>;

View File

@ -11,18 +11,25 @@
</template>
<script lang="ts" setup>
import { defineComponent, onActivated, onMounted } from "vue";
import { onActivated, onMounted, ref } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { createApi } from "./api";
import api, { SuiteDetail } from "/@/views/certd/suite/mine/api";
defineOptions({
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(() => {
crudExpose.doRefresh();
onMounted(async () => {
await loadSuiteDetail();
await crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();

View File

@ -38,38 +38,16 @@
<script lang="ts" setup>
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
import { computed, ref } from "vue";
import { request } from "/@/api/service";
import dayjs from "dayjs";
import { ref } from "vue";
import ExpiresTimeText from "/@/components/expires-time-text.vue";
import api, { SuiteDetail } from "/@/views/certd/suite/mine/api";
defineOptions({
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 api = {
async SuiteDetailGet() {
return await request({
url: "/mine/suite/detail",
method: "post"
});
}
};
async function loadSuiteDetail() {
detail.value = await api.SuiteDetailGet();
}

View File

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

View File

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

View File

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

View File

@ -34,7 +34,7 @@ import { logger } from '@certd/basic';
import { UrlService } from './url-service.js';
import { NotificationService } from './notification-service.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';
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) {
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);
if (!pipeline.id) {
pipeline.id = id;
@ -464,7 +469,14 @@ export class PipelineService extends BaseService<PipelineEntity> {
// 清除该step的状态
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) {
logger.error('执行失败:', e);
// throw e;