整理代码
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
# 仅供本地 Vite 代理读取,不会通过 VITE_ 前缀打包到浏览器。
|
||||
QHSE_API_TOKEN='AF17168B-87BD-4GLY-1111-F0A0A1158F9B'
|
||||
@@ -1,5 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
+5
-6
@@ -102,15 +102,14 @@ next/
|
||||
|
||||
### 安全与部署边界
|
||||
|
||||
- 浏览器只访问同源 `/qhse_webapi/`,前端构建产物不包含接口 token。
|
||||
- 本地 token 仅写入 `.env.local`,由 Vite 开发代理注入。
|
||||
- 生产环境由网关反向代理 `https://qhse.cncecoa.com/qhse_webapi` 并注入 token。
|
||||
- 浏览器只访问同源 `/qhse_webapi/`,由开发服务器或生产网关转发到正式接口。
|
||||
- 接口 token 固定在 `config/dashboard.ts`,由 `services/http.ts` 写入每个请求的 `token` 请求头。
|
||||
- token 会进入浏览器构建产物,只适用于当前受控内网部署环境。
|
||||
- 生产环境仍需将 `/qhse_webapi/` 反向代理到 `https://qhse.cncecoa.com/qhse_webapi`,但无需在网关重复注入 token。
|
||||
- 网关应配置上游超时、请求 ID 透传、访问日志和 5xx 告警。
|
||||
|
||||
## 本地开发
|
||||
|
||||
复制 `.env.example` 为 `.env.local`,填写仅供本地代理使用的 `QHSE_API_TOKEN`,然后执行:
|
||||
|
||||
```powershell
|
||||
npm install
|
||||
npm run dev
|
||||
@@ -133,7 +132,7 @@ npm run typecheck
|
||||
npm run build
|
||||
```
|
||||
|
||||
产物位于 `dist/`,可部署到任意静态 Web 服务。生产环境需要由同源网关代理 `/qhse_webapi/`,并在服务端注入接口 token;token 不进入浏览器构建产物。
|
||||
产物位于 `dist/`,可部署到静态 Web 服务。生产环境需要由同源网关代理 `/qhse_webapi/`;token 已由前端请求头携带。
|
||||
|
||||
建议网关同时配置 12 秒上游超时、请求 ID 透传、访问日志和 5xx 告警。应用采用哈希路由,无需 Web 服务器配置 SPA history fallback。
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/**
|
||||
* 看板静态配置。
|
||||
* 这里只存放接口相对路径、外部链接和枚举映射,认证令牌必须由开发代理或生产网关注入。
|
||||
* 这里只存放接口相对路径、固定令牌、外部链接和枚举映射,业务模块统一从这里读取运行配置。
|
||||
*/
|
||||
export const API_BASE = "/qhse_webapi/api/DataGovernance";
|
||||
|
||||
/** 数据治理接口固定令牌,由统一请求服务写入请求头。 */
|
||||
export const API_TOKEN = "AF17168B-87BD-4GLY-1111-F0A0A1158F9B";
|
||||
|
||||
export const API_ENDPOINTS = {
|
||||
monitoring: `${API_BASE}/GetMetricMonitoringSituation`,
|
||||
unitMonitoring: `${API_BASE}/GetUnitMetricMonitoringSituation`,
|
||||
|
||||
+33
-10
@@ -3,12 +3,13 @@
|
||||
* 负责查询参数编码、请求 ID、超时/取消和业务错误归一化,不承载具体业务字段转换。
|
||||
*/
|
||||
import type { ApiEnvelope } from "../types/dashboard";
|
||||
import { API_TOKEN } from "../config/dashboard";
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly status = 0,
|
||||
public readonly requestId = ""
|
||||
public readonly requestId = "",
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
@@ -18,7 +19,7 @@ export class ApiError extends Error {
|
||||
export async function getJson<T>(
|
||||
url: string,
|
||||
params: Record<string, string | number | undefined>,
|
||||
options: { timeout?: number; signal?: AbortSignal } = {}
|
||||
options: { timeout?: number; signal?: AbortSignal } = {},
|
||||
): Promise<ApiEnvelope<T>> {
|
||||
const requestId = crypto.randomUUID();
|
||||
const target = new URL(url, window.location.origin);
|
||||
@@ -29,24 +30,46 @@ export async function getJson<T>(
|
||||
|
||||
// 外部 signal 与内部超时共用一个控制器,调用方取消和超时都走相同错误出口。
|
||||
const timeoutController = new AbortController();
|
||||
const timeoutId = window.setTimeout(() => timeoutController.abort(), options.timeout ?? 12000);
|
||||
const timeoutId = window.setTimeout(
|
||||
() => timeoutController.abort(),
|
||||
options.timeout ?? 12000,
|
||||
);
|
||||
const abort = () => timeoutController.abort();
|
||||
options.signal?.addEventListener("abort", abort, { once: true });
|
||||
|
||||
try {
|
||||
const response = await fetch(target, {
|
||||
headers: { Accept: "application/json", "X-Request-Id": requestId },
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
token: API_TOKEN,
|
||||
"X-Request-Id": requestId,
|
||||
},
|
||||
cache: "no-store",
|
||||
signal: timeoutController.signal
|
||||
signal: timeoutController.signal,
|
||||
});
|
||||
if (!response.ok) throw new ApiError(`接口请求失败:HTTP ${response.status}`, response.status, requestId);
|
||||
const result = await response.json() as ApiEnvelope<T>;
|
||||
if (Number(result.code) !== 1) throw new ApiError(result.message || "接口返回业务错误", response.status, requestId);
|
||||
if (!response.ok)
|
||||
throw new ApiError(
|
||||
`接口请求失败:HTTP ${response.status}`,
|
||||
response.status,
|
||||
requestId,
|
||||
);
|
||||
const result = (await response.json()) as ApiEnvelope<T>;
|
||||
if (Number(result.code) !== 1)
|
||||
throw new ApiError(
|
||||
result.message || "接口返回业务错误",
|
||||
response.status,
|
||||
requestId,
|
||||
);
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) throw error;
|
||||
if (timeoutController.signal.aborted) throw new ApiError("接口请求超时或已取消", 0, requestId);
|
||||
throw new ApiError(error instanceof Error ? error.message : "接口请求失败", 0, requestId);
|
||||
if (timeoutController.signal.aborted)
|
||||
throw new ApiError("接口请求超时或已取消", 0, requestId);
|
||||
throw new ApiError(
|
||||
error instanceof Error ? error.message : "接口请求失败",
|
||||
0,
|
||||
requestId,
|
||||
);
|
||||
} finally {
|
||||
window.clearTimeout(timeoutId);
|
||||
options.signal?.removeEventListener("abort", abort);
|
||||
|
||||
+4
-14
@@ -1,14 +1,11 @@
|
||||
/**
|
||||
* Vite 开发与构建配置。
|
||||
* 开发环境将同源 /qhse_webapi 请求转发到正式接口域名,并从服务端环境变量注入 token。
|
||||
* 开发环境将同源 /qhse_webapi 请求转发到正式接口域名,token 由浏览器请求统一携带。
|
||||
*/
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import type { ClientRequest } from "node:http";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), "QHSE_");
|
||||
return {
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
base: "./",
|
||||
server: {
|
||||
@@ -19,15 +16,8 @@ export default defineConfig(({ mode }) => {
|
||||
changeOrigin: true,
|
||||
secure: true,
|
||||
timeout: 15000,
|
||||
proxyTimeout: 15000,
|
||||
configure(proxy) {
|
||||
// QHSE_ 变量仅存在于 Node 侧,不能使用会暴露到浏览器的 VITE_ 前缀。
|
||||
(proxy as unknown as { on: (event: string, callback: (request: ClientRequest) => void) => void }).on("proxyReq", (proxyRequest) => {
|
||||
if (env.QHSE_API_TOKEN) proxyRequest.setHeader("token", env.QHSE_API_TOKEN);
|
||||
});
|
||||
}
|
||||
proxyTimeout: 15000
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user