1416 lines
32 KiB
Markdown
1416 lines
32 KiB
Markdown
# Node Core Framework 使用说明
|
||
|
||
## 📋 概述
|
||
|
||
`node-core-framework.js` 是一个基于 Koa2 + Sequelize + MySQL 的企业级 Node.js 后端框架,提供了完整的 MVC 架构、数据库管理、API 文档生成、权限验证等功能。
|
||
|
||
## 🚀 快速开始
|
||
|
||
### 1. 环境要求
|
||
|
||
- **Node.js**: >= 16.0.0
|
||
- **MySQL**: >= 5.7
|
||
- **Redis**: >= 5.0 (可选,用于缓存)
|
||
|
||
### 2. 安装依赖
|
||
|
||
```bash
|
||
# 安装核心依赖
|
||
npm install koa koa-body koa-router koa-static koa2-cors koa2-swagger-ui
|
||
npm install sequelize mysql2 redis jsonwebtoken dayjs swagger-jsdoc
|
||
```
|
||
|
||
### 3. 基础使用
|
||
|
||
```javascript
|
||
// app.js
|
||
const Framework = require('./dist/node-core-framework.js');
|
||
|
||
// 配置文件
|
||
const config = {
|
||
// 数据库配置
|
||
db: {
|
||
username: "your_username",
|
||
password: "your_password",
|
||
database: "your_database",
|
||
host: "localhost",
|
||
port: 3306,
|
||
dialect: "mysql",
|
||
timezone: '+08:00'
|
||
},
|
||
|
||
// Redis配置(可选)
|
||
redis: {
|
||
host: 'localhost',
|
||
port: 6379,
|
||
password: '',
|
||
db: 0
|
||
},
|
||
|
||
// 日志路径
|
||
logPath: './logs',
|
||
|
||
// 基础URL
|
||
baseUrl: 'http://localhost:3001',
|
||
|
||
// API路径配置
|
||
apiPaths: [
|
||
{
|
||
path: './controllers',
|
||
prefix: '/api',
|
||
authType: 'applet'
|
||
}
|
||
],
|
||
|
||
// 模型路径
|
||
modelPaths: './models',
|
||
|
||
// 允许的URL(无需认证)
|
||
allowUrls: [
|
||
'/api/users/login',
|
||
'/api/health',
|
||
'/api/docs'
|
||
],
|
||
|
||
// 授权文件路径
|
||
license: {
|
||
licensePath: './license.lic'
|
||
}
|
||
};
|
||
|
||
// 启动应用
|
||
async function startApp() {
|
||
try {
|
||
// 创建框架实例
|
||
const framework = await Framework.init(config);
|
||
|
||
// 启动服务器
|
||
const server = await framework.start(3001);
|
||
|
||
console.log('🎉 应用启动成功!');
|
||
console.log(`📚 API 文档: http://localhost:3001/api/docs`);
|
||
|
||
// 使用日志服务
|
||
const logsService = Framework.getServices().logsService;
|
||
logsService.log('应用启动成功', { port: 3001, time: new Date() });
|
||
|
||
} catch (error) {
|
||
console.error('❌ 启动失败:', error);
|
||
|
||
// 记录启动错误
|
||
const logsService = Framework.getServices().logsService;
|
||
logsService.error('应用启动失败', error);
|
||
}
|
||
}
|
||
|
||
startApp();
|
||
```
|
||
|
||
## 🏗️ 框架架构
|
||
|
||
### 核心组件
|
||
|
||
1. **Framework**: 主框架类,负责整体协调
|
||
2. **ModelManager**: 数据库模型管理器
|
||
3. **ServiceManager**: 服务层管理器
|
||
4. **RequestManager**: 请求处理器管理器
|
||
5. **RegistrationService**: 授权验证服务
|
||
|
||
### 目录结构
|
||
|
||
```
|
||
project/
|
||
├── dist/
|
||
│ └── node-core-framework.js # 打包后的框架文件
|
||
├── controllers/ # 控制器目录
|
||
│ ├── user_controller.js
|
||
│ ├── article_controller.js
|
||
│ └── admin/
|
||
│ ├── sys_user.js # 系统用户管理
|
||
│ └── sys_log.js # 日志管理接口
|
||
├── models/ # 数据模型目录
|
||
│ ├── user.js
|
||
│ └── article.js
|
||
├── config.js # 配置文件
|
||
├── app.js # 应用入口
|
||
└── logs/ # 日志目录
|
||
├── log_2024.01.15.log # 普通日志文件
|
||
├── logError_2024.01.15.log # 错误日志文件
|
||
└── log_2024.01.15_1.log # 分割后的日志文件
|
||
```
|
||
|
||
## 📝 详细配置说明
|
||
|
||
### 数据库配置
|
||
|
||
```javascript
|
||
const config = {
|
||
db: {
|
||
username: "root", // 数据库用户名
|
||
password: "password", // 数据库密码
|
||
database: "myapp", // 数据库名
|
||
host: "localhost", // 数据库主机
|
||
port: 3306, // 数据库端口
|
||
dialect: "mysql", // 数据库类型
|
||
timezone: '+08:00', // 时区
|
||
pool: { // 连接池配置
|
||
max: 10, // 最大连接数
|
||
min: 0, // 最小连接数
|
||
acquire: 30000, // 获取连接超时时间
|
||
idle: 10000 // 连接空闲时间
|
||
},
|
||
logging: true // 是否打印SQL日志
|
||
}
|
||
};
|
||
```
|
||
|
||
### API路径配置
|
||
|
||
```javascript
|
||
const config = {
|
||
apiPaths: [
|
||
{
|
||
path: './controllers', // 控制器目录路径
|
||
prefix: '/api', // API前缀
|
||
authType: 'applet' // 认证类型:applet | admin
|
||
},
|
||
{
|
||
path: './controllers_admin', // 管理端控制器
|
||
prefix: '/admin_api', // 管理端API前缀
|
||
authType: 'admin'
|
||
}
|
||
]
|
||
};
|
||
```
|
||
|
||
### 权限配置
|
||
|
||
```javascript
|
||
const config = {
|
||
// 允许的URL(无需认证)
|
||
allowUrls: [
|
||
'/api/users/login', // 登录接口
|
||
'/api/users/register', // 注册接口
|
||
'/api/health', // 健康检查
|
||
'/api/docs', // API文档
|
||
'/api/swagger.json' // Swagger配置
|
||
],
|
||
|
||
// 授权文件路径
|
||
license: {
|
||
licensePath: './license.lic'
|
||
}
|
||
};
|
||
```
|
||
|
||
## 🎯 控制器开发
|
||
|
||
### 基础控制器结构
|
||
|
||
```javascript
|
||
// controllers/user_controller.js
|
||
const { models } = require('../database/db');
|
||
|
||
module.exports = {
|
||
/**
|
||
* @swagger
|
||
* /api/users:
|
||
* get:
|
||
* summary: 获取用户列表
|
||
* description: 分页获取用户列表
|
||
* tags:
|
||
* - 用户管理
|
||
* parameters:
|
||
* - in: query
|
||
* name: page
|
||
* schema:
|
||
* type: integer
|
||
* default: 1
|
||
* description: 页码
|
||
* - in: query
|
||
* name: pageSize
|
||
* schema:
|
||
* type: integer
|
||
* default: 20
|
||
* description: 每页数量
|
||
* responses:
|
||
* 200:
|
||
* description: 获取成功
|
||
*/
|
||
"GET /users": async (ctx) => {
|
||
const { user: UserModel } = models;
|
||
|
||
// 获取分页参数
|
||
const { limit, offset } = ctx.getPageSize();
|
||
|
||
// 查询用户列表
|
||
const users = await UserModel.findAndCountAll({
|
||
attributes: ['id', 'username', 'email', 'status', 'create_time'],
|
||
limit,
|
||
offset,
|
||
order: [['create_time', 'DESC']]
|
||
});
|
||
|
||
// 返回成功响应
|
||
ctx.success(users, '获取用户列表成功');
|
||
},
|
||
|
||
/**
|
||
* @swagger
|
||
* /api/users:
|
||
* post:
|
||
* summary: 创建用户
|
||
* description: 创建新用户
|
||
* tags:
|
||
* - 用户管理
|
||
* requestBody:
|
||
* required: true
|
||
* content:
|
||
* application/json:
|
||
* schema:
|
||
* type: object
|
||
* required:
|
||
* - username
|
||
* - email
|
||
* - password
|
||
* properties:
|
||
* username:
|
||
* type: string
|
||
* description: 用户名
|
||
* email:
|
||
* type: string
|
||
* format: email
|
||
* description: 邮箱
|
||
* password:
|
||
* type: string
|
||
* description: 密码
|
||
* responses:
|
||
* 200:
|
||
* description: 创建成功
|
||
*/
|
||
"POST /users": async (ctx) => {
|
||
const { user: UserModel } = models;
|
||
const { username, email, password } = ctx.getBody();
|
||
|
||
// 参数验证
|
||
if (!username || !email || !password) {
|
||
return ctx.fail('用户名、邮箱和密码不能为空');
|
||
}
|
||
|
||
// 检查邮箱是否已存在
|
||
const existingUser = await UserModel.findOne({ where: { email } });
|
||
if (existingUser) {
|
||
return ctx.fail('邮箱已存在');
|
||
}
|
||
|
||
// 创建用户
|
||
const newUser = await UserModel.create({
|
||
username,
|
||
email,
|
||
password,
|
||
status: 1
|
||
});
|
||
|
||
ctx.success({
|
||
id: newUser.id,
|
||
username: newUser.username,
|
||
email: newUser.email
|
||
}, '用户创建成功');
|
||
}
|
||
};
|
||
```
|
||
|
||
### 控制器方法说明
|
||
|
||
#### 请求方法格式
|
||
|
||
框架支持 **GET** 和 **POST** 两种请求方法,**推荐统一使用 POST 请求**:
|
||
|
||
- `"POST /users"`: POST 请求(推荐)
|
||
- 参数通过请求体传递:`{ id: 123, username: "张三", pageOption: { page: 1, pageSize: 20 } }`
|
||
- 适用场景:所有接口(查询、创建、更新、删除等)
|
||
- 优点:参数传递统一、支持复杂数据结构、更安全
|
||
|
||
- `"GET /users"`: GET 请求(不推荐)
|
||
- 参数通过查询字符串传递:`?id=123&status=1`
|
||
- 仅在简单查询场景下使用
|
||
|
||
#### 上下文方法
|
||
|
||
```javascript
|
||
// 获取请求体数据
|
||
const body = ctx.getBody();
|
||
|
||
// 获取查询参数
|
||
const query = ctx.getQuery();
|
||
|
||
// 获取分页参数(从前端的 pageOption 中获取 page 和 pageSize)
|
||
// 前端发送格式:{ pageOption: { page: 1, pageSize: 20 } }
|
||
// 返回值:{ limit: 20, offset: 0 }
|
||
const { limit, offset } = ctx.getPageSize();
|
||
|
||
// 成功响应
|
||
ctx.success(data, message);
|
||
|
||
// 失败响应
|
||
ctx.fail(message, code);
|
||
```
|
||
|
||
## 🗄️ 数据模型开发
|
||
|
||
### 模型定义
|
||
|
||
```javascript
|
||
// models/user.js
|
||
const Sequelize = require('sequelize');
|
||
|
||
module.exports = (db) => {
|
||
return db.define("user", {
|
||
id: {
|
||
type: Sequelize.INTEGER,
|
||
primaryKey: true,
|
||
autoIncrement: true,
|
||
comment: "用户ID"
|
||
},
|
||
username: {
|
||
type: Sequelize.STRING(50),
|
||
allowNull: false,
|
||
comment: "用户名"
|
||
},
|
||
email: {
|
||
type: Sequelize.STRING(100),
|
||
allowNull: false,
|
||
unique: true,
|
||
comment: "邮箱"
|
||
},
|
||
password: {
|
||
type: Sequelize.STRING(255),
|
||
allowNull: false,
|
||
comment: "密码"
|
||
},
|
||
avatar: {
|
||
type: Sequelize.STRING(255),
|
||
comment: "头像URL"
|
||
},
|
||
status: {
|
||
type: Sequelize.INTEGER,
|
||
defaultValue: 1,
|
||
comment: "状态:0-禁用,1-启用"
|
||
}
|
||
});
|
||
};
|
||
```
|
||
|
||
### 模型关联
|
||
|
||
```javascript
|
||
// 在启动文件中定义关联关系
|
||
const businessAssociations = (models) => {
|
||
// 用户和文章的一对多关系
|
||
if (models.user && models.article) {
|
||
models.user.hasMany(models.article, {
|
||
foreignKey: 'author_id',
|
||
as: 'articles',
|
||
constraints: false // 不创建外键约束
|
||
});
|
||
|
||
models.article.belongsTo(models.user, {
|
||
foreignKey: 'author_id',
|
||
as: 'author',
|
||
constraints: false
|
||
});
|
||
}
|
||
};
|
||
|
||
// 在框架初始化时传入
|
||
const framework = await Framework.init({
|
||
// ... 其他配置
|
||
businessAssociations: businessAssociations
|
||
});
|
||
```
|
||
|
||
## 🔐 权限验证
|
||
|
||
### Token 验证
|
||
|
||
框架支持两种认证类型:
|
||
- `applet`: 小程序端认证
|
||
- `admin`: 管理端认证
|
||
|
||
### 认证中间件
|
||
|
||
```javascript
|
||
// 在控制器中获取当前用户
|
||
"POST /profile": async (ctx) => {
|
||
// 获取当前登录用户信息
|
||
const currentUser = ctx.state.user;
|
||
|
||
if (!currentUser) {
|
||
return ctx.fail('未登录', 401);
|
||
}
|
||
|
||
ctx.success(currentUser, '获取用户信息成功');
|
||
}
|
||
```
|
||
|
||
## 📊 API 文档
|
||
|
||
### Swagger 配置
|
||
|
||
框架自动生成 API 文档,访问地址:`http://localhost:3001/api/docs`
|
||
|
||
### 自定义 Schema
|
||
|
||
```javascript
|
||
const config = {
|
||
customSchemas: {
|
||
"User": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": {
|
||
"type": "integer",
|
||
"description": "用户ID"
|
||
},
|
||
"username": {
|
||
"type": "string",
|
||
"description": "用户名"
|
||
},
|
||
"email": {
|
||
"type": "string",
|
||
"format": "email",
|
||
"description": "邮箱"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
```
|
||
|
||
## 🚀 部署配置
|
||
|
||
### 生产环境配置
|
||
|
||
```javascript
|
||
const config = {
|
||
env: 'production',
|
||
|
||
db: {
|
||
// 生产数据库配置
|
||
username: process.env.DB_USERNAME,
|
||
password: process.env.DB_PASSWORD,
|
||
database: process.env.DB_DATABASE,
|
||
host: process.env.DB_HOST,
|
||
port: process.env.DB_PORT,
|
||
dialect: "mysql",
|
||
logging: false // 生产环境关闭SQL日志
|
||
},
|
||
|
||
redis: {
|
||
host: process.env.REDIS_HOST,
|
||
port: process.env.REDIS_PORT,
|
||
password: process.env.REDIS_PASSWORD
|
||
},
|
||
|
||
server: {
|
||
port: process.env.PORT || 3001,
|
||
host: '0.0.0.0'
|
||
}
|
||
};
|
||
```
|
||
|
||
### PM2 部署
|
||
|
||
```javascript
|
||
// ecosystem.config.js
|
||
module.exports = {
|
||
apps: [{
|
||
name: 'myapp',
|
||
script: 'app.js',
|
||
instances: 'max',
|
||
exec_mode: 'cluster',
|
||
env: {
|
||
NODE_ENV: 'production',
|
||
PORT: 3001
|
||
}
|
||
}]
|
||
};
|
||
```
|
||
|
||
```bash
|
||
# 启动应用
|
||
pm2 start ecosystem.config.js
|
||
|
||
# 查看状态
|
||
pm2 status
|
||
|
||
# 查看日志
|
||
pm2 logs myapp
|
||
```
|
||
|
||
## 🔧 常用功能
|
||
|
||
### 日志服务
|
||
|
||
框架内置了完整的日志服务,支持普通日志、错误日志和请求日志记录。
|
||
|
||
#### 获取日志服务
|
||
|
||
```javascript
|
||
// 在控制器中获取日志服务
|
||
const Framework = require('./dist/node-core-framework.js');
|
||
const logsService = Framework.getServices().logsService;
|
||
|
||
|
||
|
||
#### 基本日志记录
|
||
|
||
```javascript
|
||
// 普通日志
|
||
logsService.log('用户登录成功', { userId: 123, username: 'admin' });
|
||
logsService.log('数据更新完成', '用户信息已更新');
|
||
|
||
// 错误日志
|
||
try {
|
||
// 业务逻辑
|
||
throw new Error('数据库连接失败');
|
||
} catch (error) {
|
||
logsService.error('数据库操作失败', error);
|
||
}
|
||
|
||
// 上下文错误日志(包含请求信息)
|
||
logsService.ctxError(error, ctx);
|
||
```
|
||
|
||
#### 日志文件管理
|
||
|
||
```javascript
|
||
// 日志文件自动管理
|
||
// - 按日期分割:log_2024.01.15.log
|
||
// - 错误日志:logError_2024.01.15.log
|
||
// - 文件大小超过5MB自动分割:log_2024.01.15_1.log
|
||
// - 自动创建日志目录
|
||
```
|
||
|
||
#### 日志API接口
|
||
|
||
框架提供了日志管理的API接口:
|
||
|
||
```javascript
|
||
// 获取所有日志文件列表
|
||
GET /admin_api/sys_log/all
|
||
|
||
// 查看日志文件内容
|
||
GET /admin_api/sys_log/detail?title=log_2024.01.15.log
|
||
|
||
// 删除指定日志文件
|
||
GET /admin_api/sys_log/delete?title=log_2024.01.15.log
|
||
|
||
// 删除所有日志文件
|
||
GET /admin_api/sys_log/delete_all
|
||
```
|
||
|
||
#### 日志配置
|
||
|
||
```javascript
|
||
const config = {
|
||
// 日志目录配置
|
||
logPath: './logs', // 相对路径,基于项目根目录
|
||
|
||
// 其他配置...
|
||
};
|
||
|
||
// 日志目录结构
|
||
// project/
|
||
// ├── logs/
|
||
// │ ├── log_2024.01.15.log # 普通日志
|
||
// │ ├── logError_2024.01.15.log # 错误日志
|
||
// │ └── log_2024.01.15_1.log # 分割后的日志
|
||
```
|
||
|
||
#### 实际使用示例
|
||
|
||
```javascript
|
||
// controller/user.js
|
||
const Framework = require('../framework');
|
||
const logsService = Framework.getServices().logsService;
|
||
|
||
module.exports = {
|
||
"POST /login": async (ctx) => {
|
||
try {
|
||
const { username, password } = ctx.getBody();
|
||
|
||
// 记录登录尝试
|
||
logsService.log('用户登录尝试', { username, ip: ctx.ip });
|
||
|
||
// 验证用户
|
||
const user = await UserModel.findOne({ where: { username } });
|
||
|
||
if (!user) {
|
||
logsService.log('登录失败:用户不存在', { username });
|
||
return ctx.fail('用户名或密码错误');
|
||
}
|
||
|
||
// 验证密码
|
||
const isValid = await bcrypt.compare(password, user.password);
|
||
|
||
if (!isValid) {
|
||
logsService.log('登录失败:密码错误', { username });
|
||
return ctx.fail('用户名或密码错误');
|
||
}
|
||
|
||
// 登录成功
|
||
logsService.log('用户登录成功', {
|
||
userId: user.id,
|
||
username: user.username,
|
||
ip: ctx.ip
|
||
});
|
||
|
||
ctx.success({ token: generateToken(user) }, '登录成功');
|
||
|
||
} catch (error) {
|
||
// 记录系统错误
|
||
logsService.ctxError(error, ctx);
|
||
ctx.fail('登录失败,请稍后重试');
|
||
}
|
||
}
|
||
};
|
||
```
|
||
|
||
### Token服务
|
||
|
||
框架内置了JWT Token服务,支持Token生成、验证、解析和微信数据解密。
|
||
|
||
#### 获取Token服务
|
||
|
||
```javascript
|
||
// 在控制器中获取Token服务
|
||
const Framework = require('./dist/node-core-framework.js');
|
||
const tokenService = Framework.getServices().tokenService;
|
||
|
||
// 或者通过框架实例获取
|
||
const framework = await Framework.init(config);
|
||
const tokenService = Framework.getServices().tokenService;
|
||
```
|
||
|
||
#### 基本Token操作
|
||
|
||
```javascript
|
||
// 创建Token
|
||
const userInfo = { id: 123, username: 'admin', role: 'admin' };
|
||
const token = tokenService.create(userInfo);
|
||
|
||
// 解析Token(获取用户信息)
|
||
const userData = tokenService.parse(token);
|
||
console.log(userData); // { id: 123, username: 'admin', role: 'admin' }
|
||
|
||
// 验证Token(检查是否有效)
|
||
const isValid = tokenService.verify(token);
|
||
console.log(isValid); // true 或 false
|
||
```
|
||
|
||
#### 微信数据解密
|
||
|
||
```javascript
|
||
// 解密微信小程序数据
|
||
const encryptedData = 'encrypted_data_from_wechat';
|
||
const sessionKey = 'session_key_from_wechat';
|
||
const iv = 'iv_from_wechat';
|
||
|
||
const decryptedData = tokenService.decryptWxData(encryptedData, sessionKey, iv);
|
||
console.log(decryptedData); // 解密后的用户信息
|
||
```
|
||
|
||
#### MD5加密
|
||
|
||
```javascript
|
||
// MD5加密
|
||
const password = '123456';
|
||
const encryptedPassword = tokenService.getMd5(password);
|
||
console.log(encryptedPassword); // e10adc3949ba59abbe56e057f20f883e
|
||
```
|
||
|
||
#### 实际使用示例
|
||
|
||
```javascript
|
||
// controller/auth.js
|
||
const Framework = require('../framework');
|
||
const tokenService = Framework.getServices().tokenService;
|
||
|
||
module.exports = {
|
||
"POST /login": async (ctx) => {
|
||
try {
|
||
const { username, password } = ctx.getBody();
|
||
|
||
// 验证用户
|
||
const user = await UserModel.findOne({ where: { username } });
|
||
if (!user) {
|
||
return ctx.fail('用户不存在');
|
||
}
|
||
|
||
// 验证密码
|
||
const isValidPassword = await bcrypt.compare(password, user.password);
|
||
if (!isValidPassword) {
|
||
return ctx.fail('密码错误');
|
||
}
|
||
|
||
// 生成Token
|
||
const userInfo = {
|
||
id: user.id,
|
||
username: user.username,
|
||
role: user.role
|
||
};
|
||
const token = tokenService.create(userInfo);
|
||
|
||
ctx.success({ token, user: userInfo }, '登录成功');
|
||
|
||
} catch (error) {
|
||
ctx.fail('登录失败');
|
||
}
|
||
},
|
||
|
||
"POST /verify": async (ctx) => {
|
||
const { token } = ctx.getBody();
|
||
|
||
if (!token) {
|
||
return ctx.fail('缺少Token');
|
||
}
|
||
|
||
const userData = tokenService.parse(token);
|
||
if (!userData) {
|
||
return ctx.fail('Token无效');
|
||
}
|
||
|
||
ctx.success(userData, 'Token验证成功');
|
||
}
|
||
};
|
||
```
|
||
|
||
### Redis服务
|
||
|
||
框架内置了Redis缓存服务,支持数据缓存、分布式锁等功能。
|
||
|
||
#### 获取Redis服务
|
||
|
||
```javascript
|
||
// 在控制器中获取Redis服务
|
||
const Framework = require('./dist/node-core-framework.js');
|
||
const redisService = Framework.getServices().redisService;
|
||
|
||
// 或者通过框架实例获取
|
||
const framework = await Framework.init(config);
|
||
const redisService = Framework.getServices().redisService;
|
||
```
|
||
|
||
#### 基本缓存操作
|
||
|
||
```javascript
|
||
// 设置缓存(默认12小时过期)
|
||
await redisService.set('user:123', JSON.stringify({ name: '张三', age: 25 }));
|
||
|
||
// 设置缓存(自定义过期时间,单位:秒)
|
||
await redisService.set('session:abc', 'session_data', 3600); // 1小时
|
||
|
||
// 获取缓存
|
||
const userData = await redisService.get('user:123');
|
||
console.log(JSON.parse(userData)); // { name: '张三', age: 25 }
|
||
|
||
// 删除缓存
|
||
await redisService.del('user:123');
|
||
```
|
||
|
||
#### 原子操作
|
||
|
||
```javascript
|
||
// 原子设置(仅当key不存在时设置)
|
||
const success = await redisService.setIfAbsent('lock:order:123', 'locked', 300);
|
||
if (success) {
|
||
console.log('获取锁成功');
|
||
// 执行业务逻辑
|
||
await redisService.del('lock:order:123'); // 释放锁
|
||
} else {
|
||
console.log('获取锁失败,资源被占用');
|
||
}
|
||
```
|
||
|
||
#### 连接状态管理
|
||
|
||
```javascript
|
||
// 检查Redis连接状态
|
||
const isConnected = redisService.isConnected();
|
||
console.log('Redis连接状态:', isConnected);
|
||
|
||
// 手动重连
|
||
redisService.reconnect();
|
||
|
||
// 关闭连接
|
||
redisService.close();
|
||
```
|
||
|
||
#### 实际使用示例
|
||
|
||
```javascript
|
||
// controller/cache.js
|
||
const Framework = require('../framework');
|
||
const redisService = Framework.getServices().redisService;
|
||
|
||
module.exports = {
|
||
"POST /cache/user/detail": async (ctx) => {
|
||
const { id } = ctx.getBody(); // 从请求体获取 id
|
||
|
||
if (!id) {
|
||
return ctx.fail('用户ID不能为空');
|
||
}
|
||
|
||
const cacheKey = `user:${id}`;
|
||
|
||
try {
|
||
// 先尝试从缓存获取
|
||
let userData = await redisService.get(cacheKey);
|
||
|
||
if (userData) {
|
||
// 缓存命中
|
||
console.log('从缓存获取用户数据');
|
||
return ctx.success(JSON.parse(userData));
|
||
}
|
||
|
||
// 缓存未命中,从数据库获取
|
||
const user = await UserModel.findByPk(id);
|
||
if (!user) {
|
||
return ctx.fail('用户不存在');
|
||
}
|
||
|
||
// 存入缓存(1小时过期)
|
||
await redisService.set(cacheKey, JSON.stringify(user.toJSON()), 3600);
|
||
|
||
ctx.success(user);
|
||
|
||
} catch (error) {
|
||
ctx.fail('获取用户信息失败');
|
||
}
|
||
},
|
||
|
||
"POST /cache/clear": async (ctx) => {
|
||
const { pattern } = ctx.getBody();
|
||
|
||
// 清除指定模式的缓存
|
||
// 注意:这里需要根据实际需求实现模式匹配删除
|
||
await redisService.del(pattern);
|
||
|
||
ctx.success(null, '缓存清除成功');
|
||
}
|
||
};
|
||
```
|
||
|
||
### Swagger服务
|
||
|
||
框架内置了Swagger API文档服务,支持自动生成API文档和Schema。
|
||
|
||
#### 获取Swagger服务
|
||
|
||
```javascript
|
||
// 在控制器中获取Swagger服务
|
||
const Framework = require('./dist/node-core-framework.js');
|
||
const swaggerService = Framework.getServices().createSwaggerService();
|
||
|
||
// 或者通过框架实例获取
|
||
const framework = await Framework.init(config);
|
||
const swaggerService = Framework.getServices().createSwaggerService();
|
||
```
|
||
|
||
#### 生成API文档
|
||
|
||
```javascript
|
||
// 生成Swagger规范
|
||
const swaggerSpec = swaggerService.generateSpecs(req, res);
|
||
|
||
// 获取Swagger配置
|
||
const swaggerConfig = swaggerService.generateSwaggerConfig(req, res);
|
||
|
||
// 获取Swagger选项
|
||
const swaggerOptions = swaggerService.getSwaggerOptions();
|
||
```
|
||
|
||
#### 安全配置管理
|
||
|
||
```javascript
|
||
// 获取当前安全配置
|
||
const securityConfig = swaggerService.getCurrentSecurityConfig(req, res);
|
||
|
||
// 更新安全配置
|
||
const newConfig = [
|
||
{ 'admin-token': [] },
|
||
{ 'applet-token': [] }
|
||
];
|
||
const result = swaggerService.updateSecurityConfig(req, res, newConfig);
|
||
|
||
// 清除安全配置
|
||
swaggerService.clearSecurityConfigCookie(res);
|
||
```
|
||
|
||
#### 实际使用示例
|
||
|
||
```javascript
|
||
// controller/docs.js
|
||
const Framework = require('../framework');
|
||
|
||
module.exports = {
|
||
"POST /api-docs": async (ctx) => {
|
||
const swaggerService = Framework.getServices().createSwaggerService();
|
||
|
||
// 生成Swagger规范
|
||
const swaggerSpec = swaggerService.generateSpecs(ctx.request, ctx.response);
|
||
|
||
ctx.response.type = 'application/json';
|
||
ctx.response.body = swaggerSpec;
|
||
},
|
||
|
||
"POST /swagger-ui": async (ctx) => {
|
||
// 返回Swagger UI页面
|
||
const swaggerUIManager = Framework.getSwaggerUIManager();
|
||
const html = swaggerUIManager.getSwaggerUIHTML();
|
||
|
||
ctx.response.type = 'text/html';
|
||
ctx.response.body = html;
|
||
}
|
||
};
|
||
```
|
||
|
||
### 平台项目服务
|
||
|
||
框架内置了平台项目服务,支持与外部平台的交互。
|
||
|
||
#### 获取平台项目服务
|
||
|
||
```javascript
|
||
// 在控制器中获取平台项目服务
|
||
const Framework = require('./dist/node-core-framework.js');
|
||
const platformService = Framework.getServices().platformProjectService;
|
||
|
||
// 或者通过框架实例获取
|
||
const framework = await Framework.init(config);
|
||
const platformService = Framework.getServices().platformProjectService;
|
||
```
|
||
|
||
#### 基本平台操作
|
||
|
||
```javascript
|
||
// 获取所有模型
|
||
const models = await platformService.modelAll();
|
||
|
||
// 生成表单
|
||
const formData = await platformService.formGenerate({ id: 'form_id' });
|
||
|
||
// 创建表单
|
||
const createResult = await platformService.createForm({
|
||
name: '用户表单',
|
||
fields: ['name', 'email', 'phone']
|
||
});
|
||
|
||
// 更新表单
|
||
const updateResult = await platformService.updateForm({
|
||
id: 'form_id',
|
||
name: '更新后的表单'
|
||
});
|
||
|
||
// 删除表单
|
||
const deleteResult = await platformService.delteForm({ id: 'form_id' });
|
||
|
||
// 重新生成模型
|
||
const modelResult = await platformService.modelGenerate({ id: 'model_id' });
|
||
```
|
||
|
||
#### 文件下载
|
||
|
||
```javascript
|
||
// 下载平台文件
|
||
const downloadResult = await platformService.downloadPlatformFile('/path/to/file');
|
||
```
|
||
|
||
#### 实际使用示例
|
||
|
||
```javascript
|
||
// controller/platform.js
|
||
const Framework = require('../framework');
|
||
const platformService = Framework.getServices().platformProjectService;
|
||
|
||
module.exports = {
|
||
"POST /platform/models": async (ctx) => {
|
||
try {
|
||
const models = await platformService.modelAll();
|
||
ctx.success(models);
|
||
} catch (error) {
|
||
ctx.fail('获取模型列表失败');
|
||
}
|
||
},
|
||
|
||
"POST /platform/form": async (ctx) => {
|
||
try {
|
||
const formData = ctx.getBody();
|
||
const result = await platformService.createForm(formData);
|
||
ctx.success(result, '表单创建成功');
|
||
} catch (error) {
|
||
ctx.fail('表单创建失败');
|
||
}
|
||
}
|
||
};
|
||
```
|
||
|
||
### HTTP服务
|
||
|
||
框架内置了HTTP请求服务,支持各种HTTP请求方法。
|
||
|
||
#### 获取HTTP服务
|
||
|
||
```javascript
|
||
// 直接引入HTTP服务
|
||
const { postPlatformUrl, downloadPlatformFile, post, get } = require('./services/http');
|
||
```
|
||
|
||
#### 基本HTTP操作
|
||
|
||
```javascript
|
||
// GET请求
|
||
const getData = await get('https://api.example.com/users', { page: 1, limit: 10 });
|
||
|
||
// POST请求
|
||
const postData = await post('https://api.example.com/users', {
|
||
name: '张三',
|
||
email: 'zhangsan@example.com'
|
||
});
|
||
|
||
// POST表单数据
|
||
const formData = await postFormData('https://api.example.com/upload', {
|
||
file: 'file_content',
|
||
name: 'filename.jpg'
|
||
});
|
||
```
|
||
|
||
#### 平台API调用
|
||
|
||
```javascript
|
||
// 调用平台API
|
||
const platformData = await postPlatformUrl('/form/generate', { id: 'form_id' });
|
||
|
||
// 下载平台文件
|
||
await downloadPlatformFile('/path/to/file', './local/path/file');
|
||
```
|
||
|
||
#### 实际使用示例
|
||
|
||
```javascript
|
||
// controller/external.js
|
||
const { post, get } = require('../services/http');
|
||
|
||
module.exports = {
|
||
"POST /external/users": async (ctx) => {
|
||
try {
|
||
// 调用外部API
|
||
const users = await get('https://jsonplaceholder.typicode.com/users');
|
||
ctx.success(users);
|
||
} catch (error) {
|
||
ctx.fail('获取外部数据失败');
|
||
}
|
||
},
|
||
|
||
"POST /external/sync": async (ctx) => {
|
||
try {
|
||
const { data } = ctx.getBody();
|
||
|
||
// 同步数据到外部系统
|
||
const result = await post('https://api.external.com/sync', data);
|
||
|
||
ctx.success(result, '数据同步成功');
|
||
} catch (error) {
|
||
ctx.fail('数据同步失败');
|
||
}
|
||
}
|
||
};
|
||
```
|
||
|
||
### 分页查询
|
||
|
||
框架提供了 `ctx.getPageSize()` 方法来获取分页参数。
|
||
|
||
**前端发送格式**:
|
||
```javascript
|
||
// 方式1: 通过 POST 请求体发送
|
||
{
|
||
pageOption: {
|
||
page: 1, // 页码,从1开始
|
||
pageSize: 20 // 每页数量
|
||
}
|
||
}
|
||
|
||
// 方式2: 通过 GET 查询参数发送
|
||
// GET /api/users?pageOption[page]=1&pageOption[pageSize]=20
|
||
```
|
||
|
||
**后端使用示例**:
|
||
```javascript
|
||
"POST /users/list": async (ctx) => {
|
||
// ctx.getPageSize() 从前端的 pageOption 中获取 page 和 pageSize
|
||
// 返回值:{ limit: 20, offset: 0 }
|
||
// limit: 每页数量 (pageSize)
|
||
// offset: 跳过的记录数 ((page - 1) * pageSize)
|
||
const { limit, offset } = ctx.getPageSize();
|
||
const { username, status } = ctx.getBody(); // 从请求体获取查询条件
|
||
|
||
// 构建查询条件
|
||
const where = {};
|
||
if (username) {
|
||
where.username = { [Op.like]: `%${username}%` };
|
||
}
|
||
if (status !== undefined) {
|
||
where.status = status;
|
||
}
|
||
|
||
const users = await UserModel.findAndCountAll({
|
||
where,
|
||
limit,
|
||
offset,
|
||
order: [['create_time', 'DESC']]
|
||
});
|
||
|
||
ctx.success(users, '获取用户列表成功');
|
||
}
|
||
```
|
||
|
||
### 文件上传
|
||
|
||
```javascript
|
||
"POST /upload": async (ctx) => {
|
||
const file = ctx.request.files.file;
|
||
|
||
if (!file) {
|
||
return ctx.fail('请选择文件');
|
||
}
|
||
|
||
// 处理文件上传逻辑
|
||
const fileName = `${Date.now()}_${file.name}`;
|
||
const filePath = `./upload/${fileName}`;
|
||
|
||
// 保存文件
|
||
const fs = require('fs');
|
||
fs.writeFileSync(filePath, file.buffer);
|
||
|
||
ctx.success({
|
||
fileName,
|
||
filePath,
|
||
size: file.size
|
||
}, '文件上传成功');
|
||
}
|
||
```
|
||
|
||
### 错误处理
|
||
|
||
```javascript
|
||
// 全局错误处理
|
||
framework.app.on('error', (err, ctx) => {
|
||
console.error('服务器错误:', err);
|
||
|
||
// 记录错误日志
|
||
const logger = require('./services/logs');
|
||
logger.error('服务器错误', {
|
||
error: err.message,
|
||
stack: err.stack,
|
||
url: ctx.url,
|
||
method: ctx.method
|
||
});
|
||
});
|
||
```
|
||
|
||
## 📝 最佳实践
|
||
|
||
### 1. 控制器设计
|
||
|
||
```javascript
|
||
// 好的实践:统一使用 POST 请求,清晰的路由命名
|
||
module.exports = {
|
||
"POST /users/list": getUserList, // 获取用户列表
|
||
"POST /users/detail": getUserDetail, // 获取用户详情
|
||
"POST /users/create": createUser, // 创建用户
|
||
"POST /users/update": updateUser, // 更新用户
|
||
"POST /users/delete": deleteUser // 删除用户
|
||
};
|
||
|
||
// 查询列表示例:支持分页和条件查询
|
||
async function getUserList(ctx) {
|
||
const { limit, offset } = ctx.getPageSize(); // 从 pageOption 获取分页参数
|
||
const { username, status } = ctx.getBody(); // 从请求体获取查询条件
|
||
|
||
// 构建查询条件
|
||
const where = {};
|
||
if (username) {
|
||
where.username = { [Op.like]: `%${username}%` };
|
||
}
|
||
if (status !== undefined) {
|
||
where.status = status;
|
||
}
|
||
|
||
const users = await UserModel.findAndCountAll({
|
||
where,
|
||
limit,
|
||
offset,
|
||
order: [['create_time', 'DESC']]
|
||
});
|
||
|
||
ctx.success(users, '获取用户列表成功');
|
||
}
|
||
|
||
// 查询详情示例
|
||
async function getUserDetail(ctx) {
|
||
const { id } = ctx.getBody(); // 从请求体获取 id
|
||
|
||
if (!id) {
|
||
return ctx.fail('用户ID不能为空');
|
||
}
|
||
|
||
const user = await UserModel.findByPk(id);
|
||
if (!user) {
|
||
return ctx.fail('用户不存在');
|
||
}
|
||
|
||
ctx.success(user, '获取成功');
|
||
}
|
||
|
||
// 创建示例
|
||
async function createUser(ctx) {
|
||
const { username, email, password } = ctx.getBody();
|
||
|
||
if (!username || !email || !password) {
|
||
return ctx.fail('用户名、邮箱和密码不能为空');
|
||
}
|
||
|
||
const newUser = await UserModel.create({ username, email, password });
|
||
ctx.success(newUser, '创建成功');
|
||
}
|
||
|
||
// 更新示例
|
||
async function updateUser(ctx) {
|
||
const { id, username, email } = ctx.getBody();
|
||
|
||
if (!id) {
|
||
return ctx.fail('用户ID不能为空');
|
||
}
|
||
|
||
await UserModel.update({ username, email }, { where: { id } });
|
||
ctx.success(null, '更新成功');
|
||
}
|
||
|
||
// 删除示例
|
||
async function deleteUser(ctx) {
|
||
const { id } = ctx.getBody();
|
||
|
||
if (!id) {
|
||
return ctx.fail('用户ID不能为空');
|
||
}
|
||
|
||
await UserModel.destroy({ where: { id } });
|
||
ctx.success(null, '删除成功');
|
||
}
|
||
```
|
||
|
||
### 2. 数据验证
|
||
|
||
```javascript
|
||
// 好的实践:参数验证
|
||
"POST /users": async (ctx) => {
|
||
const { username, email, password } = ctx.getBody();
|
||
|
||
// 参数验证
|
||
if (!username || !email || !password) {
|
||
return ctx.fail('用户名、邮箱和密码不能为空');
|
||
}
|
||
|
||
// 邮箱格式验证
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
if (!emailRegex.test(email)) {
|
||
return ctx.fail('邮箱格式不正确');
|
||
}
|
||
|
||
// 业务逻辑...
|
||
}
|
||
```
|
||
|
||
### 3. 错误处理
|
||
|
||
```javascript
|
||
// 好的实践:统一的错误处理
|
||
"POST /users": async (ctx) => {
|
||
try {
|
||
const { username, email, password } = ctx.getBody();
|
||
|
||
// 验证参数
|
||
validateUserInput({ username, email, password });
|
||
|
||
// 检查用户是否存在
|
||
await checkUserExists(email);
|
||
|
||
// 创建用户
|
||
const user = await createUser({ username, email, password });
|
||
|
||
ctx.success(user, '用户创建成功');
|
||
|
||
} catch (error) {
|
||
// 统一错误处理
|
||
if (error.code === 'VALIDATION_ERROR') {
|
||
return ctx.fail(error.message, 400);
|
||
}
|
||
|
||
if (error.code === 'USER_EXISTS') {
|
||
return ctx.fail(error.message, 409);
|
||
}
|
||
|
||
// 未知错误
|
||
console.error('创建用户失败:', error);
|
||
ctx.fail('服务器内部错误', 500);
|
||
}
|
||
}
|
||
```
|
||
|
||
## ❓ 常见问题
|
||
|
||
### Q1: 数据库连接失败
|
||
**解决方案**:
|
||
1. 检查数据库服务是否启动
|
||
2. 验证配置文件中的数据库连接信息
|
||
3. 确认数据库用户权限
|
||
|
||
### Q2: 端口被占用
|
||
**解决方案**:
|
||
```bash
|
||
# 查看端口占用
|
||
netstat -ano | findstr :3001
|
||
|
||
# 终止进程
|
||
taskkill /PID <进程ID> /F
|
||
```
|
||
|
||
### Q3: 模块找不到
|
||
**解决方案**:
|
||
```bash
|
||
# 重新安装依赖
|
||
npm install
|
||
|
||
# 清除缓存
|
||
npm cache clean --force
|
||
```
|
||
|
||
### Q4: 权限验证失败
|
||
**解决方案**:
|
||
1. 检查请求头中的 token
|
||
2. 确认 token 格式正确
|
||
3. 验证 token 是否过期
|
||
|
||
## 📚 更多资源
|
||
|
||
- [框架源码](https://github.com/your-repo/node-core-framework)
|
||
- [API 文档](http://localhost:3001/api/docs)
|
||
- [示例项目](./examples/)
|
||
|
||
---
|
||
|
||
**🎉 恭喜!你已经掌握了 Node Core Framework 的基本使用方法!**
|
||
|
||
如有问题,请查看项目文档或提交 Issue。
|