基于 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 会自动执行构建、测试和部署流程。
通过这种方式,项目能够快速响应代码变更,确保每次提交的代码都经过严格的测试和验证,并自动部署到生产环境。
Auction Platform Based on Nest.js and Angular
Project Overview
This project is a bidding platform based on Nest.js and Angular, designed to provide a complete bidding and management system.
Its main features include user registration and login, project creation and management, bid management, and user role management. The frontend is built with the Angular framework, the backend with the Nest.js framework, the database uses PostgreSQL, and API documentation is provided via Swagger.
The project is deployed on a DigitalOcean Droplet, with the frontend served via Nginx.
Frontend (Angular) ↓(API requests)Cognito (User authentication) ↓(after verification, forwards requests)Backend (Nest.js) ↓(database queries)Database (PostgreSQL) ↑(data return)Backend (Nest.js) ↑(processed response)Frontend (Angular)Project Structure
- frontend: Contains all frontend code, built with the Angular framework.
- backend: Contains all backend code, built with the Nest.js framework.
- .github: Contains GitHub Actions configuration files for continuous integration and deployment.
Backend
Backend Build
The backend is built using the Nest.js framework, providing a modular and extensible architecture. Main features include user authentication, project management, bid management, etc. The backend uses TypeORM for database operations, supporting multiple database types.
Backend Tech Stack
- Nest.js: Used to build efficient, scalable Node.js server-side applications.
- TypeORM: An ORM framework for database interactions.
- Swagger: Used to generate API documentation, making it easier for developers to view and test APIs.
Backend Build Steps
- Install dependencies: In the
backenddirectory, runnpm installto install all necessary dependencies. - Configure environment variables: Create a
.envfile in the project root to configure database connection information and other environment variables. - Run development server: Start the development server with
npm run start:dev, supporting hot reloading. - Production build: Use
npm run buildfor production builds; the generated files are in thedistdirectory.
Database
The project uses PostgreSQL as the database; all database operations are performed via TypeORM. The database initialization script is located at backend/SQL/init-script.sql and can be used to create and initialize the database.
The backend codebase is well organized, and the modular design makes extending functionality and maintenance easier.
Backend Security Authentication
Backend security authentication is implemented via AWS Cognito, combined with Nest.js interceptors and services to ensure user authentication and authorization.
Security Authentication Architecture
- AWS Cognito: Used for user registration, login, and authentication. Cognito provides secure user pools and identity pool management.
- Nest.js Interceptors: Used to intercept HTTP requests, validate the JWT Token in the request headers, ensuring the legitimacy of user identities.
- Service Layer: Responsible for handling interactions with Cognito and associating Cognito users with user information in the database.
Implementation Steps
-
Cognito User Pool Configuration: Create a user pool in AWS Cognito and configure the app client to support JWT token generation and validation.
-
JWT Interceptor: Create an interceptor in Nest.js to parse the JWT token from the request headers, validate its validity, and attach user information to the request object.
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();}} -
User Service: Create a user service that retrieves user information from the database and associates it with Cognito users. Use the Cognito ID as the unique identifier to store user information in the database.
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;}} -
Role and Permissions Management: Define user roles in the database (e.g., admin, client, bidder) and perform authorization based on roles in the interceptor.
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);}}Add the
@Roles('admin')decorator on APIs that require authorization to specify the required role.@Post()@Roles('admin')createProject(@Body() createProjectDto: CreateProjectDto) {return this.projectsService.createProject(createProjectDto);}
This approach allows the backend to effectively manage user identities and permissions, ensuring the security and reliability of the system.
Project Management Implementation
In the project management module, it will show how to call a Service via a Controller, then have the Service interact with the database.
Controller
In ProjectsController, define the routes and methods to handle HTTP requests.
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 handles business logic and interacts with the database.
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); }}Database Entities
Project defines the project’s structure in the database. The @Entity() decorator declares the entity, and the @Column() decorator defines its columns.
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;}Through this approach, the Controller handles HTTP requests, the Service handles business logic, and the database entity defines the data structure. The three work together to implement complete project management functionality.
Frontend
The frontend is built with the Angular framework, providing a user-friendly interface and rich interactions. Main features include project display, bid management, user registration and login, etc.
Frontend Tech Stack
- Angular: Used to build modern single-page applications.
- RxJS: Used to handle asynchronous data streams.
- Angular CLI: Provides powerful development tools and a command-line interface.
Frontend Build Steps
- Install dependencies: Run
npm installin thefrontenddirectory to install all necessary dependencies. - Development server: Start the development server with
ng serve, typically running athttp://localhost:4200/. - Production build: Use
ng buildfor production builds; the generated files are in thedistdirectory.
Project Details Component
The frontend application consists of multiple components, each responsible for a specific feature module. Here is an implementation of a sample component.
ProjectDetailComponent is used to display the details of a single project.
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 = 'Failed to load project details'; this.loading = false; console.error('Error loading project details:', err); } }); }
loadBids(projectId: number) { this.bidsService.getBidsByProjectId(projectId).subscribe({ next: (data) => { this.bids = data; }, error: (err) => { console.error('Error loading bid list:', err); } }); }}Template File
project-detail.component.html defines the structure for displaying project details.
<div class="project-detail"> <div *ngIf="loading" class="loading"> 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>Budget: ¥{{ project.budget_min }} - ¥{{ project.budget_max }}</p> <p>Deadline: {{ project.deadline | date }}</p> <p>Status: {{ project.status }}</p> </div>
<div class="project-description"> <h3>Project Description</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>Bid List</h3> <div *ngFor="let bid of bids" class="bid-card"> <p>Bidder: {{ bid.bidder_id }}</p> <p>Bid Amount: ¥{{ bid.amount }}</p> <p>Bid Description: {{ bid.message }}</p> <p>Status: {{ bid.status }}</p> </div> </div> </div></div>In this way, the frontend application can provide rich user interaction and data presentation features.
Testing
The project uses Jest for unit and integration testing to ensure code correctness and stability. ESLint is also used for code quality checks to ensure consistent coding style.
Jest Tests
Jest is a powerful JavaScript testing framework that supports assertions, mocks, and snapshot testing.
Jest Configuration
Configure Jest in the project’s package.json:
"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"}Example Test
The following is a simple service test example:
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 Code Quality Checks
ESLint is a tool that identifies and reports patterns in JavaScript code, helping developers maintain consistency and quality.
ESLint Configuration
Create a .eslintrc.js file in the project root:
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', },};Running ESLint
Add a script in package.json:
"scripts": { "lint": "eslint . --ext .ts"}Run npm run lint to check code quality.
By using Jest and ESLint, the project can ensure code correctness and consistency, improving development efficiency and code quality.
CI/CD
The project uses GitHub Actions to implement continuous integration and continuous deployment (CI/CD), ensuring code is automatically built, tested, and deployed after each commit.
GitHub Actions
GitHub Actions is a tool for automating software development workflows. By defining workflow files, you can automatically perform build, test, and deployment tasks in the repository.
Workflow Configuration
The CI/CD workflow is defined in the project at .github/workflows/deploy.yml:
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: | # Deployment scripts or commandsDeployment
- DigitalOcean: The project is deployed on a DigitalOcean Droplet, with the frontend served via Nginx.
- Automation: Each push to the main branch triggers automatic building, testing, and deployment via GitHub Actions.
This approach allows the project to respond quickly to code changes, ensuring every commit is thoroughly tested and validated, and automatically deployed to production.
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: CI/CD のための設定ファイルを含みます。
バックエンド
バックエンドの構築
バックエンドは Nest.js フレームワークで構築され、モジュール化され拡張性のあるアーキテクチャを提供します。主な機能は、ユーザー認証、プロジェクト管理、入札管理などです。バックエンドは TypeORM を通じてデータベース操作を行い、複数のデータベースタイプをサポートします。
バックエンド技術スタック
- Nest.js: 高速で拡張性の高い Node.js サーバーサイドアプリケーションを構築するためのフレームワーク。
- TypeORM: データベースとやり取りする ORM フレームワーク。
- Swagger: API ドキュメントを生成するための Swagger。
バックエンド構築手順
- 依存関係のインストール:
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 は安全なユーザープールとIDプールの管理を提供します。
- 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 が自動でビルド、テスト、デプロイのフローを実行します。
このようにして、コードの変更に迅速に対応でき、各コミットのコードが厳密にテストと検証を経て、本番環境へ自動デプロイされることを保証します。
部分信息可能已经过时









