chore: initialize application scaffold
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
# PostgreSQL 数据库主机地址
|
||||||
|
DATABASE_HOST=127.0.0.1
|
||||||
|
# PostgreSQL 数据库端口
|
||||||
|
DATABASE_PORT=5432
|
||||||
|
# PostgreSQL 数据库名称
|
||||||
|
DATABASE_NAME=example_app
|
||||||
|
# PostgreSQL 数据库用户名
|
||||||
|
DATABASE_USER=postgres
|
||||||
|
# PostgreSQL 数据库密码,请仅在本地 .env 文件中维护
|
||||||
|
DATABASE_PASSWORD=change_me
|
||||||
|
# 服务端监听端口
|
||||||
|
API_PORT=3000
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
coverage/
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
1、项目采用Nestjs+Vue3前后端分离型架构
|
||||||
|
2、项目用户前端UI框架采用TDesign Vue Next for Web组件库
|
||||||
|
3、项目后端后台UI框架采用Arco Design Vue组件库
|
||||||
|
4、后端Router要严格遵循RESTApi规范
|
||||||
|
5、禁止将所有组件写入同一个文件
|
||||||
|
6、项目数据库采用PostgreSQL数据库
|
||||||
|
7、每次开发完成执行打包,并使用本地服务器启动项目以进行预览
|
||||||
|
8、请严格为每段代码编写中文注释,包括函数、类、接口等。
|
||||||
|
9、请严格创建并更新技术文档,包括接口文档、数据库文档等以便于后续维护和扩展。
|
||||||
|
10、后端设计采用分层架构,同级解耦,以企业级规范要求进行设计。
|
||||||
@@ -1,2 +1,33 @@
|
|||||||
# zyzhixi_canva_mo
|
# zyzhixi_canva_mo
|
||||||
|
|
||||||
|
本仓库提供遵循项目规范的 NestJS、Vue 3、PostgreSQL 前后端分离示例框架。
|
||||||
|
|
||||||
|
## 应用职责
|
||||||
|
|
||||||
|
- `apps/api`:基于 NestJS 的 REST API,采用 controller、service、repository、entity、dto 分层。
|
||||||
|
- `apps/web`:基于 Vue 3 与 TDesign Vue Next 的用户端。
|
||||||
|
- `apps/admin`:基于 Vue 3 与 Arco Design Vue 的后台管理端。
|
||||||
|
- `docs`:接口、数据库及架构维护文档。
|
||||||
|
|
||||||
|
## 环境准备
|
||||||
|
|
||||||
|
1. 复制 `.env.example` 为 `.env`,填写 PostgreSQL 连接信息。
|
||||||
|
2. 在根目录执行 `npm install`。
|
||||||
|
3. 确保 PostgreSQL 中已创建 `example_app` 数据库。
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
npm run build
|
||||||
|
npm run start:api
|
||||||
|
```
|
||||||
|
|
||||||
|
用户端默认地址为 `http://localhost:5173`,管理端默认地址为 `http://localhost:5174`,API 默认地址为 `http://localhost:3000/api/v1`。
|
||||||
|
|
||||||
|
## 开发约束
|
||||||
|
|
||||||
|
- 新接口必须遵循 `/api/v1/资源名` 的 REST 路径规范。
|
||||||
|
- Controller 只处理协议转换;业务逻辑放在 Service;数据访问放在 Repository。
|
||||||
|
- 新增页面或组件时按职责分文件,禁止堆叠到单个组件文件。
|
||||||
|
- 数据库结构、接口变更必须同步更新 `docs`。
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<div id="app"></div><script type="module" src="/src/main.ts"></script>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@example/admin",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --port 5174",
|
||||||
|
"build": "vue-tsc -b && vite build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@arco-design/web-vue": "^2.57.0",
|
||||||
|
"vue": "^3.5.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^6.0.5",
|
||||||
|
"vue-tsc": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import AdminDashboard from './pages/AdminDashboard.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AdminDashboard />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { createApp } from 'vue';
|
||||||
|
import ArcoVue from '@arco-design/web-vue';
|
||||||
|
import '@arco-design/web-vue/dist/arco.css';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
/** 创建后台管理 Vue 应用实例。 */
|
||||||
|
createApp(App).use(ArcoVue).mount('#app');
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/** 定义仪表盘展示的状态文字。 */
|
||||||
|
const statusText = '管理端示例已准备就绪';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main class="dashboard-page">
|
||||||
|
<a-card title="后台管理端示例">
|
||||||
|
<a-result status="success" :title="statusText" sub-title="后续可在 pages 与 components 中按模块扩展功能。" />
|
||||||
|
</a-card>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dashboard-page { max-width: 960px; margin: 80px auto; padding: 0 24px; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.vue"]
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"root":["./src/main.ts","./src/app.vue","./src/pages/admindashboard.vue"],"version":"5.9.3"}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
|
||||||
|
/** 定义管理端开发与构建配置。 */
|
||||||
|
export default defineConfig({ plugins: [vue()] });
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src"
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "@example/api",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nest start --watch",
|
||||||
|
"build": "nest build",
|
||||||
|
"start": "node dist/main.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/common": "^11.0.1",
|
||||||
|
"@nestjs/config": "^4.0.0",
|
||||||
|
"@nestjs/core": "^11.0.1",
|
||||||
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.1",
|
||||||
|
"pg": "^8.13.1",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"rxjs": "^7.8.1",
|
||||||
|
"typeorm": "^0.3.20"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nestjs/cli": "^11.0.0",
|
||||||
|
"@nestjs/schematics": "^11.0.0",
|
||||||
|
"@nestjs/testing": "^11.0.1",
|
||||||
|
"@types/node": "^22.10.1",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.7.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { UsersModule } from './modules/users/users.module';
|
||||||
|
|
||||||
|
/** 聚合应用基础设施与业务模块。 */
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
|
TypeOrmModule.forRootAsync({
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (config: ConfigService) => ({
|
||||||
|
type: 'postgres' as const,
|
||||||
|
host: config.getOrThrow<string>('DATABASE_HOST'),
|
||||||
|
port: Number(config.get<string>('DATABASE_PORT', '5432')),
|
||||||
|
username: config.getOrThrow<string>('DATABASE_USER'),
|
||||||
|
password: config.getOrThrow<string>('DATABASE_PASSWORD'),
|
||||||
|
database: config.getOrThrow<string>('DATABASE_NAME'),
|
||||||
|
autoLoadEntities: true,
|
||||||
|
synchronize: false
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
UsersModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
/** 启动 HTTP 服务并配置全局协议规则。 */
|
||||||
|
async function bootstrap(): Promise<void> {
|
||||||
|
const app = await NestFactory.create(AppModule);
|
||||||
|
app.setGlobalPrefix('api/v1');
|
||||||
|
app.enableCors({ origin: ['http://localhost:5173', 'http://localhost:5174'] });
|
||||||
|
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true }));
|
||||||
|
await app.listen(process.env.API_PORT ?? 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bootstrap();
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { IsEmail, IsNotEmpty, IsString, MaxLength } from 'class-validator';
|
||||||
|
|
||||||
|
/** 创建用户的请求参数。 */
|
||||||
|
export class CreateUserDto {
|
||||||
|
/** 用户展示名称。 */
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@MaxLength(50)
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
/** 用户邮箱地址。 */
|
||||||
|
@IsEmail()
|
||||||
|
@MaxLength(120)
|
||||||
|
email!: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
|
/** 用户表对应的领域实体。 */
|
||||||
|
@Entity({ name: 'users' })
|
||||||
|
export class UserEntity {
|
||||||
|
/** 用户主键。 */
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
/** 用户展示名称。 */
|
||||||
|
@Column({ length: 50 })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
/** 用户邮箱地址,作为唯一业务标识。 */
|
||||||
|
@Column({ length: 120, unique: true })
|
||||||
|
email!: string;
|
||||||
|
|
||||||
|
/** 创建时间。 */
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
|
/** 最后更新时间。 */
|
||||||
|
@UpdateDateColumn({ name: 'updated_at' })
|
||||||
|
updatedAt!: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { CreateUserDto } from '../dto/create-user.dto';
|
||||||
|
import { UserEntity } from '../entities/user.entity';
|
||||||
|
|
||||||
|
/** 封装 users 表的持久化操作。 */
|
||||||
|
@Injectable()
|
||||||
|
export class UsersRepository {
|
||||||
|
/** 初始化用户实体仓储。 */
|
||||||
|
constructor(@InjectRepository(UserEntity) private readonly repository: Repository<UserEntity>) {}
|
||||||
|
|
||||||
|
/** 查询全部用户并按创建时间倒序返回。 */
|
||||||
|
findAll(): Promise<UserEntity[]> {
|
||||||
|
return this.repository.find({ order: { createdAt: 'DESC' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据邮箱查询单个用户。 */
|
||||||
|
findByEmail(email: string): Promise<UserEntity | null> {
|
||||||
|
return this.repository.findOneBy({ email });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建并保存用户实体。 */
|
||||||
|
async create(data: CreateUserDto): Promise<UserEntity> {
|
||||||
|
return this.repository.save(this.repository.create(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Body, Controller, Get, Post } from '@nestjs/common';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
import { UserEntity } from './entities/user.entity';
|
||||||
|
import { UsersService } from './users.service';
|
||||||
|
|
||||||
|
/** 提供 users 资源的 REST API。 */
|
||||||
|
@Controller('users')
|
||||||
|
export class UsersController {
|
||||||
|
/** 注入用户服务。 */
|
||||||
|
constructor(private readonly usersService: UsersService) {}
|
||||||
|
|
||||||
|
/** 响应用户集合资源。 */
|
||||||
|
@Get()
|
||||||
|
findAll(): Promise<UserEntity[]> {
|
||||||
|
return this.usersService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建一条用户资源。 */
|
||||||
|
@Post()
|
||||||
|
create(@Body() data: CreateUserDto): Promise<UserEntity> {
|
||||||
|
return this.usersService.create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { UserEntity } from './entities/user.entity';
|
||||||
|
import { UsersRepository } from './repositories/users.repository';
|
||||||
|
import { UsersController } from './users.controller';
|
||||||
|
import { UsersService } from './users.service';
|
||||||
|
|
||||||
|
/** 注册用户模块的控制器、服务与数据访问依赖。 */
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([UserEntity])],
|
||||||
|
controllers: [UsersController],
|
||||||
|
providers: [UsersService, UsersRepository]
|
||||||
|
})
|
||||||
|
export class UsersModule {}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { ConflictException, Injectable } from '@nestjs/common';
|
||||||
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
|
import { UserEntity } from './entities/user.entity';
|
||||||
|
import { UsersRepository } from './repositories/users.repository';
|
||||||
|
|
||||||
|
/** 处理用户领域业务规则。 */
|
||||||
|
@Injectable()
|
||||||
|
export class UsersService {
|
||||||
|
/** 注入用户数据仓储。 */
|
||||||
|
constructor(private readonly usersRepository: UsersRepository) {}
|
||||||
|
|
||||||
|
/** 获取用户列表。 */
|
||||||
|
findAll(): Promise<UserEntity[]> {
|
||||||
|
return this.usersRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建用户,并保证邮箱不重复。 */
|
||||||
|
async create(data: CreateUserDto): Promise<UserEntity> {
|
||||||
|
const existingUser = await this.usersRepository.findByEmail(data.email);
|
||||||
|
if (existingUser) {
|
||||||
|
throw new ConflictException('邮箱已被使用');
|
||||||
|
}
|
||||||
|
return this.usersRepository.create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"removeComments": false,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "ES2023",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"incremental": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<div id="app"></div><script type="module" src="/src/main.ts"></script>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@example/web",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --port 5173",
|
||||||
|
"build": "vue-tsc -b && vite build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tdesign-vue-next": "^1.10.3",
|
||||||
|
"vue": "^3.5.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^6.0.5",
|
||||||
|
"vue-tsc": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import UserWelcome from './pages/UserWelcome.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UserWelcome />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { createApp } from 'vue';
|
||||||
|
import TDesign from 'tdesign-vue-next';
|
||||||
|
import 'tdesign-vue-next/es/style/index.css';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
/** 创建用户端 Vue 应用实例。 */
|
||||||
|
createApp(App).use(TDesign).mount('#app');
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
/** 存储当前示例名称。 */
|
||||||
|
const name = ref('开发者');
|
||||||
|
|
||||||
|
/** 生成页面问候语。 */
|
||||||
|
const greeting = computed(() => `你好,${name.value}。示例用户端已准备就绪。`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main class="welcome-page">
|
||||||
|
<t-card title="用户端示例">
|
||||||
|
<p>{{ greeting }}</p>
|
||||||
|
<t-input v-model="name" clearable placeholder="请输入名称" />
|
||||||
|
</t-card>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.welcome-page { max-width: 720px; margin: 80px auto; padding: 0 24px; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.vue"]
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"root":["./src/main.ts","./src/app.vue","./src/pages/userwelcome.vue"],"version":"5.9.3"}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
|
||||||
|
/** 定义用户端开发与构建配置。 */
|
||||||
|
export default defineConfig({ plugins: [vue()] });
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# 接口文档
|
||||||
|
|
||||||
|
服务根路径:`/api/v1`
|
||||||
|
|
||||||
|
## 用户资源
|
||||||
|
|
||||||
|
### 获取用户列表
|
||||||
|
|
||||||
|
- 请求:`GET /api/v1/users`
|
||||||
|
- 响应:`200 OK`
|
||||||
|
|
||||||
|
```json
|
||||||
|
[{"id":"uuid","name":"示例用户","email":"demo@example.com","createdAt":"2026-08-23T00:00:00.000Z","updatedAt":"2026-08-23T00:00:00.000Z"}]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建用户
|
||||||
|
|
||||||
|
- 请求:`POST /api/v1/users`
|
||||||
|
- 请求体:`name` 为 1 至 50 个字符,`email` 必须为邮箱格式。
|
||||||
|
- 响应:`201 Created`
|
||||||
|
- 冲突:邮箱已经存在时返回 `409 Conflict`。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"name":"示例用户","email":"demo@example.com"}
|
||||||
|
```
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 架构说明
|
||||||
|
|
||||||
|
## 分层职责
|
||||||
|
|
||||||
|
用户请求经过 Controller 到达 Service;Service 负责业务规则并调用 Repository;Repository 仅负责 TypeORM 持久化。Entity 映射数据库表,DTO 校验请求输入。
|
||||||
|
|
||||||
|
## 模块扩展
|
||||||
|
|
||||||
|
新增领域模块时,在 `apps/api/src/modules` 创建独立目录,并保持 `controller`、`service`、`repository`、`dto`、`entities` 的职责边界。跨模块调用应通过导出的服务完成,不应直接访问另一模块的 Repository。
|
||||||
|
|
||||||
|
## 前端划分
|
||||||
|
|
||||||
|
用户端与管理端各自独立构建、部署。页面放在 `pages`,可复用业务单元放在 `components`,HTTP 调用应集中到 `api` 目录,避免把网络逻辑散落在页面中。
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# 数据库设计
|
||||||
|
|
||||||
|
## PostgreSQL 连接
|
||||||
|
|
||||||
|
服务端通过根目录 `.env` 中的 `DATABASE_HOST`、`DATABASE_PORT`、`DATABASE_NAME`、`DATABASE_USER` 与 `DATABASE_PASSWORD` 建立连接。生产环境禁止使用 `synchronize` 自动变更结构,应通过迁移文件管理变更。
|
||||||
|
|
||||||
|
## users 表
|
||||||
|
|
||||||
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| id | uuid | 主键 | 用户唯一标识 |
|
||||||
|
| name | varchar(50) | 非空 | 展示名称 |
|
||||||
|
| email | varchar(120) | 非空、唯一 | 邮箱地址 |
|
||||||
|
| created_at | timestamp | 非空 | 创建时间 |
|
||||||
|
| updated_at | timestamp | 非空 | 更新时间 |
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE users (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name varchar(50) NOT NULL,
|
||||||
|
email varchar(120) NOT NULL UNIQUE,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
```
|
||||||
Generated
+7271
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "react-bq-01",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"workspaces": [
|
||||||
|
"apps/*"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"dev": "concurrently \"npm run dev -w @example/api\" \"npm run dev -w @example/web\" \"npm run dev -w @example/admin\"",
|
||||||
|
"build": "npm run build -w @example/api && npm run build -w @example/web && npm run build -w @example/admin",
|
||||||
|
"start:api": "npm run start -w @example/api"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"concurrently": "^9.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user