feat: 新增套餐激活码功能,通过CDK兑换套餐

This commit is contained in:
xiaojunnuo
2026-05-31 06:00:15 +08:00
parent dc1507a5ea
commit 81d6289a86
17 changed files with 947 additions and 65 deletions
@@ -52,6 +52,7 @@ export default {
inviteLevel: "Promotion Levels",
inviteUserLevel: "User Promotion Levels",
inviteWithdraw: "Withdrawal Requests",
activationCodeManager: "Activation Code",
netTest: "Network Test",
enterpriseSetting: "Enterprise Settings",
@@ -53,6 +53,7 @@ export default {
inviteLevel: "推广等级",
inviteUserLevel: "用户推广等级",
inviteWithdraw: "提现申请记录",
activationCodeManager: "激活码管理",
netTest: "网络测试",
enterpriseManager: "企业管理设置",
projectManager: "项目管理",
@@ -354,6 +354,22 @@ export const sysResources = [
keepAlive: true,
},
},
{
title: "certd.sysResources.activationCodeManager",
name: "SysProductActivationCode",
path: "/sys/suite/activation-code",
component: "/sys/suite/activation-code/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:key-outline",
permission: "sys:settings:edit",
auth: true,
keepAlive: true,
},
},
],
},
{
@@ -78,3 +78,11 @@ export async function GetSuiteSetting() {
method: "POST",
});
}
export async function UseActivationCode(code: string) {
return await request({
url: "/suite/activation-code/use",
method: "POST",
data: { code },
});
}
@@ -14,7 +14,10 @@
</a-card>
</a-col>
</a-row>
<a-row :gutter="8" class="mt-10">
<div class="suite-buy-action-row mt-10 pl-1">
<a-button type="primary" :loading="activating" @click="openActivateDialog">激活码兑换</a-button>
</div>
<a-row :gutter="8">
<a-col v-for="item of suites" :key="item.id" class="mb-10 suite-card-col">
<product-info :product="item" @order="doOrder" />
</a-col>
@@ -32,14 +35,69 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import { message } from "ant-design-vue";
import * as api from "./api";
import ProductInfo from "/@/views/certd/suite/product-info.vue";
import OrderModal from "/@/views/certd/suite/order-modal.vue";
import { notification } from "ant-design-vue";
import { useFormDialog } from "/@/use/use-dialog";
const suites = ref([]);
const addons = ref([]);
const activationCode = ref("");
const activating = ref(false);
const { openFormDialog } = useFormDialog();
async function openActivateDialog() {
await openFormDialog({
title: "激活码兑换",
wrapper: { width: 520 },
initialForm: {
code: activationCode.value,
},
columns: {
code: {
title: "激活码",
type: "text",
form: {
col: { span: 24 },
rules: [{ required: true, message: "请输入激活码" }],
component: {
placeholder: "请输入 CDK 激活码",
},
},
},
},
async onSubmit(form: any) {
activationCode.value = form.code;
await doActivate();
},
});
}
async function doActivate() {
const code = activationCode.value.trim().toUpperCase();
if (!code) {
message.warning("请输入激活码");
return;
}
activationCode.value = code;
activating.value = true;
try {
const res = await api.UseActivationCode(code);
activationCode.value = "";
notification.success({
message: "激活成功",
description: `您已成功激活 ${res.title},时长 ${res.duration}`,
});
} catch (e: any) {
message.error(e?.message || "兑换失败");
} finally {
activating.value = false;
}
}
async function loadProducts() {
const list = await api.ProductList();
suites.value = list.filter((x: any) => x.type === "suite");
@@ -87,6 +145,12 @@ loadSuiteIntro();
//overflow: hidden;
//text-overflow: ellipsis;
}
.suite-buy-action-row {
width: 100%;
margin-bottom: 10px;
display: flex;
justify-content: flex-start;
}
.suite-list {
display: flex;
@@ -0,0 +1,67 @@
import { request } from "/src/api/service";
const apiPrefix = "/sys/suite/activation-code";
export async function GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query,
});
}
export async function Generate(data: { productId: number; duration: number; count: number; expireTime?: number; exported?: boolean; remark?: string }) {
return await request({
url: apiPrefix + "/generate",
method: "post",
data,
});
}
export async function ExportCodes(query: any) {
return await request({
url: apiPrefix + "/export",
method: "post",
data: query,
});
}
export async function Disable(id: number) {
return await request({
url: apiPrefix + "/disable",
method: "post",
params: { id },
});
}
export async function Enable(id: number) {
return await request({
url: apiPrefix + "/enable",
method: "post",
params: { id },
});
}
export async function DeleteObj(id: number) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id },
});
}
export async function GetSimpleUserByIds(ids: number[]) {
return await request({
url: "/sys/authority/user/getSimpleUserByIds",
method: "post",
data: { ids },
});
}
export async function GetProductDetail(id: number) {
return await request({
url: "/sys/suite/product/info",
method: "post",
params: { id },
});
}
@@ -0,0 +1,378 @@
import { compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { Modal, message, notification } from "ant-design-vue";
import { Ref, ref } from "vue";
import * as api from "./api";
import { useFormDialog } from "/@/use/use-dialog";
import createCrudOptionsUser from "/@/views/sys/authority/user/crud";
import { downloadFileFromBlobPart } from "/@/vben/shared/utils/download";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { openFormDialog } = useFormDialog();
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
const productDict = dict({
url: "/sys/suite/product/list",
value: "id",
label: "title",
});
const statusDict = dict({
data: [
{ label: "未使用", value: "unused", color: "success" },
{ label: "已使用", value: "used", color: "processing" },
{ label: "已禁用", value: "disabled", color: "default" },
],
});
const userDict = dict({
async getNodesByValues(ids: number[]) {
return await api.GetSimpleUserByIds(ids);
},
value: "id",
label: "nickName",
});
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const delRequest = async ({ row }: DelReq) => {
return await api.DeleteObj(row.id);
};
async function openGenerate() {
const durationOptions = ref<{ label: string; value: number }[]>([]);
async function loadDurationOptions(productId: number) {
if (!productId) {
durationOptions.value = [];
return;
}
const product = await api.GetProductDetail(productId);
const prices = JSON.parse(product.durationPrices || "[]");
durationOptions.value = prices.map((item: any) => ({
label: item.duration === -1 ? "永久" : `${item.duration}`,
value: item.duration,
}));
}
await openFormDialog({
title: "批量生成激活码",
wrapper: { width: 560 },
initialForm: {
productId: null,
duration: null,
count: 10,
expireTime: null,
exported: true,
remark: "",
},
columns: {
productId: {
title: "选择套餐",
type: "dict-select",
dict: productDict,
form: {
col: { span: 24 },
rules: [{ required: true, message: "请选择套餐" }],
valueChange({ form, value }: any) {
form.duration = null;
loadDurationOptions(value);
},
},
},
duration: {
title: "时长(天)",
type: "text",
form: {
col: { span: 24 },
rules: [{ required: true, message: "请输入时长" }],
helper: "请先选择套餐,再选择该套餐已配置的时长",
component: {
name: "a-select",
vModel: "value",
options: durationOptions,
placeholder: "请选择时长",
},
},
},
count: {
title: "生成数量",
type: "number",
form: {
col: { span: 24 },
rules: [{ required: true, message: "请输入生成数量" }],
helper: "单次最多生成 1000 个",
component: { min: 1, max: 1000 },
},
},
expireTime: {
title: "过期时间",
type: "datetime",
form: {
col: { span: 24 },
helper: "选填,留空则长期有效",
},
},
exported: {
title: "生成后立即导出",
type: "dict-switch",
dict: dict({
data: [
{ label: "导出", value: true, color: "success" },
{ label: "不导出", value: false, color: "default" },
],
}),
form: {
col: { span: 24 },
helper: "开启后生成完成会自动下载 CSV,并把激活码标记为已导出",
},
},
remark: {
title: "备注",
type: "text",
form: {
col: { span: 24 },
helper: "选填",
},
},
},
async onSubmit(form: any) {
if (form.expireTime) {
form.expireTime = form.expireTime.valueOf ? form.expireTime.valueOf() : new Date(form.expireTime).getTime();
}
const res = await api.Generate(form);
if (form.exported) {
downloadCodes(res.codes || [], "activation-codes-generated");
}
await crudExpose.doRefresh();
notification.success({
message: `激活码已生成,批次号:${res.batchNo},数量:${res.count}`,
});
},
});
}
async function doDisable(row: any) {
Modal.confirm({
title: "确认禁用激活码?",
content: `禁用后用户将不能兑换该激活码:${row.code}`,
async onOk() {
await api.Disable(row.id);
notification.success({ message: "激活码已禁用" });
await crudExpose.doRefresh();
},
});
}
async function doEnable(row: any) {
Modal.confirm({
title: "确认启用激活码?",
content: `启用后用户可以继续兑换该激活码:${row.code}`,
async onOk() {
await api.Enable(row.id);
notification.success({ message: "激活码已启用" });
await crudExpose.doRefresh();
},
});
}
function buildCsv(list: any[]) {
const headers = ["ID", "激活码", "套餐ID", "时长", "批次号", "状态", "过期时间", "备注"];
const rows = list.map(item => [item.id, item.code, item.productId, item.duration, item.batchNo, item.status, item.expireTime || "", item.remark || ""]);
const escapeCsv = (value: any) => {
const text = String(value ?? "");
return `"${text.replaceAll('"', '""')}"`;
};
return [headers, ...rows].map(row => row.map(escapeCsv).join(",")).join("\n");
}
function downloadCodes(list: any[], prefix = "activation-codes") {
downloadFileFromBlobPart({
fileName: `${prefix}-${Date.now()}.csv`,
source: "\uFEFF" + buildCsv(list),
});
}
async function doExport() {
if (selectedRowKeys.value.length === 0) {
message.warning("请先勾选要导出的激活码");
return;
}
Modal.confirm({
title: "确认导出激活码?",
content: `将导出已勾选的 ${selectedRowKeys.value.length} 个激活码,并标记导出时间。已使用和已禁用的激活码会自动跳过。`,
async onOk() {
const list = await api.ExportCodes({ ids: selectedRowKeys.value });
downloadCodes(list);
selectedRowKeys.value = [];
notification.success({ message: `已导出 ${list.length} 个激活码` });
await crudExpose.doRefresh();
},
});
}
return {
crudOptions: {
settings: {
plugins: {
rowSelection: {
enabled: true,
order: -2,
before: true,
props: {
multiple: true,
crossPage: true,
selectedRowKeys,
},
},
},
},
request: { pageRequest, delRequest },
actionbar: {
buttons: {
add: { show: false },
generate: {
text: "批量生成激活码",
type: "primary",
click: openGenerate,
},
export: {
text: "导出激活码",
click: doExport,
},
},
},
rowHandle: {
width: 210,
fixed: "right",
buttons: {
view: { show: false },
edit: { show: false },
copy: { show: false },
disable: {
text: "禁用",
type: "link",
show: compute(({ row }) => row.status === "unused" || row.status === "exported"),
click: ({ row }) => doDisable(row),
},
enable: {
text: "启用",
type: "link",
show: compute(({ row }) => row.status === "disabled"),
click: ({ row }) => doEnable(row),
},
remove: {
show: compute(({ row }) => row.status !== "used"),
},
},
},
columns: {
id: {
title: "ID",
type: "number",
column: { width: 80 },
form: { show: false },
},
code: {
title: "激活码",
type: "copyable",
search: { show: true },
column: { width: 300 },
},
productId: {
title: "绑定套餐",
type: "dict-select",
dict: productDict,
search: { show: true },
column: { width: 150 },
},
duration: {
title: "时长(天)",
type: "number",
column: { width: 100, align: "center" },
},
batchNo: {
title: "批次号",
type: "text",
search: { show: true },
column: { width: 180 },
},
status: {
title: "状态",
type: "dict-select",
dict: statusDict,
search: { show: true },
column: { width: 100, align: "center" },
},
usedUserId: {
title: "使用用户",
type: "table-select",
dict: userDict,
search: { show: true },
column: { width: 140 },
form: {
show: false,
component: {
crossPage: true,
multiple: false,
select: {
placeholder: "点击选择用户",
},
createCrudOptions: createCrudOptionsUser,
},
},
},
usedTime: {
title: "使用时间",
type: "datetime",
column: { width: 170 },
},
exported: {
title: "是否已导出",
type: "dict-switch",
dict: dict({
data: [
{ label: "未导出", value: false, color: "default" },
{ label: "已导出", value: true, color: "warning" },
],
}),
search: { show: true },
column: { width: 110, align: "center" },
},
exportTime: {
title: "导出时间",
type: "datetime",
column: { width: 170 },
},
expireTime: {
title: "过期时间",
type: "datetime",
search: { show: false },
column: { width: 170 },
},
disabledTime: {
title: "禁用时间",
type: "datetime",
column: { width: 170, show: false },
},
remark: {
title: "备注",
type: "text",
column: { width: 150 },
},
createTime: {
title: "创建时间",
type: "datetime",
form: { show: false },
column: { sorter: true, width: 170 },
},
updateTime: {
title: "更新时间",
type: "datetime",
form: { show: false },
column: { width: 170 },
},
},
},
};
}
@@ -0,0 +1,31 @@
<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 { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
defineOptions({
name: "ProductActivationCodeManager",
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
});
</script>
<style lang="less"></style>
@@ -0,0 +1,29 @@
-- 激活码表
CREATE TABLE "cd_product_activation_code"
(
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"code" varchar(50) NOT NULL,
"product_id" integer NOT NULL,
"duration" integer NOT NULL,
"batch_no" varchar(50) NOT NULL DEFAULT '',
"status" varchar(20) NOT NULL DEFAULT 'unused',
"used_user_id" integer,
"used_time" integer,
"expire_time" integer,
"disabled_time" integer,
"exported" integer NOT NULL DEFAULT 0,
"export_time" integer,
"remark" varchar(500) NOT NULL DEFAULT '',
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
);
CREATE UNIQUE INDEX "index_activation_code_code" ON "cd_product_activation_code" ("code");
CREATE INDEX "index_activation_code_batch_no" ON "cd_product_activation_code" ("batch_no");
CREATE INDEX "index_activation_code_status" ON "cd_product_activation_code" ("status");
CREATE INDEX "index_activation_code_expire_time" ON "cd_product_activation_code" ("expire_time");
CREATE INDEX "index_activation_code_exported" ON "cd_product_activation_code" ("exported");
-- cd_user_suite 增加激活码来源追溯
ALTER TABLE "cd_user_suite"
ADD COLUMN "activation_code_id" integer;
@@ -0,0 +1,129 @@
name: acmeAccount
title: ACME账号
desc: 用于复用ACME账号私钥和账号地址,证书申请时不再临时创建账号
icon: ph:certificate
subtype: caType
input:
caType:
title: 颁发机构
component:
name: a-select
options:
- value: letsencrypt
label: Let's Encrypt
- value: letsencrypt_staging
label: Let's Encrypt测试环境
- value: google
label: Google
- value: zerossl
label: ZeroSSL
- value: litessl
label: litessl
- value: sslcom
label: SSL.com
required: true
mergeScript: |2-
return {
component: {
disabled: ctx.compute(({form})=> !!form.access?.account)
}
}
email:
title: 邮箱
component:
placeholder: user@example.com
rules:
- type: email
message: 请输入正确的邮箱
required: true
mergeScript: |2-
return {
component: {
disabled: ctx.compute(({form})=> !!form.access?.account)
}
}
directoryUrl:
title: ACME Directory URL
component:
placeholder: 自定义ACME服务端点
helper: 自定义ACME时必填,其他颁发机构默认自动使用内置端点
required: false
mergeScript: |2-
return {
show: false,
}
eabKid:
title: EAB KID
component:
placeholder: 需要EAB的颁发机构生成账号时填写
helper: >-
需要提供EAB授权
ZeroSSL:请前往[zerossl开发者中心](https://app.zerossl.com/developer),生成 'EAB
Credentials'
Google:请查看[google获取eab帮助文档](https://certd.docmirror.cn/guide/use/google/),用过一次后会绑定邮箱,后续复用EAB要用同一个邮箱
SSL.com:[SSL.com账号页面](https://secure.ssl.com/account),然后点击api
credentials链接,然后点击编辑按钮,查看Secret key和HMAC key
litessl:[litesslEAB页面](https://freessl.cn/automation/eab-manager),然后点击新增EAB
required: false
encrypt: true
mergeScript: |2-
return {
show: ctx.compute(({form})=>{
const caType = form.access?.caType;
return ['google','zerossl','sslcom','litessl'].includes(caType);
}),
component: {
disabled: ctx.compute(({form})=> !!form.access?.account)
}
}
eabHmacKey:
title: EAB HMAC Key
component:
placeholder: 需要EAB的颁发机构生成账号时填写
required: false
encrypt: true
mergeScript: |2-
return {
show: ctx.compute(({form})=>{
const caType = form.access?.caType;
return ['google','zerossl','sslcom','litessl'].includes(caType);
}),
component: {
disabled: ctx.compute(({form})=> !!form.access?.account)
}
}
account:
title: ACME账号信息
component:
name: refresh-input
action: GenerateAccount
buttonText: 生成ACME账号
successMessage: ACME账号已生成,请保存授权配置
required: true
helper: 请生成ACME账号,账号一旦生成不允许修改
encrypt: true
mergeScript: |2-
return {
component: {
disabled: ctx.compute(({form})=> !!form.access?.account)
}
}
pluginType: access
type: builtIn
scriptFilePath: /plugins/plugin-cert/access/acme-account-access.js
@@ -51,6 +51,12 @@ input:
required: true
order: -1
helper: 请输入邮箱
version:
title: 版本
value: 2
isSys: true
show: false
order: 0
challengeType:
title: 域名验证方式
value: dns
@@ -60,6 +66,8 @@ input:
options:
- value: dns
label: DNS直接验证
- value: dns-persist
label: DNS持久验证
- value: cname
label: CNAME代理验证
- value: http
@@ -80,6 +88,9 @@ input:
4. <b>多DNS提供商</b>:每个域名可以选择独立的DNS提供商
5. <b>自动匹配</b>:此处无需选择校验方式,需要在[域名管理](#/certd/cert/domain)中提前配置好校验方式
6. <b>DNS持久验证</b>:需要先配置ACME账号和_validation-persist持久TXT记录,续期时不再增删DNS记录;当前仅
Let's Encrypt 测试环境可以申请
order: 0
dnsProviderType:
title: DNS解析服务商
@@ -103,7 +114,7 @@ input:
required: true
helper: |-
您的域名注册商,或者域名的dns服务器属于哪个平台
如果这里没有,请选择CNAME代理验证校验方式
如果这里没有,请选择CNAME代理验证
order: 0
dnsProviderAccess:
title: DNS解析授权
@@ -141,18 +152,30 @@ input:
}),
defaultType: ctx.compute(({form})=>{
return form.challengeType || 'cname'
}),
caType: ctx.compute(({form})=>{
return form.sslProvider
}),
acmeAccountAccessId: ctx.compute(({form})=>{
return form.acmeAccountAccessId
}),
commonAcmeAccountAccessId: ctx.compute(({form})=>{
const key = form.sslProvider + 'CommonAcmeAccountAccessId';
return form[key]
})
},
show: ctx.compute(({form})=>{
return form.challengeType === 'cname' || form.challengeType === 'http' || form.challengeType === 'dnses'
return form.challengeType === 'cname' || form.challengeType === 'http' || form.challengeType === 'dnses' || form.challengeType === 'dns-persist'
}),
helper: ctx.compute(({form})=>{
if(form.challengeType === 'cname' ){
return '请按照上面的提示,给要申请证书的域名添加CNAME记录,添加后,点击验证,验证成功后不要删除记录,申请和续期证书会一直用它'
}else if (form.challengeType === 'http'){
return '请按照上面的提示,给每个域名设置文件上传配置,证书申请过程中会上传校验文件到网站根目录的.well-known/acme-challenge/目录下'
}else if (form.challengeType === 'http'){
return '请按照上面的提示,给每个域名设置文件上传配置,证书申请过程中会上传校验文件到网站根目录文件夹下,请确保该校验文件可以公网http访问到'
}else if (form.challengeType === 'dnses'){
return '给每个域名单独配置dns提供商'
}else if (form.challengeType === 'dns-persist'){
return '请先创建并校验_validation-persist TXT持久记录,校验成功后才能提交流水线;当前仅 Let\'s Encrypt 测试环境可以申请'
}
})
}
@@ -195,21 +218,41 @@ input:
isSys: true
show: false
order: 0
googleCommonAcmeAccountAccessId:
title: Google公共ACME账号
isSys: true
show: false
order: 0
zerosslCommonEabAccessId:
title: ZeroSSL公共EAB授权
isSys: true
show: false
order: 0
zerosslCommonAcmeAccountAccessId:
title: ZeroSSL公共ACME账号
isSys: true
show: false
order: 0
sslcomCommonEabAccessId:
title: SSL.com公共EAB授权
isSys: true
show: false
order: 0
sslcomCommonAcmeAccountAccessId:
title: SSL.com公共ACME账号
isSys: true
show: false
order: 0
litesslCommonEabAccessId:
title: litessl公共EAB授权
isSys: true
show: false
order: 0
litesslCommonAcmeAccountAccessId:
title: litessl公共ACME账号
isSys: true
show: false
order: 0
eabAccessId:
title: EAB授权
component:
@@ -233,7 +276,16 @@ input:
return {
show: ctx.compute(({form})=>{
console.log("show",form)
if (form.version === 2) {
return false
}
if(form.acmeAccountAccessId){
return false
}
const commonAcmeKey = form.sslProvider + 'CommonAcmeAccountAccessId';
if (form[commonAcmeKey]) {
return false
}
return (form.sslProvider === 'zerossl' && !form.zerosslCommonEabAccessId)
|| (form.sslProvider === 'google' && !form.googleCommonEabAccessId)
|| (form.sslProvider === 'sslcom' && !form.sslcomCommonEabAccessId)
@@ -241,6 +293,36 @@ input:
})
}
order: 0
acmeAccountAccessId:
title: ACME账号
component:
name: access-selector
type: acmeAccount
required: false
helper: 请选择颁发机构对应的ACME账号
mergeScript: |2-
return {
show: ctx.compute(({form})=>{
const commonKey = form.sslProvider + 'CommonAcmeAccountAccessId';
if (form[commonKey]) {
return false
}
return !!form.sslProvider
}),
component:{
subtype: ctx.compute(({form})=> form.sslProvider)
},
required: ctx.compute(({form})=>{
const commonKey = form.sslProvider + 'CommonAcmeAccountAccessId';
if (form[commonKey]) {
return false
}
return form.version === 2
})
}
order: 0
googleAccessId:
title: 服务账号授权
@@ -257,6 +339,15 @@ input:
return {
show: ctx.compute(({form})=>{
if (form.version === 2) {
return false
}
if(form.acmeAccountAccessId){
return false
}
if(form.googleCommonAcmeAccountAccessId){
return false
}
return form.sslProvider === 'google' && !form.googleCommonEabAccessId
})
}
@@ -81,7 +81,6 @@ input:
search: false
pager: true
single: true
pageSize: 50
watches:
- certDomains
- accessId
@@ -96,7 +95,7 @@ input:
},
}
helper: 订阅模式的证书订单 Id(在新建流水线时暂时无法获取,可以先随便填个数字,先创建,进入流水线编辑页面再获取选择即可)
helper: 订阅模式的证书订单 Id
order: 0
pfxPassword:
title: 证书加密密码
@@ -134,5 +134,6 @@ export class MainConfiguration {
});
logger.info("当前环境:", this.app.getEnv()); // prod
}
}