marcomg-byte/offser
GitHub: marcomg-byte/offser
这是一个专为红队行动设计的基于 TypeScript 的 Express 服务器,集成了 SMTP 邮件发送、凭据收集存储及 Matrix 风格监控面板。
Stars: 0 | Forks: 0
# Offser
一个基于 TypeScript 的 Express.js 服务器,用于通过 SMTP 发送邮件、渲染模板、使用 MySQL 管理数据库记录、提供强大的验证、错误处理、模块化架构和高级日志记录。
## 功能
- 带有模块化路由的 Express.js API
- TypeScript 提供类型安全
- 对所有请求 payload 进行 Zod schema 验证
- 集成 Nodemailer 进行 SMTP 邮件投递
- 带有生产环境缓存的 Handlebars 模板渲染
- **MySQL 数据库集成与连接池**
- **支持 SSL/TLS 以确保数据库和 SMTP 连接安全**
- 集中式错误处理中间件
- 带有验证的环境变量配置
- 针对邮件和渲染端点的速率限制
- 用于字符串格式化和错误提取的工具函数
- **使用 [Pino](https://github.com/pinojs/pino) 和 multistream 的高级日志记录:**
- 带有颜色高亮的漂亮终端输出
- 按级别(error, info, warn, debug)输出的结构化 JSON 日志
- 在 `logs/` 目录中自动创建日志文件
- 自定义时间戳格式 (MM-DD-YYYY HH:mm:ss)
- 全面的错误日志记录和调试
- **支持带有 SSL/TLS 证书的 HTTPS 服务器**
- **带有信号拦截的优雅关机处理**
- **Matrix 主题的 UI 组件(仪表板和 404 页面)**
## 法律免责声明
本工具**专门用于经授权的渗透测试、红队操作和安全研究**,仅限于您拥有或已获得明确书面许可的系统。
未经事先同意,针对系统使用本工具是非法的,并可能违反计算机犯罪法律,包括但不限于《计算机欺诈和滥用法》(CFAA)、《英国计算机滥用法》以及您所在司法管辖区的相关法律。
作者对于因滥用本工具而造成的任何滥用、损害或非法行为**不承担任何责任**。使用本软件即表示您同意,您有责任独自遵守所有适用的地方法、州法、国家和国际法律。
**请负责任且合乎道德地使用。**
## 项目结构
```
.
├── assets/
│ ├── facebook.ico
│ └── matrix.ico
├── backup/
│ └── logs/ # (if present at runtime)
├── certs/
│ ├── db/
│ │ ├── ca-cert.pem
│ │ ├── client-cert.pem
│ │ └── client-key.pem
│ ├── server/
│ │ ├── cert.pem
│ │ └── key.pem
│ └── README.md
├── db/
│ ├── offser_passwords.sql
│ ├── offser_routines.sql
│ └── README.md
├── src/
│ ├── __mocks__/
│ │ ├── index.ts
│ │ ├── nodemailer.ts
│ │ └── server.ts
│ ├── config/
│ │ ├── __mocks__/
│ │ │ └── env.ts
│ │ ├── env.spec.ts
│ │ └── env.ts
│ ├── controllers/
│ │ ├── app.controller.ts
│ │ ├── db.controller.ts
│ │ ├── index.ts
│ │ ├── mail.controller.spec.ts
│ │ ├── mail.controller.ts
│ │ ├── template.controller.spec.ts
│ │ └── template.controller.ts
│ ├── errors/
│ │ ├── db.error.ts
│ │ ├── index.ts
│ │ ├── mail.error.ts
│ │ ├── server.error.ts
│ │ ├── template.error.ts
│ │ └── types/
│ │ ├── error.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── middleware/
│ │ ├── error.middleware.spec.ts
│ │ ├── error.middleware.ts
│ │ ├── index.ts
│ │ ├── not-found.middleware.spec.ts
│ │ └── not-found.middleware.ts
│ ├── routes/
│ │ ├── app.routes.ts
│ │ ├── db.routes.ts
│ │ ├── health.routes.spec.ts
│ │ ├── health.routes.ts
│ │ ├── index.ts
│ │ ├── mail.routes.ts
│ │ └── template.routes.ts
│ ├── schemas/
│ │ ├── __mocks__/
│ │ │ └── index.ts
│ │ ├── app.schema.ts
│ │ ├── db.schema.ts
│ │ ├── index.ts
│ │ ├── mail.schema.spec.ts
│ │ ├── mail.schema.ts
│ │ ├── template.schema.spec.ts
│ │ └── template.schema.ts
│ ├── services/
│ │ ├── __mocks__/
│ │ │ └── index.ts
│ │ ├── db.service.spec.ts
│ │ ├── db.service.ts
│ │ ├── index.ts
│ │ ├── mail.service.spec.ts
│ │ ├── mail.service.ts
│ │ ├── template.service.spec.ts
│ │ └── template.service.ts
│ ├── templates/
│ │ ├── dashboard.hbs
│ │ ├── facebook-login.hbs
│ │ ├── not-found.hbs
│ │ ├── offers.hbs
│ │ ├── sales.hbs
│ │ └── shipment.hbs
│ └── utils/
│ ├── __mocks__/
│ │ └── (if present)
│ ├── error.util.spec.ts
│ ├── error.util.ts
│ ├── format.util.spec.ts
│ ├── format.util.ts
│ ├── index.ts
│ ├── logger.util.spec.ts
│ ├── logger.util.ts
│ ├── shutdown.util.spec.ts
│ └── shutdown.util.ts
├── .github/
│ └── workflows/
│ ├── ci.yml
│ ├── container.yml
│ ├── publish.yml
│ └── release.yml
├── .dockerignore
├── .env
├── .env.container
├── .env.example
├── .gitignore
├── .prettierrc
├── .nvmrc
├── Dockerfile
├── LICENSE
├── README.md
├── esbuild.cli.mjs
├── esbuild.container.mjs
├── eslint.config.js
├── package.json
├── package-lock.json
├── tsconfig.json
├── tsconfig.test.json
├── vitest.config.ts
```
## 入门指南
### 前置条件
- [Node.js](https://nodejs.org/) (推荐 v18+)
- [npm](https://www.npmjs.com/)
- SMTP 服务器凭据(例如,使用应用专用密码的 Gmail、SendGrid 等)
- **MySQL 数据库** (推荐 v8.0+)
- **(可选)用于 HTTPS 服务器和安全 MySQL 连接的 SSL/TLS 证书**
## Docker 使用
您可以在 Docker 容器中构建和运行 Offser 以便轻松部署。
### 构建 Docker 镜像
在项目根目录下,运行:
```
docker build -t offser .
```
### 运行容器
```
docker run --env-file .env -p 8080:8080 -v $(pwd)/certs:/app/certs offser
```
- `--env-file .env` 加载环境变量(所需值请参见 `.env.example`)
- `-p 8080:8080` 将容器端口映射到您的主机
- `-v $(pwd)/certs:/app/certs` 挂载您本地的 `certs` 目录以用于 SSL/DB 证书
#### Docker 的数据库主机
如果您的 MySQL 数据库运行在您的主机(而非 Docker)上,请在您的 `.env` 中设置:
```
DB_HOST=host.docker.internal
```
这允许容器连接到您主机的 MySQL 实例。如果您的数据库在另一个容器中,请使用 Docker 网络服务名称。
#### .dockerignore
构建使用多阶段 Dockerfile 和 `.dockerignore` 文件来排除不必要的文件(例如 `node_modules`、`logs`、`dist`、本地 `.env`)。
有关更多详细信息,请参阅项目根目录中的 Dockerfile 和 `.dockerignore`。
### 安装
1. **克隆仓库**
git clone https://github.com/marcomg-byte/offser.git
cd offser
2. **安装依赖**
npm install
3. **配置环境变量**
根据 `.env.example` 创建 `.env` 文件:
# Server Configuration
NODE_ENV=development
PORT=8080
# SMTP Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
MAIL_FROM=your-email@gmail.com
# Rate Limiting
MAIL_SERVICE_RATE_LIMIT=10
MAIL_SERVICE_RATE_WINDOW=15
RENDER_SERVICE_RATE_LIMIT=20
RENDER_SERVICE_RATE_WINDOW=10
# Database Configuration
DB_HOST=localhost
DB_PORT=3306
DB_USER=your_db_user
DB_PASS=your_db_password
DB_NAME=your_database_name
DB_CONNECTION_LIMIT=10
DB_QUEUE_LIMIT=0
DB_WAIT_FOR_CONNECTIONS=true
DB_KEEP_ALIVE=true
DB_KEEP_ALIVE_INITIAL_DELAY=0
# Database SSL (Optional)
DB_SSL_ENABLED=false
# DB_SSL_CA=certs/db/ca.pem
# DB_SSL_CERT=certs/db/client-cert.pem
# DB_SSL_KEY=certs/db/client-key.pem
# DB_SSL_REJECT_UNAUTHORIZED=true
# HTTPS Configuration (Optional)
HTTPS_ENABLED=false
# HTTPS_KEY_PATH=certs/server/key.pem
# HTTPS_CERT_PATH=certs/server/cert.pem
4. **设置数据库**
从 `db/` 目录运行 SQL 脚本:
# Create the passwords table
mysql -u your_db_user -p your_database_name < db/offser_passwords.sql
# Create stored procedures
mysql -u your_db_user -p your_database_name < db/offser_routines.sql
或者在您的 MySQL 客户端中手动执行脚本。有关详细设置说明,请参阅 [db/README.md](db/README.md)。
5. **构建项目**
npm run build
6. **启动服务器**
npm start
服务器将在 `.env` 中指定的端口上运行(默认:3000)。
### 开发
对于具有自动重载和模板监视的开发:
```
npm run dev
```
这使用 `concurrently` 同时运行 TypeScript 编译器和模板监视器。
### 可用脚本
- `npm run build` - 将 TypeScript 编译为 JavaScript 并将模板复制到 dist
- `npm run build:templates` - 将 Handlebars 模板复制到 dist 目录
- `npm run dev` - 启动具有热重载的开发服务器 (Nodemon + tsx)
- `npm start` - 运行生产构建
- `npm run lint` - 使用 ESLint 检查代码
- `npm run lint:fix` - 自动修复 ESLint 问题
- `npm test` - 使用 Vitest 运行单元和集成测试(监视模式)
- `npm run test:run` - 运行一次测试
- `npm run test:coverage` - 生成测试覆盖率报告
- `npm run test:ui` - 打开 Vitest UI 进行交互式测试
- `npm run clean` - 删除 dist 文件夹
- `npm run clean:logs` - 删除 logs 文件夹
- `npm run backup:logs` - 将日志文件归档到 backup/logs 目录
- `npm run watch:templates` - 监视模板目录的更改
- `npm run prepare` - 清理并构建(在发布之前运行)
## 日志记录
本项目使用带有 multistream 的 [Pino](https://github.com/pinojs/pino) 进行高级日志记录:
- **终端输出:** 使用 [pino-pretty](https://github.com/pinojs/pino-pretty) 进行开发时的漂亮打印、彩色日志。
- **日志文件:** `logs/` 目录中按级别分类的结构化 JSON 日志:
- `error.log` - 错误级别消息
- `info.log` - 信息级别消息
- `warn.log` - 警告级别消息
- `debug.log` - 调试级别消息
- **自动创建日志目录:** 无需手动设置。
- **自定义时间戳格式:** `MM-DD-YYYY HH:mm:ss` 以便于阅读。
- **大写日志级别:** 所有日志输出格式一致。
- **日志归档:** 在清理之前使用 `npm run backup:logs` 将日志归档到 `backup/logs/`。
所有日志文件都会在 `logs/` 目录中自动创建。记录器在 [`logger.util.ts`](src/utils/logger.util.ts) 中配置。
## 架构
### 模块化结构
- **Controllers(控制器)** - 处理 HTTP 请求和响应,协调服务之间的交互
- **Services(服务)** - 业务逻辑(邮件发送、模板渲染、数据库操作)
- **Routes(路由)** - 定义 API 端点,应用速率限制和中间件
- **Schemas(模式)** - 基于请求和响应的 Zod 数据验证规则
- **Middleware(中间件)** - 横切关注点(错误处理、404 页面、请求日志记录)
- **Utils(工具)** - 可重用的工具函数(日志记录、格式化、错误提取、关机)
- **Config(配置)** - 应用程序配置和环境变量验证
- **Templates(模板)** - 用于动态 HTML 邮件和页面的 Handlebars 模板
- **Errors(错误)** - 针对不同失败场景且带有上下文的自定义错误类
### 错误处理
[`error.middleware.ts`](src/middleware/error.middleware.ts) 中的集中式错误处理中间件捕获并处理:
- **Zod 验证错误** → 400 Bad Request,附带美化的验证详细信息
- **数据库错误**(连接、查询、SSL 配置、传输器创建)→ 500 Internal Server Error,附带上下文
- **邮件/模板错误** → 500 Internal Server Error,附带详细上下文
- **SSL/证书错误**(文件丢失、路径无效)→ 500 Internal Server Error,附带文件信息
- **未知错误** → 500 Internal Server Error,附带回退消息
所有错误都使用 Pino 记录器记录,包含:
- 完整的堆栈跟踪
- 错误上下文(查询参数、请求正文摘录)
- 自定义错误属性(原因、传输器信息、模板数据)
- 时间戳和日志级别
有关错误规范化逻辑,请参阅 [`extractErrorInfo`](src/utils/error.util.ts)。
### 数据库架构
数据库层使用 MySQL 2 和连接池:
- **连接池**:可配置的 MySQL 连接池,用于高效利用资源
- **SSL/TLS 支持**:带有证书验证的可选加密数据库连接
- **存储过程**:所有数据库操作都使用 MySQL 存储过程:
- `INSERT_PASSWORD(mail, password)` - 插入新的密码记录
- `READ_PASSWORDS(upperLimit, lowerLimit)` - 读取带有范围的密码记录
- `DELETE_ENTRY(lowerLimit, upperLimit)` - 按 ID 范围删除密码记录
- **事务支持**:自动事务处理,出错时执行 BEGIN、COMMIT 和 ROLLBACK
- **连接验证**:启动验证确保在接受请求之前数据库连接正常
- **错误处理**:数据库错误被包装在带有查询上下文的自定义错误类中
配置在 [`db.service.ts`](src/services/db.service.ts) 中管理。有关设置详细信息,请参阅 [db/README.md](db/README.md) 和 [certs/README.md](certs/README.md)。
### 模板系统
带有生产环境缓存的 Handlebars 模板:
- **开发模式**:每次请求都会重新加载模板以获得即时反馈
- **生产模式**:模板预加载并缓存在内存中以提升性能
- **自定义助手**:`toCSV` 助手将密码记录转换为 CSV 格式以便导出
- **模板编译**:Handlebars 编译模板并注入数据
- **错误处理**:模板编译错误包含模板名称和数据上下文
有关实现,请参阅 [`template.service.ts`](src/services/template.service.ts)。
## API 端点
### 健康检查
- **端点**:`GET /health`
- **描述**:检查服务器运行状态、运行时间和当前时间戳
**成功响应 (200):**
```
{
"status": "ok",
"timestamp": "2024-02-05T09:07:03.000Z",
"localDate": "02-05-2024 09:07:03",
"uptime": 120.47
}
```
### 数据库操作
#### 插入数据
- **端点**:`POST /records/insert`
- **描述**:向数据库插入新的密码记录
- **速率限制**:无速率限制
**请求正文:**
```
{
"mail": "user@example.com",
"password": "securePassword123"
}
```
**验证:**
- `mail`:必须是有效的电子邮件地址
- `password`:必须是非空字符串
**成功响应 (200):**
```
{
"title": "Data Inserted Successfully!",
"data": {
"mail": "user@example.com",
"password": "securePassword123"
}
}
```
#### 读取数据
- **端点**:`GET /records/read`
- **描述**:检索指定 ID 范围内的密码记录
- **速率限制**:无速率限制
**请求正文:**
```
{
"lowerLimit": 1,
"upperLimit": 10
}
```
**验证:**
- `lowerLimit`:可选的正整数 >= 1
- `upperLimit`:必需的正整数 >= 1
**成功响应 (200):**
```
{
"title": "Data Read Successfully!",
"data": [
{
"ID": 1,
"MAIL": "user@example.com",
"PASSWORD": "securePassword123"
}
]
}
```
#### 删除数据
- **端点**:`DELETE /records/delete`
- **描述**:按 ID 或 ID 范围删除密码记录
- **速率限制**:无速率限制
**请求正文:**
```
{
"lowerLimit": 5,
"upperLimit": 10
}
```
**验证:**
- `lowerLimit`:必需的正整数 >= 1
- `upperLimit`:可选的正整数 >=1
**成功响应 (200):**
```
{
"title": "Data Deleted Successfully!",
"lowerLimit": 5,
"upperLimit": 10
}
```
#### 数据库健康检查
- **端点**:`GET /records/health`
- **描述**:验证数据库连接并返回连接状态
- **速率限制**:无速率限制
**成功响应 (200):**
```
{
"status": "OK",
"timestamp": "2024-02-05T09:07:03.000Z",
"localDate": "02-05-2024 09:07:03",
"uptime": 120.47
}
```
**错误响应 (503):**
```
{
"status": "ERROR",
"timestamp": "2024-02-05T09:07:03.000Z",
"localDate": "02-05-2024 09:07:03",
"uptime": 120.47
}
```
### 应用程序路由
#### 渲染仪表板
- **端点**:`GET /app/dashboard?lowerLimit=1&upperLimit=100`
- **描述**:渲染一个 Matrix 主题的仪表板,显示密码记录并具有 CSV 导出功能
- **速率限制**:通过 `RENDER_SERVICE_RATE_LIMIT` 和 `RENDER_SERVICE_RATE_WINDOW` 配置
**查询参数:**
- `lowerLimit`(可选,数字)- 记录的起始 ID
- `upperLimit`(必需,数字)- 记录的结束 ID
**成功响应 (200):**
- 返回渲染的 HTML 页面,包含:
- 黑底绿字的 Matrix 风格主题,带有下落代码动画
- 显示每条记录的 ID、电子邮件和密码的数据网格
- CSV 导出按钮(仅当存在数据时显示)
- 适用于移动设备的响应式设计
- 无数据可用时的空状态消息
**功能:**
- 使用 `toCSV` Handlebars 助手进行客户端 CSV 导出
- 自动下载 `password_records.csv` 文件
- 带有发光效果和动画的 Matrix 主题 UI
### 发送邮件
- **端点**:`POST /mail/send`
- **描述**:通过配置的 SMTP 服务器发送电子邮件
- **速率限制**:通过 `MAIL_SERVICE_RATE_LIMIT` 和 `MAIL_SERVICE_RATE_WINDOW` 配置
**请求正文:**
```
{
"to": "recipient@example.com",
"subject": "Email Subject",
"text": "Plain text content",
"html": "",
"accepted": ["recipient@example.com"],
"response": "250 Message accepted"
}
}
```
**验证错误响应 (400):**
```
{
"title": "Request Validation Error",
"error": "[Expected string at 'to', Expected string at 'subject']"
}
```
**服务器错误响应 (500):**
```
{
"title": "Mail Service Error",
"name": "MailDeliveryError",
"message": "Failed to send email to recipient@example.com",
"stack": "...",
"cause": {
"code": "EAUTH",
"message": "Invalid login credentials"
}
}
```
### 渲染模板
- **端点**:`GET /render/:templateName`
- **描述**:使用提供的数据渲染 Handlebars 模板
- **速率限制**:通过 `RENDER_SERVICE_RATE_LIMIT` 和 `RENDER_SERVICE_RATE_WINDOW` 配置
**请求示例:**
```
GET /render/offers
Content-Type: application/json
{
"customerName": "John",
"offers": [
{ "title": "10% Discount", "description": "On all items" }
],
"ctaUrl": "https://example.com/offer"
}
```
**可用模板:**
- `sales` - 带有产品优惠的促销邮件(需要:offers[], ctaUrl)
- `offers` - 促销优惠邮件(需要:customerName, offers[], ctaUrl)
- `shipment` - 订单发货确认(需要:customerName, orderNumber, shippingAddress, estimatedDelivery, items[], trackingUrl)
- `dashboard` - 带有 Matrix 主题的密码记录仪表板(需要:data[])
- `not-found` - 带有 Matrix 主题的 404 错误页面(不需要数据)
**成功响应 (200):**
- 返回渲染的 HTML 字符串
**验证错误响应 (400):**
```
{
"title": "Request Validation Error",
"error": "[Expected string at 'customerName']"
}
```
**模板未找到 (500):**
```
{
"title": "Template Compilation Error",
"name": "TemplateCompileError",
"message": "Template 'invalid-template' not found",
"stack": "..."
}
```
## 关键工具
- [`capitalizeWord(word)`](src/utils/format.util.ts) - 将单词的首字母大写,其余小写
- [`capitalizeString(value)`](src/utils/format.util.ts) - 将字符串中每个单词的首字母大写
- [`formatDate(date)`](src/utils/format.util.ts) - 将 Date 对象格式化为 `MM-DD-YYYY HH:mm:ss`
- [`logger`](src/utils/logger.util.ts) - 带有 multistream(终端 + 文件)的 Pino 记录器实例
- [`extractErrorInfo(error)`](src/utils/error.util.ts) - 规范化错误对象以进行一致的日志记录
- [`gracefulShutdown(server, signal)`](src/utils/shutdown.util.ts) - 处理 SIGTERM/SIGINT 时的优雅服务器关机
**错误提取:**
`extractErrorInfo` 工具处理各种错误类型:
- `ZodError` - 美化带有字段路径的验证错误
- 自定义错误(Mail, Template, Database)- 提取名称、消息、堆栈和自定义属性
- 原生错误 - 标准错误信息
- 未知类型 - 转换为字符串表示形式
## SSL/TLS 配置
### HTTPS 服务器
要为 Express 服务器启用 HTTPS:
1. **生成或获取 SSL 证书:**
# For development (self-signed):
openssl req -x509 -newkey rsa:4096 -keyout certs/server/key.pem -out certs/server/cert.pem -days 365 -nodes
2. **将证书放入 `certs/server/` 目录**
3. **更新 `.env` 文件:**
HTTPS_ENABLED=true
HTTPS_KEY_PATH=certs/server/key.pem
HTTPS_CERT_PATH=certs/server/cert.pem
4. **应用程序将:**
- 在配置的端口上启动 HTTPS 服务器
- 记录证书加载状态
- 如果文件丢失则抛出 `CertificateNotFoundError`
- 自动验证证书有效性
有关详细的证书管理说明,请参阅 [certs/README.md](certs/README.md)。
### MySQL SSL 连接
要为加密的数据库连接启用 SSL/TLS:
1. **从您的数据库提供商获取 SSL 证书**(例如,AWS RDS、Google Cloud SQL)
2. **将证书放入 `certs/db/` 目录:**
- `ca.pem` - 证书颁发机构证书
- `client-cert.pem` - 客户端证书
- `client-key.pem` - 客户端私钥
3. **更新 `.env` 文件:**
DB_SSL_ENABLED=true
DB_SSL_CA=certs/db/ca.pem
DB_SSL_CERT=certs/db/client-cert.pem
DB_SSL_KEY=certs/db/client-key.pem
DB_SSL_REJECT_UNAUTHORIZED=true
4. **配置选项:**
- `DB_SSL_ENABLED=true` - 为数据库连接启用 SSL
- `DB_SSL_CA` - CA 证书的路径(SSL 所需)
- `DB_SSL_CERT` - 客户端证书的路径(可选,用于双向 TLS)
- `DB_SSL_KEY` - 客户端密钥的路径(可选,用于双向 TLS)
- `DB_SSL_REJECT_UNAUTHORIZED=true` - 拒绝无效证书(推荐用于生产环境)
有关更多详细信息,请参阅 [certs/README.md](certs/README.md) 和 [db/README.md](db/README.md)。
## SMTP 安全配置
### `SMTP_SECURE` 和 SSL/TLS
`SMTP_SECURE` 环境变量控制 SMTP 连接的 SSL/TLS 加密:
**安全连接 (SSL/TLS):**
```
SMTP_SECURE=true
SMTP_PORT=465
```
- 从一开始就进行完整的 SSL/TLS 加密
- 使用端口 465(SMTPS 的标准端口)
- 推荐用于生产环境
- 当 `SMTP_SECURE=true` 时,应用程序强制使用端口 465
**STARTTLS 连接:**
```
SMTP_SECURE=false
SMTP_PORT=587
```
- 连接开始时未加密,然后升级到 TLS
- 使用端口 587(带有 STARTTLS 的 SMTP 的标准端口)
- 大多数电子邮件提供商都支持
- 当 `SMTP_SECURE=false` 时,应用程序强制使用端口 587
**端口强制执行:**
应用程序在启动时验证 SMTP 配置,如果出现以下情况将抛出错误:
- `SMTP_SECURE=true` 但 `SMTP_PORT` 不是 465
- `SMTP_SECURE=false` 但 `SMTP_PORT` 不是 587
这可以防止配置错误并确保使用正确的加密。
**Gmail 配置示例:**
```
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password # Use App Password, not account password
MAIL_FROM=your-email@gmail.com
```
有关实现详细信息,请参阅 [`mail.service.ts`](src/services/mail.service.ts)。
## 环境配置
所有环境变量都在启动时使用 [`env.ts`](src/config/env.ts) 中的 Zod schema 进行验证。
### 必需变量
**SMTP 配置:**
- `SMTP_HOST` - SMTP 服务器主机名(例如,`smtp.gmail.com`)
- `SMTP_USER` - SMTP 认证用户名/电子邮件
- `SMTP_PASS` - SMTP 认证密码
- `MAIL_FROM` - 默认发件人电子邮件地址
**速率限制:**
- `MAIL_SERVICE_RATE_LIMIT` - 每个时间窗口的最大邮件请求数(整数)
- `MAIL_SERVICE_RATE_WINDOW` - 速率限制时间窗口(分钟)(整数)
- `RENDER_SERVICE_RATE_LIMIT` - 每个时间窗口的最大渲染请求数(整数)
- `RENDER_SERVICE_RATE_WINDOW` - 速率限制时间窗口(分钟)(整数)
**应用程序环境:**
- `NODE_ENV` - 环境模式(`development`、`production`、`test`)
**数据库配置:**
- `DB_HOST` - MySQL 服务器主机名
- `DB_PORT` - MySQL 服务器端口(通常为 3306)
- `DB_USER` - 数据库用户名
- `DB_PASS` - 数据库密码
- `DB_NAME` - 数据库名称
### 可选变量
**服务器:**
- `PORT` - 服务器监听端口(默认:`3000`)
**SMTP:**
- `SMTP_PORT` - SMTP 服务器端口(默认:`587`)
- `SMTP_SECURE` - 使用 SSL/TLS(默认:`false`)
**数据库连接池:**
- `DB_CONNECTION_LIMIT` - 最大池连接数(默认:`10`)
- `DB_QUEUE_LIMIT` - 最大排队连接请求数(默认:`0` = 无限制)
- `DB_WAIT_FOR_CONNECTIONS` - 等待可用连接(默认:`true`)
- `DB_KEEP_ALIVE` - 启用 TCP keep-alive(默认:`true`)
- `DB_KEEP_ALIVE_INITIAL_DELAY` - Keep-alive 初始延迟(毫秒)(默认:`0`)
**数据库 SSL:**
- `DB_SSL_ENABLED` - 为数据库启用 SSL(默认:`false`)
- `DB_SSL_CA` - CA 证书的路径(可选)
- `DB_SSL_CERT` - 客户端证书的路径(可选)
- `DB_SSL_KEY` - 客户端密钥的路径(可选)
- `DB_SSL_REJECT_UNAUTHORIZED` - 拒绝无效证书(默认:`true`)
**HTTPS 服务器:**
- `HTTPS_ENABLED` - 启用 HTTPS 服务器(默认:`false`)
- `HTTPS_KEY_PATH` - HTTPS 私钥的路径(默认:`./key.pem`)
- `HTTPS_CERT_PATH` - HTTPS 证书的路径(默认:`./cert.pem`)
**验证:**
缺少必需的变量将导致应用程序在启动期间失败,并显示一条明确的错误消息,指出缺少哪个变量。
有关详细文档和验证逻辑,请参阅 [`env.ts`](src/config/env.ts)。
## 代码质量
### Linting 和格式化
- **ESLint** - 具有推荐规则的 TypeScript 感知 linting
- 配置:[`eslint.config.js`](eslint.config.js)
- 使用 `@typescript-eslint` 进行 TypeScript 支持
- 与 Prettier 集成以进行一致的格式化
- 最大行长度:120 个字符
- **Prettier** - 主观的代码格式化
- 配置:[`.prettierrc`](.prettierrc)
- 保存时自动格式化(如果编辑器已配置)
- 通过 ESLint 插件强制执行
**命令:**
```
npm run lint # Check for linting issues
npm run lint:fix # Auto-fix linting and formatting issues
```
### 类型安全
- **TypeScript** - 启用严格类型检查
- 主配置:[`tsconfig.json`](tsconfig.json)
- 测试配置:[`tsconfig.test.json`](tsconfig.test.json)
- 启用严格模式以实现最大的类型安全
- 不允许隐式 `any` 类型
## 测试
本项目使用 [Vitest](https://vitest.dev/) 进行单元和集成测试,并提供全面的覆盖率。
### 测试结构
- **配置**:[`vitest.config.ts`](vitest.config.ts)
- **测试文件**:与源文件并列存放(`.spec.ts` 扩展名)
- **模拟**:位于 `__mocks__` 目录中用于模块模拟
- **覆盖率提供者**:V8 用于准确的覆盖率报告
### 运行测试
```
npm test # Run tests in watch mode (interactive)
npm run test:run # Run all tests once (CI mode)
npm run test:coverage # Generate coverage report
npm run test:ui # Open Vitest UI (visual test runner)
npm run test:clean # Remove coverage directory
```
### 测试覆盖率
覆盖率报告在 `coverage/` 目录中生成,包含:
- **文本报告** - 控制台输出
- **JSON 报告** - 机器可读格式
- **HTML 报告** - 交互式浏览器视图(打开 `coverage/index.html`)
**覆盖率排除项:**
- `node_modules/` - 第三方依赖
- `dist/` - 编译输出
- `errors/` - 误类定义
- `config/` - 配置文件
- `*.config.*` - 所有配置文件
- `*.d.ts` - TypeScript 定义文件
- `**/index.ts` - 重新导出文件
- `**/types/**` - 类型定义目录
### 测试示例
**服务测试:**
- [`mail.service.spec.ts`](src/services/mail.service.spec.ts) - 邮件服务和传输器
- [`template.service.spec.ts`](src/services/template.service.spec.ts) - 模板编译和缓存
**控制器测试:**
- [`mail.controller.spec.ts`](src/controllers/mail.controller.spec.ts) - 邮件发送端点
**路由测试:**
- [`health.routes.spec.ts`](src/routes/health.routes.spec.ts) - 健康检查端点
**工具测试:**
- [`format.util.spec.ts`](src/utils/format.util.spec.ts) - 字符串格式化工具
- [`error.util.spec.ts`](src/utils/error.util.spec.ts) - 错误提取逻辑
**配置测试:**
- [`env.spec.ts`](src/config/env.spec.ts) - 环境变量验证
### 模拟策略
- **Nodemailer** - 为邮件测试模拟传输器
- **文件系统** - 为模板测试模拟 `fs` 模块
- **环境** - 为不同的 NODE_ENV 场景模拟配置
- **数据库** - 为控制器测试模拟连接池
## 开发工作流
1. **创建功能分支:**
git checkout -b feature/my-feature
2. **对代码库进行更改**
3. **运行 linting 并修复问题:**
npm run lint:fix
4. **运行测试并验证覆盖率:**
npm test
npm run test:coverage
5. **构建项目:**
npm run build
6. **在本地测试您的更改:**
npm run dev
7. **提交您的更改:**
git add .
git commit -m "Add new feature: description"
8. **推送到分支:**
git push origin feature/my-feature
9. **在 GitHub 上打开拉取请求**
### 开发技巧
- **热重载**:`npm run dev` 监视 TypeScript 文件和模板
- **模板开发**:模板在开发模式下自动重新加载
- **日志监视**:检查 `logs/` 目录以获取详细的应用程序日志
- **数据库测试**:使用事务安全地测试数据库操作
- **环境切换**:创建 `.env.development` 和 `.env.production` 用于不同的配置
## CLI 使用(全局安装)
全局安装 Offser 以用作命令行工具:
```
npm install -g offser
```
这使得 `offser` 命令可在系统范围内使用。
### 使用方法
从任何地方启动服务器:
```
offser
```
CLI 将:
- 在当前目录中查找 `.env` 文件
- 使用相同的配置启动服务器
- 如果未找到 `.env`,则使用 shell 中的环境变量
**要求:**
- 项目必须在发布或全局安装之前构建(`npm run build`)
- `dist/index.js` 必须存在且可执行
- `.env` 文件应位于当前工作目录中
### 发布
要发布到 npm:
```
npm run prepare # Clean and build
npm publish # Publish to npm registry
```
`prepare` 脚本在发布之前自动运行,以确保构建是最新的。
## 故障排除
### SMTP 连接问题
**症状:**
- 邮件发送失败并出现身份验证错误
- 连接超时错误
**解决方案:**
- 验证 `.env` 文件中的 SMTP 凭据
- 检查防火墙设置(允许端口 587 和 465)
- 对于 Gmail:
- 启用双重验证
- 生成并使用[应用专用密码](https://support.google.com/accounts/answer/185833)
- 不要使用您的 Google 帐户密码
- 验证 `SMTP_SECURE` 和 `SMTP_PORT` 匹配(false 为 587,true 为 465)
- 使用邮件客户端(例如 Thunderbird)测试您的凭据
### 数据库连接问题
**症状:**
- "Connection refused" 错误
- 身份验证失败
- SSL 握手错误
**解决方案:**
- 验证 `.env` 文件中的数据库凭据
- 确保 MySQL 服务器正在运行:
# Check MySQL status
sudo systemctl status mysql # Linux
brew services list # macOS with Homebrew
- 检查防火墙设置(允许端口 3306)
- 验证数据库存在且用户具有适当的权限:
SHOW DATABASES;
SHOW GRANTS FOR 'your_user'@'localhost';
- 如果使用 SSL:
- 确保 `.env` 中的证书路径正确
- 验证证书文件存在且可读
- 检查证书过期日期
- 使用 MySQL 客户端测试 SSL 连接
- 检查 MySQL 错误日志以获取详细的错误消息
- 验证连接池配置(限制、超时)
### TypeScript 编译问题
**症状:**
- 构建失败并出现类型错误
- IDE 显示红色波浪线
**解决方案:**
- 确保 `tsconfig.json` 配置正确
- 运行 `npm run build` 查看所有类型错误
- 检查缺少的类型定义:
npm install --save-dev @types/node @types/express
- 使用 `npm run lint:fix` 修复格式问题
- 清除 TypeScript 缓存:
rm -rf node_modules/.cache
- 验证 TypeScript 版本是否符合项目要求
### 端口已被占用
**症状:**
- 启动时出现 "EADDRINUSE" 错误
- 服务器无法绑定到端口
**解决方案:**
- 将 `.env` 中的 `PORT` 更改为可用端口
- 终止使用该端口的进程:
# macOS/Linux
lsof -ti:8080 | xargs kill -9
# Windows
netstat -ano | findstr :8080
taskkill /PID /F
- 使用不同的端口范围进行开发(8000-9000)
### 日志问题
**症状:**
- 未创建日志文件
- 缺少日志条目
- 权限错误
**解决方案:**
- 日志文件在 `logs/` 目录中自动创建
- 检查目录权限:
ls -la logs/
chmod 755 logs/ # If needed
- 确保应用程序进程具有写入权限
- 验证磁盘空间充足
- 在清理之前使用 `npm run backup:logs` 归档日志
- 检查 [`logger.util.ts`](src/utils/logger.util.ts) 中的记录器配置
### 证书问题
**症状:**
- "Certificate not found" 错误
- SSL 握手失败
- 浏览器安全警告
**解决方案:**
- 确保证书文件存在于 `.env` 中指定的路径:
ls -la certs/server/
ls -la certs/db/
- 检查文件权限(必须可被应用程序读取):
chmod 644 certs/server/*.pem
- 对于开发,自签名证书是可以接受的
- 对于生产,使用来自受信任 CA 的证书(Let's Encrypt 等)
- 验证证书有效性:
openssl x509 -in certs/server/cert.pem -text -noout
- 检查证书过期日期
- 有关详细的故障排除,请参阅 [certs/README.md](certs/README.md)
### 模板问题
**症状:**
- 未找到模板错误
- Handlebars 编译错误
- 缺少模板变量
**解决方案:**
- 模板在生产模式下被缓存(`NODE_ENV=production`)
- 在开发模式下,每次请求都会重新加载模板
- 确保模板文件存在于 `src/templates/` 目录中:
ls src/templates/
- 检查模板语法是否存在 Handlebars 错误
- 验证模板数据与 [`template.schema.ts`](src/schemas/template.schema.ts) 中的 schema 匹配
- 通过重启服务器清除模板缓存
- 使用 `npm run build:templates` 确保模板被复制到 `dist/`
### 速率限制问题
**症状:**
- "Too many requests" 错误
- 429 状态码
**解决方案:**
- 在 `.env` 中调整速率限制:
MAIL_SERVICE_RATE_LIMIT=100
MAIL_SERVICE_RATE_WINDOW=15
RENDER_SERVICE_RATE_LIMIT=50
RENDER_SERVICE_RATE_WINDOW=10
- 速率限制是按 IP 地址计算的
- 等待速率限制时间窗口过期
- 使用不同的 IP 地址进行测试(例如,VPN、移动网络)
## 贡献
欢迎贡献!请遵循以下准则:
1. **Fork 仓库**并创建功能分支
2. **遵循代码风格** - 使用 ESLint 和 Prettier
3. **为新功能编写测试**
4. **更新文档** - 添加 JSDoc 注释并更新 README
5. **提交消息** - 使用清晰、描述性的提交消息
6. **拉取请求** - 提供清晰的更改描述
**代码标准:**
- 遵循现有的文件结构和命名约定
- 为所有函数参数和返回值使用 TypeScript 类型
- 为公共 API 添加 JSDoc 注释
- 为工具和服务编写单元测试
- 为控制器和路由编写集成测试
- 将测试覆盖率保持在 80% 以上
## 链接
- **仓库**:[marcomg-byte/offser](https://github.com/marcomg-byte/offser)
- **问题**:[GitHub Issues](https://github.com/marcomg-byte/offser/issues)
- **拉取请求**:[GitHub PRs](https://github.com/marcomg-byte/offser/pulls)
## 致谢
- [Express.js](https://expressjs.com/) - Web 框架
- [TypeScript](https://www.typescriptlang.org/) - 类型安全的 JavaScript
- [Zod](https://zod.dev/) - Schema 验证
- [Nodemailer](https://nodemailer.com/) - 邮件发送
- [Handlebars](https://handlebarsjs.com/) - 模板引擎
- [Pino](https://github.com/pinojs/pino) - 快速记录器
- [Vitest](https://vitest.dev/) - 测试框架
- [MySQL](https://www.mysql.com/) - 数据库系统
## 发布与 npm 包
本项目使用自动化的 GitHub Actions 工作流进行版本发布和 npm 发布:
- **发布工作流**:当带有 `patch`、`minor` 或 `major` 标签的拉取请求合并到 `main` 时,版本会自动升级,创建 git 标签,并发布 GitHub Release。合并的拉取请求也会收到 `released` 标签。
- **发布工作流**:当推送新版本标签(例如 `v1.2.3`)时,包将被构建并使用受信任的发布者连接发布到 [npm 注册表](https://www.npmjs.com/package/offser)。
### 如何触发发布
1. 打开一个针对 `main` 的拉取请求,包含您的更改。
2. 添加以下标签之一:`patch`、`minor` 或 `major` 到 PR(根据[语义化版本控制](https://semver.org/))。
3. 合并 PR。
4. 工作流将:
- 升级 `package.json` 中的版本并创建 git 标签。
- 发布 GitHub Release。
- 构建并将新版本发布到 npm。
- 将 `released` 标签添加到 PR。
### npm 包
- **当前版本:** 1.1.6
- **注册表:** [https://www.npmjs.com/package/offser](https://www.npmjs.com/package/offser)
- **安装:**
npm install -g offser
- **使用:**
offser --help
## GitHub 工作流
本项目使用 GitHub Actions 进行自动化 CI、容器验证、发布管理和 npm 发布。工作流定义在 `.github/workflows/` 中:
### 1. CI 工作流 (`ci.yml`)
- **触发器:** 每次针对 `main` 分支的拉取请求
- **步骤:**
- 检出代码
- 使用 `.nvmrc` 中的版本设置 Node.js
- 使用 `npm ci` 安装依赖
- 运行所有测试(`npm run test:run`)
- 执行 `npm publish` 的空运行以验证可发布性
### 2. CD 工作流 (`cd.yml`)
- **触发器:** 每次针对 `main` 分支的拉取请求
- **步骤:**
- 检出代码
- 使用 `.nvmrc` 设置 Node.js
- 安装依赖(忽略脚本)
- 打包容器(`npm run bundle:container`)
- 构建容器镜像(`npm run build:container`)
### 3. 发布工作流 (`release.yml`)
- **触发器:** 针对到 `main` 的拉取请求事件(opened, synchronized, reopened, closed)
- **步骤:**
- 检出带有完整历史的代码
- 根据 PR 标签(`major`、`minor`、`patch`)确定版本升级类型
- (进一步的步骤可能包括版本升级、变更日志生成、标记和发布发布)
### 4. 发布工作流 (`publish.yml`)
- **触发器:**
- 推送匹配模式 `v*.*.*` 的标签时
- 针对到 `main` 的拉请求(空运行)
- **步骤:**
- 检出代码
- 使用 `.nvmrc` 设置 Node.js
- 使用 `npm ci` 安装依赖
- 对于 PR:执行 `npm publish` 的空运行
- 对于标签:使用 `NODE_AUTH_TOKEN` 密钥将包发布到 npm 并提供来源证明
有关更多详细信息,请参阅 `.github/workflows/` 中的工作流文件。
HTML content
", "templateName": "offers", "templateData": { "customerName": "John", "offers": [ { "title": "Discount", "description": "10% off" } ], "ctaUrl": "https://example.com/offer" } } ``` **请求字段:** - `to`(必需,字符串)- 收件人电子邮件地址(有效的电子邮件格式) - `subject`(必需,字符串)- 电子邮件主题行(至少 1 个字符) - `text`(可选,字符串)- 纯文本电子邮件正文 - `html`(可选,字符串)- HTML 电子邮件正文 - `templateName`(可选,字符串)- 要使用的 Handlebars 模板名称(不带 .hbs 扩展名) - `templateData`(可选,对象)- 用于模板变量的数据对象 **成功响应 (200):** ``` { "title": "Email Sent Successfully!", "data": { "to": "recipient@example.com", "subject": "Email Subject", "templateName": "offers" }, "mailResponse": { "messageId": "标签:API开发, ETW劫持, Express.js, GNU通用公共许可证, Handlebars, HTTPS, MITM代理, Node.js, Nodemailer, Pino, SMTP, SSL/TLS, TypeScript, UI组件, Web服务器, Zod, 优雅停机, 后端开发, 安全插件, 安全通信, 数据库集成, 数据验证, 日志记录, 模块化架构, 模板渲染, 网络安全, 请求拦截, 连接池, 邮件发送, 邮件安全, 错误处理, 限流, 隐私保护, 黑客帝国风格