webadmin端增加版本号和部署功能
This commit is contained in:
@@ -72,6 +72,22 @@ export type DashboardStats = {
|
|||||||
ordersByStatus: Array<{ status: string; count: number }>;
|
ordersByStatus: Array<{ status: string; count: number }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SystemVersion = {
|
||||||
|
id: string;
|
||||||
|
gitTag: string | null;
|
||||||
|
commitId: string;
|
||||||
|
commitMessage: string;
|
||||||
|
branch: string | null;
|
||||||
|
deployedBy: string | null;
|
||||||
|
deployedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeployTriggerResult = {
|
||||||
|
accepted: boolean;
|
||||||
|
started?: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type AdminUserRow = {
|
export type AdminUserRow = {
|
||||||
id: string;
|
id: string;
|
||||||
userNo: string;
|
userNo: string;
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { Card, Col, Row, Statistic, Table, Typography } from 'antd';
|
import {
|
||||||
import { request, type DashboardStats } from '../lib/api';
|
Button, Card, Col, Descriptions, Modal, Row, Space, Statistic, Table, Typography, message,
|
||||||
|
} from 'antd';
|
||||||
|
import { CloudUploadOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
request,
|
||||||
|
type DashboardStats,
|
||||||
|
type DeployTriggerResult,
|
||||||
|
type HqProfile,
|
||||||
|
type SystemVersion,
|
||||||
|
} from '../lib/api';
|
||||||
|
import { fmtTime } from '../lib/constants';
|
||||||
|
|
||||||
const STATUS_LABELS: Record<string, string> = {
|
const STATUS_LABELS: Record<string, string> = {
|
||||||
PENDING_PAY: '待付款',
|
PENDING_PAY: '待付款',
|
||||||
@@ -14,19 +24,104 @@ const STATUS_LABELS: Record<string, string> = {
|
|||||||
REFUNDED: '已退款',
|
REFUNDED: '已退款',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEPLOYED_BY_LABELS: Record<string, string> = {
|
||||||
|
webhook: 'Webhook',
|
||||||
|
manual: '手动脚本',
|
||||||
|
admin: 'Admin 发布',
|
||||||
|
};
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||||
|
const [version, setVersion] = useState<SystemVersion | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [versionLoading, setVersionLoading] = useState(true);
|
||||||
|
const [profile, setProfile] = useState<HqProfile | null>(null);
|
||||||
|
const [deploying, setDeploying] = useState(false);
|
||||||
|
const isSuperAdmin = profile?.adminRole === 'SUPER_ADMIN';
|
||||||
|
|
||||||
|
const loadVersion = useCallback(() => {
|
||||||
|
setVersionLoading(true);
|
||||||
|
return request<SystemVersion | null>('/admin/dashboard/version')
|
||||||
|
.then(setVersion)
|
||||||
|
.catch(() => setVersion(null))
|
||||||
|
.finally(() => setVersionLoading(false));
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
request<DashboardStats>('/admin/dashboard/stats')
|
request<DashboardStats>('/admin/dashboard/stats')
|
||||||
.then(setStats)
|
.then(setStats)
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, []);
|
void loadVersion();
|
||||||
|
request<HqProfile>('/admin/auth/me').then(setProfile).catch(() => {});
|
||||||
|
}, [loadVersion]);
|
||||||
|
|
||||||
|
function handleDeploy() {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认发布更新?',
|
||||||
|
content: '将触发服务器 webhook,拉取 auto-release.env 中配置的 GIT_BRANCH 并执行发版。发版通常需要数分钟,完成后可刷新版本信息。',
|
||||||
|
okText: '开始发布',
|
||||||
|
cancelText: '取消',
|
||||||
|
onOk: async () => {
|
||||||
|
setDeploying(true);
|
||||||
|
try {
|
||||||
|
const result = await request<DeployTriggerResult>('/admin/deploy/trigger', { method: 'POST' });
|
||||||
|
message.success(result.message || '已触发发布');
|
||||||
|
} catch (e) {
|
||||||
|
message.error(e instanceof Error ? e.message : '触发发布失败');
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
setDeploying(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const shortSha = version?.commitId ? version.commitId.slice(0, 7) : '—';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Typography.Title level={4}>数据概览</Typography.Title>
|
<Typography.Title level={4}>数据概览</Typography.Title>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title="系统版本"
|
||||||
|
loading={versionLoading}
|
||||||
|
style={{ marginBottom: 24 }}
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Button icon={<ReloadOutlined />} onClick={() => void loadVersion()}>
|
||||||
|
刷新版本
|
||||||
|
</Button>
|
||||||
|
{isSuperAdmin && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<CloudUploadOutlined />}
|
||||||
|
loading={deploying}
|
||||||
|
onClick={handleDeploy}
|
||||||
|
>
|
||||||
|
发布更新
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{version ? (
|
||||||
|
<Descriptions column={{ xs: 1, sm: 2, lg: 3 }} size="small">
|
||||||
|
<Descriptions.Item label="分支">{version.branch || '—'}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="Tag">{version.gitTag || '—'}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="Commit">
|
||||||
|
<Typography.Text copyable={{ text: version.commitId }}>{shortSha}</Typography.Text>
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="提交说明" span={3}>{version.commitMessage}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="发布时间">{fmtTime(version.deployedAt)}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="触发来源">
|
||||||
|
{DEPLOYED_BY_LABELS[version.deployedBy || ''] || version.deployedBy || '—'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
) : (
|
||||||
|
<Typography.Text type="secondary">尚未记录发版信息</Typography.Text>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||||
<Col xs={24} sm={12} lg={6}>
|
<Col xs={24} sm={12} lg={6}>
|
||||||
<Card loading={loading}>
|
<Card loading={loading}>
|
||||||
|
|||||||
@@ -16,3 +16,6 @@ DEPLOY_LOCK_FILE=/var/run/dukang-deploy.lock
|
|||||||
|
|
||||||
# Prisma db push 遇到 schema 变更警告时自动继续(开发/测试环境可开)
|
# Prisma db push 遇到 schema 变更警告时自动继续(开发/测试环境可开)
|
||||||
PRISMA_ACCEPT_DATA_LOSS=true
|
PRISMA_ACCEPT_DATA_LOSS=true
|
||||||
|
|
||||||
|
# 注意:Admin「发布更新」读取的是 server/dukang-api/.env.production 中的
|
||||||
|
# DEPLOY_WEBHOOK_URL / DEPLOY_WEBHOOK_SECRET,须与下方 DEPLOY_WEBHOOK_SECRET 一致
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ APP_ROOT="$APP_ROOT"
|
|||||||
GIT_REMOTE="$GIT_REMOTE"
|
GIT_REMOTE="$GIT_REMOTE"
|
||||||
GIT_BRANCH="$GIT_BRANCH"
|
GIT_BRANCH="$GIT_BRANCH"
|
||||||
RELEASE_ARGS="$REMOTE_RELEASE_ARGS"
|
RELEASE_ARGS="$REMOTE_RELEASE_ARGS"
|
||||||
|
export DEPLOY_TRIGGER=manual
|
||||||
|
export APP_ROOT
|
||||||
|
|
||||||
cd "\$APP_ROOT"
|
cd "\$APP_ROOT"
|
||||||
|
|
||||||
|
|||||||
@@ -92,4 +92,10 @@ curl -sf -o /dev/null -w "api: %{http_code}\n" http://127.0.0.1:8090/api/v1/heal
|
|||||||
|| curl -sf -o /dev/null -w "api: %{http_code}\n" http://127.0.0.1:8090/ \
|
|| curl -sf -o /dev/null -w "api: %{http_code}\n" http://127.0.0.1:8090/ \
|
||||||
|| echo "api: FAIL"
|
|| echo "api: FAIL"
|
||||||
|
|
||||||
|
echo "==> 7. 记录系统版本"
|
||||||
|
cd "$APP_ROOT/server/dukang-api"
|
||||||
|
APP_ROOT="$APP_ROOT" DEPLOY_TRIGGER="${DEPLOY_TRIGGER:-manual}" \
|
||||||
|
node scripts/with-api-env.cjs node scripts/record-system-version.cjs \
|
||||||
|
|| echo "WARN: system_version 写入失败"
|
||||||
|
|
||||||
echo "==> 发版完成"
|
echo "==> 发版完成"
|
||||||
|
|||||||
@@ -121,13 +121,16 @@ const server = http.createServer(async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const started = triggerRelease(ref || 'manual');
|
const source = typeof payload.source === 'string' ? payload.source : '';
|
||||||
|
const trigger = source === 'admin' ? 'admin' : ref || 'manual';
|
||||||
|
const started = triggerRelease(trigger);
|
||||||
return json(res, 202, {
|
return json(res, 202, {
|
||||||
ok: true,
|
ok: true,
|
||||||
accepted: true,
|
accepted: true,
|
||||||
started,
|
started,
|
||||||
message: started ? 'deploy started' : 'deploy debounced (duplicate webhook)',
|
message: started ? 'deploy started' : 'deploy debounced (duplicate webhook)',
|
||||||
ref: ref || ALLOWED_REF,
|
ref: ref || ALLOWED_REF,
|
||||||
|
source: trigger,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,3 +13,19 @@ export interface AdminPageResult<T> {
|
|||||||
export interface ExportRequest {
|
export interface ExportRequest {
|
||||||
format?: 'csv';
|
format?: 'csv';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SystemVersionDto {
|
||||||
|
id: string;
|
||||||
|
gitTag: string | null;
|
||||||
|
commitId: string;
|
||||||
|
commitMessage: string;
|
||||||
|
branch: string | null;
|
||||||
|
deployedBy: string | null;
|
||||||
|
deployedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeployTriggerResult {
|
||||||
|
accepted: boolean;
|
||||||
|
started?: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -88,3 +88,7 @@ SHIP_FROM_ADDRESS=河南省郑州市金水区
|
|||||||
SHIP_FROM_ADDRESS_DETAIL=杜康酒业仓
|
SHIP_FROM_ADDRESS_DETAIL=杜康酒业仓
|
||||||
SHIP_FROM_LNG=113.665
|
SHIP_FROM_LNG=113.665
|
||||||
SHIP_FROM_LAT=34.757
|
SHIP_FROM_LAT=34.757
|
||||||
|
|
||||||
|
# Admin 概览「发布更新」:调用本机 deploy webhook(与 deploy/auto-release.env 中 SECRET 一致)
|
||||||
|
# DEPLOY_WEBHOOK_URL=http://127.0.0.1:8095/deploy
|
||||||
|
# DEPLOY_WEBHOOK_SECRET=change-me-to-a-long-random-string
|
||||||
|
|||||||
@@ -263,6 +263,19 @@ enum BenefitLedgerType {
|
|||||||
|
|
||||||
// ─── COMMON ───────────────────────────────────────────
|
// ─── COMMON ───────────────────────────────────────────
|
||||||
|
|
||||||
|
model SystemVersion {
|
||||||
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
||||||
|
gitTag String? @map("git_tag") @db.VarChar(128)
|
||||||
|
commitId String @map("commit_id") @db.VarChar(64)
|
||||||
|
commitMessage String @map("commit_message") @db.VarChar(512)
|
||||||
|
branch String? @db.VarChar(64)
|
||||||
|
deployedBy String? @map("deployed_by") @db.VarChar(32)
|
||||||
|
deployedAt DateTime @default(now()) @map("deployed_at") @db.DateTime(3)
|
||||||
|
|
||||||
|
@@index([deployedAt])
|
||||||
|
@@map("system_version")
|
||||||
|
}
|
||||||
|
|
||||||
model CommonWxAppConfig {
|
model CommonWxAppConfig {
|
||||||
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
||||||
clientApp ClientApp @unique @map("client_app")
|
clientApp ClientApp @unique @map("client_app")
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* 发版成功后写入 system_version。
|
||||||
|
* 须经 with-api-env.cjs 加载 DATABASE_URL;失败不阻断发版(exit 0)。
|
||||||
|
*/
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const { resolve } = require('path');
|
||||||
|
|
||||||
|
const apiRoot = resolve(__dirname, '..');
|
||||||
|
const appRoot = process.env.APP_ROOT || resolve(apiRoot, '../..');
|
||||||
|
|
||||||
|
function git(cmd) {
|
||||||
|
try {
|
||||||
|
return execSync(cmd, { cwd: appRoot, encoding: 'utf8' }).trim();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const commitId = git('git rev-parse HEAD');
|
||||||
|
if (!commitId) {
|
||||||
|
console.warn('[record-system-version] WARN: 无法读取 git HEAD,跳过');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let commitMessage = git('git log -1 --pretty=%s') || '';
|
||||||
|
if (commitMessage.length > 512) {
|
||||||
|
commitMessage = commitMessage.slice(0, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitTag = git('git describe --tags --exact-match') || null;
|
||||||
|
const branchRaw = git('git rev-parse --abbrev-ref HEAD');
|
||||||
|
const branch = branchRaw && branchRaw !== 'HEAD' ? branchRaw : null;
|
||||||
|
|
||||||
|
const trigger = (process.env.DEPLOY_TRIGGER || '').trim();
|
||||||
|
let deployedBy = 'manual';
|
||||||
|
if (trigger) {
|
||||||
|
if (trigger === 'manual' || trigger === 'admin') {
|
||||||
|
deployedBy = trigger;
|
||||||
|
} else {
|
||||||
|
deployedBy = 'webhook';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
try {
|
||||||
|
const row = await prisma.systemVersion.create({
|
||||||
|
data: {
|
||||||
|
gitTag,
|
||||||
|
commitId,
|
||||||
|
commitMessage: commitMessage || '(no message)',
|
||||||
|
branch,
|
||||||
|
deployedBy,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
`[record-system-version] ok id=${row.id} commit=${commitId.slice(0, 7)} tag=${gitTag || '-'} by=${deployedBy}`,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.warn('[record-system-version] WARN:', err instanceof Error ? err.message : err);
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
@@ -52,6 +52,7 @@ export const HqOperationAction = {
|
|||||||
PROMO_CODE_UPDATE_STATUS: 'PROMO_CODE_UPDATE_STATUS',
|
PROMO_CODE_UPDATE_STATUS: 'PROMO_CODE_UPDATE_STATUS',
|
||||||
REDEEM_PENDING_COMPLETE: 'REDEEM_PENDING_COMPLETE',
|
REDEEM_PENDING_COMPLETE: 'REDEEM_PENDING_COMPLETE',
|
||||||
REDEEM_PENDING_REJECT: 'REDEEM_PENDING_REJECT',
|
REDEEM_PENDING_REJECT: 'REDEEM_PENDING_REJECT',
|
||||||
|
DEPLOY_TRIGGER: 'DEPLOY_TRIGGER',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type HqOperationActionCode = (typeof HqOperationAction)[keyof typeof HqOperationAction];
|
export type HqOperationActionCode = (typeof HqOperationAction)[keyof typeof HqOperationAction];
|
||||||
@@ -109,6 +110,7 @@ export const HQ_OPERATION_ACTION_LABELS: Record<string, string> = {
|
|||||||
[HqOperationAction.PROMO_CODE_UPDATE_STATUS]: '推广码启停',
|
[HqOperationAction.PROMO_CODE_UPDATE_STATUS]: '推广码启停',
|
||||||
[HqOperationAction.REDEEM_PENDING_COMPLETE]: '弱网待处理单-补核销',
|
[HqOperationAction.REDEEM_PENDING_COMPLETE]: '弱网待处理单-补核销',
|
||||||
[HqOperationAction.REDEEM_PENDING_REJECT]: '弱网待处理单-驳回',
|
[HqOperationAction.REDEEM_PENDING_REJECT]: '弱网待处理单-驳回',
|
||||||
|
[HqOperationAction.DEPLOY_TRIGGER]: '触发系统发布',
|
||||||
STORE_PAYOUT: '门店打款确认',
|
STORE_PAYOUT: '门店打款确认',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,4 +11,9 @@ export class AdminDashboardController {
|
|||||||
stats() {
|
stats() {
|
||||||
return this.dashboardService.getStats();
|
return this.dashboardService.getStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('version')
|
||||||
|
version() {
|
||||||
|
return this.dashboardService.getLatestVersion();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,4 +65,20 @@ export class AdminDashboardService {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getLatestVersion() {
|
||||||
|
const row = await this.prisma.systemVersion.findFirst({
|
||||||
|
orderBy: { deployedAt: 'desc' },
|
||||||
|
});
|
||||||
|
if (!row) return null;
|
||||||
|
return {
|
||||||
|
id: row.id.toString(),
|
||||||
|
gitTag: row.gitTag,
|
||||||
|
commitId: row.commitId,
|
||||||
|
commitMessage: row.commitMessage,
|
||||||
|
branch: row.branch,
|
||||||
|
deployedBy: row.deployedBy,
|
||||||
|
deployedAt: row.deployedAt.toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Controller, Post, UseGuards } from '@nestjs/common';
|
||||||
|
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
|
||||||
|
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
|
||||||
|
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
|
||||||
|
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
|
||||||
|
import { AdminDeployService } from './admin-deploy.service';
|
||||||
|
|
||||||
|
@Controller('admin/deploy')
|
||||||
|
@UseGuards(HqAuthGuard, SuperAdminGuard)
|
||||||
|
export class AdminDeployController {
|
||||||
|
constructor(private readonly deployService: AdminDeployService) {}
|
||||||
|
|
||||||
|
@Post('trigger')
|
||||||
|
@HqOperation({
|
||||||
|
action: HqOperationAction.DEPLOY_TRIGGER,
|
||||||
|
refType: 'DEPLOY',
|
||||||
|
batch: true,
|
||||||
|
})
|
||||||
|
trigger() {
|
||||||
|
return this.deployService.triggerDeploy();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { BadRequestException, Injectable, ServiceUnavailableException } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AdminDeployService {
|
||||||
|
async triggerDeploy() {
|
||||||
|
const url = (process.env.DEPLOY_WEBHOOK_URL || 'http://127.0.0.1:8095/deploy').trim();
|
||||||
|
const secret = (process.env.DEPLOY_WEBHOOK_SECRET || '').trim();
|
||||||
|
if (!secret) {
|
||||||
|
throw new ServiceUnavailableException('未配置 DEPLOY_WEBHOOK_SECRET,无法触发发布');
|
||||||
|
}
|
||||||
|
|
||||||
|
let res: Response;
|
||||||
|
try {
|
||||||
|
res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Deploy-Token': secret,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ source: 'admin' }),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new ServiceUnavailableException(
|
||||||
|
`无法连接部署 webhook:${err instanceof Error ? err.message : 'network error'}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = await res.text();
|
||||||
|
let data: { ok?: boolean; accepted?: boolean; started?: boolean; message?: string; skipped?: boolean } = {};
|
||||||
|
try {
|
||||||
|
data = text ? (JSON.parse(text) as typeof data) : {};
|
||||||
|
} catch {
|
||||||
|
throw new ServiceUnavailableException(`部署 webhook 返回非 JSON(HTTP ${res.status})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status === 403) {
|
||||||
|
throw new BadRequestException(data.message || '部署 webhook 鉴权失败');
|
||||||
|
}
|
||||||
|
if (!res.ok && res.status !== 202) {
|
||||||
|
throw new ServiceUnavailableException(data.message || `部署 webhook 失败(HTTP ${res.status})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
accepted: true,
|
||||||
|
started: data.started !== false && !data.skipped,
|
||||||
|
message: data.message || (data.started === false ? 'deploy debounced' : 'deploy started'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,11 +49,14 @@ import { AdminWechatBindingsController } from './admin-wechat-bindings.controlle
|
|||||||
import { AdminWechatBindingsService } from './admin-wechat-bindings.service';
|
import { AdminWechatBindingsService } from './admin-wechat-bindings.service';
|
||||||
import { AdminHqPermissionsController } from './admin-hq-permissions.controller';
|
import { AdminHqPermissionsController } from './admin-hq-permissions.controller';
|
||||||
import { AdminHqPermissionsService } from './admin-hq-permissions.service';
|
import { AdminHqPermissionsService } from './admin-hq-permissions.service';
|
||||||
|
import { AdminDeployController } from './admin-deploy.controller';
|
||||||
|
import { AdminDeployService } from './admin-deploy.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [CityScopeModule, IamModule, TradeModule, BenefitModule, CommonModule, IntegrationsModule, RedeemModule],
|
imports: [CityScopeModule, IamModule, TradeModule, BenefitModule, CommonModule, IntegrationsModule, RedeemModule],
|
||||||
controllers: [
|
controllers: [
|
||||||
AdminDashboardController,
|
AdminDashboardController,
|
||||||
|
AdminDeployController,
|
||||||
AdminUsersController,
|
AdminUsersController,
|
||||||
AdminOrdersController,
|
AdminOrdersController,
|
||||||
AdminStoresController,
|
AdminStoresController,
|
||||||
@@ -104,6 +107,7 @@ import { AdminHqPermissionsService } from './admin-hq-permissions.service';
|
|||||||
AdminRedeemDebugService,
|
AdminRedeemDebugService,
|
||||||
AdminWechatBindingsService,
|
AdminWechatBindingsService,
|
||||||
AdminHqPermissionsService,
|
AdminHqPermissionsService,
|
||||||
|
AdminDeployService,
|
||||||
SuperAdminGuard,
|
SuperAdminGuard,
|
||||||
],
|
],
|
||||||
exports: [CityScopeModule],
|
exports: [CityScopeModule],
|
||||||
|
|||||||
Reference in New Issue
Block a user