基于 Nest.js 和 Angular 的竞价平台
项目总体描述
本项目是一个基于 Nest.js 和 Angular 的竞价平台,旨在提供一个完整的竞标和管理系统。
项目的主要功能包括用户注册和登录、项目创建和管理、投标管理以及用户角色管理。项目的前端使用 Angular 框架构建,后端使用 Nest.js 框架构建,数据库使用 PostgreSQL,并通过 Swagger 提供 API 文档。
项目部署在 DigitalOcean 的 Droplet 上,前端通过 Nginx 进行部署。
前端 (Angular) ↓(API 请求)Cognito (用户认证) ↓(验证通过后请求转发)后端 (Nest.js) ↓(数据库查询)数据库 (PostgreSQL) ↑(数据返回)后端 (Nest.js) ↑(处理后的响应)前端 (Angular)项目结构
- frontend: 包含所有前端代码,使用 Angular 框架构建。
- backend: 包含所有后端代码,使用 Nest.js 框架构建。
- .github: 包含 GitHub Actions 的配置文件,用于持续集成和部署。
后端
后端构建
后端使用 Nest.js 框架构建,提供了一个模块化、可扩展的架构。主要功能包括用户认证、项目管理、投标管理等。后端通过 TypeORM 进行数据库操作,支持多种数据库类型。
后端技术栈
- Nest.js: 用于构建高效、可扩展的 Node.js 服务器端应用程序。
- TypeORM: 用于数据库交互的 ORM 框架。
- Swagger: 用于生成 API 文档,方便开发者查看和测试 API。
后端构建步骤
- 安装依赖: 在
backend目录下运行npm install安装所有必要的依赖。 - 配置环境变量: 在项目根目录下创建
.env文件,配置数据库连接信息和其他环境变量。 - 运行开发服务器: 使用
npm run start:dev启动开发服务器,支持热重载。 - 生产构建: 使用
npm run build进行生产环境构建,生成的文件位于dist目录。
数据库
项目使用 PostgreSQL 作为数据库,所有的数据库操作通过 TypeORM 进行。数据库初始化脚本位于 backend/SQL/init-script.sql,可以用于创建和初始化数据库。
后端代码结构清晰,模块化设计使得功能扩展和维护更加方便。
后端安全认证
后端的安全认证通过 AWS Cognito 实现,结合 Nest.js 的拦截器和服务,确保用户的身份验证和授权。
安全认证架构
- AWS Cognito: 用于用户注册、登录和身份验证。Cognito 提供了安全的用户池和身份池管理。
- Nest.js 拦截器: 用于拦截 HTTP 请求,验证请求头中的 JWT Token,确保用户身份的合法性。
- Service 层: 负责处理与 Cognito 的交互,以及将 Cognito 用户与数据库中的用户信息关联。
实现步骤
-
Cognito 用户池配置: 在 AWS Cognito 中创建用户池,并配置应用客户端以支持 JWT Token 的生成和验证。
-
JWT 拦截器: 在 Nest.js 中创建一个拦截器,解析请求头中的 JWT Token,验证其有效性,并将用户信息附加到请求对象中。
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, UnauthorizedException } from '@nestjs/common';import { Observable } from 'rxjs';import { AuthService } from './auth.service';@Injectable()export class JwtInterceptor implements NestInterceptor {constructor(private readonly authService: AuthService) {}intercept(context: ExecutionContext, next: CallHandler): Observable<any> {const request = context.switchToHttp().getRequest();const token = request.headers.authorization?.split(' ')[1];if (!token) {throw new UnauthorizedException('Token not found');}const user = this.authService.validateToken(token);if (!user) {throw new UnauthorizedException('Invalid token');}request.user = user;return next.handle();}} -
用户服务: 创建一个用户服务,负责从数据库中检索用户信息,并将其与 Cognito 用户进行关联。通过 Cognito ID 作为唯一标识符,将用户信息存储在数据库中。
import { Injectable } from '@nestjs/common';import { UsersRepository } from './users.repository';@Injectable()export class UsersService {constructor(private readonly usersRepository: UsersRepository) {}async findOrCreateUser(cognitoId: string, email: string) {let user = await this.usersRepository.findOneByCognitoId(cognitoId);if (!user) {user = await this.usersRepository.create({ cognitoId, email });}return user;}} -
角色和权限管理: 在数据库中定义用户角色(如管理员、客户、投标者),并在拦截器中根据角色进行权限验证。
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';import { Reflector } from '@nestjs/core';@Injectable()export class RolesGuard implements CanActivate {constructor(private reflector: Reflector) {}canActivate(context: ExecutionContext): boolean {const roles = this.reflector.get<string[]>('roles', context.getHandler());if (!roles) {return true;}const request = context.switchToHttp().getRequest();const user = request.user;return roles.includes(user.role);}}在需要权限验证的 API 上添加
@Roles('admin')装饰器,指定需要的角色。@Post()@Roles('admin')createProject(@Body() createProjectDto: CreateProjectDto) {return this.projectsService.createProject(createProjectDto);}
通过这种方式,后端能够有效地管理用户身份和权限,确保系统的安全性和可靠性。
项目管理实现
在项目管理模块中,将展示如何通过 Controller 调用 Service,再通过 Service 与数据库交互。
Controller
在 ProjectsController 中,定义处理 HTTP 请求的路由和方法。
import { Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common';import { ProjectsService } from './projects.service';import { ProjectsDto } from '../entities/DTO/projects.dto';
@Controller('projects')export class ProjectsController { constructor(private readonly projectsService: ProjectsService) {}
@Get() findAll() { return this.projectsService.findAll(); }
@Get(':id') findOne(@Param('id') id: number) { return this.projectsService.findOne(id); }
@Post() create(@Body() projectDto: ProjectsDto) { return this.projectsService.create(projectDto); }
@Put(':id') update(@Param('id') id: number, @Body() projectDto: ProjectsDto) { return this.projectsService.update(id, projectDto); }
@Delete(':id') delete(@Param('id') id: number) { return this.projectsService.delete(id); }}Service
ProjectsService 负责业务逻辑处理,并与数据库进行交互。
import { Injectable } from '@nestjs/common';import { DataSource } from 'typeorm';import { Project } from '../entities/projects.entity';import { ProjectsDto } from '../entities/DTO/projects.dto';
@Injectable()export class ProjectsService { constructor(private dataSource: DataSource) {}
findAll() { return this.dataSource.getRepository(Project).find(); }
findOne(id: number) { return this.dataSource.getRepository(Project).findOneBy({ project_id: id }); }
create(project: ProjectsDto) { return this.dataSource.getRepository(Project).save(project); }
update(id: number, project: ProjectsDto) { return this.dataSource.getRepository(Project).update(id, project); }
delete(id: number) { return this.dataSource.getRepository(Project).delete(id); }}数据库实体
Project 实体定义了项目在数据库中的结构,通过 @Entity() 装饰器定义实体,通过 @Column() 装饰器定义列。
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()export class Project { @PrimaryGeneratedColumn() project_id: number;
@Column() title: string;
@Column() description: string;
@Column('decimal') budget_min: number;
@Column('decimal') budget_max: number;
@Column('date') deadline: Date;
@Column({ default: 'open' }) status: string;}通过这种方式,Controller 负责处理 HTTP 请求,Service 负责业务逻辑,数据库实体定义数据结构,三者协同工作,实现完整的项目管理功能。
前端
前端使用 Angular 框架构建,提供了用户友好的界面和交互体验。主要功能包括项目展示、投标管理、用户注册和登录等。
前端技术栈
- Angular: 用于构建现代化的单页应用程序。
- RxJS: 用于处理异步数据流。
- Angular CLI: 提供了强大的开发工具和命令行接口。
前端构建步骤
- 安装依赖: 在
frontend目录下运行npm install安装所有必要的依赖。 - 开发服务器: 使用
ng serve启动开发服务器,默认在http://localhost:4200/运行。 - 生产构建: 使用
ng build进行生产环境构建,生成的文件位于dist目录。
项目详情组件
前端应用由多个组件组成,每个组件负责特定的功能模块。以下是一个示例组件的实现。
ProjectDetailComponent 用于展示单个项目的详细信息。
import { Component, OnInit } from '@angular/core';import { ActivatedRoute } from '@angular/router';import { ProjectsService } from '../../services/projects.service';import { BidsService } from '../../services/bids.service';import { AuthService } from '../../services/auth.service';
@Component({ selector: 'app-project-detail', templateUrl: './project-detail.component.html', styleUrls: ['./project-detail.component.css']})export class ProjectDetailComponent implements OnInit { project: any = null; bids: any[] = []; loading = false; error = ''; userRole: string = '';
constructor( private route: ActivatedRoute, private projectsService: ProjectsService, private bidsService: BidsService, private authService: AuthService ) {}
ngOnInit() { this.userRole = this.authService.getUserRole(); const projectId = this.route.snapshot.paramMap.get('id'); if (projectId) { this.loadProject(+projectId); this.loadBids(+projectId); } }
loadProject(id: number) { this.loading = true; this.projectsService.getProjectById(id).subscribe({ next: (data) => { this.project = data; this.loading = false; }, error: (err) => { this.error = '加载项目详情失败'; this.loading = false; console.error('加载项目详情错误:', err); } }); }
loadBids(projectId: number) { this.bidsService.getBidsByProjectId(projectId).subscribe({ next: (data) => { this.bids = data; }, error: (err) => { console.error('加载投标列表错误:', err); } }); }}模板文件
project-detail.component.html 定义了项目详情的展示结构。
<div class="project-detail"> <div *ngIf="loading" class="loading"> 加载中... </div>
<div *ngIf="error" class="error"> {{ error }} </div>
<div *ngIf="project && !loading" class="project-info"> <h2>{{ project.title }}</h2> <div class="project-meta"> <p>预算: ¥{{ project.budget_min }} - ¥{{ project.budget_max }}</p> <p>截止日期: {{ project.deadline | date }}</p> <p>状态: {{ project.status }}</p> </div>
<div class="project-description"> <h3>项目描述</h3> <p>{{ project.description }}</p> </div>
<app-bid-form *ngIf="userRole === 'bidder' && project.status === 'open'" [projectId]="project.project_id" (bidSubmitted)="loadBids(project.project_id)"> </app-bid-form>
<div class="bids-section" *ngIf="userRole === 'client' || userRole === 'admin'"> <h3>投标列表</h3> <div *ngFor="let bid of bids" class="bid-card"> <p>投标人: {{ bid.bidder_id }}</p> <p>投标金额: ¥{{ bid.amount }}</p> <p>投标说明: {{ bid.message }}</p> <p>状态: {{ bid.status }}</p> </div> </div> </div></div>通过这种方式,前端应用能够提供丰富的用户交互和数据展示功能。
测试
项目使用 Jest 进行单元测试和集成测试,确保代码的正确性和稳定性。同时,使用 ESLint 进行代码质量检查,确保代码风格一致。
Jest 测试
Jest 是一个强大的 JavaScript 测试框架,支持断言、模拟和快照测试。
Jest 配置
在项目的 package.json 中配置 Jest:
"scripts": { "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage"},"jest": { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": "src", "testRegex": ".*\\\\.spec\\\\.ts$", "transform": { "^.+\\\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node"}示例测试
以下是一个简单的服务测试示例:
import { Test, TestingModule } from '@nestjs/testing';import { ProjectsService } from './projects.service';
describe('ProjectsService', () => { let service: ProjectsService;
beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ProjectsService], }).compile();
service = module.get<ProjectsService>(ProjectsService); });
it('should be defined', () => { expect(service).toBeDefined(); });
describe('findOne', () => { it('应该返回单个项目', async () => { const result = await service.findOne(1); expect(result).toEqual(mockProject); }); });});ESLint 代码质量检查
ESLint 是一个用于识别和报告 JavaScript 代码中的模式的工具,帮助开发者保持代码的一致性和质量。
ESLint 配置
在项目根目录下创建 .eslintrc.js 文件:
module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', },};运行 ESLint
在 package.json 中添加脚本:
"scripts": { "lint": "eslint . --ext .ts"}通过运行 npm run lint 来检查代码质量。
通过使用 Jest 和 ESLint,项目能够确保代码的正确性和一致性,提高开发效率和代码质量。
CI/CD
项目使用 GitHub Actions 实现持续集成和持续部署(CI/CD),确保代码在每次提交后自动构建、测试和部署。
GitHub Actions
GitHub Actions 是一个用于自动化软件开发工作流的工具。通过定义工作流文件,可以在代码库中自动执行构建、测试和部署任务。
工作流配置
在项目的 .github/workflows/deploy.yml 文件中定义了 CI/CD 工作流:
name: CI/CD Pipeline
on: push: branches: - main pull_request: branches: - main
jobs: build: runs-on: ubuntu-latest
steps: - name: Checkout code uses: actions/checkout@v2
- name: Set up Node.js uses: actions/setup-node@v2 with: node-version: '20.18.0'
- name: Install dependencies run: | cd backend npm install cd ../frontend npm install
- name: Run tests run: | cd backend npm run test:cov cd ../frontend npm run test
- name: Lint code run: | cd backend npm run lint cd ../frontend npm run lint
- name: Build project run: | cd backend npm run build cd ../frontend npm run build
- name: Create Release Package run: | mkdir -p build cd backend tar -czvf ../build/backend.tar.gz dist cd ../frontend tar -czvf ../build/frontend.tar.gz dist cd ..
- name: Deploy to DigitalOcean uses: digitalocean/action-doctl@v2 with: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} run: | # 部署脚本或命令部署
- DigitalOcean: 项目部署在 DigitalOcean 的 Droplet 上,前端通过 Nginx 进行部署。
- 自动化流程: 每次代码提交到
main分支时,GitHub Actions 会自动执行构建、测试和部署流程。
通过这种方式,项目能够快速响应代码变更,确保每次提交的代码都经过严格的测试和验证,并自动部署到生产环境。
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时





