feat: 项目初始化

This commit is contained in:
2026-05-22 00:50:13 +08:00
commit 397ccdca13
17 changed files with 11107 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example
/prisma/generated
+131
View File
@@ -0,0 +1,131 @@
# CLAUDE.md
## 目标
本文档用于约束 AI 在本项目内的实现方式,确保代码简洁、可维护、安全,并与当前 Nuxt、Nitro、Prisma 项目结构保持一致。
## 核心原则
- 默认最小改动,优先复用现有目录结构、工具函数、类型定义和项目约定。
- 保持单一入口和单一职责,避免同一业务暴露多套路由或重复实现。
- 前端不接触数据库连接串、session、完整 API Key、token、cookie 等敏感信息。
- 所有返回给前端的 `msg` 必须使用本地业务表达,不暴露内部服务名、第三方来源、token、key、连接串或调试细节。
- 内部异常统一返回“服务器内部错误”;具体异常写入服务端日志。
- 注释和日志要解释意图、边界和排查信息,不输出密码、cookie、完整 key、token、图片二进制、base64 或完整敏感请求内容。
- 已存在的说明性注释不要随意删除;确需重写文件时,保留或迁移原注释表达的信息。
- 不要写任何可能阻塞整个进程的代码。
- 不为未来不确定需求预建兼容路由、抽象层或配置项。
## 目录结构约定
- 前端应用代码放在 `app/`
- `app/assets/`:需要被构建工具处理的资源。
- `app/components/`Vue 组件。
- `app/composables/`Vue composables。
- `app/layouts/`:页面布局组件。
- `app/middleware/`:前端路由中间件。
- `app/pages/`:文件路由页面。
- `app/plugins/`Nuxt 应用插件。
- `app/services/`:前端服务封装,按业务拆分。
- `app/stores/`Pinia store,按业务拆分。
- `app/utils/`:前端通用工具。
- 服务端代码放在 `server/`
- `server/api/`JSON API 路由。
- `server/routes/`:服务端非 API 路由,例如动态 sitemap。
- `server/middleware/`:服务端中间件。
- `server/plugins/`Nitro 服务端插件。
- `server/utils/`:服务端通用工具和外部服务适配。
- 前后端都需要复用的类型和纯函数放在 `shared/`
- 静态公开文件放在 `public/`,不经过构建处理。
- 不再使用 `@prisma/nuxt`;Prisma 只允许在服务端工具和 API 路由中使用。
## API 路由规范
- API 路由按业务模块放在 `server/api/<module>/` 或明确的 `server/api/*.ts` 文件中。
- 不为同一能力创建多个本地别名路由。
- 临时排查接口不要长期保留,例如数据库健康检查类接口排查结束后必须删除。
- Handler 只负责入参校验、鉴权、调用业务函数和统一响应;可复用逻辑放入 `server/utils`
- 新增或修改接口时,同步维护共享类型、服务端实现和前端调用。
- API 响应只返回前端需要的字段,不透出内部响应体、数据库异常、第三方错误详情或完整调试对象。
## 请求、响应与错误处理
- 所有 JSON 接口先校验请求体:只接受 JSON 对象或 `null`,数组、字符串等无效 body 直接返回 400。
- 对客户端传入和第三方透传字段必须使用白名单过滤,只接受类型正确且当前接口声明的字段。
- 成功响应的 `msg` 只写本地业务结果,例如“创建成功”“更新成功”“查询成功”。
- 错误响应的 `msg` 只写本地业务错误,例如“未登录”“请求体必须是 JSON 对象”“记录不存在”。
- 内部或第三方异常统一转换为“服务器内部错误”,具体异常必须在返回前写入服务端日志。
- 401 或登录态失效时统一清理本地 auth cookie。
- 不在 handler 内重复实现复杂错误解析,通用判断放到 `server/utils`
## 数据库与 Prisma
- Prisma schema 位于 `prisma/schema.prisma`,实际数据库结构以 Prisma 为准。
- 当前使用 Prisma 7 + `@prisma/adapter-mariadb`
- Prisma Client 生成目录为 `prisma/generated`,不要改回 `app/generated`
- Prisma Client 统一从 `server/utils/db.ts` 创建和复用;业务代码不要直接 `new PrismaClient()`
- 数据库连接只在服务端读取 `DATABASE_URL`,前端不允许读取或间接获取连接信息。
- 修改数据库结构时,优先使用 Prisma migration;不要绕过 Prisma 手动改表。
- 数据库结构变更的标准流程:
1. 修改 `prisma/schema.prisma`
2. 新增对应 migration,或在全新开发库且确认可接受时说明原因后使用 `prisma db push`
3. 运行 `.\node_modules\.bin\prisma.CMD generate` 重新生成客户端。
4. 对实际数据库执行同步命令,并确认 schema 已落库。
5. 运行类型检查;涉及服务启动或构建行为时再运行构建。
- 如果 migration 状态和数据库实际结构不一致,先停下来检查原因,不要直接强行部署或 resolve。
- 数据库不要保存大体积二进制、base64 或敏感明文;确需保存排查字段时必须确认不会返回给前端。
## 日志规范
- 日志必须包含足够排查信息,例如 `requestId`、阶段、用户标识、记录 id、分页、耗时、状态。
- 全局请求日志只记录 method、path、status、耗时,不记录请求体和响应体。
- 内部响应、第三方响应、数据库异常等细节只允许写入服务端日志或服务端排查字段,不允许通过接口返回给前端。
- 不记录密码、cookie、完整 API key、token、完整连接串、完整敏感 prompt、图片二进制或 base64。
- 后台任务失败不能影响主请求成功结果时,应记录失败摘要,并在业务状态中使用本地描述。
## 类型规范
- 只有前后端共享的接口契约放入 `shared/types`
- 服务端内部适配类型留在 `server/utils` 或对应 handler 内。
- 新增或修改接口时先更新共享类型,再更新服务端和前端调用。
- 类型字段应有中文注释,说明含义、可选性和是否可能为空。
- TypeScript 严格类型优先,避免 `any` 扩散;确需使用时必须收敛在边界处。
## 前端接入约定
- 前端服务封装放在 `app/services/`,按业务模块拆分。
- 所有接口请求逻辑和相关状态统一放在对应 Pinia store 中,不在组件内直接调用 service。
- Store 文件放在 `app/stores/` 下,按业务模块单独拆文件。
- 组件只负责 UI 渲染和交互,通过 `useXxxStore()``storeToRefs()` 消费状态,通过 store action 发起操作。
- Store 内部的通用常量作为具名导出,供组件引用,不在组件中重复定义。
- 组件不得展示内部服务名、第三方错误、token、key、连接串或原始异常信息。
## 代码风格
- 修改优先小步快改,不重构无关代码。
- 新增通用逻辑优先抽到 `server/utils``app/utils``shared` 中合适的位置。
- `server/` 下新增或大改文件时,文件顶部写一行中文用途说明,格式建议为 `// server/... - 用途说明`
- 复杂 `defineEventHandler` 上方写清楚接口流程、鉴权边界、数据是否入库、哪些信息不会返回前端。
- 注释使用中文,描述意图与边界,不写机械解释。
- 不删除已有有价值注释。
## AI 执行清单
当 AI 新增或调整 API、数据结构或核心业务逻辑时,按以下顺序执行:
1. 先阅读现有 handler、utils、shared types、Prisma schema,确认真实实现。
2. 更新共享类型和服务端内部类型。
3. 在 handler 中完成请求体校验、白名单过滤、鉴权和日志。
4. 将可复用业务逻辑抽到合适的 `server/utils``app/utils``shared`
5. 涉及数据库时同步修改 Prisma schema、migration,并对实际数据库执行同步。
6. 确认前端响应中的 `msg``data` 不包含内部或第三方细节。
7. 确认敏感信息不进入前端响应、日志或不该保存的数据库字段。
8. 运行类型检查;涉及 Prisma schema 时必须运行 `prisma generate`,涉及服务启动或构建行为时再运行构建。
## 非目标
- 不为未来不确定需求预建兼容路由。
- 不引入与当前需求无关的抽象层。
- 不在多个地方维护同一配置值。
- 不保存大体积 base64 或敏感明文。
- 不把服务端密钥、session、token、cookie 或数据库连接信息暴露给前端。
+75
View File
@@ -0,0 +1,75 @@
# Nuxt Minimal Starter
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
+3
View File
@@ -0,0 +1,3 @@
<template>
<NuxtPage />
</template>
View File
+6
View File
@@ -0,0 +1,6 @@
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt(
// Your custom configs here
)
+14
View File
@@ -0,0 +1,14 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
modules: [
'@nuxtjs/tailwindcss',
'@pinia/nuxt',
'@vueuse/nuxt',
'dayjs-nuxt',
'@nuxt/eslint',
'shadcn-nuxt'
]
})
+31
View File
@@ -0,0 +1,31 @@
{
"name": "ocs_nuxt",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "prisma generate && nuxt prepare"
},
"dependencies": {
"@nuxt/eslint": "1.15.2",
"@nuxtjs/tailwindcss": "6.14.0",
"@pinia/nuxt": "0.11.3",
"@prisma/adapter-mariadb": "7.8.0",
"@prisma/client": "7.8.0",
"@vueuse/nuxt": "14.3.0",
"dayjs-nuxt": "2.1.11",
"eslint": "^10.4.0",
"nuxt": "^4.4.6",
"pinia": "^3.0.4",
"shadcn-nuxt": "2.7.3",
"vue": "^3.5.34",
"vue-router": "^5.0.7"
},
"devDependencies": {
"dotenv": "^17.4.2",
"prisma": "^7.8.0"
}
}
+10700
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
allowBuilds:
'@prisma/client': false
'@prisma/engines': false
prisma: false
+14
View File
@@ -0,0 +1,14 @@
// This file was generated by Prisma, and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: env("DATABASE_URL"),
},
});
+29
View File
@@ -0,0 +1,29 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Get a free hosted Postgres database in seconds: `npx create-db`
generator client {
provider = "prisma-client"
output = "./generated"
}
datasource db {
provider = "mysql"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
+2
View File
@@ -0,0 +1,2 @@
User-Agent: *
Disallow:
+6
View File
@@ -0,0 +1,6 @@
export default defineEventHandler(async () => {
return prisma.user.findMany({
include: { posts: true },
orderBy: { id: "desc" },
});
});
+10
View File
@@ -0,0 +1,10 @@
export default defineEventHandler(async (event) => {
const body = await readBody<{ email: string; name?: string | null }>(event);
return prisma.user.create({
data: {
email: body.email,
name: body.name,
},
});
});
+38
View File
@@ -0,0 +1,38 @@
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import { PrismaClient } from "../../prisma/generated/client";
const createMariaDbConfig = () => {
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error("DATABASE_URL is not set.");
}
const url = new URL(databaseUrl);
return {
host: url.hostname,
port: Number(url.port || 3306),
user: decodeURIComponent(url.username),
password: decodeURIComponent(url.password),
database: decodeURIComponent(url.pathname.slice(1)),
connectionLimit: 5,
connectTimeout: 15_000,
acquireTimeout: 20_000,
};
};
const prismaClientSingleton = () => {
const adapter = new PrismaMariaDb(createMariaDbConfig());
return new PrismaClient({ adapter });
};
type PrismaClientSingleton = ReturnType<typeof prismaClientSingleton>;
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClientSingleton | undefined;
};
export const prisma = globalForPrisma.prisma ?? prismaClientSingleton();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
+18
View File
@@ -0,0 +1,18 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"files": [],
"references": [
{
"path": "./.nuxt/tsconfig.app.json"
},
{
"path": "./.nuxt/tsconfig.server.json"
},
{
"path": "./.nuxt/tsconfig.shared.json"
},
{
"path": "./.nuxt/tsconfig.node.json"
}
]
}