docs(engineering): add project audit report and improvement plan
This commit introduces a comprehensive engineering audit report for the Tootaio Studio project. The report is structured into documents covering architecture, code quality, performance, security, CI/CD, and observability. It also includes a phased improvement roadmap and a set of `.patch` files to apply immediate fixes for content schemas, package scripts, and CI configuration.
This commit is contained in:
28
docs/20251106/README.md
Normal file
28
docs/20251106/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Tootaio Studio 工程与架构审计(2025-11-06)
|
||||
|
||||
本报告对当前基于 Nuxt 4.2.0 的网站项目进行一次性全方位工程化审计,涵盖架构、代码质量、性能、CI/CD、安全与可观测性,并提供可直接落地的改进方案与补丁。
|
||||
|
||||
## 摘要
|
||||
- 架构总体清晰,采用 Nuxt Content + i18n + @nuxt/ui,模块职责明确(Low)。
|
||||
- 发现内容 Schema 与实际 YAML 不一致(Critical),导致构建期校验风险与类型漂移。
|
||||
- 缺少统一工程工具链(lint/typecheck/CI)(High)。
|
||||
- 未配置 Route Rules/SWR 与静态资源缓存(Medium)。
|
||||
- 运行时配置、监控与安全头未体系化(Medium)。
|
||||
|
||||
## 关键输出
|
||||
- 补丁:
|
||||
- 修正 `content.config.ts` Schema 与内容对齐(0001)。
|
||||
- 增加 `package.json` 脚本(lint/typecheck/check)(0002)。
|
||||
- 提供 GitHub Actions 工作流(lint → typecheck → build)(0003)。
|
||||
- 可选:为首页与静态资源增加 `routeRules`(0004)。
|
||||
- 提供 8 份专题文档:架构、质量、性能、CI/CD、安全、可观测性与路线图。
|
||||
|
||||
## 执行指引
|
||||
1) 审阅 `docs/20251106/` 文档;
|
||||
2) 依次应用补丁(位于 `docs/20251106/patches/`):
|
||||
- `git apply docs/20251106/patches/0001-content-config-align.patch`
|
||||
- `git apply docs/20251106/patches/0002-package-scripts.patch`
|
||||
- `git apply docs/20251106/patches/0003-github-actions-ci.patch`
|
||||
- (可选)`git apply docs/20251106/patches/0004-nuxt-route-rules.patch`
|
||||
3) 本地验证:`pnpm i && pnpm check && pnpm preview`。
|
||||
|
||||
52
docs/20251106/architecture.md
Normal file
52
docs/20251106/architecture.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 架构结构与改进方案
|
||||
|
||||
本文审计 Nuxt 4.2.0 架构与模块使用,指出结构异味并提供可落地改进。
|
||||
|
||||
## 现状评估
|
||||
- 模块与目录(Low)
|
||||
- `app/`(pages/layouts/composables/utils/assets)职责清晰。
|
||||
- `content/` 与 `content.config.ts` 使用 zod 定义 Schema,利于类型驱动内容。
|
||||
- `i18n/` 采用 `locales/<locale>` 结构;`no_prefix` 策略与当前站点定位一致。
|
||||
- Nuxt 4 特性使用(Medium)
|
||||
- 自动导入:已使用(如 `useLocalizedCollection`)。建议将文件命名统一为 `useXxx.ts` 便于识别。
|
||||
- Runtime Config:未使用。建议引入 `runtimeConfig` 管理外部域名、监控 DSN 等。
|
||||
- Nitro Route Rules/缓存:未配置。建议为首页与静态资源设置 `prerender`+`swr` 与长缓存。
|
||||
- 设计风险(Critical)
|
||||
- `content.config.ts` 的 Index Schema 与 YAML 不一致:缺少 `title/description/seo` 字段;`icon/highlight/spotlight` 约束与内容不符。
|
||||
|
||||
## 改进策略
|
||||
1) 内容 Schema 对齐(Critical)
|
||||
- 为 Index 增加 `title/description/seo`;将 `icon` 设为可选,将 `highlight/spotlight` 设为可选并默认 `false`。
|
||||
- 参见补丁:`0001-content-config-align.patch`。
|
||||
2) 运行时配置(Medium)
|
||||
- 在 `nuxt.config.ts` 中:
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
runtimeConfig: {
|
||||
sentryDsn: '',
|
||||
public: { assetBase: 'https://img.tootaio.com' },
|
||||
},
|
||||
})
|
||||
```
|
||||
- 在代码中读取 `useRuntimeConfig()`,避免硬编码外链与密钥。
|
||||
3) 路由规则与缓存(Medium)
|
||||
- 为首页预渲染并设置 SWR,为构建产物设置 immutable 缓存:
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
routeRules: {
|
||||
'/': { prerender: true, swr: 3600 },
|
||||
'/_nuxt/**': {
|
||||
headers: { 'cache-control': 'public, max-age=31536000, immutable' },
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
- 参见补丁:`0004-nuxt-route-rules.patch`。
|
||||
4) 组合式命名与分层(Low)
|
||||
- 将 `app/composables/LocalizedCollection.ts` 重命名为 `useLocalizedCollection.ts`。
|
||||
- 保持 composable 只做“取数+组装”,复杂 UI/状态拆到组件/Pinia(若引入)。
|
||||
|
||||
## 与 Nuxt 4.2.0 兼容性
|
||||
- 现有 `compatibilityDate: '2025-07-15'` 符合要求。
|
||||
- 上述配置属于稳定 API:`routeRules/runtimeConfig/auto-import`,安全可用。
|
||||
|
||||
40
docs/20251106/ci-cd.md
Normal file
40
docs/20251106/ci-cd.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# CI/CD 设计与部署方案
|
||||
|
||||
## 目标
|
||||
提供从代码质量到产物构建的自动化流程:lint → typecheck → build。
|
||||
|
||||
## GitHub Actions(推荐)
|
||||
- 工作流文件参见补丁 `0003-github-actions-ci.patch`,关键步骤:
|
||||
- 使用 `actions/setup-node@v4` + `corepack enable`;
|
||||
- 安装依赖:`pnpm i --frozen-lockfile`;
|
||||
- 执行 `pnpm lint && pnpm typecheck && pnpm build`。
|
||||
|
||||
## 部署对比
|
||||
- Vercel(推荐)
|
||||
- 优点:零配置、内置 CDN、适配 Nuxt/Nitro;Preview 环境完善。
|
||||
- 缺点:免费额度受限;对长时间 SSR 任务需商用套餐。
|
||||
- Docker(自管)
|
||||
- 优点:环境一致性高;更灵活接入内网服务。
|
||||
- 缺点:需自建 CI/CD 与监控;维护成本更高。
|
||||
- 自建 Node + Nginx
|
||||
- 优点:成本可控;传统可见性强。
|
||||
- 缺点:手工配置多;需要额外缓存/CDN 配合。
|
||||
|
||||
## Dockerfile(示例)
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN corepack enable && pnpm i --frozen-lockfile && pnpm build
|
||||
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=build /app/.output ./.output
|
||||
EXPOSE 3000
|
||||
CMD ["node","./.output/server/index.mjs"]
|
||||
```
|
||||
|
||||
## 环境变量与密钥
|
||||
- 在 CI 中设置 `NITRO_PRESET`、`SENTRY_DSN` 等;Nuxt 通过 `runtimeConfig` 注入,避免硬编码。
|
||||
|
||||
41
docs/20251106/code-quality.md
Normal file
41
docs/20251106/code-quality.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 代码规范与质量分析
|
||||
|
||||
## 主要发现
|
||||
- 类型定义(High)
|
||||
- `content.config.ts` 缺少 Index 页的 `title/description/seo`,导致 `page?.title` 等缺少类型提示;特定字段(`icon/highlight/spotlight`)与 YAML 不一致。
|
||||
- 组合式命名(Medium)
|
||||
- `LocalizedCollection.ts` 建议改为 `useLocalizedCollection.ts`,与 Nuxt 习惯保持一致,便于检索与自动导入心智统一。
|
||||
|
||||
## 统一约定
|
||||
- 文件与目录
|
||||
- composables:`useXxx.ts`(示例:`useLocalizedCollection.ts`)。
|
||||
- 页面:`pages/webDev.vue`(帕斯卡命名不用于路由页)。
|
||||
- 工具:`utils/some-helper.ts`,避免与 composables 混用。
|
||||
- 代码风格
|
||||
- TypeScript、ESM、Vue SFC `<script setup lang="ts">`。
|
||||
- 缩进 2 空格;组件/组合式函数采用明确的返回类型。
|
||||
- i18n 使用嵌套点式键:`$t('index.featuredProjects.viewDemo')`。
|
||||
- 提交规范
|
||||
- 参考历史:`feat(content): ...`、`refactor(ui): ...`、`feat(pages): ...`。
|
||||
- 分支:`feat/<desc>`、`fix/<desc>`。
|
||||
|
||||
## 重构建议(示例)
|
||||
1) 内容类型对齐(Critical)
|
||||
- 见补丁 `0001-content-config-align.patch`。
|
||||
2) 工程脚本完善(High)
|
||||
- 新增脚本:`lint/typecheck/check`,见补丁 `0002-package-scripts.patch`。
|
||||
3) 组合式命名(Medium)
|
||||
- 将 `LocalizedCollection.ts` 重命名为 `useLocalizedCollection.ts`(建议)。
|
||||
|
||||
## 示例:更严格的返回类型
|
||||
```ts
|
||||
export function useLocalizedCollection<B extends string>(baseName: B) {
|
||||
const { locale } = useI18n()
|
||||
type LocalizedKey = keyof { [K in keyof Collections as K extends `${B}_${string}` ? K : never]: any }
|
||||
type Schema = Collections[LocalizedKey]
|
||||
return useAsyncData<Schema | null>(`${baseName}-${locale.value}`, async () => {
|
||||
// ...
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
40
docs/20251106/observability.md
Normal file
40
docs/20251106/observability.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# 监控、日志与可观测性
|
||||
|
||||
## 目标
|
||||
构建可追踪错误、性能瓶颈与用户关键路径的最小监控闭环。
|
||||
|
||||
## Sentry(推荐)
|
||||
1) 配置 `runtimeConfig.sentryDsn`;
|
||||
2) 插件(示例,待引入依赖后启用):
|
||||
```ts
|
||||
// plugins/sentry.client.ts
|
||||
export default defineNuxtPlugin(() => {
|
||||
const { public: pub, sentryDsn } = useRuntimeConfig()
|
||||
if (!sentryDsn) return
|
||||
// @ts-ignore: add SDK in deps later
|
||||
const Sentry = (window as any).Sentry
|
||||
if (Sentry) {
|
||||
Sentry.init({ dsn: sentryDsn, tracesSampleRate: 0.1 })
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 日志与错误边界
|
||||
- 服务器:使用 Nitro 默认日志(生产应接入集中式日志,如 CloudWatch/ELK)。
|
||||
- 客户端:全局错误捕获(`app:error` 事件)上报到 Sentry/自建网关。
|
||||
|
||||
## Web Vitals(可选)
|
||||
收集 LCP/CLS/INP 等指标(示例):
|
||||
```ts
|
||||
// plugins/web-vitals.client.ts
|
||||
import { onCLS, onINP, onLCP } from 'web-vitals'
|
||||
export default defineNuxtPlugin(() => {
|
||||
const send = (metric: any) => navigator.sendBeacon('/vitals', JSON.stringify(metric))
|
||||
onLCP(send); onCLS(send); onINP(send)
|
||||
})
|
||||
```
|
||||
|
||||
## Dashboard 路线
|
||||
- 第一步:仅错误计数与关键事务。
|
||||
- 第二步:接口慢查询、资源错误分布、版本追踪(commit SHA)。
|
||||
|
||||
89
docs/20251106/patches/0001-content-config-align.patch
Normal file
89
docs/20251106/patches/0001-content-config-align.patch
Normal file
@@ -0,0 +1,89 @@
|
||||
diff --git a/content.config.ts b/content.config.ts
|
||||
--- a/content.config.ts
|
||||
+++ b/content.config.ts
|
||||
@@
|
||||
const defineIndexSchema = () =>
|
||||
- z.object({
|
||||
- capabilities: z.object({
|
||||
- title: z.string(),
|
||||
- features: z.array(
|
||||
- z.object({
|
||||
- title: z.string(),
|
||||
- description: z.string(),
|
||||
- icon: z.string(),
|
||||
- })
|
||||
- ),
|
||||
- }),
|
||||
- featuredProjects: z.object({
|
||||
- title: z.string(),
|
||||
- projects: z.array(
|
||||
- z.object({
|
||||
- title: z.string(),
|
||||
- description: z.string(),
|
||||
- image: z.string(),
|
||||
- demoLink: z.string(),
|
||||
- highlight: z.boolean(),
|
||||
- spotlight: z.boolean(),
|
||||
- })
|
||||
- ),
|
||||
- }),
|
||||
- techStack: z.object({
|
||||
- title: z.string(),
|
||||
- }),
|
||||
- whyChooseUs: z.object({
|
||||
- title: z.string(),
|
||||
- features: z.array(
|
||||
- z.object({
|
||||
- title: z.string(),
|
||||
- description: z.string(),
|
||||
- icon: z.string(),
|
||||
- })
|
||||
- ),
|
||||
- }),
|
||||
- });
|
||||
+ z.object({
|
||||
+ title: z.string(),
|
||||
+ description: z.string(),
|
||||
+ seo: z
|
||||
+ .object({
|
||||
+ title: z.string().optional(),
|
||||
+ description: z.string().optional(),
|
||||
+ })
|
||||
+ .optional(),
|
||||
+ capabilities: z.object({
|
||||
+ title: z.string(),
|
||||
+ features: z.array(
|
||||
+ z.object({
|
||||
+ title: z.string(),
|
||||
+ description: z.string(),
|
||||
+ icon: z.string().optional(),
|
||||
+ })
|
||||
+ ),
|
||||
+ }),
|
||||
+ featuredProjects: z.object({
|
||||
+ title: z.string(),
|
||||
+ projects: z.array(
|
||||
+ z.object({
|
||||
+ title: z.string(),
|
||||
+ description: z.string(),
|
||||
+ image: z.string(),
|
||||
+ demoLink: z.string().url().optional(),
|
||||
+ highlight: z.boolean().optional().default(false),
|
||||
+ spotlight: z.boolean().optional().default(false),
|
||||
+ })
|
||||
+ ),
|
||||
+ }),
|
||||
+ techStack: z.object({
|
||||
+ title: z.string(),
|
||||
+ }),
|
||||
+ whyChooseUs: z.object({
|
||||
+ title: z.string(),
|
||||
+ features: z.array(
|
||||
+ z.object({
|
||||
+ title: z.string(),
|
||||
+ description: z.string(),
|
||||
+ icon: z.string().optional(),
|
||||
+ })
|
||||
+ ),
|
||||
+ }),
|
||||
+ });
|
||||
39
docs/20251106/patches/0002-package-scripts.patch
Normal file
39
docs/20251106/patches/0002-package-scripts.patch
Normal file
@@ -0,0 +1,39 @@
|
||||
diff --git a/package.json b/package.json
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@
|
||||
"scripts": {
|
||||
- "build": "nuxt build",
|
||||
- "dev": "nuxt dev",
|
||||
- "generate": "nuxt generate",
|
||||
- "preview": "nuxt preview",
|
||||
- "postinstall": "nuxt prepare"
|
||||
+ "build": "nuxt build",
|
||||
+ "dev": "nuxt dev",
|
||||
+ "generate": "nuxt generate",
|
||||
+ "preview": "nuxt preview",
|
||||
+ "postinstall": "nuxt prepare",
|
||||
+ "typecheck": "nuxt typecheck",
|
||||
+ "lint": "eslint --ext .ts,.js,.mjs,.vue --ignore-path .gitignore .",
|
||||
+ "lint:fix": "pnpm lint -- --fix",
|
||||
+ "check": "pnpm lint && pnpm typecheck && pnpm build"
|
||||
},
|
||||
@@
|
||||
"dependencies": {
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@nuxt/content": "3.8.0",
|
||||
"@nuxt/eslint": "1.10.0",
|
||||
"@nuxt/ui": "4.1.0",
|
||||
"@nuxtjs/i18n": "10.2.0",
|
||||
"@nuxtjs/seo": "3.2.2",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"eslint": "^9.39.0",
|
||||
"nuxt": "^4.2.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.6.3"
|
||||
- }
|
||||
+ },
|
||||
+ "engines": {
|
||||
+ "node": ">=18.20.0"
|
||||
+ }
|
||||
34
docs/20251106/patches/0003-github-actions-ci.patch
Normal file
34
docs/20251106/patches/0003-github-actions-ci.patch
Normal file
@@ -0,0 +1,34 @@
|
||||
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/.github/workflows/ci.yml
|
||||
@@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
- name: Install dependencies
|
||||
run: pnpm i --frozen-lockfile
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
- name: Typecheck
|
||||
run: pnpm typecheck
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
50
docs/20251106/patches/0004-nuxt-route-rules.patch
Normal file
50
docs/20251106/patches/0004-nuxt-route-rules.patch
Normal file
@@ -0,0 +1,50 @@
|
||||
diff --git a/nuxt.config.ts b/nuxt.config.ts
|
||||
--- a/nuxt.config.ts
|
||||
+++ b/nuxt.config.ts
|
||||
@@
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: "2025-07-15",
|
||||
devtools: { enabled: true },
|
||||
modules: [
|
||||
"@nuxt/content",
|
||||
"@nuxt/ui",
|
||||
"@nuxt/eslint",
|
||||
"@nuxtjs/i18n",
|
||||
"@nuxtjs/seo",
|
||||
],
|
||||
css: ["@/assets/css/main.css"],
|
||||
app: {
|
||||
head: {
|
||||
titleTemplate: "%s - Tootaio Studio",
|
||||
meta: [
|
||||
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
||||
{ charset: "utf-8" },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@
|
||||
seo: {
|
||||
meta: {
|
||||
title: DEFAULT_SEO.title,
|
||||
description: DEFAULT_SEO.description,
|
||||
keywords: DEFAULT_SEO.keywords,
|
||||
ogTitle: DEFAULT_SEO.title,
|
||||
ogDescription: DEFAULT_SEO.description,
|
||||
ogImage: DEFAULT_SEO.image,
|
||||
ogUrl: DEFAULT_SEO.url,
|
||||
twitterCard: "summary_large_image",
|
||||
twitterTitle: DEFAULT_SEO.title,
|
||||
twitterDescription: DEFAULT_SEO.description,
|
||||
twitterImage: DEFAULT_SEO.image,
|
||||
},
|
||||
},
|
||||
site: {
|
||||
url: "https://tootaio.com",
|
||||
},
|
||||
+ routeRules: {
|
||||
+ "/": { prerender: true, swr: 3600 },
|
||||
+ "/_nuxt/**": {
|
||||
+ headers: { "cache-control": "public, max-age=31536000, immutable" },
|
||||
+ },
|
||||
+ },
|
||||
});
|
||||
33
docs/20251106/performance.md
Normal file
33
docs/20251106/performance.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 性能诊断与优化建议
|
||||
|
||||
## 现状与影响
|
||||
- SSR/CSR(Medium):默认 SSR,未针对首页静态化或增量缓存配置,变化不频繁的内容可降本提速。
|
||||
- 资源缓存(Medium):未设置构建产物(`/_nuxt/**`)的长期缓存与 immutable,重复访问浪费带宽。
|
||||
- 图片与外链(Low):OG 图与背景图使用外部域名,建议统一通过 CDN 域名与缓存规则管理。
|
||||
|
||||
## Nuxt 4 定向优化
|
||||
1) Route Rules 与 SWR(Medium)
|
||||
- 预渲染首页并启用 SWR:降低首屏 TTFB。
|
||||
- 长缓存构建产物:
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
routeRules: {
|
||||
'/': { prerender: true, swr: 3600 },
|
||||
'/_nuxt/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' } },
|
||||
},
|
||||
})
|
||||
```
|
||||
- 参见补丁:`0004-nuxt-route-rules.patch`。
|
||||
2) 代码分割与懒加载(Low)
|
||||
- 仅在首屏需要时挂载大型组件(如轮播);通过 `v-if` 配合 `onMounted` 或 `client-only` 降低 SSR 负载。
|
||||
3) 构建与依赖(Low)
|
||||
- 保持 `pnpm-lock.yaml` 锁定;CI 使用 `pnpm i --frozen-lockfile` 保证可重复构建。
|
||||
4) 图片与静态资源(Medium)
|
||||
- 将站点图片统一迁到自有 CDN(如 Cloudflare/Vercel Assets),配置缓存与压缩。
|
||||
- 如未来引入 `@nuxt/image`,优先使用自动格式与尺寸。
|
||||
|
||||
## 部署建议
|
||||
- Node/Nitro(通用):适合 SSR + SWR 与渐进更新。
|
||||
- Edge(部份路由):静态资源 + 首页可下放至边缘,注意依赖兼容性。
|
||||
- Static/Generate:若大部分页面静态,可考虑 `pnpm generate` 并配合 CDN。
|
||||
|
||||
34
docs/20251106/roadmap.md
Normal file
34
docs/20251106/roadmap.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 分阶段工程化改进路线图
|
||||
|
||||
## 阶段一:基础规范化(1–2 天)
|
||||
- 目标:消除 Critical/High 风险,建立最小工程基线。
|
||||
- 动作:
|
||||
- 应用补丁 0001/0002;
|
||||
- 启用 `pnpm check` 在本地与 CI;
|
||||
- 修复内容与 Schema 校验。
|
||||
- 验证:`pnpm check` 全绿;手测多语言与内容渲染。
|
||||
|
||||
## 阶段二:模块解耦(2–3 天)
|
||||
- 目标:清晰分层,降低耦合与心智成本。
|
||||
- 动作:
|
||||
- 统一 composables 命名为 `useXxx.ts`;
|
||||
- 将硬编码外链迁到 `runtimeConfig.public.assetBase`;
|
||||
- 视需要引入 Pinia 拆分跨页状态。
|
||||
- 验证:路由与数据流稳定;影响面与回归点有测试用例(增量引入 Vitest)。
|
||||
|
||||
## 阶段三:自动化部署(1–2 天)
|
||||
- 目标:PR 即可验证、主干可随时发布。
|
||||
- 动作:
|
||||
- 应用补丁 0003(GitHub Actions);
|
||||
- 配置 Vercel/自建 Docker 部署;
|
||||
- 产出构建产物与环境变量清单。
|
||||
- 验证:PR 触发 CI,产物可预览。
|
||||
|
||||
## 阶段四:性能与监控(2–4 天)
|
||||
- 目标:稳定响应、端到端可观测。
|
||||
- 动作:
|
||||
- 应用补丁 0004(Route Rules/SWR);
|
||||
- 引入 Sentry(DSN + 插件);
|
||||
- 图片与静态资源走 CDN 与长缓存。
|
||||
- 验证:关键页面首屏数据、错误率与 Web Vitals。
|
||||
|
||||
33
docs/20251106/security.md
Normal file
33
docs/20251106/security.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 安全审计与防护建议
|
||||
|
||||
## 发现与等级
|
||||
- 外链与配置(Medium)
|
||||
- 外部图片/链接硬编码在源码中;建议迁移到 `runtimeConfig.public.assetBase` 并集中管理。
|
||||
- 内容与校验(High)
|
||||
- Content Schema 与数据不一致会在构建期暴露为错误或导致运行时空值处理不当。
|
||||
- 依赖与脚本(Low)
|
||||
- 缺少 `typecheck`/`lint` 脚本,易让问题晚发现。
|
||||
|
||||
## 防护清单
|
||||
1) 运行时配置(Medium)
|
||||
```ts
|
||||
export default defineNuxtConfig({
|
||||
runtimeConfig: {
|
||||
// 仅服务器可见
|
||||
sentryDsn: '',
|
||||
// 客户端可见
|
||||
public: {
|
||||
assetBase: 'https://img.tootaio.com',
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
2) 安全头(建议由网关/Nginx 配置)(Low)
|
||||
- `Content-Security-Policy`(允许必要的域名);
|
||||
- `X-Content-Type-Options: nosniff`、`Referrer-Policy: strict-origin-when-cross-origin`;
|
||||
- 静态资源 `Cache-Control: immutable`。
|
||||
3) 敏感信息检查(Medium)
|
||||
- 使用 `.gitignore` 与 CI 检查(如 `trufflehog`/`gitleaks`,后续可引入)。
|
||||
4) 依赖与锁定(Low)
|
||||
- 全量使用 `pnpm-lock.yaml`,CI 中启用 `--frozen-lockfile` 保证可复现构建。
|
||||
|
||||
Reference in New Issue
Block a user