feat: 项目初始化
This commit is contained in:
+26
@@ -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
|
||||
@@ -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 或数据库连接信息暴露给前端。
|
||||
@@ -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.
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<NuxtPage />
|
||||
</template>
|
||||
@@ -0,0 +1,6 @@
|
||||
// @ts-check
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default withNuxt(
|
||||
// Your custom configs here
|
||||
)
|
||||
@@ -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'
|
||||
]
|
||||
})
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Generated
+10700
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
allowBuilds:
|
||||
'@prisma/client': false
|
||||
'@prisma/engines': false
|
||||
prisma: false
|
||||
@@ -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"),
|
||||
},
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,6 @@
|
||||
export default defineEventHandler(async () => {
|
||||
return prisma.user.findMany({
|
||||
include: { posts: true },
|
||||
orderBy: { id: "desc" },
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user