feat: 实现微信小程序后端接口与用户认证系统
新增微信登录/注册合一接口、资料完善接口和token刷新接口 重构用户服务层,支持自动维护用户类型和资料完整度 引入JWT认证中间件和请求验证中间件 更新文档与测试用例,支持dist构建部署
This commit is contained in:
193
docs/ARCHIVE.md
Normal file
193
docs/ARCHIVE.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# OpenSpec 开发归档
|
||||
|
||||
## 归档日期
|
||||
|
||||
- 2026-03-20
|
||||
|
||||
## 归档范围
|
||||
|
||||
本次归档覆盖微信小程序后端交互相关接口的设计、实现、规范同步与部署调整,涉及:
|
||||
|
||||
- 接口路径统一收敛到 `/api`
|
||||
- 微信登录/注册合一
|
||||
- 微信资料完善接口重构
|
||||
- 微信手机号服务端换取
|
||||
- `users_type` 自动维护
|
||||
- JWT 认证与刷新 token
|
||||
- PocketBase Token 模式访问
|
||||
- OpenAPI 与项目文档同步
|
||||
- dist 构建与部署产物
|
||||
|
||||
---
|
||||
|
||||
## 一、接口演进结果
|
||||
|
||||
### 系统接口
|
||||
|
||||
- `POST /api/test-helloworld`
|
||||
- `POST /api/health`
|
||||
|
||||
### 微信小程序接口
|
||||
|
||||
- `POST /api/wechat/login`
|
||||
- 登录/注册合一
|
||||
- 接收 `users_wx_code`
|
||||
- 自动换取 `users_wx_openid`
|
||||
- 若无账号则自动创建游客账号
|
||||
- 返回 `status`、`is_info_complete`、`token`、完整用户信息
|
||||
|
||||
- `POST /api/wechat/profile`
|
||||
- 从 headers 读取 `users_wx_openid`
|
||||
- 需要 `Authorization`
|
||||
- body 接收:
|
||||
- `users_name`
|
||||
- `users_phone_code`
|
||||
- `users_picture`
|
||||
- 服务端调用微信接口换取真实手机号后写入 `users_phone`
|
||||
|
||||
- `POST /api/wechat/refresh-token`
|
||||
- 仅依赖 `users_wx_openid`
|
||||
- 不要求旧 `Authorization`
|
||||
- 返回新的 JWT token
|
||||
|
||||
---
|
||||
|
||||
## 二、关键业务规则
|
||||
|
||||
### 1. 用户类型 `users_type`
|
||||
|
||||
- 新账号初始化:`游客`
|
||||
- 当且仅当用户首次从:
|
||||
- `users_name` 为空
|
||||
- `users_phone` 为空
|
||||
- `users_picture` 为空
|
||||
|
||||
变为:
|
||||
- 三项全部完整
|
||||
|
||||
时,自动升级为:`注册用户`
|
||||
|
||||
- 后续资料修改不再覆盖已确定类型
|
||||
|
||||
### 2. 用户资料完整度 `is_info_complete`
|
||||
|
||||
以下三项同时存在时为 `true`:
|
||||
|
||||
- `users_name`
|
||||
- `users_phone`
|
||||
- `users_picture`
|
||||
|
||||
否则为 `false`
|
||||
|
||||
### 3. 微信手机号获取
|
||||
|
||||
服务端使用微信官方接口:
|
||||
|
||||
- `getuserphonenumber`
|
||||
|
||||
通过 `users_phone_code` 换取真实手机号,再写入数据库字段 `users_phone`。
|
||||
|
||||
---
|
||||
|
||||
## 三、鉴权规则
|
||||
|
||||
### 标准请求头
|
||||
|
||||
- `Authorization: Bearer <token>`
|
||||
- `users_wx_openid: <openid>`
|
||||
|
||||
### 当前规则
|
||||
|
||||
- `/api/wechat/login`:不需要 token
|
||||
- `/api/wechat/profile`:需要 `users_wx_openid + Authorization`
|
||||
- `/api/wechat/refresh-token`:**只需要** `users_wx_openid`
|
||||
|
||||
---
|
||||
|
||||
## 四、请求格式规则
|
||||
|
||||
所有微信写接口统一要求:
|
||||
|
||||
- `Content-Type: application/json`
|
||||
|
||||
不符合时返回:
|
||||
|
||||
- `415 请求体必须为 application/json`
|
||||
|
||||
---
|
||||
|
||||
## 五、PocketBase 访问策略
|
||||
|
||||
当前统一使用:
|
||||
|
||||
- `POCKETBASE_API_URL`
|
||||
- `POCKETBASE_AUTH_TOKEN`
|
||||
|
||||
已移除:
|
||||
|
||||
- `POCKETBASE_USER_NAME`
|
||||
- `POCKETBASE_PASSWORD`
|
||||
|
||||
---
|
||||
|
||||
## 六、部署与产物
|
||||
|
||||
后端已支持 dist 构建:
|
||||
|
||||
- `npm run build`
|
||||
- 产物目录:`back-end/dist/`
|
||||
|
||||
当前发布目录包含:
|
||||
|
||||
- `dist/src/`
|
||||
- `dist/spec/`
|
||||
- `dist/package.json`
|
||||
- `dist/package-lock.json`
|
||||
- `dist/.env`
|
||||
|
||||
生产启动方式:
|
||||
|
||||
- `npm start`
|
||||
- 实际运行:`node dist/src/index.js`
|
||||
|
||||
---
|
||||
|
||||
## 七、文档同步结果
|
||||
|
||||
已同步更新:
|
||||
|
||||
- `back-end/spec/openapi.yaml`
|
||||
- `docs/api.md`
|
||||
- `docs/deployment.md`
|
||||
- `README.md`
|
||||
|
||||
其中 `openapi.yaml` 已与当前真实接口行为对齐。
|
||||
|
||||
---
|
||||
|
||||
## 八、质量验证结果
|
||||
|
||||
本次归档前已验证通过:
|
||||
|
||||
- `npm run lint`
|
||||
- `npm run test`
|
||||
- `npm run build`
|
||||
|
||||
测试覆盖包括:
|
||||
|
||||
- 系统接口
|
||||
- 统一 404
|
||||
- 登录/注册合一
|
||||
- 非 JSON 拒绝
|
||||
- 资料更新
|
||||
- token 刷新
|
||||
- `users_type` 升级逻辑
|
||||
- `is_info_complete` 返回逻辑
|
||||
|
||||
---
|
||||
|
||||
## 九、当前已知边界
|
||||
|
||||
1. `refresh-token` 当前仅依赖 `users_wx_openid`,未校验旧 token
|
||||
2. 微信手机号能力依赖微信官方 `access_token` 与 `users_phone_code`
|
||||
3. 当前后端为 JavaScript + Express 架构,未引入 TypeScript 编译链
|
||||
511
docs/api.md
511
docs/api.md
@@ -1,332 +1,285 @@
|
||||
# API接口文档
|
||||
# API 接口文档
|
||||
|
||||
## 接口概述
|
||||
## 文档说明
|
||||
|
||||
本文档定义了微信小程序前端调用的API接口,包括认证、用户、数据等相关接口。
|
||||
本文档描述当前项目中**已经真实实现**并可直接调用的后端接口。
|
||||
当前接口统一特征如下:
|
||||
|
||||
## 基础信息
|
||||
- 基础路径(生产):`https://bai-api.blv-oa.com/api`
|
||||
- 基础路径(本地):`http://localhost:3000/api`
|
||||
- 响应格式:JSON
|
||||
- 业务响应结构统一为:`code`、`msg`、`data`
|
||||
- 当前公开接口统一使用 **POST** 方法
|
||||
- 微信写接口统一要求 `Content-Type: application/json`
|
||||
|
||||
- API基础路径: `http://localhost:3000/api`
|
||||
- 响应格式: JSON
|
||||
- 认证方式: JWT
|
||||
---
|
||||
|
||||
## 认证接口
|
||||
## 一、统一响应格式
|
||||
|
||||
### 1. 用户登录
|
||||
|
||||
**接口地址**: `/auth/login`
|
||||
**请求方式**: POST
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|-------|------|------|------|
|
||||
| username | string | 是 | 用户名 |
|
||||
| password | string | 是 | 密码 |
|
||||
|
||||
**响应示例**:
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "登录成功",
|
||||
"msg": "操作成功",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 失败响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "错误信息",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、系统接口
|
||||
|
||||
### 1. HelloWorld 测试接口
|
||||
|
||||
- **接口地址**:`/test-helloworld`
|
||||
- **请求方式**:`POST`
|
||||
- **请求头**:无特殊要求
|
||||
- **请求体**:可为空 `{}`
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "请求成功",
|
||||
"data": {
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"message": "Hello, World!",
|
||||
"timestamp": "2026-03-20T00:00:00.000Z",
|
||||
"status": "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 健康检查接口
|
||||
|
||||
- **接口地址**:`/health`
|
||||
- **请求方式**:`POST`
|
||||
- **请求头**:无特殊要求
|
||||
- **请求体**:可为空 `{}`
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "服务运行正常",
|
||||
"data": {
|
||||
"status": "healthy",
|
||||
"timestamp": "2026-03-20T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、微信小程序接口
|
||||
|
||||
## 1. 登录/注册合一
|
||||
|
||||
- **接口地址**:`/wechat/login`
|
||||
- **请求方式**:`POST`
|
||||
- **请求头**:
|
||||
- `Content-Type: application/json`
|
||||
|
||||
### 请求参数
|
||||
|
||||
```json
|
||||
{
|
||||
"users_wx_code": "0a1b2c3d4e5f6g"
|
||||
}
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `users_wx_code` | string | 是 | 微信小程序登录临时凭证 code,用于换取 `users_wx_openid` |
|
||||
|
||||
### 处理逻辑
|
||||
|
||||
- 使用 `users_wx_code` 向微信服务端换取 `users_wx_openid`
|
||||
- 如果数据库中不存在该用户,则自动创建新账号:
|
||||
- 初始化 `users_type = 游客`
|
||||
- 如果数据库中已存在该用户,则直接登录
|
||||
- 返回:
|
||||
- `status`
|
||||
- `is_info_complete`
|
||||
- `token`
|
||||
- 完整用户信息
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "登录成功",
|
||||
"data": {
|
||||
"status": "login_success",
|
||||
"is_info_complete": true,
|
||||
"token": "jwt-token",
|
||||
"user": {
|
||||
"id": "123",
|
||||
"username": "admin",
|
||||
"nickname": "管理员"
|
||||
"users_id": "U202603190001",
|
||||
"users_type": "注册用户",
|
||||
"users_name": "张三",
|
||||
"users_phone": "13800138000",
|
||||
"users_phone_masked": "138****8000",
|
||||
"users_picture": "https://example.com/avatar.png",
|
||||
"users_wx_openid": "oAbCdEfGh123456789",
|
||||
"company_id": "C10001",
|
||||
"company": null,
|
||||
"pb_id": "abc123xyz",
|
||||
"created": "2026-03-20T00:00:00.000Z",
|
||||
"updated": "2026-03-20T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 用户注册
|
||||
---
|
||||
|
||||
**接口地址**: `/auth/register`
|
||||
**请求方式**: POST
|
||||
**请求参数**:
|
||||
## 2. 完善/修改用户资料
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|-------|------|------|------|
|
||||
| username | string | 是 | 用户名 |
|
||||
| password | string | 是 | 密码 |
|
||||
| nickname | string | 是 | 昵称 |
|
||||
- **接口地址**:`/wechat/profile`
|
||||
- **请求方式**:`POST`
|
||||
- **请求头**:
|
||||
- `Content-Type: application/json`
|
||||
- `users_wx_openid: 微信用户唯一标识`
|
||||
- `Authorization: Bearer <token>`
|
||||
|
||||
**响应示例**:
|
||||
### 请求参数
|
||||
|
||||
```json
|
||||
{
|
||||
"users_name": "张三",
|
||||
"users_phone_code": "2b7d9f2e3c4a5b6d7e8f",
|
||||
"users_picture": "https://example.com/avatar.png"
|
||||
}
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `users_name` | string | 是 | 用户姓名 |
|
||||
| `users_phone_code` | string | 是 | 微信手机号获取凭证 code,后端将据此换取真实手机号 |
|
||||
| `users_picture` | string | 是 | 用户头像 URL |
|
||||
|
||||
### 处理逻辑
|
||||
|
||||
- 从请求头 `users_wx_openid` 读取当前用户身份
|
||||
- 校验 `Authorization`
|
||||
- 不再从 body 读取 `users_wx_code`
|
||||
- 使用 `users_phone_code` 调微信官方接口换取真实手机号
|
||||
- 将真实手机号写入数据库字段 `users_phone`
|
||||
- 若用户首次从“三项资料均为空”变为“三项资料均完整”,则将 `users_type` 从 `游客` 升级为 `注册用户`
|
||||
- 返回更新后的完整用户信息
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "注册成功",
|
||||
"msg": "信息更新成功",
|
||||
"data": {
|
||||
"id": "123",
|
||||
"username": "user1",
|
||||
"nickname": "新用户"
|
||||
"status": "update_success",
|
||||
"user": {
|
||||
"users_id": "U202603190001",
|
||||
"users_type": "注册用户",
|
||||
"users_name": "张三",
|
||||
"users_phone": "13800138000",
|
||||
"users_phone_masked": "138****8000",
|
||||
"users_picture": "https://example.com/avatar.png",
|
||||
"users_wx_openid": "oAbCdEfGh123456789",
|
||||
"company_id": "",
|
||||
"company": null,
|
||||
"pb_id": "abc123xyz",
|
||||
"created": "2026-03-20T00:00:00.000Z",
|
||||
"updated": "2026-03-20T00:10:00.000Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 用户接口
|
||||
---
|
||||
|
||||
### 1. 获取用户信息
|
||||
## 3. 刷新 token
|
||||
|
||||
**接口地址**: `/user/info`
|
||||
**请求方式**: GET
|
||||
**请求头**:
|
||||
- Authorization: Bearer {token}
|
||||
- **接口地址**:`/wechat/refresh-token`
|
||||
- **请求方式**:`POST`
|
||||
- **请求头**:
|
||||
- `users_wx_openid: 微信用户唯一标识`
|
||||
|
||||
**响应示例**:
|
||||
> 说明:本接口**不要求旧 `Authorization`**。
|
||||
|
||||
### 请求体
|
||||
|
||||
- 无 body 参数,可传 `{}` 或空体
|
||||
|
||||
### 处理逻辑
|
||||
|
||||
- 仅通过请求头中的 `users_wx_openid` 定位用户
|
||||
- 若用户存在,则签发新的 JWT token
|
||||
- 若用户不存在,则返回 `404`
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"msg": "刷新成功",
|
||||
"data": {
|
||||
"id": "123",
|
||||
"username": "admin",
|
||||
"nickname": "管理员",
|
||||
"avatar": "https://example.com/avatar.jpg",
|
||||
"createdAt": "2026-03-18T00:00:00Z"
|
||||
"token": "new-jwt-token"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 更新用户信息
|
||||
---
|
||||
|
||||
**接口地址**: `/user/update`
|
||||
**请求方式**: PUT
|
||||
**请求头**:
|
||||
- Authorization: Bearer {token}
|
||||
**请求参数**:
|
||||
## 四、错误码说明
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|-------|------|------|------|
|
||||
| nickname | string | 否 | 昵称 |
|
||||
| avatar | string | 否 | 头像URL |
|
||||
| 错误码 | 说明 |
|
||||
|---|---|
|
||||
| `200` | 成功 |
|
||||
| `400` | 请求参数错误 |
|
||||
| `401` | 请求头缺失、令牌无效或用户身份不匹配 |
|
||||
| `404` | 用户不存在或路由不存在 |
|
||||
| `415` | 请求体不是 `application/json` |
|
||||
| `429` | 请求过于频繁 |
|
||||
| `500` | 服务器内部错误 |
|
||||
|
||||
**响应示例**:
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
"data": {
|
||||
"id": "123",
|
||||
"username": "admin",
|
||||
"nickname": "新昵称",
|
||||
"avatar": "https://example.com/new-avatar.jpg"
|
||||
}
|
||||
}
|
||||
## 五、调用建议
|
||||
|
||||
### 1. 所有微信写接口都使用 JSON
|
||||
|
||||
请求头应设置:
|
||||
|
||||
```http
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
## 数据接口
|
||||
### 2. 资料接口与资料详情接口都要带标准 JWT 头
|
||||
|
||||
### 1. 获取数据列表
|
||||
|
||||
**接口地址**: `/data/list`
|
||||
**请求方式**: GET
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|-------|------|------|------|
|
||||
| page | number | 否 | 页码,默认1 |
|
||||
| size | number | 否 | 每页条数,默认10 |
|
||||
| keyword | string | 否 | 搜索关键词 |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": "1",
|
||||
"title": "数据1",
|
||||
"content": "内容1",
|
||||
"createdAt": "2026-03-18T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"size": 10
|
||||
}
|
||||
}
|
||||
```http
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
### 2. 获取数据详情
|
||||
### 3. `refresh-token` 接口当前只需要:
|
||||
|
||||
**接口地址**: `/data/detail/{id}`
|
||||
**请求方式**: GET
|
||||
**路径参数**:
|
||||
- id: 数据ID
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"title": "数据1",
|
||||
"content": "内容1",
|
||||
"createdAt": "2026-03-18T00:00:00Z",
|
||||
"updatedAt": "2026-03-18T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```http
|
||||
users_wx_openid: <openid>
|
||||
```
|
||||
|
||||
## AI接口
|
||||
|
||||
### 1. 智能问答
|
||||
|
||||
**接口地址**: `/ai/chat`
|
||||
**请求方式**: POST
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|-------|------|------|------|
|
||||
| question | string | 是 | 问题 |
|
||||
| context | string | 否 | 上下文 |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"data": {
|
||||
"answer": "这是AI的回答",
|
||||
"thinking": "AI的思考过程",
|
||||
"tokens": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 视频接口
|
||||
|
||||
### 1. 上传视频
|
||||
|
||||
**接口地址**: `/video/upload`
|
||||
**请求方式**: POST
|
||||
**请求头**:
|
||||
- Content-Type: multipart/form-data
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|-------|------|------|------|
|
||||
| video | file | 是 | 视频文件 |
|
||||
| title | string | 是 | 视频标题 |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "上传成功",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"title": "视频标题",
|
||||
"url": "https://example.com/video.mp4",
|
||||
"duration": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取视频列表
|
||||
|
||||
**接口地址**: `/video/list`
|
||||
**请求方式**: GET
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|-------|------|------|------|
|
||||
| page | number | 否 | 页码,默认1 |
|
||||
| size | number | 否 | 每页条数,默认10 |
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": "1",
|
||||
"title": "视频1",
|
||||
"url": "https://example.com/video1.mp4",
|
||||
"duration": 60,
|
||||
"createdAt": "2026-03-18T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"size": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|-------|------|
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权,请登录 |
|
||||
| 403 | 禁止访问 |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 调用示例
|
||||
|
||||
### 使用axios调用
|
||||
|
||||
```javascript
|
||||
// 登录
|
||||
axios.post('/api/auth/login', {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
}).then(response => {
|
||||
const token = response.data.data.token;
|
||||
// 存储token
|
||||
localStorage.setItem('token', token);
|
||||
});
|
||||
|
||||
// 带认证的请求
|
||||
axios.get('/api/user/info', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 使用fetch调用
|
||||
|
||||
```javascript
|
||||
// 登录
|
||||
fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
})
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
const token = data.data.token;
|
||||
// 存储token
|
||||
localStorage.setItem('token', token);
|
||||
});
|
||||
|
||||
// 带认证的请求
|
||||
fetch('/api/user/info', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
}).then(response => response.json())
|
||||
.then(data => console.log(data));
|
||||
```
|
||||
不需要旧 `Authorization`。
|
||||
|
||||
@@ -91,13 +91,14 @@ npm run test
|
||||
|
||||
### 数据库操作
|
||||
|
||||
使用Pocketbase作为数据库,提供轻量级的数据存储解决方案。
|
||||
使用Pocketbase作为数据库,提供轻量级的数据存储解决方案。通过Pocketbase API进行数据操作,支持CRUD操作和实时数据同步。
|
||||
|
||||
### 环境变量
|
||||
|
||||
环境变量配置位于 `.env` 文件,包括:
|
||||
- 服务器端口
|
||||
- 数据库连接信息
|
||||
- Pocketbase API URL
|
||||
- Pocketbase认证信息
|
||||
- JWT密钥
|
||||
- 其他配置参数
|
||||
|
||||
|
||||
@@ -60,12 +60,20 @@ wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo b
|
||||
|
||||
## 后端部署
|
||||
|
||||
后端建议采用 **dist 发布目录部署**,即:
|
||||
|
||||
1. 在构建机执行 `npm run build`
|
||||
2. 生成 `back-end/dist/` 发布产物
|
||||
3. 服务器仅部署 `dist/`、`package.json`、`package-lock.json` 与 `.env`
|
||||
4. 服务器执行 `npm install --omit=dev`
|
||||
5. 服务器执行 `npm start`
|
||||
|
||||
### 1. 创建Dockerfile
|
||||
|
||||
在 `back-end` 目录创建 `Dockerfile` 文件:
|
||||
|
||||
```dockerfile
|
||||
FROM node:22-alpine
|
||||
FROM node:22-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -73,10 +81,57 @@ COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:22-alpine AS production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/package*.json ./
|
||||
RUN npm install --omit=dev
|
||||
|
||||
COPY --from=build /app/dist ./dist
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "src/index.js"]
|
||||
CMD ["node", "dist/src/index.js"]
|
||||
```
|
||||
|
||||
### 1.1 本地构建与上传发布包
|
||||
|
||||
在 `back-end/` 目录执行:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run lint
|
||||
npm run test
|
||||
npm run build
|
||||
```
|
||||
|
||||
构建成功后会生成:
|
||||
|
||||
```text
|
||||
back-end/dist/
|
||||
src/
|
||||
spec/
|
||||
package.json
|
||||
package-lock.json
|
||||
.env
|
||||
eslint.config.js
|
||||
```
|
||||
|
||||
如果你采用服务器源码分离部署,建议上传以下内容到服务器:
|
||||
|
||||
- `dist/`
|
||||
- `package.json`
|
||||
- `package-lock.json`
|
||||
- `.env`
|
||||
|
||||
然后在服务器执行:
|
||||
|
||||
```bash
|
||||
npm install --omit=dev
|
||||
npm start
|
||||
```
|
||||
|
||||
### 2. 创建docker-compose.yml
|
||||
@@ -202,20 +257,21 @@ chmod +x deploy.sh
|
||||
3. 配置前端代理:
|
||||
- 代理名称:frontend
|
||||
- 目标URL:http://localhost:80
|
||||
- 发送域名:你的域名
|
||||
- 发送域名:`bai-api.blv-oa.com`(如前后端分域,请按实际前端域名填写)
|
||||
4. 配置后端代理:
|
||||
- 代理名称:backend
|
||||
- 目标URL:http://localhost:3000
|
||||
- 发送域名:你的域名
|
||||
- 发送域名:`bai-api.blv-oa.com`
|
||||
- 路径:/api
|
||||
5. 点击「保存」
|
||||
|
||||
### 3. 配置SSL证书(可选)
|
||||
### 3. 配置SSL证书(必须)
|
||||
|
||||
1. 进入网站设置
|
||||
2. 点击「SSL」→「Let's Encrypt」
|
||||
3. 申请并安装SSL证书
|
||||
4. 开启「强制HTTPS」
|
||||
5. 确保域名 `bai-api.blv-oa.com` 已正确解析到服务器公网 IP
|
||||
|
||||
## 环境变量配置
|
||||
|
||||
@@ -227,6 +283,9 @@ chmod +x deploy.sh
|
||||
# Server Configuration
|
||||
PORT=3000
|
||||
NODE_ENV=production
|
||||
APP_PROTOCOL=https
|
||||
APP_DOMAIN=bai-api.blv-oa.com
|
||||
APP_BASE_URL=https://bai-api.blv-oa.com
|
||||
|
||||
# Database Configuration
|
||||
DB_HOST=db
|
||||
@@ -238,7 +297,7 @@ JWT_SECRET=your_jwt_secret_key
|
||||
JWT_EXPIRES_IN=24h
|
||||
|
||||
# CORS Configuration
|
||||
CORS_ORIGIN=*
|
||||
CORS_ORIGIN=https://bai-api.blv-oa.com
|
||||
```
|
||||
|
||||
### 前端环境变量
|
||||
@@ -246,11 +305,21 @@ CORS_ORIGIN=*
|
||||
在 `front-end/.env.production` 文件中配置:
|
||||
|
||||
```env
|
||||
VUE_APP_API_BASE_URL=http://your-domain.com/api
|
||||
VUE_APP_BASE_URL=https://bai-api.blv-oa.com/api
|
||||
VUE_APP_TITLE=BAI管理系统
|
||||
VUE_APP_VERSION=1.0.0
|
||||
```
|
||||
|
||||
## 域名解析与 HTTPS 部署建议
|
||||
|
||||
正式环境建议按以下方式部署:
|
||||
|
||||
1. 将域名 `bai-api.blv-oa.com` 的 DNS A 记录指向服务器公网 IP
|
||||
2. 宝塔/Nginx 为该域名签发并启用 SSL 证书
|
||||
3. Nginx 对外暴露 `443`,再反向代理到容器内 `backend:3000`
|
||||
4. 前端生产环境接口地址统一使用:`https://bai-api.blv-oa.com/api`
|
||||
5. 后端对外公开地址统一使用 `APP_BASE_URL=https://bai-api.blv-oa.com`
|
||||
|
||||
## 数据库配置
|
||||
|
||||
### Pocketbase设置
|
||||
@@ -265,6 +334,18 @@ VUE_APP_VERSION=1.0.0
|
||||
|
||||
## 监控与维护
|
||||
|
||||
### 后端发布命令
|
||||
|
||||
后端推荐命令:
|
||||
|
||||
```bash
|
||||
# 构建发布产物
|
||||
npm run build
|
||||
|
||||
# 生产启动
|
||||
npm start
|
||||
```
|
||||
|
||||
### 查看日志
|
||||
|
||||
```bash
|
||||
|
||||
42
docs/example.md
Normal file
42
docs/example.md
Normal file
@@ -0,0 +1,42 @@
|
||||
// 获取微信小程序OpenID
|
||||
private async Task<string> GetWxOpenIdAsync(string code)
|
||||
{
|
||||
try
|
||||
{
|
||||
var appId = configuration["WeChat:AppId"];
|
||||
var appSecret = configuration["WeChat:AppSecret"];
|
||||
|
||||
if (string.IsNullOrEmpty(appId) || string.IsNullOrEmpty(appSecret))
|
||||
{
|
||||
throw new Exception("微信小程序配置缺失");
|
||||
}
|
||||
|
||||
var httpClient = _httpClientFactory.CreateClient();
|
||||
var url = $"https://api.weixin.qq.com/sns/jscode2session?appid={appId}&secret={appSecret}&js_code={code}&grant_type=authorization_code";
|
||||
|
||||
var response = await httpClient.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
var jsonDocument = JsonDocument.Parse(responseContent);
|
||||
|
||||
if (jsonDocument.RootElement.TryGetProperty("openid", out var openidElement))
|
||||
{
|
||||
return openidElement.GetString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果有错误信息,抛出异常
|
||||
if (jsonDocument.RootElement.TryGetProperty("errcode", out var errcodeElement) &&
|
||||
jsonDocument.RootElement.TryGetProperty("errmsg", out var errmsgElement))
|
||||
{
|
||||
throw new Exception($"获取OpenID失败: {errcodeElement.GetInt32()} - {errmsgElement.GetString()}");
|
||||
}
|
||||
throw new Exception("获取OpenID失败: 响应中未包含openid");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"获取微信OpenID时发生错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user