perf: 用户创建证书流水线没有购买套餐或者超限时提前报错

pull/330/head
xiaojunnuo 2024-12-25 23:20:07 +08:00
parent f5ec9870fd
commit 472f06c2d1
13 changed files with 108 additions and 24 deletions

View File

@ -34,6 +34,8 @@ services:
# #↓↓↓↓ ----------------------------- 如果忘记管理员密码可以设置为true重启之后管理员密码将改成123456然后请及时修改回false # #↓↓↓↓ ----------------------------- 如果忘记管理员密码可以设置为true重启之后管理员密码将改成123456然后请及时修改回false
- certd_system_resetAdminPasswd=false - certd_system_resetAdminPasswd=false
# 默认使用sqlite文件数据库如果需要使用其他数据库请设置以下环境变量 # 默认使用sqlite文件数据库如果需要使用其他数据库请设置以下环境变量
# 注意: 选定使用一种数据库之后,不支持更换数据库。
# 数据库迁移方法1、使用新数据库重新部署一套然后将旧数据同步过去注意flyway_history表的数据不要同步
# #↓↓↓↓ ----------------------------- 使用postgresql数据库需要提前创建数据库 # #↓↓↓↓ ----------------------------- 使用postgresql数据库需要提前创建数据库
# - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录 # - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录
# - certd_typeorm_dataSource_default_type=postgres # 数据库类型 # - certd_typeorm_dataSource_default_type=postgres # 数据库类型

View File

@ -146,6 +146,10 @@ export class RunnableCollection {
static initPipelineRunnableType(pipeline: Pipeline) { static initPipelineRunnableType(pipeline: Pipeline) {
pipeline.runnableType = "pipeline"; pipeline.runnableType = "pipeline";
if (pipeline.stages === undefined) {
pipeline.stages = [];
return;
}
pipeline.stages.forEach((stage) => { pipeline.stages.forEach((stage) => {
stage.runnableType = "stage"; stage.runnableType = "stage";
stage.tasks.forEach((task) => { stage.tasks.forEach((task) => {

View File

@ -4,10 +4,13 @@ import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, Edi
import { siteInfoApi } from "./api"; import { siteInfoApi } from "./api";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { notification } from "ant-design-vue"; import { notification } from "ant-design-vue";
import { useSettingStore } from "/@/store/modules/settings";
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n(); const { t } = useI18n();
const api = siteInfoApi; const api = siteInfoApi;
const { crudBinding } = crudExpose;
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query); return await api.GetList(query);
}; };
@ -28,6 +31,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
return res; return res;
}; };
const settingsStore = useSettingStore();
return { return {
crudOptions: { crudOptions: {
request: { request: {
@ -51,6 +56,37 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 600 width: 600
} }
}, },
actionbar: {
buttons: {
add: {
async click() {
if (!settingsStore.isPlus) {
//非plus
if (crudBinding.value.data.length >= 1) {
notification.error({
message: "基础版只能添加一个监控站点"
});
return;
}
}
//检查是否监控站点数量超出限制
if (settingsStore.isComm && settingsStore.suiteSetting.enabled) {
//检查数量是否超限
const suiteDetail = await mySuiteApi.SuiteDetailGet();
const max = suiteDetail.monitorCount.max;
if (max != -1 && max <= suiteDetail.monitorCount.used) {
notification.error({
message: `对不起,您最多只能创建条${max}监控记录,请购买或升级套餐`
});
return;
}
}
await crudExpose.openAdd({});
}
}
}
},
rowHandle: { rowHandle: {
fixed: "right", fixed: "right",
width: 240, width: 240,
@ -111,6 +147,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
form: { form: {
rules: [ rules: [
{ required: true, message: "请输入域名" }, { required: true, message: "请输入域名" },
//@ts-ignore
{ type: "domains", message: "请输入正确的域名" } { type: "domains", message: "请输入正确的域名" }
] ]
}, },

View File

@ -15,6 +15,7 @@ import { useModal } from "/@/use/use-modal";
import CertView from "./cert-view.vue"; import CertView from "./cert-view.vue";
import { eachStages } from "./utils"; import { eachStages } from "./utils";
import { createNotificationApi as createNotificationApi } from "../notification/api"; import { createNotificationApi as createNotificationApi } from "../notification/api";
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
export default function ({ crudExpose, context: { certdFormRef, groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context: { certdFormRef, groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
@ -94,7 +95,23 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
lastResRef.value = res; lastResRef.value = res;
return res; return res;
}; };
function addCertdPipeline() {
const settingsStore = useSettingStore();
async function addCertdPipeline() {
//检查是否流水线数量超出限制
if (settingsStore.isComm && settingsStore.suiteSetting.enabled) {
//检查数量是否超限
const suiteDetail = await mySuiteApi.SuiteDetailGet();
const max = suiteDetail.pipelineCount.max;
if (max != -1 && max <= suiteDetail.pipelineCount.used) {
notification.error({
message: `对不起,您最多只能创建${max}条流水线,请购买或升级套餐`
});
return;
}
}
certdFormRef.value.open(async ({ form }: any) => { certdFormRef.value.open(async ({ form }: any) => {
// 添加certd pipeline // 添加certd pipeline
const triggers = []; const triggers = [];

View File

@ -16,7 +16,7 @@ export type SuiteDetail = {
monitorCount?: SuiteValue; monitorCount?: SuiteValue;
}; };
export default { export const mySuiteApi = {
async GetList(query: any) { async GetList(query: any) {
return await request({ return await request({
url: apiPrefix + "/page", url: apiPrefix + "/page",

View File

@ -1,10 +1,9 @@
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import api from "./api"; import { mySuiteApi as 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";
import DurationValue from "/@/views/sys/suite/product/duration-value.vue"; import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
import dayjs from "dayjs";
import UserSuiteStatus from "/@/views/certd/suite/mine/user-suite-status.vue"; import UserSuiteStatus from "/@/views/certd/suite/mine/user-suite-status.vue";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {

View File

@ -18,7 +18,7 @@
import { computed, onActivated, onMounted, ref } from "vue"; import { computed, 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 api, { SuiteDetail } from "/@/views/certd/suite/mine/api"; import { mySuiteApi, SuiteDetail } from "/@/views/certd/suite/mine/api";
import SuiteCard from "/@/views/framework/home/dashboard/suite-card.vue"; import SuiteCard from "/@/views/framework/home/dashboard/suite-card.vue";
defineOptions({ defineOptions({
@ -35,7 +35,7 @@ const currentSuite = computed(() => {
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { detail, currentSuite } }); const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { detail, currentSuite } });
async function loadSuiteDetail() { async function loadSuiteDetail() {
detail.value = await api.SuiteDetailGet(); detail.value = await mySuiteApi.SuiteDetailGet();
} }
// //

View File

@ -49,7 +49,7 @@
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue"; import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
import { ref } from "vue"; import { ref } from "vue";
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"; import { mySuiteApi, SuiteDetail } from "/@/views/certd/suite/mine/api";
import { FsIcon } from "@fast-crud/fast-crud"; import { FsIcon } from "@fast-crud/fast-crud";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
@ -60,7 +60,7 @@ defineOptions({
const detail = ref<SuiteDetail>({}); const detail = ref<SuiteDetail>({});
async function loadSuiteDetail() { async function loadSuiteDetail() {
detail.value = await api.SuiteDetailGet(); detail.value = await mySuiteApi.SuiteDetailGet();
} }
loadSuiteDetail(); loadSuiteDetail();

View File

@ -166,6 +166,11 @@ export default defineComponent({
{ {
required: true, required: true,
message: "请输入图片验证码" message: "请输入图片验证码"
},
{
min: 4,
max: 4,
message: "请输入4位图片验证码"
} }
], ],
smsCode: [ smsCode: [

View File

@ -17,7 +17,7 @@
<div style="height: 400px"> <div style="height: 400px">
<ProductManager @refreshed="onTableRefresh"></ProductManager> <ProductManager @refreshed="onTableRefresh"></ProductManager>
</div> </div>
<div class="helper">不建议设置免费套餐可以在下方配置注册赠送套餐</div> <div class="helper">不建议设置免费套餐可以在下方配置注册赠送套餐或者在用户套餐管理中手动赠送套餐</div>
</a-form-item> </a-form-item>
<a-form-item label="注册赠送套餐" name="registerGift"> <a-form-item label="注册赠送套餐" name="registerGift">

View File

@ -139,17 +139,27 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "dict-select", type: "dict-select",
column: { show: false }, column: { show: false },
addForm: { addForm: {
show: true,
component: { component: {
name: SuiteDurationSelector, name: SuiteDurationSelector,
vModel: "modelValue" vModel: "modelValue"
}, },
rules: [{ required: true, message: "请选择套餐" }] rules: [
{
validator: async (rule, value) => {
if (value && value.productId) {
return true;
}
throw new Error("请选择套餐");
}
}
]
}, },
valueResolve({ form, value }) { valueResolve({ form, value }) {
if (value) { debugger;
const arr = value.splict("_"); if (value && value.productId) {
form.productId = parseInt(arr[0]); form.productId = value.productId;
form.duration = parseInt(arr[1]); form.duration = value.duration;
} }
}, },
form: { show: false } form: { show: false }

View File

@ -1,7 +1,7 @@
import { Provide } from '@midwayjs/core'; import { Provide } from '@midwayjs/core';
import { IWebMiddleware, IMidwayKoaContext, NextFunction } from '@midwayjs/koa'; import { IMidwayKoaContext, IWebMiddleware, NextFunction } from '@midwayjs/koa';
import { logger } from '@certd/basic'; import { logger } from '@certd/basic';
import { BaseException, Result } from '@certd/lib-server'; import { Result } from '@certd/lib-server';
@Provide() @Provide()
export class GlobalExceptionMiddleware implements IWebMiddleware { export class GlobalExceptionMiddleware implements IWebMiddleware {
@ -14,11 +14,7 @@ export class GlobalExceptionMiddleware implements IWebMiddleware {
await next(); await next();
logger.info('请求完成:', url, Date.now() - startTime + 'ms'); logger.info('请求完成:', url, Date.now() - startTime + 'ms');
} catch (err) { } catch (err) {
logger.error('请求异常:', url, Date.now() - startTime + 'ms', err.message); logger.error('请求异常:', url, Date.now() - startTime + 'ms', err);
if (!(err instanceof BaseException)) {
logger.error(err);
}
ctx.status = 200; ctx.status = 200;
if (err.code == null || typeof err.code !== 'number') { if (err.code == null || typeof err.code !== 'number') {
err.code = 1; err.code = 1;

View File

@ -30,6 +30,19 @@ export class QywxNotification extends BaseNotification {
}) })
mentionedList!: string[]; mentionedList!: string[];
@NotificationInput({
title: '提醒指定手机号成员',
component: {
name: 'a-select',
vModel: 'value',
mode: 'tags',
open: false,
},
required: false,
helper: '填写成员手机号,@all 为提醒所有人',
})
mentionedMobileList!: string[];
async send(body: NotificationBody) { async send(body: NotificationBody) {
if (!this.webhook) { if (!this.webhook) {
throw new Error('webhook地址不能为空'); throw new Error('webhook地址不能为空');
@ -47,10 +60,11 @@ export class QywxNotification extends BaseNotification {
url: this.webhook, url: this.webhook,
method: 'POST', method: 'POST',
data: { data: {
msgtype: 'markdown', msgtype: 'text',
markdown: { text: {
content: `# ${body.title}\n\n${body.content}\n\n[查看详情](${body.url})`, content: `· ${body.title}\${body.content}\n· 查看详情: ${body.url}`,
mentioned_list: this.mentionedList, mentioned_list: this.mentionedList,
mentioned_mobile_list: this.mentionedMobileList,
}, },
}, },
}); });