docs(wiki): 更新API参考文档格式与内容

- 优化API参考文档的段落排版和表格对齐
- 补充签名机制和支付接口的详细说明- 完善错误码与解决方案的描述
- 统一文档中的代码引用和示例格式

docs(beego):优化Beego框架集成文档结构

- 改进Beego框架文档的换行和段落布局
- 完善控制器继承和中间件集成的说明
- 优化ORM模型注册和路由机制的描述- 统一文档中的技术术语表达方式

docs(docker): 改进Docker部署指南文档格式

- 优化Dockerfile多阶段构建的描述
- 完善docker-compose配置文件说明
- 改进本地部署步骤和故障排除指南- 统一文档中的命令行示例格式feat(supplier): 新增LianIns卡发送任务类型- 在枚举中添加SendCardTaskTypeEnumLianIns类型
- 更新GetAllSendCardTaskType函数返回值
- 实现LianIns任务类型的工厂方法

chore(deps): 更新项目依赖版本

- 升级github.com/bytedance/sonic至v1.14.2
- 升级github.com/duke-git/lancet/v2至v2.3.8
- 升级github.com/bytedance/sonic/loader至v0.4.0
- 移除natefinch/lumberjack和yaml.v2依赖- 清理间接依赖中的toml库引用
This commit is contained in:
danial
2025-11-04 16:04:08 +08:00
parent f9a2f8a12b
commit ea089b7be8
46 changed files with 3302 additions and 465 deletions

View File

@@ -11,6 +11,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [API认证与签名机制](#api认证与签名机制)
3. [支付下单接口](#支付下单接口)
@@ -21,24 +22,30 @@
8. [常见错误码与解决方案](#常见错误码与解决方案)
## 简介
本文档为kami_gateway系统的公开RESTful API提供完整的技术参考。文档基于`router.go`中的路由定义,系统性地描述了支付下单、订单查询和回调通知三大核心流程的接口规范。所有请求和响应结构均引用`internal/schema/request``internal/schema/response`中的定义。本文档还详细说明了API的签名验证机制、安全要求并提供实际使用示例。
本文档为kami_gateway系统的公开RESTful API提供完整的技术参考。文档基于`router.go`
中的路由定义,系统性地描述了支付下单、订单查询和回调通知三大核心流程的接口规范。所有请求和响应结构均引用
`internal/schema/request``internal/schema/response`中的定义。本文档还详细说明了API的签名验证机制、安全要求并提供实际使用示例。
## API认证与签名机制
所有API请求必须通过签名验证以确保安全性和完整性。签名算法基于MD5具体实现位于`sign_verify.go`中。不同接口可能使用不同的签名方法主要分为标准MD5签名和MF格式签名。
所有API请求必须通过签名验证以确保安全性和完整性。签名算法基于MD5具体实现位于`sign_verify.go`
中。不同接口可能使用不同的签名方法主要分为标准MD5签名和MF格式签名。
### 签名生成规则
1. **标准MD5签名GetMD5Sign**
- 将所有非空参数按键名的字母顺序排序
- 拼接格式为:`key1=value1&key2=value2&...&paySecret=商户密钥`
- 对拼接后的字符串进行MD5加密并转换为大写
- 将所有非空参数按键名的字母顺序排序
- 拼接格式为:`key1=value1&key2=value2&...&paySecret=商户密钥`
- 对拼接后的字符串进行MD5加密并转换为大写
2. **MF格式签名GetMD5SignMF**
- 将所有非空参数按键名的字母顺序排序
- 拼接格式为:`key1value1key2value2...paySecret`
- 对拼接后的字符串进行MD5加密并转换为小写
- 将所有非空参数按键名的字母顺序排序
- 拼接格式为:`key1value1key2value2...paySecret`
- 对拼接后的字符串进行MD5加密并转换为小写
### 签名验证流程
1. 服务端接收到请求后,提取`sign`参数
2. 根据接口类型选择相应的签名算法
3. 使用商户密钥重新计算签名
@@ -46,6 +53,7 @@
5. 签名验证失败将返回错误响应
**Section sources**
- [sign_verify.go](file://internal/utils/sign_verify.go#L1-L104)
## 支付下单接口
@@ -53,33 +61,38 @@
支付下单接口用于创建新的支付订单,是支付流程的起点。
### 接口定义
- **HTTP方法**POST
- **URL模式**`/gateway/scan`
- **认证方式**MD5签名
### 请求头
| 头部字段 | 是否必需 | 描述 |
|---------|--------|------|
| Content-Type | 是 | 必须为 `application/json` |
| 头部字段 | 是否必需 | 描述 |
|--------------|------|------------------------|
| Content-Type | 是 | 必须为 `application/json` |
### 请求体Schema: CreatedOrder
请求体结构定义在`internal/schema/request/order.go`中,具体字段如下:
| 字段名 | 类型 | 是否必需 | 描述 |
|-------|------|--------|------|
| payKey | string | 是 | 商户唯一标识key由系统分配 |
| orderNo | string | 是 | 商户侧订单号,需保证唯一性 |
| orderPrice | float64 | 是 | 订单金额,单位为元 |
| orderPeriod | int | 否 | 订单周期(天) |
| notifyUrl | string | 是 | 支付成功后的回调通知地址 |
| sign | string | 是 | 签名字符串,用于验证请求合法性 |
| productCode | string | 是 | 产品编码,标识支付产品类型 |
| timestamp | int64 | 是 | 时间戳,单位为毫秒 |
| 字段名 | 类型 | 是否必需 | 描述 |
|-------------|---------|------|-----------------|
| payKey | string | 是 | 商户唯一标识key由系统分配 |
| orderNo | string | 是 | 商户侧订单号,需保证唯一性 |
| orderPrice | float64 | 是 | 订单金额,单位为元 |
| orderPeriod | int | 否 | 订单周期(天) |
| notifyUrl | string | 是 | 支付成功后的回调通知地址 |
| sign | string | 是 | 签名字符串,用于验证请求合法性 |
| productCode | string | 是 | 产品编码,标识支付产品类型 |
| timestamp | int64 | 是 | 时间戳,单位为毫秒 |
### 响应体
成功响应返回`ScanSuccessData`结构,失败返回`ScanFailData`结构。
#### 成功响应示例
```json
{
"orderNo": "ORD20231101001",
@@ -93,6 +106,7 @@
```
#### 失败响应示例
```json
{
"payKey": "MCH123456",
@@ -103,6 +117,7 @@
```
**Section sources**
- [router.go](file://internal/routers/router.go#L1-L75)
- [order.go](file://internal/schema/request/order.go#L1-L35)
- [pay_resp.go](file://internal/schema/response/pay_resp.go#L1-L38)
@@ -113,32 +128,36 @@
订单查询接口允许商户查询已创建订单的状态和详细信息。
### 接口定义
- **HTTP方法**GET
- **URL模式**`/gateway/merchant/query`
- **认证方式**MF格式MD5签名
### 请求参数
| 参数名 | 类型 | 是否必需 | 描述 |
|-------|------|--------|------|
| appKey | string | 是 | 商户应用key用于身份识别 |
| orderNo | string | 是 | 商户侧订单号 |
| timestamp | string | 是 | 请求时间戳格式为Unix时间戳字符串 |
| sign | string | 是 | MF格式签名 |
| 参数名 | 类型 | 是否必需 | 描述 |
|-----------|--------|------|---------------------|
| appKey | string | 是 | 商户应用key用于身份识别 |
| orderNo | string | 是 | 商户侧订单号 |
| timestamp | string | 是 | 请求时间戳格式为Unix时间戳字符串 |
| sign | string | 是 | MF格式签名 |
### 响应体Schema: OrderQueryResp
响应体结构定义在`internal/schema/response`中,包含订单的完整状态信息。
| 字段名 | 类型 | 描述 |
|-------|------|------|
| orderNo | string | 系统生成的订单号 |
| cardNo | string | 卡号(如适用) |
| cardPwd | string | 卡密(如适用) |
| status | string | 订单状态:`wait`(等待)、`success`(成功)、`fail`(失败) |
| faceVal | float64 | 实际面值金额 |
| cardReturnData | string | 通道返回的原始数据 |
| amount | string | 显示金额,格式化为字符串 |
| 字段名 | 类型 | 描述 |
|----------------|---------|------------------------------------------|
| orderNo | string | 系统生成的订单号 |
| cardNo | string | 卡号(如适用) |
| cardPwd | string | 卡密(如适用) |
| status | string | 订单状态:`wait`(等待)、`success`(成功)、`fail`(失败) |
| faceVal | float64 | 实际面值金额 |
| cardReturnData | string | 通道返回的原始数据 |
| amount | string | 显示金额,格式化为字符串 |
### 成功响应示例
```json
{
"code": 0,
@@ -156,6 +175,7 @@
```
### 错误响应示例
```json
{
"code": -1,
@@ -164,6 +184,7 @@
```
**Section sources**
- [router.go](file://internal/routers/router.go#L1-L75)
- [order_controller.go](file://internal/controllers/order_controller.go#L1-L226)
@@ -172,24 +193,28 @@
回调通知接口用于接收第三方支付平台的支付结果通知,是支付流程的终点。
### 接口定义
- **HTTP方法**POST
- **URL模式**`/gateway/supplier/order/query`
- **认证方式**:无(由第三方系统调用)
### 请求参数
| 参数名 | 类型 | 是否必需 | 描述 |
|-------|------|--------|------|
| bankOrderId | string | 是 | 系统内部订单号 |
| 参数名 | 类型 | 是否必需 | 描述 |
|-------------|--------|------|---------|
| bankOrderId | string | 是 | 系统内部订单号 |
### 响应格式
接口返回纯文本响应,用于确认接收状态。
| 状态 | 响应内容 |
|------|----------|
| 状态 | 响应内容 |
|----|-----------|
| 成功 | "success" |
| 失败 | 错误描述信息 |
| 失败 | 错误描述信息 |
### 处理流程
1. 系统接收到第三方通知
2. 根据`bankOrderId`查询订单信息
3. 更新订单状态为成功
@@ -209,6 +234,7 @@ Gateway-->>ThirdParty : "success"
```
**Diagram sources**
- [router.go](file://internal/routers/router.go#L1-L75)
- [order_controller.go](file://internal/controllers/order_controller.go#L1-L226)
@@ -217,6 +243,7 @@ Gateway-->>ThirdParty : "success"
代付接口用于处理商户的代付请求,包括代付下单、余额查询和结果查询。
### 接口列表
- **代付下单**`/gateway/payfor` (当前注释状态)
- **代付查询**`/gateway/payfor/query` (当前注释状态)
- **余额查询**`/gateway/balance` (当前注释状态)
@@ -226,11 +253,13 @@ Gateway-->>ThirdParty : "success"
> **注意**:根据`router.go`中的代码,大部分代付接口当前处于注释状态,表示功能尚未启用或正在开发中。
**Section sources**
- [router.go](file://internal/routers/router.go#L1-L75)
## 客户端调用示例
### Go语言调用示例
```go
package main
@@ -316,19 +345,21 @@ func main() {
```
**Section sources**
- [order.go](file://internal/schema/request/order.go#L1-L35)
## 常见错误码与解决方案
| 错误码 | 错误信息 | 可能原因 | 解决方案 |
|-------|--------|--------|--------|
| -1 | 参数错误 | 必填参数缺失或格式不正确 | 检查所有必需参数是否完整且格式正确 |
| -1 | key错误 | appKey或payKey无效 | 确认使用的key是否正确且已激活 |
| -1 | 签名错误 | 签名计算不正确 | 严格按照签名规则重新计算签名,注意参数排序和拼接方式 |
| -1 | 订单不存在 | 查询的订单号不存在 | 确认订单号是否正确,或检查是否已成功创建订单 |
| 400 | 参数验证失败 | 请求参数不符合验证规则 | 检查参数类型、长度和格式是否符合API文档要求 |
| 500 | 内部错误 | 服务器内部处理异常 | 联系技术支持,提供请求时间、订单号等信息 |
| 错误码 | 错误信息 | 可能原因 | 解决方案 |
|-----|--------|-----------------|----------------------------|
| -1 | 参数错误 | 必填参数缺失或格式不正确 | 检查所有必需参数是否完整且格式正确 |
| -1 | key错误 | appKey或payKey无效 | 确认使用的key是否正确且已激活 |
| -1 | 签名错误 | 签名计算不正确 | 严格按照签名规则重新计算签名,注意参数排序和拼接方式 |
| -1 | 订单不存在 | 查询的订单号不存在 | 确认订单号是否正确,或检查是否已成功创建订单 |
| 400 | 参数验证失败 | 请求参数不符合验证规则 | 检查参数类型、长度和格式是否符合API文档要求 |
| 500 | 内部错误 | 服务器内部处理异常 | 联系技术支持,提供请求时间、订单号等信息 |
**Section sources**
- [order_controller.go](file://internal/controllers/order_controller.go#L1-L226)
- [pay_resp.go](file://internal/schema/response/pay_resp.go#L1-L38)

View File

@@ -11,6 +11,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
@@ -22,9 +23,11 @@
9. [结论](#结论)
## 简介
本文档旨在为kami_gateway系统的代付功能创建全面的API文档。尽管相关控制器代码被注释但基于现有代码结构和命名约定详细说明了代付API的设计意图和预期功能。文档涵盖了代付请求、结果查询、余额查询以及手动处理代付结果等关键端点解释了代付流程中的核心参数、签名验证机制和参数校验逻辑。
## 项目结构
kami_gateway项目采用分层架构设计主要分为conf、deploy、internal三大目录。代付功能的核心代码位于internal目录下包括controllers、service、models、schema等子模块。这种结构实现了关注点分离便于维护和扩展。
```mermaid
@@ -48,23 +51,28 @@ deploy --> internal
```
**图示来源**
- [payfor_controller.go](file://internal/controllers/payfor_controller.go)
- [router.go](file://internal/routers/router.go)
**本节来源**
- [payfor_controller.go](file://internal/controllers/payfor_controller.go)
- [router.go](file://internal/routers/router.go)
## 核心组件
代付功能的核心组件包括PayForGateway控制器、pay_for服务、PayforInfo模型和相关响应结构体。这些组件协同工作处理代付请求的接收、验证、处理和结果返回。控制器负责接收HTTP请求服务层处理业务逻辑模型定义数据结构响应结构体规范API输出格式。
**本节来源**
- [payfor_controller.go](file://internal/controllers/payfor_controller.go)
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_info.go](file://internal/models/payfor/payfor_info.go)
- [payfor_resp.go](file://internal/schema/response/payfor_resp.go)
## 架构概述
代付功能的架构采用典型的MVC模式通过控制器接收请求调用服务层处理业务逻辑操作模型层的数据并返回标准化的响应。整个流程涉及签名验证、参数校验、数据库操作和事务管理确保了代付操作的安全性和一致性。
```mermaid
@@ -81,6 +89,7 @@ Controller --> Client
```
**图示来源**
- [payfor_controller.go](file://internal/controllers/payfor_controller.go#L7-L7)
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_info.go](file://internal/models/payfor/payfor_info.go)
@@ -88,9 +97,11 @@ Controller --> Client
## 详细组件分析
### 代付网关控制器分析
PayForGateway控制器是代付功能的入口点继承自web.Controller提供了多个代付相关的API端点。尽管这些方法被注释但其设计意图清晰涵盖了代付请求、结果查询、余额查询和手动结果处理等完整流程。
#### 控制器结构
```mermaid
classDiagram
class PayForGateway {
@@ -105,9 +116,11 @@ PayForGateway --> web.Controller : "继承"
```
**图示来源**
- [payfor_controller.go](file://internal/controllers/payfor_controller.go#L7-L7)
#### API端点流程
```mermaid
flowchart TD
Start([接收请求]) --> ValidateParams["验证参数完整性"]
@@ -124,16 +137,20 @@ ReturnAuthError --> End
```
**图示来源**
- [payfor_controller.go](file://internal/controllers/payfor_controller.go)
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
**本节来源**
- [payfor_controller.go](file://internal/controllers/payfor_controller.go)
### 代付服务分析
pay_for服务模块实现了代付功能的核心业务逻辑包括自动代付、结果查询和余额查询等功能。服务层与模型层紧密协作确保数据的一致性和完整性。
#### 服务功能关系
```mermaid
classDiagram
class PayForResultQuery {
@@ -156,16 +173,20 @@ BalanceQuery --> GetMD5Sign : "调用"
```
**图示来源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go)
**本节来源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
### 代付信息模型分析
PayforInfo模型定义了代付记录的数据结构包含代付交易的完整信息从商户信息到银行账户详情再到交易状态和金额明细。
#### 数据模型结构
```mermaid
classDiagram
class PayforInfo {
@@ -199,15 +220,19 @@ PayforInfo <|-- PayforInfoMethods : "实现"
```
**图示来源**
- [payfor_info.go](file://internal/models/payfor/payfor_info.go)
**本节来源**
- [payfor_info.go](file://internal/models/payfor/payfor_info.go)
### 响应结构分析
响应结构体定义了API的输出格式确保了前后端交互的一致性和可预测性。不同的代付操作返回不同但结构相似的响应。
#### 响应结构关系
```mermaid
classDiagram
class PayForResponse {
@@ -240,12 +265,15 @@ class BalanceResponse {
```
**图示来源**
- [payfor_resp.go](file://internal/schema/response/payfor_resp.go)
**本节来源**
- [payfor_resp.go](file://internal/schema/response/payfor_resp.go)
## 依赖分析
代付功能依赖于多个内部模块和外部服务,形成了复杂的依赖网络。理解这些依赖关系对于维护和扩展系统至关重要。
```mermaid
@@ -262,27 +290,33 @@ PayForSolve --> AccountModel
```
**图示来源**
- [payfor_controller.go](file://internal/controllers/payfor_controller.go)
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go)
- [router.go](file://internal/routers/router.go)
**本节来源**
- [payfor_controller.go](file://internal/controllers/payfor_controller.go)
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go)
- [router.go](file://internal/routers/router.go)
## 性能考虑
代付功能在设计时考虑了性能和可靠性。通过使用数据库事务确保数据一致性,采用签名验证机制保障安全性,并通过合理的错误处理提高系统的健壮性。尽管功能被注释,但其设计体现了对高并发场景下性能和一致性的考量。
## 故障排除指南
当代付功能出现问题时,应首先检查日志输出,重点关注签名验证失败、参数校验错误和数据库操作异常。由于相关路由被注释,需要确认是否已正确启用代付功能。同时,检查商户配置和通道设置是否正确,确保代付流程所需的各项条件都已满足。
**本节来源**
- [payfor_controller.go](file://internal/controllers/payfor_controller.go)
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go)
## 结论
尽管kami_gateway的代付功能当前被注释但其代码结构清晰设计完整。系统提供了从代付请求到结果处理的全流程支持包括自动代付、结果查询、余额查询和手动结果修正等关键功能。通过详细的API文档和清晰的代码结构为未来启用和维护代付功能奠定了坚实基础。

View File

@@ -9,6 +9,7 @@
</cite>
## 目录
1. [回调API](#回调api)
2. [核心组件](#核心组件)
3. [处理流程](#处理流程)
@@ -18,22 +19,31 @@
## 回调API
kami_gateway系统通过一系列回调API端点接收来自不同第三方支付渠道的支付结果通知。这些端点包括`/mfcard/notifyV2``/appleCard/notify``/tMallGame/notify`每个端点对应一个特定的支付渠道如爱博、京东卡、天猫游戏等。当支付渠道完成交易后会向这些预设的URL发送HTTP请求携带支付结果信息从而触发网关内部的订单状态更新流程。
kami_gateway系统通过一系列回调API端点接收来自不同第三方支付渠道的支付结果通知。这些端点包括`/mfcard/notifyV2`
`/appleCard/notify``/tMallGame/notify`
每个端点对应一个特定的支付渠道如爱博、京东卡、天猫游戏等。当支付渠道完成交易后会向这些预设的URL发送HTTP请求携带支付结果信息从而触发网关内部的订单状态更新流程。
这些回调接口由`internal/routers/router.go`文件中的路由配置定义将不同的URL路径映射到对应的实现结构体`MFCardV2Impl``AppleCardImpl``TMAllGameImpl`)的`PayNotify`方法上。整个回调系统的设计旨在确保支付结果的最终一致性,通过验证、处理和状态更新等一系列操作,保证商户和平台的账务准确无误。
这些回调接口由`internal/routers/router.go`文件中的路由配置定义将不同的URL路径映射到对应的实现结构体`MFCardV2Impl`
`AppleCardImpl``TMAllGameImpl`)的`PayNotify`方法上。整个回调系统的设计旨在确保支付结果的最终一致性,通过验证、处理和状态更新等一系列操作,保证商户和平台的账务准确无误。
**Section sources**
- [router.go](file://internal/routers/router.go#L49-L74)
## 核心组件
回调API的核心处理逻辑由三个关键组件构成路由控制器、支付渠道实现和订单解决服务。
首先,`internal/routers/router.go`文件定义了所有回调端点的路由规则。例如,`web.Router("/mfcard/notifyV2", &third_party.MFCardV2Impl{}, "*:PayNotify")`这一行代码将`/mfcard/notifyV2`路径的请求路由到`MFCardV2Impl`结构体的`PayNotify`方法。这为每个支付渠道提供了独立的入口。
首先,`internal/routers/router.go`文件定义了所有回调端点的路由规则。例如,
`web.Router("/mfcard/notifyV2", &third_party.MFCardV2Impl{}, "*:PayNotify")`这一行代码将`/mfcard/notifyV2`路径的请求路由到
`MFCardV2Impl`结构体的`PayNotify`方法。这为每个支付渠道提供了独立的入口。
其次,各个支付渠道的处理逻辑封装在`internal/service/supplier/third_party/`目录下的具体实现中。`MFCardV2Impl``AppleCardImpl``TMAllGameImpl`等结构体都嵌入了`web.Controller`,并实现了`PayNotify`方法。这些方法负责接收、解析和验证来自不同渠道的回调数据。
其次,各个支付渠道的处理逻辑封装在`internal/service/supplier/third_party/`目录下的具体实现中。`MFCardV2Impl`
`AppleCardImpl``TMAllGameImpl`等结构体都嵌入了`web.Controller`,并实现了`PayNotify`方法。这些方法负责接收、解析和验证来自不同渠道的回调数据。
最后,`SolvePaySuccess``SolvePayFail`函数是订单状态更新的核心。它们位于`internal/service/pay_solve.go`文件中,被所有`PayNotify`方法调用。`SolvePaySuccess`在支付成功时执行,负责更新订单状态为成功、增加商户账户余额、记录动账历史和更新通道统计数据。`SolvePayFail`则在支付失败时调用,将订单状态标记为失败,并记录失败原因。
最后,`SolvePaySuccess``SolvePayFail`函数是订单状态更新的核心。它们位于`internal/service/pay_solve.go`文件中,被所有
`PayNotify`方法调用。`SolvePaySuccess`在支付成功时执行,负责更新订单状态为成功、增加商户账户余额、记录动账历史和更新通道统计数据。
`SolvePayFail`则在支付失败时调用,将订单状态标记为失败,并记录失败原因。
```mermaid
classDiagram
@@ -56,6 +66,7 @@ TMAllGameImpl --> PaySolveService : "调用"
```
**Diagram sources**
- [router.go](file://internal/routers/router.go#L49-L74)
- [pay_solve.go](file://internal/service/pay_solve.go#L37-L254)
- [mf178_v2.go](file://internal/service/supplier/third_party/mf178_v2.go#L187-L253)
@@ -63,6 +74,7 @@ TMAllGameImpl --> PaySolveService : "调用"
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L167-L223)
**Section sources**
- [pay_solve.go](file://internal/service/pay_solve.go#L37-L254)
- [mf178_v2.go](file://internal/service/supplier/third_party/mf178_v2.go#L37-L39)
- [apple.go](file://internal/service/supplier/third_party/apple.go#L33-L35)
@@ -72,13 +84,19 @@ TMAllGameImpl --> PaySolveService : "调用"
回调API的处理流程是一个严谨的、多步骤的验证和更新过程确保了数据的完整性和业务逻辑的正确性。
1. **接收与查找**:当一个回调请求到达时,其`PayNotify`方法首先从请求参数中提取出订单号(如`attach``merchantOrder`)。然后,系统调用`order.GetOrderByBankOrderId(ctx, orderNo)`来根据这个订单号从数据库中查找对应的订单信息。如果订单不存在,系统会立即终止处理并返回失败响应。
1. **接收与查找**:当一个回调请求到达时,其`PayNotify`方法首先从请求参数中提取出订单号(如`attach``merchantOrder`
)。然后,系统调用`order.GetOrderByBankOrderId(ctx, orderNo)`来根据这个订单号从数据库中查找对应的订单信息。如果订单不存在,系统会立即终止处理并返回失败响应。
2. **验证与完整性检查**:在确认订单存在后,系统会进行一系列验证。这包括检查支付通道是否有效(通过`road.GetRoadInfoByRoadUid`),以及验证回调数据的签名。例如,`MFCardV2Impl`使用`utils.GetMD5SignMF`函数根据预设的密钥重新计算签名,并与回调中的`sign`参数进行比对。如果签名不匹配,处理将被终止。
2. **验证与完整性检查**:在确认订单存在后,系统会进行一系列验证。这包括检查支付通道是否有效(通过`road.GetRoadInfoByRoadUid`
),以及验证回调数据的签名。例如,`MFCardV2Impl`使用`utils.GetMD5SignMF`函数根据预设的密钥重新计算签名,并与回调中的`sign`
参数进行比对。如果签名不匹配,处理将被终止。
3. **状态判断与解决**:一旦数据通过验证,系统会根据回调中的状态码来决定后续操作。以`MFCardV2Impl`为例,当`status``"9"`时,系统会调用`service.SolvePaySuccess`;当`status``"8"`时,则调用`service.SolvePayFail``SolvePaySuccess`函数在一个数据库事务中执行,确保了所有相关操作(更新订单、更新账户、插入动账记录等)的原子性。
3. **状态判断与解决**:一旦数据通过验证,系统会根据回调中的状态码来决定后续操作。以`MFCardV2Impl`为例,当`status``"9"`
时,系统会调用`service.SolvePaySuccess`;当`status``"8"`时,则调用`service.SolvePayFail``SolvePaySuccess`
函数在一个数据库事务中执行,确保了所有相关操作(更新订单、更新账户、插入动账记录等)的原子性。
4. **响应返回**:处理完成后,系统会向支付渠道返回一个响应。通常,处理成功时返回`"SUCCESS"`,失败时返回`"FAIL"`。这个响应告诉支付渠道网关是否成功接收并处理了通知。
4. **响应返回**:处理完成后,系统会向支付渠道返回一个响应。通常,处理成功时返回`"SUCCESS"`,失败时返回`"FAIL"`
。这个响应告诉支付渠道网关是否成功接收并处理了通知。
```mermaid
flowchart TD
@@ -100,12 +118,14 @@ M --> N[结束]
```
**Diagram sources**
- [mf178_v2.go](file://internal/service/supplier/third_party/mf178_v2.go#L187-L253)
- [apple.go](file://internal/service/supplier/third_party/apple.go#L202-L240)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L167-L223)
- [pay_solve.go](file://internal/service/pay_solve.go#L37-L254)
**Section sources**
- [mf178_v2.go](file://internal/service/supplier/third_party/mf178_v2.go#L187-L253)
- [apple.go](file://internal/service/supplier/third_party/apple.go#L202-L240)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L167-L223)
@@ -115,18 +135,22 @@ M --> N[结束]
尽管各个支付渠道的回调接口各不相同,但它们遵循一个高度统一的处理模式,同时在细节上存在必要的差异。
**统一处理模式**
所有`PayNotify`方法都遵循相同的高层逻辑:接收参数 -> 查找订单 -> 验证通道 -> 验证签名 -> 判断状态 -> 调用`SolvePaySuccess``SolvePayFail` -> 返回响应。这种模式确保了核心业务逻辑(订单状态更新)的集中化和一致性,避免了代码重复。
所有`PayNotify`方法都遵循相同的高层逻辑:接收参数 -> 查找订单 -> 验证通道 -> 验证签名 -> 判断状态 -> 调用
`SolvePaySuccess``SolvePayFail` -> 返回响应。这种模式确保了核心业务逻辑(订单状态更新)的集中化和一致性,避免了代码重复。
**渠道间差异**
差异主要体现在以下几个方面:
1. **URL路径**:每个渠道有唯一的回调路径,如`/mfcard/notifyV2``/appleCard/notify`
2. **参数名称**:不同渠道使用的参数名不同。例如,爱博使用`attach`作为订单号,而天猫游戏使用`merchantOrder`
3. **签名算法**:虽然都使用签名验证,但具体的算法和密钥来源可能不同。`MFCardV2Impl``roadInfo.Params`中获取`appSecret`,而`TMAllGameImpl`使用一个名为`TmpEncrypt`的通用加密函数
4. **状态码定义**:各渠道的状态码含义不同。爱博用`"9"`表示成功,`"8"`表示失败;天猫游戏用`"finished"`表示成功。
1. **URL路径**:每个渠道有唯一的回调路径,如`/mfcard/notifyV2``/appleCard/notify`
2. **参数名称**:不同渠道使用的参数名不同。例如,爱博使用`attach`作为订单号,而天猫游戏使用`merchantOrder`
3. **签名算法**:虽然都使用签名验证,但具体的算法和密钥来源可能不同。`MFCardV2Impl``roadInfo.Params`中获取`appSecret`,而
`TMAllGameImpl`使用一个名为`TmpEncrypt`的通用加密函数。
4. **状态码定义**:各渠道的状态码含义不同。爱博用`"9"`表示成功,`"8"`表示失败;天猫游戏用`"finished"`表示成功。
这些差异通过在各自的`PayNotify`方法中实现特定的解析和验证逻辑来处理,而核心的订单解决逻辑则保持不变。
**Section sources**
- [mf178_v2.go](file://internal/service/supplier/third_party/mf178_v2.go#L187-L253)
- [apple.go](file://internal/service/supplier/third_party/apple.go#L202-L240)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L167-L223)
@@ -135,18 +159,22 @@ M --> N[结束]
系统不仅处理来自支付渠道的回调,还负责向商户系统发送支付结果通知,并为此设计了完善的错误处理和重试机制,以确保最终一致性。
`SolvePaySuccess``SolvePayFail`被调用后,它们会异步触发`CreateOrderNotifyInfo`函数。该函数将商户的回调信息(包括订单号、金额、状态等)插入`notify_info`数据库表,并将订单号发送到名为`config.MqOrderNotify`的消息队列中。
`SolvePaySuccess``SolvePayFail`被调用后,它们会异步触发`CreateOrderNotifyInfo`函数。该函数将商户的回调信息(包括订单号、金额、状态等)插入
`notify_info`数据库表,并将订单号发送到名为`config.MqOrderNotify`的消息队列中。
`internal/service/notify/order_notify.go`文件中的`CreateOrderNotifyConsumer`函数是一个消息队列消费者,它监听该队列。每当收到一个订单号,它就会启动`SendOrderNotify`流程,向商户配置的`notifyUrl`发起HTTP GET请求。
`internal/service/notify/order_notify.go`文件中的`CreateOrderNotifyConsumer`函数是一个消息队列消费者,它监听该队列。每当收到一个订单号,它就会启动
`SendOrderNotify`流程,向商户配置的`notifyUrl`发起HTTP GET请求。
为了应对网络波动或商户服务器暂时不可用的情况,系统实现了指数退避重试策略:
- 第1次重试延迟1分钟
- 第2次重试延迟2分钟
- 第3次重试延迟5分钟
- 第4次重试延迟15分钟
- 第5次及以后延迟30分钟
这个策略由`GetOrderNotifyMinute`函数定义。如果在`consts.LimitTimes`通常为5次尝试后仍未能收到商户返回的`"SUCCESS"`响应,该订单的回调状态将被标记为`"fail"`,并停止重试。
- 第1次重试延迟1分钟
- 第2次重试延迟2分钟
- 第3次重试延迟5分钟
- 第4次重试延迟15分钟
- 第5次及以后延迟30分钟
这个策略由`GetOrderNotifyMinute`函数定义。如果在`consts.LimitTimes`通常为5次尝试后仍未能收到商户返回的`"SUCCESS"`
响应,该订单的回调状态将被标记为`"fail"`,并停止重试。
```mermaid
sequenceDiagram
@@ -169,13 +197,17 @@ end
```
**Diagram sources**
- [order_notify.go](file://internal/service/notify/order_notify.go#L0-L218)
- [pay_solve.go](file://internal/service/pay_solve.go#L148-L195)
**Section sources**
- [order_notify.go](file://internal/service/notify/order_notify.go#L0-L218)
- [pay_solve.go](file://internal/service/pay_solve.go#L148-L195)
## 结论
kami_gateway的回调API系统是一个设计精良、健壮可靠的支付结果处理机制。它通过统一的`PayNotify`接口模式和集中的`SolvePaySuccess`/`SolvePayFail`服务,有效地管理了多个第三方支付渠道的集成。系统在接收和处理支付渠道通知时,严格执行订单查找、通道验证和签名验证,确保了数据的安全性。同时,通过基于消息队列和指数退避的重试机制,系统能够可靠地将支付结果通知到商户端,即使在面对网络或服务故障时,也能保证最终一致性。这种分层、模块化的设计使得系统易于维护和扩展,为平台的稳定运行提供了坚实的基础。
kami_gateway的回调API系统是一个设计精良、健壮可靠的支付结果处理机制。它通过统一的`PayNotify`接口模式和集中的
`SolvePaySuccess`/`SolvePayFail`
服务,有效地管理了多个第三方支付渠道的集成。系统在接收和处理支付渠道通知时,严格执行订单查找、通道验证和签名验证,确保了数据的安全性。同时,通过基于消息队列和指数退避的重试机制,系统能够可靠地将支付结果通知到商户端,即使在面对网络或服务故障时,也能保证最终一致性。这种分层、模块化的设计使得系统易于维护和扩展,为平台的稳定运行提供了坚实的基础。

View File

@@ -13,6 +13,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [核心功能概述](#核心功能概述)
3. [API端点详解](#api端点详解)
@@ -25,25 +26,33 @@
10. [JSON请求/响应示例](#json请求响应示例)
## 简介
本文档详细说明了kami_gateway系统中与支付相关的两个核心API端点`/gateway/scan``/gateway/createOrder`。这两个接口负责处理商户发起的支付下单请求涵盖从参数验证、签名校验、订单生成到最终调用第三方支付渠道的完整流程。文档重点解析了请求参数结构、MD5签名机制、支付通道选择逻辑ChooseRoadV2、订单记录生成以及自有渠道与第三方渠道的区别处理方式。
本文档详细说明了kami_gateway系统中与支付相关的两个核心API端点`/gateway/scan``/gateway/createOrder`
。这两个接口负责处理商户发起的支付下单请求涵盖从参数验证、签名校验、订单生成到最终调用第三方支付渠道的完整流程。文档重点解析了请求参数结构、MD5签名机制、支付通道选择逻辑ChooseRoadV2、订单记录生成以及自有渠道与第三方渠道的区别处理方式。
**Section sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L1-L543)
## 核心功能概述
/gateway/scan 和 /gateway/createOrder 是kami_gateway处理支付请求的核心入口。它们共同实现了商户支付订单的创建与处理支持扫码支付场景。系统通过统一的参数验证、商户身份识别、支付通道选择和订单状态管理确保交易的安全性与可靠性。两个接口均采用基于MD5的签名验证机制来保证请求的完整性并通过灵活的支付通道配置支持多种第三方支付供应商。
/gateway/scan 和 /gateway/createOrder
是kami_gateway处理支付请求的核心入口。它们共同实现了商户支付订单的创建与处理支持扫码支付场景。系统通过统一的参数验证、商户身份识别、支付通道选择和订单状态管理确保交易的安全性与可靠性。两个接口均采用基于MD5的签名验证机制来保证请求的完整性并通过灵活的支付通道配置支持多种第三方支付供应商。
**Section sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L1-L543)
- [pay_service.go](file://internal/service/pay_service.go#L30-L74)
## API端点详解
### /gateway/scan 端点
该端点用于处理商户直接提交的扫码支付请求。它接收包含订单信息的HTTP GET请求执行完整的支付流程包括参数验证、签名校验、支付通道选择、订单生成及调用上游支付接口。
**请求方法**: GET
**主要参数**:
- `orderNo`: 商户订单号
- `productCode`: 产品编码
- `orderPrice`: 订单金额
@@ -55,11 +64,13 @@
- `deviceId`: 设备ID
### /gateway/createOrder 端点
该端点用于创建支付订单并返回支付链接payUrl。与/gateway/scan不同此接口不立即执行支付动作而是生成一个可后续访问的支付页面链接适用于需要跳转支付页面的场景。
**请求方法**: POST
**请求体格式**: JSON
**主要参数**:
- `payKey`: 商户密钥
- `orderNo`: 商户订单号
- `orderPrice`: 订单金额
@@ -70,10 +81,12 @@
- `sign`: 签名值
**Section sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L1-L543)
- [order.go](file://internal/schema/request/order.go#L1-L35)
## 请求流程分析
当商户调用支付API时系统执行以下关键步骤
1. **参数提取与基础验证**:从请求中提取所有参数并进行非空和格式校验。
@@ -99,31 +112,37 @@ I --> J[返回响应]
```
**Diagram sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L1-L543)
- [pay_service.go](file://internal/service/pay_service.go#L30-L74)
**Section sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L1-L543)
- [pay_service.go](file://internal/service/pay_service.go#L30-L74)
## 签名验证机制
系统采用基于MD5的签名验证机制来保障API请求的安全性。相关逻辑实现在`sign_verify.go`文件中,主要涉及以下函数:
- `GetMD5SignMF`: 生成MD5签名的核心函数对参数按字典序排序后拼接并附加商户密钥进行MD5计算。
- `Md5MFVerify`: 验证请求签名的函数,会排除`sign``deviceId``ip`字段后重新计算签名进行比对。
签名生成规则如下:
1. 将除`sign``deviceId``ip`外的所有请求参数按键名进行字典序排序。
2. 拼接所有参数为`key1value1key2value2...`格式。
3. 在末尾追加商户密钥(`paySecret`)。
4. 对最终字符串进行MD5哈希计算并转换为小写。
**Section sources**
- [sign_verify.go](file://internal/utils/sign_verify.go#L1-L104)
## 响应结构说明
### ScanSuccessData 成功响应
当支付请求成功处理时,返回`ScanSuccessData`结构:
```json
@@ -138,17 +157,18 @@ I --> J[返回响应]
}
```
| 字段 | 类型 | 描述 |
|------|------|------|
| orderNo | string | 系统生成的订单号 |
| sign | string | 响应签名 |
| orderPrice | string | 订单金额 |
| 字段 | 类型 | 描述 |
|------------|--------|----------------|
| orderNo | string | 系统生成的订单号 |
| sign | string | 响应签名 |
| orderPrice | string | 订单金额 |
| statusCode | string | 状态码 ("00"表示成功) |
| msg | string | 响应消息 |
| code | int | 业务状态码 (0表示成功) |
| payUrl | string | 支付链接(如有) |
| msg | string | 响应消息 |
| code | int | 业务状态码 (0表示成功) |
| payUrl | string | 支付链接(如有) |
### ScanFailData 失败响应
当支付请求处理失败时,返回`ScanFailData`结构:
```json
@@ -160,20 +180,23 @@ I --> J[返回响应]
}
```
| 字段 | 类型 | 描述 |
|------|------|------|
| payKey | string | 商户密钥 |
| 字段 | 类型 | 描述 |
|------------|--------|----------------|
| payKey | string | 商户密钥 |
| statusCode | string | 状态码 ("01"表示失败) |
| msg | string | 错误消息 |
| code | int | 业务状态码 (-1表示失败) |
| msg | string | 错误消息 |
| code | int | 业务状态码 (-1表示失败) |
**Section sources**
- [pay_resp.go](file://internal/schema/response/pay_resp.go#L1-L38)
## 错误处理与限制策略
系统实现了多层次的错误处理与安全限制机制:
### 订单重复提交限制
通过`sync.Map`类型的`orderSubmitLimiter`全局变量实现订单号提交频率限制。默认情况下同一订单号在2秒内只能提交一次防止恶意重放攻击。
```go
@@ -181,20 +204,25 @@ func isAllowed(orderNo string, intervalSec int64) bool
```
### IP限制机制
系统支持基于IP的访问控制可通过`backend.GetIPIsRestricted`函数检查特定IP是否被限制。虽然相关代码被注释但架构上已预留此功能接口。
### 其他验证规则
- **商户状态验证**:确保商户处于激活状态(`ACTIVE`
- **通道配置验证**:检查商户是否已配置对应支付通道
- **金额范围验证**:确保订单金额在通道允许的最小/最大限额内
- **产品编码验证**:确认产品编码对应的支付通道已配置
**Section sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L1-L543)
- [order_info.go](file://internal/models/order/order_info.go#L1-L437)
## 支付通道选择逻辑
`ChooseRoadV2`函数是支付通道选择的核心逻辑,其实现位于`pay_service.go`文件中。该函数根据商户请求中的`productCode`查找对应的支付通道配置,并进行一系列有效性验证:
`ChooseRoadV2`函数是支付通道选择的核心逻辑,其实现位于`pay_service.go`文件中。该函数根据商户请求中的`productCode`
查找对应的支付通道配置,并进行一系列有效性验证:
1. 解析订单金额
2. 根据`productCode`获取`roadInfo`
@@ -206,10 +234,12 @@ func isAllowed(orderNo string, intervalSec int64) bool
该函数确保只有符合所有业务规则的请求才能进入后续支付流程。
**Section sources**
- [pay_service.go](file://internal/service/pay_service.go#L30-L74)
- [road_info.go](file://internal/models/road/road_info.go#L1-L202)
## 第三方支付渠道调用
系统通过工厂模式管理第三方支付渠道,核心函数为`GetPaySupplierByCode`,定义在`third_party/init.go`中:
```go
@@ -228,12 +258,14 @@ func GetPaySupplierByCode(code string) supplier.PayInterface {
系统支持区分自有渠道和第三方渠道自有渠道可能直接返回支付链接而第三方渠道需通过API调用完成支付。
**Section sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L1-L543)
- [init.go](file://internal/service/supplier/third_party/init.go#L151-L153)
## JSON请求/响应示例
### /gateway/createOrder 请求示例
```json
{
"payKey": "merchant_123",
@@ -248,6 +280,7 @@ func GetPaySupplierByCode(code string) supplier.PayInterface {
```
### /gateway/createOrder 成功响应示例
```json
{
"code": 0,
@@ -265,6 +298,7 @@ func GetPaySupplierByCode(code string) supplier.PayInterface {
```
### /gateway/scan 失败响应示例
```json
{
"payKey": "merchant_123",
@@ -275,6 +309,7 @@ func GetPaySupplierByCode(code string) supplier.PayInterface {
```
**Section sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L1-L543)
- [order.go](file://internal/schema/request/order.go#L1-L35)
- [pay_resp.go](file://internal/schema/response/pay_resp.go#L1-L38)

View File

@@ -9,6 +9,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [商户查询接口](#商户查询接口)
3. [供应商订单查询接口](#供应商订单查询接口)
@@ -16,15 +17,18 @@
5. [响应格式](#响应格式)
## 简介
本文档详细说明了kami_gateway系统中的查询API包括商户查询、供应商订单查询和订单重新调度功能。文档涵盖了接口的认证机制、参数验证流程、业务逻辑处理以及各种情况下的响应格式。
## 商户查询接口
`/gateway/merchant/query`端点为下游商户提供订单查询服务。该接口通过appKey、timestamp和sign参数进行安全验证确保只有授权商户可以查询其订单信息。
接口首先验证所有必需参数是否提供然后通过appKey查找商户信息。认证机制使用两种签名验证方法`GetMD5SignMF``GetMD5Sign`,确保签名的正确性。验证通过后,系统查询订单信息并返回包含订单号、卡号、卡密、面值等敏感信息的响应。
接口首先验证所有必需参数是否提供然后通过appKey查找商户信息。认证机制使用两种签名验证方法`GetMD5SignMF``GetMD5Sign`
,确保签名的正确性。验证通过后,系统查询订单信息并返回包含订单号、卡号、卡密、面值等敏感信息的响应。
**Section sources**
- [order_controller.go](file://internal/controllers/order_controller.go#L140-L224)
- [sign_verify.go](file://internal/utils/sign_verify.go#L89-L102)
@@ -32,7 +36,8 @@
`/gateway/supplier/order/query`端点为上游供应商提供订单查询服务。该接口允许供应商通过bankOrderId查询订单状态和相关信息。
接口处理流程包括接收bankOrderId参数记录查询日志然后调用`SupplierOrderQueryResult`函数获取查询结果。该函数会从数据库中检索订单信息,并以字符串形式返回结果。
接口处理流程包括接收bankOrderId参数记录查询日志然后调用`SupplierOrderQueryResult`
函数获取查询结果。该函数会从数据库中检索订单信息,并以字符串形式返回结果。
```mermaid
sequenceDiagram
@@ -47,9 +52,11 @@ participant 查询服务 as query.SupplierOrderQueryResult
```
**Diagram sources**
- [order_controller.go](file://internal/controllers/order_controller.go#L30-L40)
**Section sources**
- [order_controller.go](file://internal/controllers/order_controller.go#L30-L40)
- [merchant_query.go](file://internal/schema/query/merchant_query.go#L28-L75)
@@ -62,6 +69,7 @@ participant 查询服务 as query.SupplierOrderQueryResult
3. 通道必须允许重新调度
调度执行流程:
1. 验证调度条件
2. 修改订单状态为"wait"
3. 调用供应商的Scan方法重新提交订单
@@ -84,14 +92,17 @@ Start([开始]) --> 验证订单状态
```
**Diagram sources**
- [order_controller.go](file://internal/controllers/order_controller.go#L42-L95)
**Section sources**
- [order_controller.go](file://internal/controllers/order_controller.go#L42-L95)
## 响应格式
### 成功响应示例
```json
{
"code": 0,
@@ -109,6 +120,7 @@ Start([开始]) --> 验证订单状态
```
### 错误响应示例
```json
{
"code": -1,
@@ -164,7 +176,9 @@ Resp[OrderQueryResp] --> OrderQueryResp : "包含"
```
**Diagram sources**
- [response.go](file://internal/models/response/response.go#L1-L28)
**Section sources**
- [response.go](file://internal/models/response/response.go#L1-L28)

View File

@@ -13,6 +13,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [开发环境设置](#开发环境设置)
@@ -23,9 +24,11 @@
8. [测试与覆盖率](#测试与覆盖率)
## 简介
本指南旨在为新加入的开发者提供一个全面的入门指导帮助您快速搭建本地开发环境并理解项目的基本架构。我们将详细介绍从克隆仓库到成功运行应用的完整流程包括环境配置、构建运行、Docker部署、调试技巧以及代码修改示例。
## 项目结构
本项目采用模块化设计,主要目录结构如下:
- `conf/`:配置文件目录,包含应用的主要配置文件 `app.conf`
@@ -53,15 +56,19 @@ J --> L[third_party]
```
**图示来源**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
**本节来源**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
## 开发环境设置
### Go版本要求
根据 `go.mod` 文件中的声明,本项目需要 Go 1.24.0 或更高版本:
```go
@@ -73,6 +80,7 @@ toolchain go1.24.6
建议使用 Go 1.24.6 工具链以确保兼容性。
### 依赖安装
使用 Go Modules 管理依赖,通过以下命令安装所有依赖:
```bash
@@ -80,6 +88,7 @@ go mod tidy
```
此命令将根据 `go.mod` 文件下载并整理所有必需的依赖包,包括:
- Beego Web框架
- OpenTelemetry追踪库
- Redis客户端
@@ -87,6 +96,7 @@ go mod tidy
- 各种工具库
### 数据库初始化
根据 `conf/app.conf` 文件中的配置,数据库连接信息如下:
```ini
@@ -100,16 +110,20 @@ debug = true
```
初始化步骤:
1. 确保MySQL服务正在运行
2. 创建名为 `kami` 的数据库
3. 导入项目所需的表结构和初始数据具体SQL文件未在项目中提供
**本节来源**
- [go.mod](file://go.mod#L2-L85)
- [conf/app.conf](file://conf/app.conf#L20-L25)
## 构建与运行
### 构建方法
项目提供了 `build.sh` 脚本用于构建可执行文件:
```bash
@@ -117,14 +131,17 @@ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
```
此脚本配置了以下构建参数:
- `CGO_ENABLED=0`禁用CGO使二进制文件静态链接
- `GOOS=linux`目标操作系统为Linux
- `GOARCH=amd64`目标架构为AMD64
### 运行方法
有两种方式可以运行应用:
#### 使用构建脚本
```bash
# 构建
./build.sh
@@ -133,11 +150,13 @@ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
```
#### 直接运行
```bash
go run main.go
```
### 应用初始化流程
`main.go` 文件中的 `main()` 函数定义了应用的初始化流程:
```mermaid
@@ -160,16 +179,20 @@ Web --> End([应用运行中])
```
**图示来源**
- [main.go](file://main.go#L23-L57)
**本节来源**
- [main.go](file://main.go#L1-L58)
- [build.sh](file://build.sh#L1-L2)
## Docker Compose 配置
项目提供了两个Docker Compose配置文件分别用于本地开发和生产部署。
### 本地开发配置
`deploy/docker-compose-local.yaml` 文件定义了本地开发环境:
```yaml
@@ -187,11 +210,13 @@ services:
```
特点:
- 使用 `latest` 标签的镜像
- 将容器的12309端口映射到主机的12309端口
- 连接到外部网络 `1panel-network`
### 生产部署配置
`deploy/docker-compose.yaml` 文件定义了生产环境:
```yaml
@@ -215,17 +240,21 @@ services:
```
特点:
- 使用 `$VERSION` 环境变量指定镜像版本
- 设置容器自动重启
- 仅允许本地回环地址访问服务端口
- 挂载外部配置和日志目录
**本节来源**
- [deploy/docker-compose.yaml](file://deploy/docker-compose.yaml#L1-L23)
- [deploy/docker-compose-local.yaml](file://deploy/docker-compose-local.yaml#L1-L17)
## 调试与性能分析
### pprof性能分析
项目已通过导入 `_ "net/http/pprof"` 包启用了pprof性能分析功能。可以通过以下URL访问pprof接口
- `http://localhost:12309/debug/pprof/`pprof主页
@@ -233,6 +262,7 @@ services:
- `http://localhost:12309/debug/pprof/heap`:内存堆分析
使用方法:
```bash
# 获取30秒的CPU性能分析数据
go tool pprof http://localhost:12309/debug/pprof/profile?seconds=30
@@ -242,6 +272,7 @@ go tool pprof http://localhost:12309/debug/pprof/heap
```
### 日志配置
根据 `conf/app.conf` 文件,日志配置如下:
```ini
@@ -255,11 +286,14 @@ maxdays=10
日志级别为7DEBUG级别日志文件保存在 `./logs/jhmerchant.log`最多保留10天。
**本节来源**
- [main.go](file://main.go#L10-L11)
- [conf/app.conf](file://conf/app.conf#L7-L15)
## 代码修改示例
### 添加健康检查API
以下是如何添加一个新的健康检查API的示例
1. **创建控制器方法**:在 `internal/controllers/base_controller.go` 中添加健康检查方法
@@ -286,11 +320,14 @@ web.Router("/health", &controllers.BaseController{}, "*:HealthCheck")
此示例展示了项目的基本代码结构控制器处理请求路由配置定义端点。通过这种方式您可以轻松添加新的API端点。
**本节来源**
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
- [main.go](file://main.go#L1-L58)
## 测试与覆盖率
### 运行测试套件
使用Go内置的测试工具运行所有测试
```bash
@@ -300,6 +337,7 @@ go test ./...
此命令将递归执行项目中所有包的测试文件(以 `_test.go` 结尾的文件)。
### 查看覆盖率报告
生成测试覆盖率报告:
```bash
@@ -316,7 +354,9 @@ go tool cover -html=coverage.out -o coverage.html
HTML报告将显示每行代码的执行情况绿色表示已覆盖红色表示未覆盖。
### 测试文件示例
项目中包含多个测试文件,如:
- `internal/dto/order_test.go`DTO层测试
- `internal/service/supplier/third_party/pool/worker_test.go`:第三方供应商服务测试
- `internal/cache/redis_test.go`Redis缓存测试
@@ -324,6 +364,7 @@ HTML报告将显示每行代码的执行情况绿色表示已覆盖红色
这些测试文件遵循Go测试惯例使用 `testing` 包和 `testify` 断言库。
**本节来源**
- [internal/dto/order_test.go](file://internal/dto/order_test.go#L1-L65)
- [internal/cache/redis_test.go](file://internal/cache/redis_test.go#L1-L2)
- [go.mod](file://go.mod#L70-L71)

View File

@@ -11,6 +11,7 @@
</cite>
## 目录
1. [介绍](#介绍)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
@@ -22,7 +23,9 @@
9. [结论](#结论)
## 介绍
本文档详细说明了 Beego v2 框架在 kami_gateway 项目中的集成与应用。重点阐述了 Web 服务的启动流程、路由注册机制、控制器继承结构、中间件集成方式、RESTful API 接口的注解路由映射、Beego ORM 的自动模型注册机制及其与 MySQL 数据库的交互模式。同时提供性能调优建议、常见陷阱规避策略以及框架升级兼容性注意事项。
本文档详细说明了 Beego v2 框架在 kami_gateway 项目中的集成与应用。重点阐述了 Web 服务的启动流程、路由注册机制、控制器继承结构、中间件集成方式、RESTful
API 接口的注解路由映射、Beego ORM 的自动模型注册机制及其与 MySQL 数据库的交互模式。同时提供性能调优建议、常见陷阱规避策略以及框架升级兼容性注意事项。
## 项目结构
@@ -56,18 +59,22 @@ M --> Z[init.go]
```
**图表来源**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
**章节来源**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
## 核心组件
kami_gateway 项目基于 Beego v2 框架构建,实现了网关服务的核心功能,包括订单处理、支付回调、商户查询等。系统通过 Beego 的 MVC 架构组织代码利用其强大的路由、控制器、ORM 和中间件功能,构建了一个高并发、可扩展的支付网关系统。
kami_gateway 项目基于 Beego v2 框架构建,实现了网关服务的核心功能,包括订单处理、支付回调、商户查询等。系统通过 Beego 的 MVC
架构组织代码利用其强大的路由、控制器、ORM 和中间件功能,构建了一个高并发、可扩展的支付网关系统。
**章节来源**
- [main.go](file://main.go#L1-L58)
- [internal/controllers/base_controller.go](file://internal/controllers/base_controller.go#L1-L9)
- [internal/models/init.go](file://internal/models/init.go#L1-L56)
@@ -92,6 +99,7 @@ Middleware --> Logging[日志记录]
```
**图表来源**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
- [internal/controllers/base_controller.go](file://internal/controllers/base_controller.go#L1-L9)
@@ -100,7 +108,8 @@ Middleware --> Logging[日志记录]
### Web 服务启动流程
Web 服务的启动通过 `web.Run()` 方法实现。在 `main.go` 文件中,程序首先进行各种初始化操作,包括配置加载、代理池初始化、分布式追踪初始化、消息队列消费者启动、缓存服务启动等,最后调用 `web.Run()` 启动 Web 服务器。
Web 服务的启动通过 `web.Run()` 方法实现。在 `main.go` 文件中,程序首先进行各种初始化操作,包括配置加载、代理池初始化、分布式追踪初始化、消息队列消费者启动、缓存服务启动等,最后调用
`web.Run()` 启动 Web 服务器。
```mermaid
sequenceDiagram
@@ -122,14 +131,17 @@ Web-->>Main : 服务启动成功
```
**图表来源**
- [main.go](file://main.go#L1-L58)
**章节来源**
- [main.go](file://main.go#L1-L58)
### 路由注册机制
路由注册在 `internal/routers/router.go` 文件的 `init()` 函数中完成。系统使用 `web.Router()` 方法将不同的 URL 路径映射到相应的控制器和方法。同时,通过 `web.InsertFilterChain()` 注册了全局中间件,用于处理分布式追踪。
路由注册在 `internal/routers/router.go` 文件的 `init()` 函数中完成。系统使用 `web.Router()` 方法将不同的 URL
路径映射到相应的控制器和方法。同时,通过 `web.InsertFilterChain()` 注册了全局中间件,用于处理分布式追踪。
```mermaid
flowchart TD
@@ -155,14 +167,17 @@ O --> T["/walMart/notify -> WalMartImpl.PayNotify"]
```
**图表来源**
- [internal/routers/router.go](file://internal/routers/router.go#L12-L74)
**章节来源**
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
### 控制器继承结构
控制器采用继承结构,所有控制器都继承自 `BaseGateway` 结构体,而 `BaseGateway` 又继承自 Beego 的 `web.Controller`。这种设计实现了代码复用和统一的基类功能。
控制器采用继承结构,所有控制器都继承自 `BaseGateway` 结构体,而 `BaseGateway` 又继承自 Beego 的 `web.Controller`
。这种设计实现了代码复用和统一的基类功能。
```mermaid
classDiagram
@@ -205,18 +220,21 @@ BaseGateway <|-- PayForGateway
```
**图表来源**
- [internal/controllers/base_controller.go](file://internal/controllers/base_controller.go#L6-L8)
- [internal/controllers/scan_controller.go](file://internal/controllers/scan_controller.go#L64-L66)
- [internal/controllers/order_controller.go](file://internal/controllers/order_controller.go#L26-L28)
**章节来源**
- [internal/controllers/base_controller.go](file://internal/controllers/base_controller.go#L1-L9)
- [internal/controllers/scan_controller.go](file://internal/controllers/scan_controller.go#L1-L542)
- [internal/controllers/order_controller.go](file://internal/controllers/order_controller.go#L1-L225)
### 中间件集成方式
系统通过 `web.InsertFilterChain()` 方法集成全局中间件。在 `router.go` 文件中,所有请求路径 (`*`) 都会经过 `otelTrace.Middleware` 中间件处理,实现分布式追踪功能。
系统通过 `web.InsertFilterChain()` 方法集成全局中间件。在 `router.go` 文件中,所有请求路径 (`*`) 都会经过
`otelTrace.Middleware` 中间件处理,实现分布式追踪功能。
```mermaid
sequenceDiagram
@@ -233,9 +251,11 @@ Beego->>Client : HTTP响应
```
**图表来源**
- [internal/routers/router.go](file://internal/routers/router.go#L12-L14)
**章节来源**
- [internal/routers/router.go](file://internal/routers/router.go#L12-L74)
### RESTful API 接口映射
@@ -267,15 +287,18 @@ end
```
**图表来源**
- [internal/routers/router.go](file://internal/routers/router.go#L18-L21)
- [internal/controllers/scan_controller.go](file://internal/controllers/scan_controller.go#L69-L541)
**章节来源**
- [internal/controllers/scan_controller.go](file://internal/controllers/scan_controller.go#L69-L541)
### Beego ORM 自动模型注册
Beego ORM 的自动模型注册在 `internal/models/init.go` 文件的 `init()` 函数中完成。系统通过 `orm.RegisterModel()` 方法注册所有数据模型,实现数据库表的自动映射和管理。
Beego ORM 的自动模型注册在 `internal/models/init.go` 文件的 `init()` 函数中完成。系统通过 `orm.RegisterModel()`
方法注册所有数据模型,实现数据库表的自动映射和管理。
```mermaid
classDiagram
@@ -342,12 +365,14 @@ models --> OrderInfo : 注册模型
```
**图表来源**
- [internal/models/init.go](file://internal/models/init.go#L1-L56)
- [internal/models/accounts/account.go](file://internal/models/accounts/account.go#L1-L114)
- [internal/models/merchant/merchant_info.go](file://internal/models/merchant/merchant_info.go#L1-L208)
- [internal/models/order/order_info.go](file://internal/models/order/order_info.go#L1-L436)
**章节来源**
- [internal/models/init.go](file://internal/models/init.go#L1-L56)
## 依赖分析
@@ -377,10 +402,12 @@ otelTrace --> OTel
```
**图表来源**
- [main.go](file://main.go#L1-L58)
- [go.mod](file://go.mod)
**章节来源**
- [main.go](file://main.go#L1-L58)
## 性能考虑
@@ -392,6 +419,7 @@ otelTrace --> OTel
5. **异步处理**:将耗时操作(如回调通知、消息发送)放入协程池异步执行,避免阻塞主流程。
**章节来源**
- [internal/service/pay_solve.go](file://internal/service/pay_solve.go#L1-L580)
- [internal/controllers/scan_controller.go](file://internal/controllers/scan_controller.go#L1-L542)
@@ -400,14 +428,17 @@ otelTrace --> OTel
1. **服务无法启动**:检查 `main.go` 中的初始化顺序,确保数据库连接、缓存服务等依赖项正常启动。
2. **路由不生效**:确认 `routers/router.go` 文件被正确导入(`_ "gateway/internal/routers"`),且路由路径和控制器方法名正确。
3. **数据库连接失败**:检查 `models/init.go` 中的数据库配置是否正确,确保 MySQL 服务正常运行。
4. **ORM 模型未注册**:确认 `models/init.go` 文件被正确导入(`_ "gateway/internal/models"`),且所有模型都已通过 `RegisterModel` 注册。
4. **ORM 模型未注册**:确认 `models/init.go` 文件被正确导入(`_ "gateway/internal/models"`),且所有模型都已通过
`RegisterModel` 注册。
5. **中间件不生效**:检查 `InsertFilterChain` 的调用时机,确保在路由注册之前完成中间件注册。
**章节来源**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
- [internal/models/init.go](file://internal/models/init.go#L1-L56)
## 结论
kami_gateway 项目成功集成了 Beego v2 框架构建了一个功能完整、性能优良的支付网关系统。通过合理的架构设计和代码组织系统实现了高内聚、低耦合的模块化结构。Beego 框架的路由、控制器、ORM 和中间件功能为系统开发提供了强大支持,使得开发效率和代码质量得到显著提升。未来可进一步优化异步处理机制,增强系统的可扩展性和容错能力。
kami_gateway 项目成功集成了 Beego v2 框架构建了一个功能完整、性能优良的支付网关系统。通过合理的架构设计和代码组织系统实现了高内聚、低耦合的模块化结构。Beego
框架的路由、控制器、ORM 和中间件功能为系统开发提供了强大支持,使得开发效率和代码质量得到显著提升。未来可进一步优化异步处理机制,增强系统的可扩展性和容错能力。

View File

@@ -11,6 +11,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [核心组件](#核心组件)
3. [熔断器保护机制](#熔断器保护机制)
@@ -21,7 +22,10 @@
8. [结论](#结论)
## 简介
本文档详细说明了 OpenTelemetry 中间件在 Beego 框架中的实现机制,重点分析了全链路追踪功能的实现方式。文档涵盖了中间件如何作为 Beego 的 FilterFunc 注入请求处理链,如何从 HeaderCarrier 提取上游 trace context 并创建新的 Server Span以及熔断器保护机制如何确保在追踪系统异常时不影响核心业务流程。同时文档还分析了中间件的性能影响和优化策略。
本文档详细说明了 OpenTelemetry 中间件在 Beego 框架中的实现机制,重点分析了全链路追踪功能的实现方式。文档涵盖了中间件如何作为
Beego 的 FilterFunc 注入请求处理链,如何从 HeaderCarrier 提取上游 trace context 并创建新的 Server
Span以及熔断器保护机制如何确保在追踪系统异常时不影响核心业务流程。同时文档还分析了中间件的性能影响和优化策略。
## 核心组件
@@ -55,11 +59,13 @@ CircuitBreaker --> CircuitBreakerState : "包含"
```
**Diagram sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L21-L140)
- [circuit_breaker.go](file://internal/otelTrace/circuit_breaker.go#L5-L11)
- [consts.go](file://internal/otelTrace/consts.go#L50-L55)
**Section sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
- [circuit_breaker.go](file://internal/otelTrace/circuit_breaker.go#L1-L51)
- [consts.go](file://internal/otelTrace/consts.go#L1-L56)
@@ -91,16 +97,19 @@ end note
```
**Diagram sources**
- [circuit_breaker.go](file://internal/otelTrace/circuit_breaker.go#L5-L50)
- [consts.go](file://internal/otelTrace/consts.go#L50-L55)
**Section sources**
- [circuit_breaker.go](file://internal/otelTrace/circuit_breaker.go#L1-L51)
- [consts.go](file://internal/otelTrace/consts.go#L50-L55)
## 中间件执行流程
OpenTelemetry 中间件作为 Beego 框架的 FilterFunc 被注入到请求处理链中,实现了全链路追踪功能。中间件在请求开始时从 HeaderCarrier 提取上游 trace context并创建新的 Server Span。
OpenTelemetry 中间件作为 Beego 框架的 FilterFunc 被注入到请求处理链中,实现了全链路追踪功能。中间件在请求开始时从
HeaderCarrier 提取上游 trace context并创建新的 Server Span。
```mermaid
sequenceDiagram
@@ -142,10 +151,12 @@ Middleware->>Client : 返回响应
```
**Diagram sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L21-L140)
- [utils.go](file://internal/otelTrace/utils.go#L60-L75)
**Section sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L21-L140)
- [utils.go](file://internal/otelTrace/utils.go#L60-L75)
@@ -181,10 +192,12 @@ EndSpan --> End([请求结束])
```
**Diagram sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L21-L140)
- [utils.go](file://internal/otelTrace/utils.go#L80-L90)
**Section sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L21-L140)
- [utils.go](file://internal/otelTrace/utils.go#L80-L90)
@@ -221,10 +234,12 @@ StartMonitor --> monitorExporterHealth["monitorExporterHealth()"]
```
**Diagram sources**
- [init.go](file://internal/otelTrace/init.go#L29-L205)
- [consts.go](file://internal/otelTrace/consts.go#L10-L45)
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L29-L205)
- [consts.go](file://internal/otelTrace/consts.go#L10-L45)
@@ -252,6 +267,7 @@ style simple.go fill:#9ff,stroke:#333
```
**Diagram sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
- [circuit_breaker.go](file://internal/otelTrace/circuit_breaker.go#L1-L51)
- [consts.go](file://internal/otelTrace/consts.go#L1-L56)
@@ -260,6 +276,7 @@ style simple.go fill:#9ff,stroke:#333
- [simple.go](file://internal/otelTrace/simple.go#L1-L93)
**Section sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
- [circuit_breaker.go](file://internal/otelTrace/circuit_breaker.go#L1-L51)
- [consts.go](file://internal/otelTrace/consts.go#L1-L56)
@@ -269,8 +286,12 @@ style simple.go fill:#9ff,stroke:#333
## 结论
OpenTelemetry 中间件在 Beego 框架中的实现充分考虑了生产环境的需求,通过熔断器保护机制确保了系统的稳定性。中间件能够有效地集成到 Beego 的请求处理链中,实现全链路追踪功能。通过从 HeaderCarrier 提取上游 trace context 并创建新的 Server Span实现了跨服务的追踪链路。中间件添加了丰富的监控指标包括 HTTP 方法、URL、状态码、响应大小、持续时间以及客户端 IP 等,为系统监控和问题排查提供了有力支持。
OpenTelemetry 中间件在 Beego 框架中的实现充分考虑了生产环境的需求,通过熔断器保护机制确保了系统的稳定性。中间件能够有效地集成到
Beego 的请求处理链中,实现全链路追踪功能。通过从 HeaderCarrier 提取上游 trace context 并创建新的 Server
Span实现了跨服务的追踪链路。中间件添加了丰富的监控指标包括 HTTP 方法、URL、状态码、响应大小、持续时间以及客户端 IP
等,为系统监控和问题排查提供了有力支持。
熔断器机制在追踪系统异常时能够捕获错误并降级确保不影响核心业务流程。defer 函数中实现了 panic 的记录和优雅降级返回 500 错误,同时确保 Span 能够正确结束。系统初始化过程配置了生产环境优化的参数,包括网络超时、重试机制、批量处理等,有效降低了中间件对系统性能的影响。
熔断器机制在追踪系统异常时能够捕获错误并降级确保不影响核心业务流程。defer 函数中实现了 panic 的记录和优雅降级返回 500
错误,同时确保 Span 能够正确结束。系统初始化过程配置了生产环境优化的参数,包括网络超时、重试机制、批量处理等,有效降低了中间件对系统性能的影响。
总体而言,该中间件设计合理,功能完善,能够在保证系统稳定性的同时提供全面的监控能力,是生产环境中理想的分布式追踪解决方案。

View File

@@ -12,6 +12,7 @@
</cite>
## 目录
1. [OpenTelemetry 初始化机制](#opentelemetry-初始化机制)
2. [全局上下文与Span生命周期管理](#全局上下文与span生命周期管理)
3. [日志与追踪上下文关联](#日志与追踪上下文关联)
@@ -22,9 +23,12 @@
## OpenTelemetry 初始化机制
`otelTrace.InitTracer()` 函数是 OpenTelemetry 在 kami_gateway 中的核心初始化入口,负责配置和启动追踪、指标和日志的导出功能。该函数在 `main.go``main()` 函数中被调用,是整个分布式追踪系统的起点。
`otelTrace.InitTracer()` 函数是 OpenTelemetry 在 kami_gateway 中的核心初始化入口,负责配置和启动追踪、指标和日志的导出功能。该函数在
`main.go``main()` 函数中被调用,是整个分布式追踪系统的起点。
初始化过程首先创建 OTLP (OpenTelemetry Protocol) 的 gRPC 客户端用于将追踪数据发送到指定的收集器collector。在 `internal/otelTrace/init.go` 文件中,通过 `otlptracegrpc.NewClient` 配置了生产环境优化的网络参数,包括 5 秒的超时时间、启用重试机制(初始间隔 1 秒,最大间隔 10 秒)以及使用 gzip 压缩来减少网络传输量。
初始化过程首先创建 OTLP (OpenTelemetry Protocol) 的 gRPC 客户端用于将追踪数据发送到指定的收集器collector。在
`internal/otelTrace/init.go` 文件中,通过 `otlptracegrpc.NewClient` 配置了生产环境优化的网络参数,包括 5
秒的超时时间、启用重试机制(初始间隔 1 秒,最大间隔 10 秒)以及使用 gzip 压缩来减少网络传输量。
```mermaid
sequenceDiagram
@@ -44,20 +48,26 @@ InitTracer-->>main : 返回清理函数
```
**Diagram sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L29-L205)
- [main.go](file://main.go#L1-L58)
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L29-L205)
- [main.go](file://main.go#L1-L58)
## 全局上下文与Span生命周期管理
kami_gateway 使用 `InitCtx` 作为全局的上下文Context它在 `internal/otelTrace/consts.go` 文件中被定义为 `context.Background()`。这个上下文贯穿整个应用的生命周期,用于传递追踪信息和控制取消信号。
kami_gateway 使用 `InitCtx` 作为全局的上下文Context它在 `internal/otelTrace/consts.go` 文件中被定义为
`context.Background()`。这个上下文贯穿整个应用的生命周期,用于传递追踪信息和控制取消信号。
Span 的生命周期管理在 `internal/otelTrace/span.go` 文件中实现。`Span()` 函数是创建新 Span 的主要方法,它接受一个父上下文、追踪名称和 Span 名称,并返回一个新的上下文和 Span 对象。该函数内部使用 `otel.Tracer(traceName).Start()` 来启动一个 Span并自动添加了通用属性`tracer.name``span.kind`,以优化指标收集。
Span 的生命周期管理在 `internal/otelTrace/span.go` 文件中实现。`Span()` 函数是创建新 Span 的主要方法,它接受一个父上下文、追踪名称和
Span 名称,并返回一个新的上下文和 Span 对象。该函数内部使用 `otel.Tracer(traceName).Start()` 来启动一个 Span并自动添加了通用属性
`tracer.name``span.kind`,以优化指标收集。
对于异步操作,系统提供了 `CreateLinkContext``CreateAsyncContext` 等函数。这些函数创建的上下文与父 Span 保持关联(通过 `trace.WithLinks`),但不继承父上下文的取消信号。这确保了异步任务(如后台处理)可以独立完成,不会因为父请求的取消而中断,同时又能将追踪信息链接到原始请求链路中。
对于异步操作,系统提供了 `CreateLinkContext``CreateAsyncContext` 等函数。这些函数创建的上下文与父 Span 保持关联(通过
`trace.WithLinks`),但不继承父上下文的取消信号。这确保了异步任务(如后台处理)可以独立完成,不会因为父请求的取消而中断,同时又能将追踪信息链接到原始请求链路中。
```mermaid
classDiagram
@@ -82,17 +92,22 @@ SpanManager --> Span : 创建和管理
```
**Diagram sources**
- [internal/otelTrace/span.go](file://internal/otelTrace/span.go#L1-L151)
**Section sources**
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L11-L11)
- [internal/otelTrace/span.go](file://internal/otelTrace/span.go#L1-L151)
## 日志与追踪上下文关联
为了实现日志与追踪的上下文关联kami_gateway 实现了一个自定义的 `CustomLogger` 结构体。该结构体在 `internal/otelTrace/logs.go` 文件中定义,包装了 `zap.Logger`,并提供了一个 `WithContext(ctx context.Context)` 方法。
为了实现日志与追踪的上下文关联kami_gateway 实现了一个自定义的 `CustomLogger` 结构体。该结构体在
`internal/otelTrace/logs.go` 文件中定义,包装了 `zap.Logger`,并提供了一个 `WithContext(ctx context.Context)` 方法。
当调用 `WithContext` 方法时,它会检查传入的上下文。如果上下文有效且包含一个有效的 Span则会将该上下文作为 `zap.Reflect("ctx", ctx)` 添加到日志记录器中。这样,当一条日志被记录时,它就携带了当前的追踪上下文(包括 Trace ID 和 Span ID使得在日志系统中可以轻松地通过 Trace ID 关联到完整的调用链路。
当调用 `WithContext` 方法时,它会检查传入的上下文。如果上下文有效且包含一个有效的 Span则会将该上下文作为
`zap.Reflect("ctx", ctx)` 添加到日志记录器中。这样,当一条日志被记录时,它就携带了当前的追踪上下文(包括 Trace ID 和 Span
ID使得在日志系统中可以轻松地通过 Trace ID 关联到完整的调用链路。
```mermaid
flowchart TD
@@ -108,16 +123,22 @@ G --> H[日志包含 Trace ID/Span ID]
```
**Diagram sources**
- [internal/otelTrace/logs.go](file://internal/otelTrace/logs.go#L1-L29)
**Section sources**
- [internal/otelTrace/logs.go](file://internal/otelTrace/logs.go#L1-L29)
## Beego HTTP服务集成
OpenTelemetry 通过中间件Middleware的方式集成到 Beego 的 HTTP 服务中。`internal/otelTrace/middleware.go` 文件中的 `Middleware` 函数是一个 Beego 过滤器,它在每个 HTTP 请求处理前被调用。
OpenTelemetry 通过中间件Middleware的方式集成到 Beego 的 HTTP 服务中。`internal/otelTrace/middleware.go` 文件中的
`Middleware` 函数是一个 Beego 过滤器,它在每个 HTTP 请求处理前被调用。
该中间件首先从 HTTP 请求头中提取上游服务传递的追踪上下文(如 `traceparent` 头),确保了分布式调用链路的连续性。然后,它使用 `otel.Tracer("gateway-router").Start()` 创建一个新的服务器端 Span并设置了一系列关键属性包括 HTTP 方法、URL、状态码、客户端 IP 等。为了保护业务逻辑不受追踪系统故障的影响中间件还集成了一个熔断器Circuit Breaker当追踪导出器连续失败达到阈值时会自动开启熔断绕过追踪逻辑确保业务请求的正常处理。
该中间件首先从 HTTP 请求头中提取上游服务传递的追踪上下文(如 `traceparent` 头),确保了分布式调用链路的连续性。然后,它使用
`otel.Tracer("gateway-router").Start()` 创建一个新的服务器端 Span并设置了一系列关键属性包括 HTTP 方法、URL、状态码、客户端
IP 等。为了保护业务逻辑不受追踪系统故障的影响中间件还集成了一个熔断器Circuit
Breaker当追踪导出器连续失败达到阈值时会自动开启熔断绕过追踪逻辑确保业务请求的正常处理。
```mermaid
sequenceDiagram
@@ -138,39 +159,49 @@ Beego-->>Client : 返回HTTP响应
```
**Diagram sources**
- [internal/otelTrace/middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
**Section sources**
- [internal/otelTrace/middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
## 资源释放与清理机制
`main.go` 文件中,`otelTrace.InitTracer()` 的返回值被赋值给 `cleanup1`, `cleanup2`, `cleanup3` 三个函数。这些函数分别对应追踪、指标和日志导出器的 `Shutdown` 方法。
`main.go` 文件中,`otelTrace.InitTracer()` 的返回值被赋值给 `cleanup1`, `cleanup2`, `cleanup3`
三个函数。这些函数分别对应追踪、指标和日志导出器的 `Shutdown` 方法。
通过 `defer` 语句,这些清理函数被注册为程序退出时执行。当 `main()` 函数执行完毕或程序因异常退出时,`defer` 会确保这些清理函数被调用。`Shutdown` 方法会优雅地关闭导出器,将内存中尚未导出的缓冲数据(如队列中的 Span刷新到后端收集器然后断开与收集器的连接。这种机制保证了在程序终止前所有已生成的追踪数据都能被完整地发送出去避免了数据丢失。
通过 `defer` 语句,这些清理函数被注册为程序退出时执行。当 `main()` 函数执行完毕或程序因异常退出时,`defer` 会确保这些清理函数被调用。
`Shutdown` 方法会优雅地关闭导出器,将内存中尚未导出的缓冲数据(如队列中的
Span刷新到后端收集器然后断开与收集器的连接。这种机制保证了在程序终止前所有已生成的追踪数据都能被完整地发送出去避免了数据丢失。
**Section sources**
- [main.go](file://main.go#L1-L58)
## OTLP导出与采样策略
kami_gateway 使用 OTLP/gRPC 协议将数据导出到位于 `38.38.251.113:31547` 的收集器。为了优化性能和资源使用,系统配置了多项生产环境策略:
* **批量导出 (Batch Export):** 使用 `sdktrace.NewBatchSpanProcessor` 将多个 Span 批量发送。配置了 5 秒的批量发送间隔 (`BatchTimeout`) 和 512 条的最大批量大小 (`MaxExportBatchSize`),平衡了实时性和网络开销。
* **采样策略 (Sampling):** 采用基于 Trace ID 比例的采样策略 (`traceIDRatioBased`),默认采样率为 10% (`DefaultSamplingRatio`)。这在保证可观测性的同时,有效控制了高负载下的数据量和系统开销。
* **熔断保护 (Circuit Breaker):**`internal/otelTrace/circuit_breaker.go` 中实现了一个简单的熔断器。当导出失败次数达到阈值默认5次熔断器会开启暂时停止导出操作防止因后端收集器故障导致应用资源耗尽。
* **资源限制:** 配置了最大队列大小 (`MaxQueueSize=2048`) 和最大并发导出数,防止内存溢出
* **批量导出 (Batch Export):** 使用 `sdktrace.NewBatchSpanProcessor` 将多个 Span 批量发送。配置了 5 秒的批量发送间隔 (
`BatchTimeout`) 和 512 条的最大批量大小 (`MaxExportBatchSize`),平衡了实时性和网络开销。
* **采样策略 (Sampling):** 采用基于 Trace ID 比例的采样策略 (`traceIDRatioBased`),默认采样率为 10% (
`DefaultSamplingRatio`)。这在保证可观测性的同时,有效控制了高负载下的数据量和系统开销
* **熔断保护 (Circuit Breaker):** 在 `internal/otelTrace/circuit_breaker.go`
中实现了一个简单的熔断器。当导出失败次数达到阈值默认5次熔断器会开启暂时停止导出操作防止因后端收集器故障导致应用资源耗尽。
* **资源限制:** 配置了最大队列大小 (`MaxQueueSize=2048`) 和最大并发导出数,防止内存溢出。
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L29-L205)
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L1-L55)
- [internal/otelTrace/circuit_breaker.go](file://internal/otelTrace/circuit_breaker.go#L1-L50)
## 链路追踪调试最佳实践
1. **检查初始化:** 确保 `otelTrace.InitTracer()``main()` 函数早期被成功调用,并且返回的清理函数被正确注册。
2. **验证中间件:** 确认 Beego 的路由配置中已注册 `otelTrace.Middleware`,否则 HTTP 请求将无法被追踪。
3. **分析日志:** 利用 `CustomLogger.WithContext()` 记录的日志,通过 `ctx` 字段中的 Trace ID 在日志系统中搜索,可以快速定位到与特定请求相关的所有日志。
4. **监控导出器健康:** 关注 `monitorExporterHealth` goroutine 的输出,它会监控导出器的健康状态。如果发现大量导出失败,应检查网络连接和收集器状态。
5. **利用熔断器:** 在生产环境中,熔断器是保护系统稳定的关键。如果发现追踪数据突然中断,应首先检查熔断器是否已开启,并排查后端收集器的问题。
6. **调整采样率:** 在调试阶段,可以临时提高采样率以获取更完整的数据;在生产环境稳定后,应恢复到较低的采样率以控制成本。
1. **检查初始化:** 确保 `otelTrace.InitTracer()``main()` 函数早期被成功调用,并且返回的清理函数被正确注册。
2. **验证中间件:** 确认 Beego 的路由配置中已注册 `otelTrace.Middleware`,否则 HTTP 请求将无法被追踪。
3. **分析日志:** 利用 `CustomLogger.WithContext()` 记录的日志,通过 `ctx` 字段中的 Trace ID 在日志系统中搜索,可以快速定位到与特定请求相关的所有日志。
4. **监控导出器健康:** 关注 `monitorExporterHealth` goroutine 的输出,它会监控导出器的健康状态。如果发现大量导出失败,应检查网络连接和收集器状态。
5. **利用熔断器:** 在生产环境中,熔断器是保护系统稳定的关键。如果发现追踪数据突然中断,应首先检查熔断器是否已开启,并排查后端收集器的问题。
6. **调整采样率:** 在调试阶段,可以临时提高采样率以获取更完整的数据;在生产环境稳定后,应恢复到较低的采样率以控制成本。

View File

@@ -13,6 +13,7 @@
# Span生命周期管理
## Table of Contents
1. [引言](#引言)
2. [核心Span管理函数](#核心span管理函数)
3. [异步与可追踪上下文](#异步与可追踪上下文)
@@ -22,9 +23,12 @@
## 引言
`kami_gateway` 系统中OpenTelemetry (OTel) 被用于实现全面的分布式追踪。`internal/otelTrace` 包提供了对 OTel API 的封装和扩展,旨在简化开发者在创建、关联和管理追踪 Span 时的操作。本文档将深入解析该包中关于 Span 生命周期管理的核心机制,包括 `Span()` 辅助函数、`CreateAsyncContext()``CreateTraceableContext()` 等关键函数的设计意图与实现细节,并结合订单处理、渠道调用等业务代码,展示其在实际场景中的应用模式。
`kami_gateway` 系统中OpenTelemetry (OTel) 被用于实现全面的分布式追踪。`internal/otelTrace` 包提供了对 OTel API
的封装和扩展,旨在简化开发者在创建、关联和管理追踪 Span 时的操作。本文档将深入解析该包中关于 Span 生命周期管理的核心机制,包括
`Span()` 辅助函数、`CreateAsyncContext()``CreateTraceableContext()` 等关键函数的设计意图与实现细节,并结合订单处理、渠道调用等业务代码,展示其在实际场景中的应用模式。
**Section sources**
- [span.go](file://internal/otelTrace/span.go#L13-L129)
## 核心Span管理函数
@@ -33,22 +37,32 @@
`Span()` 函数是创建追踪 Span 的核心入口,它封装了 `otel.Tracer().Start()` 的调用,并自动注入了通用的追踪属性,极大地简化了开发者的使用。
该函数通过 `trace.WithAttributes()` 选项,为所有创建的 Span 统一添加了 `tracer.name``span.kind` 两个关键属性。其中,`tracer.name` 的值由调用者传入,用于标识追踪的来源;`span.kind` 固定为 `"internal"`,表明这是一个内部处理的 Span。这种设计确保了追踪数据的一致性和可读性便于在监控系统中进行聚合分析。
该函数通过 `trace.WithAttributes()` 选项,为所有创建的 Span 统一添加了 `tracer.name``span.kind` 两个关键属性。其中,
`tracer.name` 的值由调用者传入,用于标识追踪的来源;`span.kind` 固定为 `"internal"`,表明这是一个内部处理的
Span。这种设计确保了追踪数据的一致性和可读性便于在监控系统中进行聚合分析。
此外,函数还包含了一个安全检查,当传入的 `ctx``nil` 时,会自动创建一个 `context.Background()`,以避免程序因空上下文而 panic。
此外,函数还包含了一个安全检查,当传入的 `ctx``nil` 时,会自动创建一个 `context.Background()`,以避免程序因空上下文而
panic。
**Section sources**
- [span.go](file://internal/otelTrace/span.go#L13-L30)
## 异步与可追踪上下文
### CreateLinkContext() 与关联关系
`CreateLinkContext()` 函数的设计核心在于创建一种“关联关系”Linked Relationship而非传统的“父子关系”Parent-Child Relationship。在标准的上下文传播中父 Context 的取消会通过 `context.WithCancel` 等机制传播到所有子 Context导致所有关联的 Goroutine 被强制终止。
`CreateLinkContext()` 函数的设计核心在于创建一种“关联关系”Linked Relationship而非传统的“父子关系”Parent-Child
Relationship。在标准的上下文传播中父 Context 的取消会通过 `context.WithCancel` 等机制传播到所有子 Context导致所有关联的
Goroutine 被强制终止。
`CreateLinkContext()` 通过 `trace.ContextWithSpanContext(context.Background(), parentSpanCtx)` 创建了一个新的、独立于父 Context 生命周期的上下文。它使用 `context.Background()` 作为新上下文的根,从而切断了取消信号的传播链。同时,它通过 `trace.WithLinks()` 选项,将新创建的 Span 与父 Span 的 `SpanContext` 进行链接。这使得在追踪系统中,我们仍然可以看到这两个 Span 之间的关联,但它们的执行是独立的。
`CreateLinkContext()` 通过 `trace.ContextWithSpanContext(context.Background(), parentSpanCtx)` 创建了一个新的、独立于父
Context 生命周期的上下文。它使用 `context.Background()` 作为新上下文的根,从而切断了取消信号的传播链。同时,它通过
`trace.WithLinks()` 选项,将新创建的 Span 与父 Span 的 `SpanContext` 进行链接。这使得在追踪系统中,我们仍然可以看到这两个
Span 之间的关联,但它们的执行是独立的。
**Diagram sources**
- [span.go](file://internal/otelTrace/span.go#L42-L57)
```mermaid
@@ -64,45 +78,57 @@ note right of E: 生命周期独立<br/>不继承取消信号
### CreateAsyncContext()
`CreateAsyncContext()``CreateLinkContext()` 的一个特化版本,专门用于处理异步任务(如 Goroutine。它在调用 `CreateLinkContext()` 的基础上,进一步添加了 `async.operation``async.type``relationship` 等属性,明确标识了这是一个异步操作,并且与父 Span 是关联关系。
`CreateAsyncContext()``CreateLinkContext()` 的一个特化版本,专门用于处理异步任务(如 Goroutine。它在调用
`CreateLinkContext()` 的基础上,进一步添加了 `async.operation``async.type``relationship` 等属性,明确标识了这是一个异步操作,并且与父
Span 是关联关系。
这种设计模式在处理后台任务时非常有用,例如,当一个订单请求需要异步调用第三方渠道进行处理时,即使用户的 HTTP 请求已经超时或被取消,后台的渠道调用任务仍能独立完成,确保业务逻辑的完整性。
这种设计模式在处理后台任务时非常有用,例如,当一个订单请求需要异步调用第三方渠道进行处理时,即使用户的 HTTP
请求已经超时或被取消,后台的渠道调用任务仍能独立完成,确保业务逻辑的完整性。
**Section sources**
- [span.go](file://internal/otelTrace/span.go#L61-L69)
### CreateTraceableContext()
`CreateTraceableContext()` 函数旨在创建一个“可追踪的上下文”,它不仅保持了与父 Span 的关联,还通过 `trace.WithAttributes()` 传递了更丰富的追踪信息,如 `parent.trace_id``parent.span_id` 等。这使得在复杂的分布式系统中,即使经过多层异步调用,也能完整地追溯到原始请求的链路。
`CreateTraceableContext()` 函数旨在创建一个“可追踪的上下文”,它不仅保持了与父 Span 的关联,还通过 `trace.WithAttributes()`
传递了更丰富的追踪信息,如 `parent.trace_id``parent.span_id` 等。这使得在复杂的分布式系统中,即使经过多层异步调用,也能完整地追溯到原始请求的链路。
此函数同样使用 `context.Background()` 作为新上下文的根,保证了生命周期的独立性,同时通过链接和属性注入,实现了追踪链路的完整传递。
**Section sources**
- [span.go](file://internal/otelTrace/span.go#L73-L93)
## 状态与属性管理
### SetSpanError() 与 SetSpanSuccess()
`SetSpanError()``SetSpanSuccess()` 函数提供了标准化的错误和成功状态标记方法。`SetSpanError()` 会将 Span 的状态码设置为 `codes.Error`,并添加 `error.message``error.type` 属性。`SetSpanSuccess()` 则将状态码设置为 `codes.Ok`,并添加 `operation.status` 属性。
`SetSpanError()``SetSpanSuccess()` 函数提供了标准化的错误和成功状态标记方法。`SetSpanError()` 会将 Span 的状态码设置为
`codes.Error`,并添加 `error.message``error.type` 属性。`SetSpanSuccess()` 则将状态码设置为 `codes.Ok`,并添加
`operation.status` 属性。
这种标准化的处理方式,使得在追踪系统中可以轻松地根据这些预定义的属性进行错误率统计、成功率分析等监控操作,而无需在每个业务代码中重复编写类似的逻辑。
**Section sources**
- [span.go](file://internal/otelTrace/span.go#L96-L120)
### AddSpanAttributes()
`AddSpanAttributes()` 函数提供了一个便捷的接口,用于向 Span 添加自定义属性。它直接调用了 `span.SetAttributes()`,并处理了 `span``nil` 的边界情况。开发者可以利用此函数轻松地将业务相关的上下文信息如订单号、用户ID等注入到追踪数据中极大地增强了追踪信息的诊断价值。
`AddSpanAttributes()` 函数提供了一个便捷的接口,用于向 Span 添加自定义属性。它直接调用了 `span.SetAttributes()`,并处理了
`span``nil` 的边界情况。开发者可以利用此函数轻松地将业务相关的上下文信息如订单号、用户ID等注入到追踪数据中极大地增强了追踪信息的诊断价值。
**Section sources**
- [span.go](file://internal/otelTrace/span.go#L123-L129)
## 业务场景中的应用
### 订单池匹配中的异步处理
在订单池服务 `OrderPoolServiceImpl.matchOrdersForFaceValue` 中,为了处理高并发的订单匹配,系统使用了 Goroutine 来并行处理不同面值的订单。在此场景下,`CreateAsyncContext()` 被用来创建独立的追踪上下文。
在订单池服务 `OrderPoolServiceImpl.matchOrdersForFaceValue` 中,为了处理高并发的订单匹配,系统使用了 Goroutine
来并行处理不同面值的订单。在此场景下,`CreateAsyncContext()` 被用来创建独立的追踪上下文。
```go
func (s *OrderPoolServiceImpl) matchOrdersForFaceValue(ctx context.Context, channel card_sender.SendCardTaskEnum, roadUid string, faceValue float64) {
@@ -115,11 +141,13 @@ func (s *OrderPoolServiceImpl) matchOrdersForFaceValue(ctx context.Context, chan
这确保了即使主订单处理流程被取消,正在进行的订单匹配任务仍能继续执行,避免了因上游请求取消而导致的业务中断。
**Section sources**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L336-L526)
### 队列任务处理中的追踪
在队列系统 `QueueManager.EnqueueTask` 中,当一个任务被加入队列时,会创建一个 Span 来追踪 `EnqueueTask` 操作。而在 `RedisQueue.processQueue` 方法中,为了处理队列中的任务,系统使用 `CreateAsyncContext` 创建了一个新的上下文来处理每个任务。
在队列系统 `QueueManager.EnqueueTask` 中,当一个任务被加入队列时,会创建一个 Span 来追踪 `EnqueueTask` 操作。而在
`RedisQueue.processQueue` 方法中,为了处理队列中的任务,系统使用 `CreateAsyncContext` 创建了一个新的上下文来处理每个任务。
```go
func (q *RedisQueue) processQueue(ctx context.Context) {
@@ -135,18 +163,26 @@ func (q *RedisQueue) processQueue(ctx context.Context) {
这种方式将队列的消费过程与生产过程在追踪链路上关联起来,同时保证了任务处理的独立性,是典型的异步任务追踪模式。
**Section sources**
- [queue.go](file://internal/service/supplier/third_party/queue/queue.go#L380-L400)
### 渠道调用中的复杂追踪
`BatchSixChannelHandler.SubmitCard``SendCardTaskTypeFatSix.HandleSendCardTask` 等渠道调用的实现中,`Span()` 函数被广泛用于创建追踪 Span。这些 Span 会携带 `channelCode``faceValue``cardNo` 等关键业务参数作为属性,使得在排查渠道调用失败问题时,能够快速定位到具体的调用实例。
`BatchSixChannelHandler.SubmitCard``SendCardTaskTypeFatSix.HandleSendCardTask` 等渠道调用的实现中,`Span()`
函数被广泛用于创建追踪 Span。这些 Span 会携带 `channelCode``faceValue``cardNo`
等关键业务参数作为属性,使得在排查渠道调用失败问题时,能够快速定位到具体的调用实例。
例如,在 `SendCardTaskTypeFatSix.HandleSendCardTask`Span 被用来追踪整个提交卡密的复杂流程,包括获取代理、识别验证码、提交数据等步骤,并通过 `span.AddEvent()` 记录关键事件点,为性能分析和故障排查提供了详尽的数据。
例如,在 `SendCardTaskTypeFatSix.HandleSendCardTask`Span 被用来追踪整个提交卡密的复杂流程,包括获取代理、识别验证码、提交数据等步骤,并通过
`span.AddEvent()` 记录关键事件点,为性能分析和故障排查提供了详尽的数据。
**Section sources**
- [batch_six_handler.go](file://internal/service/supplier/third_party/queue/channel/batch_six_handler.go#L105-L212)
- [fat_six.go](file://internal/service/supplier/third_party/pool/card_sender/fat_six.go#L156-L307)
## 总结
`kami_gateway` 的 OTel 追踪系统通过精心设计的辅助函数,有效地解决了分布式系统中 Span 的创建、关联与管理问题。`Span()` 函数简化了基础 Span 的创建;`CreateAsyncContext()``CreateTraceableContext()` 通过“关联关系”模式,巧妙地平衡了追踪链路的完整性与异步任务的独立性;`SetSpanError()``SetSpanSuccess()``AddSpanAttributes()` 则提供了标准化的状态和属性管理接口。这些机制在订单处理、渠道调用等核心业务场景中得到了广泛应用,为系统的可观测性奠定了坚实的基础。
`kami_gateway` 的 OTel 追踪系统通过精心设计的辅助函数,有效地解决了分布式系统中 Span 的创建、关联与管理问题。`Span()`
函数简化了基础 Span 的创建;`CreateAsyncContext()``CreateTraceableContext()` 通过“关联关系”模式,巧妙地平衡了追踪链路的完整性与异步任务的独立性;
`SetSpanError()``SetSpanSuccess()``AddSpanAttributes()`
则提供了标准化的状态和属性管理接口。这些机制在订单处理、渠道调用等核心业务场景中得到了广泛应用,为系统的可观测性奠定了坚实的基础。

View File

@@ -8,6 +8,7 @@
</cite>
## 目录
1. [引言](#引言)
2. [上下文传播机制配置](#上下文传播机制配置)
3. [W3C Trace Context 传播](#w3c-trace-context-传播)
@@ -16,13 +17,19 @@
6. [结论](#结论)
## 引言
kami_gateway 系统通过 OpenTelemetry 实现了完整的分布式追踪能力,其中上下文传播机制是确保跨服务调用链路连续性的核心。本系统通过配置复合文本映射传播器,集成了 W3C Trace Context 和 W3C Baggage 两种标准,实现了调用链路追踪和业务上下文数据的跨服务传递。在支付网关的多通道调用场景中,这一机制确保了从订单调度到代付处理的全链路追踪完整性,为系统监控、故障排查和性能优化提供了坚实基础。
kami_gateway 系统通过 OpenTelemetry 实现了完整的分布式追踪能力,其中上下文传播机制是确保跨服务调用链路连续性的核心。本系统通过配置复合文本映射传播器,集成了
W3C Trace Context 和 W3C Baggage
两种标准,实现了调用链路追踪和业务上下文数据的跨服务传递。在支付网关的多通道调用场景中,这一机制确保了从订单调度到代付处理的全链路追踪完整性,为系统监控、故障排查和性能优化提供了坚实基础。
## 上下文传播机制配置
kami_gateway 系统在初始化阶段通过 `otel.SetTextMapPropagator` 配置了复合文本映射传播器,该传播器集成了 W3C Trace Context 和 W3C Baggage 两种标准。这种配置确保了系统能够同时处理追踪上下文和业务上下文的传播需求。
kami_gateway 系统在初始化阶段通过 `otel.SetTextMapPropagator` 配置了复合文本映射传播器,该传播器集成了 W3C Trace Context
和 W3C Baggage 两种标准。这种配置确保了系统能够同时处理追踪上下文和业务上下文的传播需求。
`internal/otelTrace/init.go` 文件中,系统通过 `propagation.NewCompositeTextMapPropagator` 创建了一个复合传播器,将 `propagation.TraceContext{}``propagation.Baggage{}` 两种传播器组合在一起。这种设计允许系统在单个 HTTP 请求中同时传播追踪信息和业务数据,实现了功能的解耦和复用。
`internal/otelTrace/init.go` 文件中,系统通过 `propagation.NewCompositeTextMapPropagator` 创建了一个复合传播器,将
`propagation.TraceContext{}``propagation.Baggage{}` 两种传播器组合在一起。这种设计允许系统在单个 HTTP
请求中同时传播追踪信息和业务数据,实现了功能的解耦和复用。
```mermaid
graph TD
@@ -35,16 +42,21 @@ D --> G[业务上下文数据]
```
**Diagram sources**
- [init.go](file://internal/otelTrace/init.go#L150-L153)
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L150-L153)
## W3C Trace Context 传播
W3C Trace Context 标准通过 `traceparent``tracestate` HTTP 头在服务间传递追踪上下文,确保调用链的连续性。在 kami_gateway 系统中,这一机制通过中间件在请求处理的各个阶段被激活和维护。
W3C Trace Context 标准通过 `traceparent``tracestate` HTTP 头在服务间传递追踪上下文,确保调用链的连续性。在
kami_gateway 系统中,这一机制通过中间件在请求处理的各个阶段被激活和维护。
当 HTTP 请求到达网关时,`internal/otelTrace/middleware.go` 中的 `Middleware` 函数会从请求头中提取上游的 trace context。通过 `otel.GetTextMapPropagator()` 获取配置的传播器,并使用 `propagator.Extract` 方法从请求头中提取上下文信息。这一过程确保了即使在复杂的微服务架构中,每个服务实例也能正确地继承和延续调用链。
当 HTTP 请求到达网关时,`internal/otelTrace/middleware.go` 中的 `Middleware` 函数会从请求头中提取上游的 trace context。通过
`otel.GetTextMapPropagator()` 获取配置的传播器,并使用 `propagator.Extract`
方法从请求头中提取上下文信息。这一过程确保了即使在复杂的微服务架构中,每个服务实例也能正确地继承和延续调用链。
```mermaid
sequenceDiagram
@@ -59,27 +71,36 @@ Gateway->>Client : 响应
```
**Diagram sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L30-L33)
**Section sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L30-L33)
## W3C Baggage 传播
W3C Baggage 标准允许在服务间传递业务相关的上下文数据,这些数据可以包含商户信息、订单属性、风险控制参数等业务关键信息。在 kami_gateway 系统中Baggage 传播机制为跨服务的业务逻辑处理提供了必要的上下文支持。
W3C Baggage 标准允许在服务间传递业务相关的上下文数据,这些数据可以包含商户信息、订单属性、风险控制参数等业务关键信息。在
kami_gateway 系统中Baggage 传播机制为跨服务的业务逻辑处理提供了必要的上下文支持。
虽然代码中没有直接展示 Baggage 的具体使用,但通过配置 `propagation.Baggage{}` 作为复合传播器的一部分,系统具备了处理业务上下文数据的能力。在实际应用中,这些数据可以用于实现基于上下文的路由决策、风险控制策略应用和个性化服务处理。
虽然代码中没有直接展示 Baggage 的具体使用,但通过配置 `propagation.Baggage{}`
作为复合传播器的一部分,系统具备了处理业务上下文数据的能力。在实际应用中,这些数据可以用于实现基于上下文的路由决策、风险控制策略应用和个性化服务处理。
在支付网关的场景中Baggage 可以携带如商户等级、风险评分、特殊处理要求等信息,确保下游服务能够根据这些上下文做出正确的业务决策。例如,在订单调度过程中,可以根据 Baggage 中携带的商户优先级信息,优先选择高优先级商户的支付通道。
在支付网关的场景中Baggage 可以携带如商户等级、风险评分、特殊处理要求等信息,确保下游服务能够根据这些上下文做出正确的业务决策。例如,在订单调度过程中,可以根据
Baggage 中携带的商户优先级信息,优先选择高优先级商户的支付通道。
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L152)
## 支付网关场景应用
在支付网关的多通道调用场景中,上下文传播机制在订单调度和代付处理等关键流程中发挥着重要作用。以 `internal/controllers/order_controller.go` 中的 `OrderSchedule` 方法为例,当商户请求重新调度订单时,系统需要确保从接收请求到最终调用第三方供应商的整个流程中,追踪上下文保持连续。
在支付网关的多通道调用场景中,上下文传播机制在订单调度和代付处理等关键流程中发挥着重要作用。以
`internal/controllers/order_controller.go` 中的 `OrderSchedule`
方法为例,当商户请求重新调度订单时,系统需要确保从接收请求到最终调用第三方供应商的整个流程中,追踪上下文保持连续。
`internal/service/pay_solve.go` 文件中,`SolvePaySuccess``SolvePayFail` 方法处理支付成功和失败的后续操作。这些方法通过 `ctx` 参数接收并传递上下文信息,确保在异步通知、账户更新等操作中,追踪信息能够正确关联到原始请求。
`internal/service/pay_solve.go` 文件中,`SolvePaySuccess``SolvePayFail` 方法处理支付成功和失败的后续操作。这些方法通过
`ctx` 参数接收并传递上下文信息,确保在异步通知、账户更新等操作中,追踪信息能够正确关联到原始请求。
```mermaid
flowchart TD
@@ -96,12 +117,16 @@ G --> K[发送失败通知]
```
**Diagram sources**
- [order_controller.go](file://internal/controllers/order_controller.go#L30-L75)
- [pay_solve.go](file://internal/service/pay_solve.go#L10-L100)
**Section sources**
- [order_controller.go](file://internal/controllers/order_controller.go#L30-L75)
- [pay_solve.go](file://internal/service/pay_solve.go#L10-L100)
## 结论
kami_gateway 系统通过精心设计的上下文传播机制,实现了 W3C Trace Context 和 W3C Baggage 标准的集成应用。这一机制不仅确保了全链路追踪的完整性,还为跨服务的业务上下文传递提供了支持。在支付网关的实际应用中,该机制有效支持了订单调度、代付处理等复杂业务流程的监控和管理,为系统的稳定运行和持续优化提供了重要保障。
kami_gateway 系统通过精心设计的上下文传播机制,实现了 W3C Trace Context 和 W3C Baggage
标准的集成应用。这一机制不仅确保了全链路追踪的完整性,还为跨服务的业务上下文传递提供了支持。在支付网关的实际应用中,该机制有效支持了订单调度、代付处理等复杂业务流程的监控和管理,为系统的稳定运行和持续优化提供了重要保障。

View File

@@ -8,6 +8,7 @@
</cite>
## 目录
1. [OpenTelemetry 初始化机制](#opentelemetry-初始化机制)
2. [OTLP 协议连接与网络优化](#otlp-协议连接与网络优化)
3. [资源对象与上下文标识](#资源对象与上下文标识)
@@ -17,23 +18,29 @@
## OpenTelemetry 初始化机制
`kami_gateway` 项目通过 `otelTrace.InitTracer()` 函数实现 OpenTelemetry 的全面初始化。该函数不仅负责追踪Tracing功能的启动还同时初始化了指标Metrics和日志Logs的导出构建了一个完整的可观测性Observability体系。此初始化过程是整个网关服务可观测性的基石确保了所有运行时数据能够被有效收集和分析。
`kami_gateway` 项目通过 `otelTrace.InitTracer()` 函数实现 OpenTelemetry
的全面初始化。该函数不仅负责追踪Tracing功能的启动还同时初始化了指标Metrics和日志Logs的导出构建了一个完整的可观测性Observability体系。此初始化过程是整个网关服务可观测性的基石确保了所有运行时数据能够被有效收集和分析。
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L29-L205)
## OTLP 协议连接与网络优化
`InitTracer()` 函数通过 OTLP (OpenTelemetry Protocol) 协议连接到后端的 OpenTelemetry 收集器Collector。其核心配置如下
1. **非安全模式连接 (Insecure Mode)**:通过 `otlptracegrpc.WithInsecure()` 配置,使用明文 gRPC 连接。这适用于内部网络或已通过其他方式(如 VPC、服务网格保障安全的环境避免了 TLS 握手的性能开销。
2. **超时设置 (Timeout)**:通过 `otlptracegrpc.WithTimeout(DefaultTimeout)` 设置了 5 秒的超时时间。该值在 `consts.go` 文件中定义为 `DefaultTimeout = 5 * time.Second`,旨在避免因网络延迟或收集器故障导致的应用程序长时间阻塞
3. **网络传输优化**
* **Gzip 压缩**:通过 `otlptracegrpc.WithCompressor("gzip")` 启用 Gzip 压缩,显著减少了网络传输的数据量,降低了带宽消耗和传输延迟
* **重试机制**:配置了指数退避重试策略(`WithRetry`),初始重试间隔为 1 秒,最大为 10 秒,总重试时间不超过 30 秒,增强了在短暂网络抖动下的鲁棒性。
* **批量处理**:使用 `sdktrace.NewBatchSpanProcessor` 将多个追踪片段Span批量导出减少了与收集器的连接次数提升了整体性能
1. **非安全模式连接 (Insecure Mode)**:通过 `otlptracegrpc.WithInsecure()` 配置,使用明文 gRPC 连接。这适用于内部网络或已通过其他方式(如
VPC、服务网格保障安全的环境避免了 TLS 握手的性能开销
2. **超时设置 (Timeout)**:通过 `otlptracegrpc.WithTimeout(DefaultTimeout)` 设置了 5 秒的超时时间。该值在 `consts.go`
文件中定义为 `DefaultTimeout = 5 * time.Second`,旨在避免因网络延迟或收集器故障导致的应用程序长时间阻塞
3. **网络传输优化**
* **Gzip 压缩**:通过 `otlptracegrpc.WithCompressor("gzip")` 启用 Gzip 压缩,显著减少了网络传输的数据量,降低了带宽消耗和传输延迟
* **重试机制**:配置了指数退避重试策略(`WithRetry`),初始重试间隔为 1 秒,最大为 10 秒,总重试时间不超过 30
秒,增强了在短暂网络抖动下的鲁棒性。
* **批量处理**:使用 `sdktrace.NewBatchSpanProcessor` 将多个追踪片段Span批量导出减少了与收集器的连接次数提升了整体性能。
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L32-L54)
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L14-L18)
@@ -41,9 +48,9 @@
为了确保追踪数据具备正确的上下文信息,`InitTracer()` 函数创建了一个 `Resource` 对象,并通过 `attribute.String` 设置了关键的标识属性:
* **服务名称 (service.name)**:从环境变量 `serverName` 中读取,确保每个服务实例都有唯一的标识。
* **部署环境 (deployment.environment)**:从环境变量 `ENVIRONMENT` 中读取,用于区分开发、测试、生产等不同环境的追踪数据。
* **语言类型 (library.language)**:固定为 `go`,标识了服务的开发语言。
* **服务名称 (service.name)**:从环境变量 `serverName` 中读取,确保每个服务实例都有唯一的标识。
* **部署环境 (deployment.environment)**:从环境变量 `ENVIRONMENT` 中读取,用于区分开发、测试、生产等不同环境的追踪数据。
* **语言类型 (library.language)**:固定为 `go`,标识了服务的开发语言。
这些属性作为追踪数据的元数据,使得在后端分析系统(如 Jaeger、Zipkin中能够方便地对数据进行过滤、聚合和分析。
@@ -58,27 +65,33 @@ F --> G["返回 Shutdown 函数"]
```
**Diagram sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L56-L70)
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L56-L70)
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L37-L38)
## 分布式追踪上下文传播
为了实现跨服务的分布式追踪,`InitTracer()` 函数通过 `otel.SetTextMapPropagator()` 配置了全局的文本映射传播器TextMapPropagator。它使用 `propagation.NewCompositeTextMapPropagator` 组合了两种 W3C 标准:
为了实现跨服务的分布式追踪,`InitTracer()` 函数通过 `otel.SetTextMapPropagator()` 配置了全局的文本映射传播器TextMapPropagator。它使用
`propagation.NewCompositeTextMapPropagator` 组合了两种 W3C 标准:
* **W3C Trace Context (`propagation.TraceContext{}`)**:负责在 HTTP 请求头(如 `traceparent``tracestate`中传递追踪的上下文信息Trace ID, Span ID 等),确保调用链路的连续性。
* **W3C Baggage (`propagation.Baggage{}`)**允许在请求中携带用户自定义的键值对数据如用户ID、租户ID这些数据会随着调用链路在所有服务间传递为业务逻辑提供上下文
* **W3C Trace Context (`propagation.TraceContext{}`)**:负责在 HTTP 请求头(如 `traceparent``tracestate`
中传递追踪的上下文信息Trace ID, Span ID 等),确保调用链路的连续性
* **W3C Baggage (`propagation.Baggage{}`)**允许在请求中携带用户自定义的键值对数据如用户ID、租户ID这些数据会随着调用链路在所有服务间传递为业务逻辑提供上下文。
此配置确保了 `kami_gateway` 发起或接收的任何请求都能正确地传播和接收分布式追踪上下文。
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L198-L203)
## 追踪资源的优雅关闭
`main.go` 文件中,`InitTracer()` 函数被调用,并通过 `defer` 语句注册了其返回的关闭函数。`InitTracer()` 函数返回了三个 `Shutdown` 函数,分别用于优雅地关闭追踪、指标和日志的导出器。
`main.go` 文件中,`InitTracer()` 函数被调用,并通过 `defer` 语句注册了其返回的关闭函数。`InitTracer()` 函数返回了三个
`Shutdown` 函数,分别用于优雅地关闭追踪、指标和日志的导出器。
```go
cleanup1, cleanup2, cleanup3 := otelTrace.InitTracer()
@@ -96,13 +109,15 @@ defer func() {
```
当应用程序接收到终止信号(如 SIGTERM并开始退出时`defer` 语句会执行。此时,`Shutdown` 函数会被调用,它们会:
1. 将导出器缓冲区中所有待处理的追踪、指标和日志数据强制刷新flush到后端收集器。
2. 等待数据发送完成或达到超时时间
3. 释放相关资源
1. 将导出器缓冲区中所有待处理的追踪、指标和日志数据强制刷新flush到后端收集器
2. 等待数据发送完成或达到超时时间
3. 释放相关资源。
这个过程确保了在服务关闭前,所有重要的可观测性数据都不会丢失,实现了资源的优雅关闭。
**Section sources**
- [main.go](file://main.go#L25-L35)
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L205)
@@ -110,8 +125,10 @@ defer func() {
基于 `kami_gateway` 的实现,以下是一些生产环境的配置调整建议:
1. **安全连接**:在生产环境中,应优先使用 `otlptracegrpc.WithTLSCredentials()` 替代 `WithInsecure()`,通过 TLS 加密保障数据传输安全。
2. **动态采样**:当前代码中使用了固定的采样率(`DefaultSamplingRatio`)。在高流量场景下,可以考虑实现更复杂的采样策略,如基于请求速率、错误率或特定业务逻辑的动态采样,以平衡观测需求和系统开销。
3. **收集器地址**`collectorURL``consts.go` 中硬编码。建议将其移至配置文件或环境变量中,以提高部署的灵活性
4. **监控与告警**:代码中启动了 `monitorExporterHealth()` goroutine 来监控导出器健康状态。应确保此监控机制有效,并与告警系统集成,以便在数据导出失败时及时通知运维人员
5. **资源限制**:根据实际的服务器资源和流量情况,调整 `MaxQueueSize``MaxExportBatchSize` 等参数,避免因缓冲区过大导致内存溢出,或因批量过小影响性能
1. **安全连接**:在生产环境中,应优先使用 `otlptracegrpc.WithTLSCredentials()` 替代 `WithInsecure()`,通过 TLS 加密保障数据传输安全。
2. **动态采样**:当前代码中使用了固定的采样率(`DefaultSamplingRatio`
)。在高流量场景下,可以考虑实现更复杂的采样策略,如基于请求速率、错误率或特定业务逻辑的动态采样,以平衡观测需求和系统开销
3. **收集器地址**`collectorURL``consts.go` 中硬编码。建议将其移至配置文件或环境变量中,以提高部署的灵活性
4. **监控与告警**:代码中启动了 `monitorExporterHealth()` goroutine 来监控导出器健康状态。应确保此监控机制有效,并与告警系统集成,以便在数据导出失败时及时通知运维人员
5. **资源限制**:根据实际的服务器资源和流量情况,调整 `MaxQueueSize``MaxExportBatchSize`
等参数,避免因缓冲区过大导致内存溢出,或因批量过小影响性能。

View File

@@ -9,6 +9,7 @@
</cite>
## 目录
1. [资源对象构建](#资源对象构建)
2. [关键属性配置](#关键属性配置)
3. [上下文共享机制](#上下文共享机制)
@@ -16,22 +17,31 @@
## 资源对象构建
`kami_gateway` 项目中OpenTelemetry 的资源Resource对象是通过 `resource.New` 函数构建的,该过程是整个可观测性系统的基础。资源对象的创建发生在 `InitTracer` 函数内部,作为初始化追踪系统的一部分。此对象封装了服务的静态元数据,为后续的追踪、指标和日志数据提供了统一的上下文标识。
`kami_gateway` 项目中OpenTelemetry 的资源Resource对象是通过 `resource.New` 函数构建的,该过程是整个可观测性系统的基础。资源对象的创建发生在
`InitTracer` 函数内部,作为初始化追踪系统的一部分。此对象封装了服务的静态元数据,为后续的追踪、指标和日志数据提供了统一的上下文标识。
`resource.New` 函数接收一个上下文和一系列配置选项,其中核心是 `resource.WithAttributes`,它允许开发者定义一组键值对属性。这些属性在分布式系统中至关重要,它们使得来自不同服务的遥测数据能够被正确地关联、聚合和过滤。资源对象的构建是整个 `InitTracer` 流程中的关键步骤,它确保了所有后续生成的追踪、指标和日志都携带一致的元数据。
`resource.New` 函数接收一个上下文和一系列配置选项,其中核心是 `resource.WithAttributes`
,它允许开发者定义一组键值对属性。这些属性在分布式系统中至关重要,它们使得来自不同服务的遥测数据能够被正确地关联、聚合和过滤。资源对象的构建是整个
`InitTracer` 流程中的关键步骤,它确保了所有后续生成的追踪、指标和日志都携带一致的元数据。
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L47-L55)
## 关键属性配置
资源对象通过 `attribute.String` 函数设置了三个核心属性,这些属性构成了服务的身份标识。
`library.language` 属性被硬编码为 `"go"`,明确标识了该服务是使用 Go 语言编写的。这一信息对于运维团队和监控系统至关重要,因为它允许按编程语言对服务进行分类和分析,例如,可以快速筛选出所有 Go 语言服务的性能指标或错误日志。
`library.language` 属性被硬编码为 `"go"`,明确标识了该服务是使用 Go
语言编写的。这一信息对于运维团队和监控系统至关重要,因为它允许按编程语言对服务进行分类和分析,例如,可以快速筛选出所有 Go
语言服务的性能指标或错误日志。
`service.name` 属性的值来源于 `consts.go` 文件中定义的 `serviceName` 变量。该变量的值是字符串 `"网关服务——"` 与环境变量 `serverName` 的拼接结果。这种动态命名方式确保了即使在同一个代码库下部署多个实例(如不同的网关实例),每个实例也能拥有一个唯一且可识别的服务名称,便于在服务发现和监控中进行区分。
`service.name` 属性的值来源于 `consts.go` 文件中定义的 `serviceName` 变量。该变量的值是字符串 `"网关服务——"` 与环境变量
`serverName` 的拼接结果。这种动态命名方式确保了即使在同一个代码库下部署多个实例(如不同的网关实例),每个实例也能拥有一个唯一且可识别的服务名称,便于在服务发现和监控中进行区分。
`deployment.environment` 属性的值通过 `env.Get("ENVIRONMENT", "production")` 从环境变量 `ENVIRONMENT` 中读取。如果该环境变量未设置,则默认值为 `"production"`。这一设计实践遵循了十二要素应用12-Factor App的原则使得同一份代码可以在开发、测试、预发布和生产等不同环境中运行而无需修改代码。监控系统可以利用此属性对不同环境的数据进行隔离分析例如可以对比生产环境和预发布环境的性能差异。
`deployment.environment` 属性的值通过 `env.Get("ENVIRONMENT", "production")` 从环境变量 `ENVIRONMENT`
中读取。如果该环境变量未设置,则默认值为 `"production"`。这一设计实践遵循了十二要素应用12-Factor
App的原则使得同一份代码可以在开发、测试、预发布和生产等不同环境中运行而无需修改代码。监控系统可以利用此属性对不同环境的数据进行隔离分析例如可以对比生产环境和预发布环境的性能差异。
```mermaid
flowchart TD
@@ -49,22 +59,30 @@ UseDefault --> End
```
**Diagram sources**
- [init.go](file://internal/otelTrace/init.go#L49-L51)
- [consts.go](file://internal/otelTrace/consts.go#L47)
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L49-L51)
- [consts.go](file://internal/otelTrace/consts.go#L47)
## 上下文共享机制
资源对象在 `kami_gateway` 中扮演着“上下文锚点”的角色,它被 `TracerProvider``MeterProvider``LoggerProvider` 共享,从而实现了追踪、指标和日志三者之间的上下文一致性。
资源对象在 `kami_gateway` 中扮演着“上下文锚点”的角色,它被 `TracerProvider``MeterProvider``LoggerProvider`
共享,从而实现了追踪、指标和日志三者之间的上下文一致性。
`TracerProvider` 在创建时,通过 `sdktrace.WithResource(resources)` 将资源对象注入。这意味着该服务生成的所有追踪Trace和跨度Span都会自动携带 `library.language``service.name``deployment.environment` 这些标签。这使得在分布式追踪系统中,可以轻松地根据服务名称或部署环境来过滤和聚合链路。
`TracerProvider` 在创建时,通过 `sdktrace.WithResource(resources)` 将资源对象注入。这意味着该服务生成的所有追踪Trace和跨度Span都会自动携带
`library.language``service.name``deployment.environment` 这些标签。这使得在分布式追踪系统中,可以轻松地根据服务名称或部署环境来过滤和聚合链路。
`MeterProvider` 同样通过 `sdkMetric.WithResource(resources)` 接收资源对象。这确保了该服务上报的所有指标(如请求延迟、错误率)都带有相同的服务标识。监控系统可以基于这些标签进行多维度的数据切片和聚合,例如,计算生产环境中所有网关服务的平均响应时间。
`MeterProvider` 同样通过 `sdkMetric.WithResource(resources)`
接收资源对象。这确保了该服务上报的所有指标(如请求延迟、错误率)都带有相同的服务标识。监控系统可以基于这些标签进行多维度的数据切片和聚合,例如,计算生产环境中所有网关服务的平均响应时间。
`LoggerProvider` 通过 `sdklog.WithResource(resources)` 将资源对象与日志系统绑定。这使得通过 OpenTelemetry 导出的日志条目也包含了服务的元数据。更重要的是,项目中定义了一个 `CustomLogger` 结构体,它封装了 `zap.Logger`,并提供了一个 `WithContext` 方法。该方法能够从传入的 `context.Context` 中提取追踪上下文(如 Trace ID 和 Span ID并将其作为字段注入到日志中。这实现了日志与追踪的深度关联运维人员可以通过一个 Trace ID 在日志系统中直接搜索到与该次请求相关的所有日志。
`LoggerProvider` 通过 `sdklog.WithResource(resources)` 将资源对象与日志系统绑定。这使得通过 OpenTelemetry
导出的日志条目也包含了服务的元数据。更重要的是,项目中定义了一个 `CustomLogger` 结构体,它封装了 `zap.Logger`,并提供了一个
`WithContext` 方法。该方法能够从传入的 `context.Context` 中提取追踪上下文(如 Trace ID 和 Span
ID并将其作为字段注入到日志中。这实现了日志与追踪的深度关联运维人员可以通过一个 Trace ID 在日志系统中直接搜索到与该次请求相关的所有日志。
```mermaid
graph TD
@@ -86,6 +104,7 @@ classDef provider fill:#f3e5f5,stroke:#8e24aa,stroke-width:2px;
```
**Diagram sources**
- [init.go](file://internal/otelTrace/init.go#L58-L62)
- [init.go](file://internal/otelTrace/init.go#L84-L88)
- [init.go](file://internal/otelTrace/init.go#L112-L116)
@@ -93,6 +112,7 @@ classDef provider fill:#f3e5f5,stroke:#8e24aa,stroke-width:2px;
- [logs.go](file://internal/otelTrace/logs.go#L9-L11)
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L58-L62)
- [init.go](file://internal/otelTrace/init.go#L84-L88)
- [init.go](file://internal/otelTrace/init.go#L112-L116)
@@ -101,9 +121,13 @@ classDef provider fill:#f3e5f5,stroke:#8e24aa,stroke-width:2px;
## 初始化流程
资源对象的构建和共享是 `InitTracer` 函数执行流程中的一个环节。该函数的调用发生在 `main.go` 文件的 `main` 函数中,是服务启动过程的关键步骤。`InitTracer` 不仅负责创建资源对象还负责配置追踪、指标和日志的导出器Exporter并将它们与各自的 Provider 绑定。
资源对象的构建和共享是 `InitTracer` 函数执行流程中的一个环节。该函数的调用发生在 `main.go` 文件的 `main`
函数中,是服务启动过程的关键步骤。`InitTracer` 不仅负责创建资源对象还负责配置追踪、指标和日志的导出器Exporter并将它们与各自的
Provider 绑定。
`main` 函数中,`otelTrace.InitTracer()` 被调用,其返回的三个 `Shutdown` 函数通过 `defer` 语句注册,确保在服务退出时能够优雅地关闭所有可观测性组件,避免数据丢失。`InitTracer` 函数内部的执行顺序是:首先创建资源对象,然后基于该资源对象初始化 `TracerProvider``MeterProvider``LoggerProvider`,最后将这些 Provider 设置为全局单例。
`main` 函数中,`otelTrace.InitTracer()` 被调用,其返回的三个 `Shutdown` 函数通过 `defer`
语句注册,确保在服务退出时能够优雅地关闭所有可观测性组件,避免数据丢失。`InitTracer` 函数内部的执行顺序是:首先创建资源对象,然后基于该资源对象初始化
`TracerProvider``MeterProvider``LoggerProvider`,最后将这些 Provider 设置为全局单例。
这种设计模式确保了整个应用生命周期内,所有组件使用的都是同一个、配置一致的可观测性基础设施。通过在应用启动时集中配置,避免了在代码各处分散配置所带来的不一致性和维护困难。
@@ -127,9 +151,11 @@ Main->>Main : defer 执行 cleanup
```
**Diagram sources**
- [main.go](file://main.go#L23-L26)
- [init.go](file://internal/otelTrace/init.go#L29-L205)
**Section sources**
- [main.go](file://main.go#L23-L26)
- [init.go](file://internal/otelTrace/init.go#L29-L205)

View File

@@ -11,6 +11,7 @@
</cite>
## 目录
1. [项目结构](#项目结构)
2. [核心组件](#核心组件)
3. [追踪导出器配置机制](#追踪导出器配置机制)
@@ -50,27 +51,33 @@ otelTrace --> logs[logs.go]
```
**Diagram sources**
- [main.go](file://main.go#L1-L58)
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L1-L56)
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L1-L257)
**Section sources**
- [main.go](file://main.go#L1-L58)
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L1-L56)
## 核心组件
本项目的核心组件包括OpenTelemetry追踪系统、配置管理、服务路由和代理池等模块。其中OpenTelemetry追踪系统通过`internal/otelTrace`包实现负责分布式追踪、指标收集和日志导出功能。该系统与后端收集器通过gRPC协议通信实现了生产级的可观测性解决方案。
本项目的核心组件包括OpenTelemetry追踪系统、配置管理、服务路由和代理池等模块。其中OpenTelemetry追踪系统通过
`internal/otelTrace`包实现负责分布式追踪、指标收集和日志导出功能。该系统与后端收集器通过gRPC协议通信实现了生产级的可观测性解决方案。
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L1-L257)
- [internal/config/config.go](file://internal/config/config.go)
## 追踪导出器配置机制
kami_gateway中的OpenTelemetry追踪导出器配置机制通过`InitTracer`函数实现,该函数在应用启动时初始化追踪、指标和日志的导出器。配置机制采用生产环境优化策略,包括动态采样率、批量传输优化和资源标识配置。
kami_gateway中的OpenTelemetry追踪导出器配置机制通过`InitTracer`
函数实现,该函数在应用启动时初始化追踪、指标和日志的导出器。配置机制采用生产环境优化策略,包括动态采样率、批量传输优化和资源标识配置。
导出器配置的核心是`otlptracegrpc.NewClient`它创建了一个gRPC客户端用于与后端收集器通信。同时系统还配置了指标导出器和日志导出器形成完整的可观测性解决方案。资源标识配置包含了服务名称、部署环境等关键属性便于在分布式系统中识别和关联追踪数据。
导出器配置的核心是`otlptracegrpc.NewClient`
它创建了一个gRPC客户端用于与后端收集器通信。同时系统还配置了指标导出器和日志导出器形成完整的可观测性解决方案。资源标识配置包含了服务名称、部署环境等关键属性便于在分布式系统中识别和关联追踪数据。
```mermaid
classDiagram
@@ -103,17 +110,21 @@ InitTracer --> global : "设置LoggerProvider"
```
**Diagram sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L29-L205)
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L29-L205)
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L1-L56)
## gRPC通信配置
追踪导出器通过gRPC协议与后端收集器通信配置中包含了多个关键参数。`WithInsecure`模式用于建立非加密连接适用于内部网络环境降低了TLS握手的性能开销。`WithEndpoint`指定了收集器的端点地址,当前配置为`38.38.251.113:31547`
追踪导出器通过gRPC协议与后端收集器通信配置中包含了多个关键参数。`WithInsecure`模式用于建立非加密连接适用于内部网络环境降低了TLS握手的性能开销。
`WithEndpoint`指定了收集器的端点地址,当前配置为`38.38.251.113:31547`
gRPC通信配置还包含了批量传输队列的优化设置`WithBatchTimeout`设置为5秒的批量发送间隔`WithMaxExportBatchSize`限制单次导出批量为512条`WithMaxQueueSize`设置最大队列大小为2048条。这些配置平衡了实时性和性能防止内存溢出。
gRPC通信配置还包含了批量传输队列的优化设置`WithBatchTimeout`设置为5秒的批量发送间隔`WithMaxExportBatchSize`
限制单次导出批量为512条`WithMaxQueueSize`设置最大队列大小为2048条。这些配置平衡了实时性和性能防止内存溢出。
```mermaid
sequenceDiagram
@@ -136,18 +147,23 @@ end
```
**Diagram sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L33-L75)
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L1-L56)
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L33-L75)
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L1-L56)
## 超时与重试机制
系统配置了5秒的默认超时`DefaultTimeout`),这对系统稳定性至关重要。超时设置平衡了响应性和稳定性,避免因网络延迟导致的长时间阻塞。同时,系统实现了生产级的重试机制,通过`RetryConfig`进行调优。
系统配置了5秒的默认超时`DefaultTimeout`),这对系统稳定性至关重要。超时设置平衡了响应性和稳定性,避免因网络延迟导致的长时间阻塞。同时,系统实现了生产级的重试机制,通过
`RetryConfig`进行调优。
重试机制的生产级调优策略包括1秒的初始重试间隔`InitialRetryInterval`允许快速恢复10秒的最大重试间隔`MaxRetryInterval`避免频繁重试造成网络拥塞30秒的最大重试总时间`MaxRetryElapsedTime`),防止长时间阻塞。这些参数共同构成了一个指数退避重试策略,有效应对网络抖动。
重试机制的生产级调优策略包括1秒的初始重试间隔`InitialRetryInterval`允许快速恢复10秒的最大重试间隔
`MaxRetryInterval`避免频繁重试造成网络拥塞30秒的最大重试总时间`MaxRetryElapsedTime`
),防止长时间阻塞。这些参数共同构成了一个指数退避重试策略,有效应对网络抖动。
```mermaid
flowchart TD
@@ -170,29 +186,35 @@ End --> Cleanup["清理资源"]
```
**Diagram sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L38-L75)
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L1-L56)
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L38-L75)
- [internal/otelTrace/consts.go](file://internal/otelTrace/consts.go#L1-L56)
## 数据压缩与网络优化
系统通过`WithCompressor("gzip")`配置启用gzip压缩显著降低网络传输开销。这一配置应用于追踪、指标和日志三种数据类型的导出器实现了全面的网络优化。gzip压缩在高负载场景下尤为重要可以大幅减少带宽消耗和传输延迟。
系统通过`WithCompressor("gzip")`
配置启用gzip压缩显著降低网络传输开销。这一配置应用于追踪、指标和日志三种数据类型的导出器实现了全面的网络优化。gzip压缩在高负载场景下尤为重要可以大幅减少带宽消耗和传输延迟。
除了数据压缩系统还实施了多项网络优化策略。批量传输机制减少了网络请求次数5秒的批量发送间隔平衡了实时性和性能。最大导出批量512条和最大队列大小2048条的设置防止了单次传输过大导致的网络拥塞和内存溢出问题。
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L68-L75)
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L104-L111)
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L135-L140)
## 优雅关闭机制
`traceExporter.Shutdown`函数在`main.go`中通过defer调用实现优雅关闭行为。该机制确保在应用终止时所有待处理的追踪数据都能被完整导出避免数据丢失。优雅关闭是生产环境的关键特性保证了可观测性数据的完整性。
`traceExporter.Shutdown`函数在`main.go`
中通过defer调用实现优雅关闭行为。该机制确保在应用终止时所有待处理的追踪数据都能被完整导出避免数据丢失。优雅关闭是生产环境的关键特性保证了可观测性数据的完整性。
`main.go`中,`InitTracer`函数返回三个清理函数分别用于追踪、指标和日志导出器的关闭。defer语句确保这些清理函数在main函数退出时执行即使发生panic也能保证资源的正确释放。这种设计模式提高了系统的可靠性和稳定性。
`main.go`中,`InitTracer`
函数返回三个清理函数分别用于追踪、指标和日志导出器的关闭。defer语句确保这些清理函数在main函数退出时执行即使发生panic也能保证资源的正确释放。这种设计模式提高了系统的可靠性和稳定性。
```mermaid
sequenceDiagram
@@ -212,18 +234,22 @@ Shutdown-->>Main : 完成优雅关闭
```
**Diagram sources**
- [main.go](file://main.go#L30-L38)
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L204-L205)
**Section sources**
- [main.go](file://main.go#L30-L38)
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L204-L205)
## 熔断与健康监控
系统实现了熔断器模式和健康监控机制,增强了追踪系统的可靠性。`CircuitBreaker`结构体实现了简单的熔断器当连续失败达到阈值5次熔断器开启避免对后端收集器造成持续压力。30秒的重置超时允许系统在故障恢复后自动恢复正常。
系统实现了熔断器模式和健康监控机制,增强了追踪系统的可靠性。`CircuitBreaker`
结构体实现了简单的熔断器当连续失败达到阈值5次熔断器开启避免对后端收集器造成持续压力。30秒的重置超时允许系统在故障恢复后自动恢复正常。
`monitorExporterHealth`函数作为后台goroutine运行每30秒检查一次导出器的健康状态。它监控错误率当失败次数超过阈值时发出警告。同时系统实现了动态采样率调整机制在检测到高负载时自动降低采样率从10%降至5%),保护系统稳定性。
`monitorExporterHealth`
函数作为后台goroutine运行每30秒检查一次导出器的健康状态。它监控错误率当失败次数超过阈值时发出警告。同时系统实现了动态采样率调整机制在检测到高负载时自动降低采样率从10%降至5%),保护系统稳定性。
```mermaid
stateDiagram-v2
@@ -248,9 +274,11 @@ end note
```
**Diagram sources**
- [internal/otelTrace/circuit_breaker.go](file://internal/otelTrace/circuit_breaker.go#L1-L50)
- [internal/otelTrace/utils.go](file://internal/otelTrace/utils.go#L16-L46)
**Section sources**
- [internal/otelTrace/circuit_breaker.go](file://internal/otelTrace/circuit_breaker.go#L1-L50)
- [internal/otelTrace/utils.go](file://internal/otelTrace/utils.go#L16-L46)

View File

@@ -10,6 +10,7 @@
</cite>
## 目录
1. [引言](#引言)
2. [采样策略详解](#采样策略详解)
3. [批量处理机制](#批量处理机制)
@@ -18,15 +19,20 @@
6. [结论](#结论)
## 引言
本文档详细解析 kami_gateway 项目中 OpenTelemetry 的采样策略与批量处理机制。通过分析 `traceIDRatioBased` 采样器如何基于比率控制生成的追踪数据量,以及 `sdktrace.NewBatchSpanProcessor` 的配置参数,深入探讨这些机制如何在监控精度与系统性能开销之间取得平衡。文档结合生产环境需求,分析这些参数对高并发支付场景下的性能影响,并提供相应的调优建议。
本文档详细解析 kami_gateway 项目中 OpenTelemetry 的采样策略与批量处理机制。通过分析 `traceIDRatioBased`
采样器如何基于比率控制生成的追踪数据量,以及 `sdktrace.NewBatchSpanProcessor`
的配置参数,深入探讨这些机制如何在监控精度与系统性能开销之间取得平衡。文档结合生产环境需求,分析这些参数对高并发支付场景下的性能影响,并提供相应的调优建议。
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L29-L205)
- [simple.go](file://internal/otelTrace/simple.go#L80-L93)
## 采样策略详解
### traceIDRatioBased 采样器
`traceIDRatioBased` 采样器是 kami_gateway 项目中用于控制追踪数据生成量的核心机制。该采样器基于给定的比率对追踪进行采样,确保在高负载场景下既能保持足够的监控精度,又不会对系统性能造成过大开销。
```mermaid
@@ -44,23 +50,32 @@ Record --> End
```
**Diagram sources**
- [simple.go](file://internal/otelTrace/simple.go#L80-L93)
#### 采样逻辑实现
`traceIDRatioBased` 函数接收一个浮点数 `fraction` 作为采样比率参数。当比率大于等于1时返回 `sdkTrace.AlwaysSample()`表示始终采样当比率小于等于0时将其设置为0表示不采样。对于介于0和1之间的比率函数创建并返回一个 `traceIDRatioSampler` 结构体实例,该实例包含 `traceIDUpperBound` 和描述信息。
`traceIDUpperBound` 是通过将采样比率乘以 `1 << 63` 计算得出的,这确保了采样决策的均匀分布。在 `ShouldSample` 方法中,系统通过比较生成的 TraceID 与 `traceIDUpperBound` 来决定是否采样该 Span。
`traceIDRatioBased` 函数接收一个浮点数 `fraction` 作为采样比率参数。当比率大于等于1时返回 `sdkTrace.AlwaysSample()`
表示始终采样当比率小于等于0时将其设置为0表示不采样。对于介于0和1之间的比率函数创建并返回一个 `traceIDRatioSampler`
结构体实例,该实例包含 `traceIDUpperBound` 和描述信息。
`traceIDUpperBound` 是通过将采样比率乘以 `1 << 63` 计算得出的,这确保了采样决策的均匀分布。在 `ShouldSample` 方法中,系统通过比较生成的
TraceID 与 `traceIDUpperBound` 来决定是否采样该 Span。
#### 强制采样机制
除了基于比率的采样外,系统还实现了强制采样机制。对于特定的追踪名称(如 "PayNotify"、"HandleSendCardTask")或包含错误属性的 Span系统会强制进行采样确保关键业务流程和错误情况能够被完整记录。
除了基于比率的采样外,系统还实现了强制采样机制。对于特定的追踪名称(如 "PayNotify"、"HandleSendCardTask")或包含错误属性的
Span系统会强制进行采样确保关键业务流程和错误情况能够被完整记录。
**Section sources**
- [simple.go](file://internal/otelTrace/simple.go#L1-L94)
- [consts.go](file://internal/otelTrace/consts.go#L30-L33)
## 批量处理机制
### 批量处理器配置
kami_gateway 使用 `sdktrace.NewBatchSpanProcessor` 来优化追踪数据的导出过程。该处理器通过批量处理 Span显著减少了网络传输次数和系统开销。
```mermaid
@@ -86,9 +101,11 @@ BatchSpanProcessor --> SpanExporter : "使用"
```
**Diagram sources**
- [init.go](file://internal/otelTrace/init.go#L60-L75)
#### 核心配置参数
批量处理器的配置参数经过精心设计,以优化内存使用与吞吐量:
- **BatchTimeout (5秒)**控制批量发送的频率。每5秒处理器会将队列中的 Span 批量导出,平衡了实时性与性能。
@@ -97,12 +114,14 @@ BatchSpanProcessor --> SpanExporter : "使用"
- **ExportTimeout (5秒)**防止导出操作阻塞。如果导出操作在5秒内未完成将被中断避免影响主业务流程。
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L60-L75)
- [consts.go](file://internal/otelTrace/consts.go#L20-L25)
## 生产环境性能分析
### 系统性能影响
在高并发支付场景下OpenTelemetry 的采样与批量处理机制对系统性能有着重要影响。合理的配置能够显著降低系统开销,同时保持足够的监控精度。
```mermaid
@@ -126,10 +145,12 @@ end
```
**Diagram sources**
- [init.go](file://internal/otelTrace/init.go#L29-L205)
- [consts.go](file://internal/otelTrace/consts.go#L15-L35)
#### 动态采样率调整
系统实现了动态采样率调整机制,通过 `monitorExporterHealth` 函数定期检查导出器的健康状态。当导出失败次数超过阈值时系统会自动降低采样率从默认的10%降至5%,以保护系统稳定性。
```mermaid
@@ -152,16 +173,19 @@ Monitor->>Monitor : 重置失败计数器
```
**Diagram sources**
- [utils.go](file://internal/otelTrace/utils.go#L16-L46)
- [consts.go](file://internal/otelTrace/consts.go#L33-L34)
**Section sources**
- [utils.go](file://internal/otelTrace/utils.go#L16-L46)
- [consts.go](file://internal/otelTrace/consts.go#L33-L34)
## 高并发支付场景调优建议
### 参数调优策略
针对高并发支付场景,建议根据实际负载情况对 OpenTelemetry 的配置参数进行调优:
- **采样率**在正常负载下保持10%的采样率以确保足够的监控精度。在高负载或系统资源紧张时可动态降低至5%,以保护系统稳定性。
@@ -170,11 +194,16 @@ Monitor->>Monitor : 重置失败计数器
- **队列大小**2048条的最大队列大小能够有效应对突发流量。在内存充足的系统中可适当增加至4096条以提供更大的缓冲空间。
### 监控与告警
建议建立完善的监控与告警机制,实时监控 OpenTelemetry 导出器的健康状态。当导出失败率持续升高时,应及时调整采样率或检查后端服务的稳定性。
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L29-L205)
- [utils.go](file://internal/otelTrace/utils.go#L16-L46)
## 结论
kami_gateway 项目中的 OpenTelemetry 采样与批量处理机制经过精心设计,能够在高并发支付场景下有效平衡监控精度与系统性能开销。通过 `traceIDRatioBased` 采样器和 `sdktrace.NewBatchSpanProcessor` 的合理配置,系统能够在保证关键业务流程监控的同时,最大限度地减少对主业务流程的影响。建议根据实际生产环境的需求,灵活调整相关参数,以达到最佳的性能与监控效果。
kami_gateway 项目中的 OpenTelemetry 采样与批量处理机制经过精心设计,能够在高并发支付场景下有效平衡监控精度与系统性能开销。通过
`traceIDRatioBased` 采样器和 `sdktrace.NewBatchSpanProcessor`
的合理配置,系统能够在保证关键业务流程监控的同时,最大限度地减少对主业务流程的影响。建议根据实际生产环境的需求,灵活调整相关参数,以达到最佳的性能与监控效果。

View File

@@ -8,6 +8,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [CustomLogger 结构体设计](#customlogger-结构体设计)
3. [日志与 OTLP 集成机制](#日志与-otlp-集成机制)
@@ -16,13 +17,18 @@
6. [总结](#总结)
## 简介
本文档详细阐述 kami_gateway 项目中日志系统与分布式追踪系统的深度集成方案。重点分析 `CustomLogger` 结构体的设计原理,特别是 `WithContext()` 方法如何从 `context.Context` 中提取有效的 Span并将上下文信息注入日志字段。同时说明在上下文无效或缺失时的降级处理逻辑以及如何通过 OTLP 协议将日志导出至集中式观测平台,同时保留本地控制台输出用于调试。该集成机制有效实现了日志与追踪上下文的关联,极大提升了分布式系统的问题排查效率。
本文档详细阐述 kami_gateway 项目中日志系统与分布式追踪系统的深度集成方案。重点分析 `CustomLogger` 结构体的设计原理,特别是
`WithContext()` 方法如何从 `context.Context` 中提取有效的 Span并将上下文信息注入日志字段。同时说明在上下文无效或缺失时的降级处理逻辑以及如何通过
OTLP 协议将日志导出至集中式观测平台,同时保留本地控制台输出用于调试。该集成机制有效实现了日志与追踪上下文的关联,极大提升了分布式系统的问题排查效率。
## CustomLogger 结构体设计
`CustomLogger` 是 kami_gateway 中用于封装日志功能的核心结构体,其设计目标是实现日志与 OpenTelemetry 分布式追踪上下文的无缝集成。该结构体通过 `WithContext()` 方法实现上下文感知的日志记录能力。
`CustomLogger` 是 kami_gateway 中用于封装日志功能的核心结构体,其设计目标是实现日志与 OpenTelemetry 分布式追踪上下文的无缝集成。该结构体通过
`WithContext()` 方法实现上下文感知的日志记录能力。
`WithContext()` 方法的实现逻辑如下:
- 当传入的 `context.Context``nil` 时,直接返回原始日志记录器,避免空指针异常。
- 使用 `trace.SpanFromContext(ctx)` 从上下文中提取当前活动的 Span。
- 检查 Span 上下文的有效性(`IsValid()`),若无效则返回原始日志记录器或 `zap.NewNop()`(当日志器为空时)。
@@ -30,7 +36,8 @@
这种设计确保了在任何执行路径下日志系统都能稳定运行,即使在无追踪上下文的场景中也不会中断日志输出。
**节来源**
**节来源**
- [logs.go](file://internal/otelTrace/logs.go#L9-L28)
## 日志与 OTLP 集成机制
@@ -38,23 +45,30 @@
`init.go` 文件中,通过 `otelzap.NewCore` 实现了日志到 OTLP 的导出集成。具体实现方式如下:
日志核心core通过 `zapcore.NewTee` 组合了两个输出目标:
1. **OTLP 日志导出器**:使用 `otelzap.NewCore(serviceName, otelzap.WithLoggerProvider(loggerProvider))` 创建,将日志发送至配置的 OTLP 收集器collectorURL
1. **OTLP 日志导出器**:使用 `otelzap.NewCore(serviceName, otelzap.WithLoggerProvider(loggerProvider))` 创建,将日志发送至配置的
OTLP 收集器collectorURL
2. **控制台输出**:使用 `zapcore.NewConsoleEncoder` 配合 `os.Stdout` 实现,保留本地调试输出能力。
该机制确保了生产环境中日志既能上报至集中式观测平台又能在开发或调试阶段提供实时控制台输出。同时日志导出器配置了超时、重试、gzip 压缩等生产级优化参数,保障了高负载场景下的稳定性。
该机制确保了生产环境中日志既能上报至集中式观测平台又能在开发或调试阶段提供实时控制台输出。同时日志导出器配置了超时、重试、gzip
压缩等生产级优化参数,保障了高负载场景下的稳定性。
**节来源**
**节来源**
- [init.go](file://internal/otelTrace/init.go#L130-L180)
## 日志与 Trace ID 关联分析
kami_gateway 通过将 `context.Context` 注入日志字段的方式,实现了日志与 Trace ID 的自动关联。当请求经过启用了 OpenTelemetry 的中间件时,上下文会携带当前 Span 信息。通过 `WithContext()` 方法,该上下文被附加到日志记录器中,使得所有后续日志条目都隐式包含追踪上下文。
kami_gateway 通过将 `context.Context` 注入日志字段的方式,实现了日志与 Trace ID 的自动关联。当请求经过启用了 OpenTelemetry
的中间件时,上下文会携带当前 Span 信息。通过 `WithContext()` 方法,该上下文被附加到日志记录器中,使得所有后续日志条目都隐式包含追踪上下文。
在观测平台中,可通过 Trace ID 直接检索与该请求相关的所有日志条目,形成完整的调用链视图。这种关联机制无需在业务代码中手动传递 Trace ID降低了开发复杂度同时保证了日志与追踪数据的一致性。
在观测平台中,可通过 Trace ID 直接检索与该请求相关的所有日志条目,形成完整的调用链视图。这种关联机制无需在业务代码中手动传递
Trace ID降低了开发复杂度同时保证了日志与追踪数据的一致性。
此外,系统还配置了 W3C Trace Context 和 Baggage 的复合传播器,确保跨服务调用时上下文信息的正确传递,支持完整的端到端分布式追踪。
**节来源**
**节来源**
- [init.go](file://internal/otelTrace/init.go#L200-L205)
- [logs.go](file://internal/otelTrace/logs.go#L20-L27)
@@ -83,13 +97,19 @@ func HandleRequest(ctx context.Context, req *Request) {
```
关键要点:
- 始终使用 `otelTrace.Logger.WithContext(ctx)` 获取日志器,而非直接使用 `zap.L()`
- 确保传入有效的 `context.Context`,通常由 HTTP 中间件或 RPC 框架提供。
- 避免在无上下文的场景中强制传递 `nil``WithContext()` 方法已处理该情况。
**节来源**
**节来源**
- [logs.go](file://internal/otelTrace/logs.go#L13-L28)
- [init.go](file://internal/otelTrace/init.go#L178)
## 总结
kami_gateway 通过 `CustomLogger` 结构体和 `WithContext()` 方法,实现了日志系统与 OpenTelemetry 分布式追踪的深度集成。该方案具备良好的健壮性,支持上下文缺失时的优雅降级,并通过 OTLP 协议实现日志的集中化管理。日志与 Trace ID 的自动关联极大提升了系统可观测性,为分布式环境下的问题排查提供了有力支持。建议在所有业务组件中统一采用 `WithContext()` 模式,确保日志上下文的一致性和完整性。
kami_gateway 通过 `CustomLogger` 结构体和 `WithContext()` 方法,实现了日志系统与 OpenTelemetry
分布式追踪的深度集成。该方案具备良好的健壮性,支持上下文缺失时的优雅降级,并通过 OTLP 协议实现日志的集中化管理。日志与 Trace
ID 的自动关联极大提升了系统可观测性,为分布式环境下的问题排查提供了有力支持。建议在所有业务组件中统一采用 `WithContext()`
模式,确保日志上下文的一致性和完整性。

View File

@@ -14,6 +14,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
@@ -25,10 +26,18 @@
9. [结论](#结论)
## 简介
本文档详细阐述了 `kami_gateway` 项目的技术栈及其在项目中的具体应用。文档深入分析了 Beego v2 框架的使用,包括路由配置、控制器继承和中间件机制。同时,详细说明了 OpenTelemetry 分布式追踪系统的实现,涵盖 `otelTrace` 包中的 span 创建、日志集成和链路追踪上下文传递。文档还解释了 Redis 在缓存(`internal/cache`)和代理池管理中的双重角色,描述了 MySQL 数据库通过 Beego ORM 进行数据访问的模式,以及 RabbitMQ 在订单通知和查询消费者中的异步消息处理机制。最后,结合 `main.go` 中的初始化代码,说明了这些技术组件是如何被集成和启动的,并提供了每个技术选型的决策理由、版本兼容性要求和性能考量,以及配置最佳实践。
本文档详细阐述了 `kami_gateway` 项目的技术栈及其在项目中的具体应用。文档深入分析了 Beego v2
框架的使用,包括路由配置、控制器继承和中间件机制。同时,详细说明了 OpenTelemetry 分布式追踪系统的实现,涵盖 `otelTrace` 包中的
span 创建、日志集成和链路追踪上下文传递。文档还解释了 Redis 在缓存(`internal/cache`)和代理池管理中的双重角色,描述了 MySQL
数据库通过 Beego ORM 进行数据访问的模式,以及 RabbitMQ 在订单通知和查询消费者中的异步消息处理机制。最后,结合 `main.go`
中的初始化代码,说明了这些技术组件是如何被集成和启动的,并提供了每个技术选型的决策理由、版本兼容性要求和性能考量,以及配置最佳实践。
## 项目结构
项目采用分层架构,主要分为 `conf``deploy``internal` 和根目录文件。`conf` 目录存放应用配置文件 `app.conf``deploy` 目录包含 Docker 部署相关的 `Dockerfile``docker-compose` 文件。`internal` 目录是项目的核心,包含了所有业务逻辑和基础设施代码,主要子目录包括:
项目采用分层架构,主要分为 `conf``deploy``internal` 和根目录文件。`conf` 目录存放应用配置文件 `app.conf``deploy` 目录包含
Docker 部署相关的 `Dockerfile``docker-compose` 文件。`internal` 目录是项目的核心,包含了所有业务逻辑和基础设施代码,主要子目录包括:
- `cache`: Redis 缓存客户端封装。
- `config`: 应用配置和常量定义。
- `controllers`: Beego 控制器,处理 HTTP 请求。
@@ -80,23 +89,33 @@ deploy --> main
```
**Diagram sources**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
**Section sources**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
## 核心组件
本项目的核心组件围绕 Beego Web 框架构建,通过 `main.go` 文件进行初始化和集成。核心功能包括基于 Beego 的 HTTP 路由和控制器处理、通过 `otelTrace` 实现的分布式追踪、利用 Redis 实现的缓存和代理池、通过 Beego ORM 访问 MySQL 数据库,以及使用 RabbitMQ 进行异步消息处理。这些组件共同构成了一个高可用、可观测的网关服务。
本项目的核心组件围绕 Beego Web 框架构建,通过 `main.go` 文件进行初始化和集成。核心功能包括基于 Beego 的 HTTP 路由和控制器处理、通过
`otelTrace` 实现的分布式追踪、利用 Redis 实现的缓存和代理池、通过 Beego ORM 访问 MySQL 数据库,以及使用 RabbitMQ
进行异步消息处理。这些组件共同构成了一个高可用、可观测的网关服务。
**Section sources**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L1-L256)
## 架构概览
系统采用典型的微服务网关架构。外部请求首先到达 Beego Web 服务器,经过 `otelTrace` 中间件进行分布式追踪和性能监控。请求被路由到相应的控制器(如 `ScanController`),控制器调用 `service` 层进行业务逻辑处理。业务逻辑层会通过 `models` 层的 Beego ORM 与 MySQL 数据库交互,并利用 `cache` 模块中的 Redis 客户端进行缓存读写。同时,系统通过 `notify``query` 模块启动后台消费者,从 RabbitMQ 消息队列中消费订单通知和查询任务,实现异步解耦。整个系统的可观测性由 OpenTelemetry 提供,所有追踪、指标和日志数据被导出到后端收集器。
系统采用典型的微服务网关架构。外部请求首先到达 Beego Web 服务器,经过 `otelTrace` 中间件进行分布式追踪和性能监控。请求被路由到相应的控制器(如
`ScanController`),控制器调用 `service` 层进行业务逻辑处理。业务逻辑层会通过 `models` 层的 Beego ORM 与 MySQL 数据库交互,并利用
`cache` 模块中的 Redis 客户端进行缓存读写。同时,系统通过 `notify``query` 模块启动后台消费者,从 RabbitMQ
消息队列中消费订单通知和查询任务,实现异步解耦。整个系统的可观测性由 OpenTelemetry 提供,所有追踪、指标和日志数据被导出到后端收集器。
```mermaid
graph TD
@@ -130,6 +149,7 @@ style Cache fill:#fcc,stroke:#333
```
**Diagram sources**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
- [internal/otelTrace/middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
@@ -139,21 +159,30 @@ style Cache fill:#fcc,stroke:#333
## 详细组件分析
### Beego v2 框架应用分析
Beego v2 框架是本项目的核心 Web 框架。它负责处理所有 HTTP 请求、路由分发和控制器逻辑。
#### 路由配置
项目的路由配置位于 `internal/routers/router.go` 文件中。通过调用 `web.Router` 函数,将不同的 URL 路径映射到对应的控制器和方法。例如,`/gateway/scan` 路径被映射到 `ScanController``Scan` 方法。此外,通过 `web.InsertFilterChain` 注册了一个全局过滤器链,将 `otelTrace.Middleware` 应用于所有路径(`*`),实现了对所有请求的统一追踪。
项目的路由配置位于 `internal/routers/router.go` 文件中。通过调用 `web.Router` 函数,将不同的 URL 路径映射到对应的控制器和方法。例如,
`/gateway/scan` 路径被映射到 `ScanController``Scan` 方法。此外,通过 `web.InsertFilterChain` 注册了一个全局过滤器链,将
`otelTrace.Middleware` 应用于所有路径(`*`),实现了对所有请求的统一追踪。
#### 控制器继承
控制器通过继承 Beego 的 `web.Controller` 来获得处理 HTTP 请求的能力。`internal/controllers/base_controller.go` 定义了 `BaseGateway` 结构体,它直接嵌入了 `web.Controller`。项目中的其他控制器,如 `ScanController`,通过继承 `BaseGateway` 来复用基础功能,实现了代码的复用和分层。
控制器通过继承 Beego 的 `web.Controller` 来获得处理 HTTP 请求的能力。`internal/controllers/base_controller.go` 定义了
`BaseGateway` 结构体,它直接嵌入了 `web.Controller`。项目中的其他控制器,如 `ScanController`,通过继承 `BaseGateway`
来复用基础功能,实现了代码的复用和分层。
#### 中间件使用
`otelTrace.Middleware` 是一个关键的中间件,它被注入到请求处理链中。该中间件的主要职责是:
1. **创建追踪 Span**:为每个 HTTP 请求创建一个新的追踪 Span记录请求的路径、方法、状态码等信息。
2. **上下文传递**从请求头中提取上游服务的追踪上下文Trace Context并将其注入到当前请求的上下文中实现跨服务的链路追踪
3. **性能监控**记录请求的处理时长并对慢请求超过5秒进行告警
4. **错误处理**:捕获处理过程中的 panic记录错误信息并返回 500 错误,实现优雅降级
5. **熔断保护**:集成了熔断器,当追踪系统自身出现异常时,可以自动熔断,避免影响核心业务
1. **创建追踪 Span**:为每个 HTTP 请求创建一个新的追踪 Span记录请求的路径、方法、状态码等信息
2. **上下文传递**从请求头中提取上游服务的追踪上下文Trace Context并将其注入到当前请求的上下文中实现跨服务的链路追踪
3. **性能监控**记录请求的处理时长并对慢请求超过5秒进行告警
4. **错误处理**:捕获处理过程中的 panic记录错误信息并返回 500 错误,实现优雅降级
5. **熔断保护**:集成了熔断器,当追踪系统自身出现异常时,可以自动熔断,避免影响核心业务。
```mermaid
sequenceDiagram
@@ -174,26 +203,37 @@ Beego-->>Client : HTTP 响应
```
**Diagram sources**
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
- [internal/controllers/base_controller.go](file://internal/controllers/base_controller.go#L1-L9)
- [internal/otelTrace/middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
**Section sources**
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
- [internal/controllers/base_controller.go](file://internal/controllers/base_controller.go#L1-L9)
- [internal/otelTrace/middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
### OpenTelemetry 分布式追踪分析
`internal/otelTrace` 包负责实现系统的分布式追踪、指标和日志功能。
#### 初始化与配置
`InitTracer` 函数在 `main.go` 中被调用,负责初始化 OpenTelemetry 的 Tracer、Meter 和 Logger Provider。它创建了 OTLP 导出器,将数据通过 gRPC 发送到后端收集器(如 Jaeger 或 Prometheus。配置中包含了超时、重试、批量发送和 Gzip 压缩等生产环境优化选项,以确保数据传输的可靠性和效率。
`InitTracer` 函数在 `main.go` 中被调用,负责初始化 OpenTelemetry 的 Tracer、Meter 和 Logger Provider。它创建了 OTLP
导出器,将数据通过 gRPC 发送到后端收集器(如 Jaeger 或 Prometheus。配置中包含了超时、重试、批量发送和 Gzip
压缩等生产环境优化选项,以确保数据传输的可靠性和效率。
#### Span 创建与上下文传递
如上文的序列图所示,`Middleware` 函数是创建 Span 的主要入口。它使用 `otel.Tracer("gateway-router").Start` 创建一个新的服务器 Span并通过 `propagator.Extract` 从 HTTP 请求头中提取 `traceparent` 等标准头信息,将当前 Span 与上游调用关联起来,形成完整的调用链路。
如上文的序列图所示,`Middleware` 函数是创建 Span 的主要入口。它使用 `otel.Tracer("gateway-router").Start` 创建一个新的服务器
Span并通过 `propagator.Extract` 从 HTTP 请求头中提取 `traceparent` 等标准头信息,将当前 Span 与上游调用关联起来,形成完整的调用链路。
#### 日志集成
项目集成了 `zap` 日志库,并通过 `otelzap` 桥接器将日志与 OpenTelemetry 关联。`InitTracer` 函数中配置了 `LoggerProvider`,使得所有通过 `otelTrace.Logger` 记录的日志都会携带当前的 Span 上下文。这使得在追踪系统中可以将日志与特定的请求 Span 关联起来,极大地提升了问题排查的效率。
项目集成了 `zap` 日志库,并通过 `otelzap` 桥接器将日志与 OpenTelemetry 关联。`InitTracer` 函数中配置了 `LoggerProvider`
,使得所有通过 `otelTrace.Logger` 记录的日志都会携带当前的 Span 上下文。这使得在追踪系统中可以将日志与特定的请求 Span
关联起来,极大地提升了问题排查的效率。
```mermaid
classDiagram
@@ -218,21 +258,29 @@ Middleware --> CustomLogger : 使用进行日志记录
```
**Diagram sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L1-L256)
- [internal/otelTrace/middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
**Section sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L1-L256)
- [internal/otelTrace/middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
### Redis 缓存与代理池分析
Redis 在本项目中扮演着双重角色:通用缓存和代理池管理。
#### 缓存角色 (internal/cache)
`internal/cache/redis.go` 文件封装了一个 `RedisClient` 结构体,提供了对 Redis 的高级操作。`Start` 函数在 `main.go` 中被调用,负责根据配置初始化 Redis 连接。该模块提供了 `Set``Get``Delete` 等基本操作,以及对 Redis List 和 Stream 数据结构的封装,如 `LPush``RPopUnmarshal``XAdd``XReadUnmarshal` 等,方便业务层使用。`sync.Once` 确保了连接的单例模式。
`internal/cache/redis.go` 文件封装了一个 `RedisClient` 结构体,提供了对 Redis 的高级操作。`Start` 函数在 `main.go`
中被调用,负责根据配置初始化 Redis 连接。该模块提供了 `Set``Get``Delete` 等基本操作,以及对 Redis List 和 Stream
数据结构的封装,如 `LPush``RPopUnmarshal``XAdd``XReadUnmarshal` 等,方便业务层使用。`sync.Once` 确保了连接的单例模式。
#### 代理池角色
代理池的管理也依赖于 Redis。`proxy.InitProxyPool` 函数在 `main.go` 中被调用,负责初始化代理池。虽然具体实现未在上下文中,但可以推断它会从配置中读取代理列表,并将其存储在 Redis 中(可能使用 List 或 Hash 结构),并通过 `utils.StartProxyPool` 启动一个后台任务来维护和轮询这些代理。
代理池的管理也依赖于 Redis。`proxy.InitProxyPool` 函数在 `main.go` 中被调用,负责初始化代理池。虽然具体实现未在上下文中,但可以推断它会从配置中读取代理列表,并将其存储在
Redis 中(可能使用 List 或 Hash 结构),并通过 `utils.StartProxyPool` 启动一个后台任务来维护和轮询这些代理。
```mermaid
flowchart TD
@@ -249,21 +297,32 @@ F --> J[代理池使用 LPush/RPop]
```
**Diagram sources**
- [main.go](file://main.go#L1-L58)
- [internal/cache/redis.go](file://internal/cache/redis.go#L1-L294)
**Section sources**
- [main.go](file://main.go#L1-L58)
- [internal/cache/redis.go](file://internal/cache/redis.go#L1-L294)
### MySQL 与 RabbitMQ 数据访问分析
系统通过 Beego ORM 访问 MySQL 数据库,并通过消息队列与外部系统异步通信。
#### MySQL 与 Beego ORM
`internal/models/init.go` 文件负责初始化数据库连接。它从 `app.conf` 配置文件中读取 MySQL 的连接信息(主机、用户名、密码、数据库名),并使用 `orm.RegisterDataBase` 注册一个名为 "default" 的数据库连接。`orm.RegisterModel` 函数注册了所有需要映射到数据库表的 Go 结构体(如 `MerchantInfo`, `OrderInfo` 等)。业务代码通过 `orm.NewOrm()` 创建 ORM 实例,然后使用 `QueryTable``Filter``All``One` 等方法进行数据查询和操作。
`internal/models/init.go` 文件负责初始化数据库连接。它从 `app.conf` 配置文件中读取 MySQL 的连接信息(主机、用户名、密码、数据库名),并使用
`orm.RegisterDataBase` 注册一个名为 "default" 的数据库连接。`orm.RegisterModel` 函数注册了所有需要映射到数据库表的 Go
结构体(如 `MerchantInfo`, `OrderInfo` 等)。业务代码通过 `orm.NewOrm()` 创建 ORM 实例,然后使用 `QueryTable``Filter`
`All``One` 等方法进行数据查询和操作。
#### RabbitMQ 异步消息处理
`main.go` 文件中的 `go notify.CreateOrderNotifyConsumer(otelTrace.InitCtx)``go query.CreateSupplierOrderQueryCuConsumer(otelTrace.InitCtx)` 启动了两个后台 goroutine分别作为 RabbitMQ 的消费者。`CreateOrderNotifyConsumer` 负责消费订单通知消息,`CreateSupplierOrderQueryCuConsumer` 负责消费供应商订单查询任务。这种异步模式解耦了核心交易流程和后续的通知/查询逻辑,提高了系统的吞吐量和响应速度。
`main.go` 文件中的 `go notify.CreateOrderNotifyConsumer(otelTrace.InitCtx)`
`go query.CreateSupplierOrderQueryCuConsumer(otelTrace.InitCtx)` 启动了两个后台 goroutine分别作为 RabbitMQ 的消费者。
`CreateOrderNotifyConsumer` 负责消费订单通知消息,`CreateSupplierOrderQueryCuConsumer`
负责消费供应商订单查询任务。这种异步模式解耦了核心交易流程和后续的通知/查询逻辑,提高了系统的吞吐量和响应速度。
```mermaid
erDiagram
@@ -292,15 +351,22 @@ MERCHANT_INFO ||--o{ PAYFOR_INFO : "拥有"
```
**Diagram sources**
- [internal/models/init.go](file://internal/models/init.go#L1-L56)
- [main.go](file://main.go#L1-L58)
**Section sources**
- [internal/models/init.go](file://internal/models/init.go#L1-L56)
- [main.go](file://main.go#L1-L58)
## 依赖分析
项目的主要依赖关系清晰地体现在 `main.go` 的初始化流程中。`main` 函数按顺序调用了 `config.GetMQAddress``proxy.InitProxyPool``otelTrace.InitTracer` 等函数,这些函数之间没有循环依赖,保证了启动的稳定性。`controllers` 依赖于 `service``service` 依赖于 `models``cache``models` 依赖于 Beego ORM形成了一个清晰的依赖树。外部依赖包括 `github.com/beego/beego/v2``github.com/go-sql-driver/mysql``github.com/redis/go-redis/v9``go.opentelemetry.io/otel` 等。
项目的主要依赖关系清晰地体现在 `main.go` 的初始化流程中。`main` 函数按顺序调用了 `config.GetMQAddress`
`proxy.InitProxyPool``otelTrace.InitTracer` 等函数,这些函数之间没有循环依赖,保证了启动的稳定性。`controllers` 依赖于
`service``service` 依赖于 `models``cache``models` 依赖于 Beego ORM形成了一个清晰的依赖树。外部依赖包括
`github.com/beego/beego/v2``github.com/go-sql-driver/mysql``github.com/redis/go-redis/v9`
`go.opentelemetry.io/otel` 等。
```mermaid
graph TD
@@ -322,33 +388,44 @@ otelTrace --> "go.opentelemetry.io/otel"
```
**Diagram sources**
- [main.go](file://main.go#L1-L58)
- [internal/routers/router.go](file://internal/routers/router.go#L1-L75)
**Section sources**
- [main.go](file://main.go#L1-L58)
## 性能考量
项目在多个层面进行了性能优化:
1. **连接池**Beego ORM 和 Redis 客户端内部都使用了连接池,避免了频繁创建和销毁连接的开销。
2. **异步处理**:通过 RabbitMQ 消费者异步处理订单通知和查询,避免了阻塞主请求流程
3. **缓存**Redis 被用作缓存,可以显著减少对数据库的直接访问
4. **批量操作**OpenTelemetry 的导出器配置了批量发送(`WithBatchTimeout`, `WithMaxExportBatchSize`),减少了网络请求次数
5. **熔断机制**`otelTrace` 中间件集成了熔断器,当追踪系统过载时,可以自动降级,保障核心业务不受影响
6. **资源复用**Redis 连接使用 `sync.Once` 保证单例,避免了资源浪费
1. **连接池**Beego ORM 和 Redis 客户端内部都使用了连接池,避免了频繁创建和销毁连接的开销
2. **异步处理**:通过 RabbitMQ 消费者异步处理订单通知和查询,避免了阻塞主请求流程
3. **缓存**Redis 被用作缓存,可以显著减少对数据库的直接访问
4. **批量操作**OpenTelemetry 的导出器配置了批量发送(`WithBatchTimeout`, `WithMaxExportBatchSize`),减少了网络请求次数
5. **熔断机制**`otelTrace` 中间件集成了熔断器,当追踪系统过载时,可以自动降级,保障核心业务不受影响
6. **资源复用**Redis 连接使用 `sync.Once` 保证单例,避免了资源浪费。
## 故障排除指南
* **Redis 连接失败**:检查 `app.conf` 中的 Redis 配置是否正确,确认 Redis 服务是否正常运行。查看日志中是否有 "redis 连接失败" 的错误信息。
* **MySQL 连接失败**:检查 `app.conf` 中的 MySQL 配置(主机、端口、用户名、密码、数据库名),确认 MySQL 服务是否正常运行。查看日志中是否有 "failed to create ORM" 或类似错误。
* **追踪数据未上报**:检查 `collectorURL` 配置是否正确,确认 OpenTelemetry Collector 服务是否正常运行。检查网络连通性
* **HTTP 500 错误**:查看日志中的 panic 信息或 "服务暂时不可用" 的错误,定位到具体的控制器或服务方法进行排查。
* **慢请求告警**:在日志中搜索 "慢请求检测",分析具体是哪个接口或哪段代码导致了性能瓶颈
* **Redis 连接失败**:检查 `app.conf` 中的 Redis 配置是否正确,确认 Redis 服务是否正常运行。查看日志中是否有 "redis
连接失败" 的错误信息
* **MySQL 连接失败**:检查 `app.conf` 中的 MySQL 配置(主机、端口、用户名、密码、数据库名),确认 MySQL
服务是否正常运行。查看日志中是否有 "failed to create ORM" 或类似错误
* **追踪数据未上报**:检查 `collectorURL` 配置是否正确,确认 OpenTelemetry Collector 服务是否正常运行。检查网络连通性。
* **HTTP 500 错误**:查看日志中的 panic 信息或 "服务暂时不可用" 的错误,定位到具体的控制器或服务方法进行排查。
* **慢请求告警**:在日志中搜索 "慢请求检测",分析具体是哪个接口或哪段代码导致了性能瓶颈。
**Section sources**
- [internal/cache/redis.go](file://internal/cache/redis.go#L1-L294)
- [internal/models/init.go](file://internal/models/init.go#L1-L56)
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L1-L256)
- [internal/otelTrace/middleware.go](file://internal/otelTrace/middleware.go#L1-L141)
## 结论
`kami_gateway` 项目构建了一个技术栈选型合理、架构清晰的微服务网关。通过 Beego v2 框架提供了稳定高效的 Web 服务OpenTelemetry 实现了全面的可观测性Redis 和 MySQL 分别承担了缓存和持久化存储的角色RabbitMQ 则保证了系统的异步解耦。各组件通过 `main.go` 文件被有序地集成和启动,形成了一个高可用、高性能、易于维护的系统。遵循文档中提到的配置最佳实践,可以确保系统在生产环境中的稳定运行。
`kami_gateway` 项目构建了一个技术栈选型合理、架构清晰的微服务网关。通过 Beego v2 框架提供了稳定高效的 Web 服务OpenTelemetry
实现了全面的可观测性Redis 和 MySQL 分别承担了缓存和持久化存储的角色RabbitMQ 则保证了系统的异步解耦。各组件通过
`main.go` 文件被有序地集成和启动,形成了一个高可用、高性能、易于维护的系统。遵循文档中提到的配置最佳实践,可以确保系统在生产环境中的稳定运行。

View File

@@ -9,6 +9,7 @@
</cite>
## 目录
1. [数据库初始化流程](#数据库初始化流程)
2. [配置文件解析](#配置文件解析)
3. [ORM模型注册机制](#orm模型注册机制)
@@ -19,7 +20,9 @@
## 数据库初始化流程
系统在启动时通过`internal/models/init.go`文件中的`init`函数完成MySQL数据库的初始化配置。该函数在程序启动时自动执行负责从配置文件读取数据库连接参数构造连接字符串并完成Beego ORM的注册与配置。
系统在启动时通过`internal/models/init.go`文件中的`init`
函数完成MySQL数据库的初始化配置。该函数在程序启动时自动执行负责从配置文件读取数据库连接参数构造连接字符串并完成Beego
ORM的注册与配置。
```mermaid
flowchart TD
@@ -34,14 +37,17 @@ RegisterModels --> End([数据库初始化完成])
```
**Diagram sources**
- [internal/models/init.go](file://internal/models/init.go#L22-L56)
**Section sources**
- [internal/models/init.go](file://internal/models/init.go#L1-L56)
## 配置文件解析
数据库连接参数从`conf/app.conf`配置文件中读取采用分段式配置结构。系统通过Beego框架提供的`web.AppConfig`接口获取配置值,确保了配置管理的统一性和灵活性。
数据库连接参数从`conf/app.conf`配置文件中读取采用分段式配置结构。系统通过Beego框架提供的`web.AppConfig`
接口获取配置值,确保了配置管理的统一性和灵活性。
```ini
[mysql]
@@ -54,6 +60,7 @@ debug = true
```
配置项说明:
- **dbhost**: 数据库服务器地址
- **dbport**: 数据库服务端口
- **dbuser**: 数据库用户名
@@ -64,12 +71,14 @@ debug = true
系统使用`web.AppConfig.String()`方法读取字符串类型配置,使用`web.AppConfig.DefaultBool()`方法读取布尔类型配置并提供默认值。
**Section sources**
- [conf/app.conf](file://conf/app.conf#L40-L47)
- [internal/models/init.go](file://internal/models/init.go#L22-L30)
## ORM模型注册机制
Beego ORM通过`RegisterModel`函数注册所有需要映射到数据库表的实体模型。系统采用集中式注册方式,在`init`函数中一次性注册所有模型,确保了模型管理的统一性。
Beego ORM通过`RegisterModel`函数注册所有需要映射到数据库表的实体模型。系统采用集中式注册方式,在`init`
函数中一次性注册所有模型,确保了模型管理的统一性。
```mermaid
classDiagram
@@ -128,11 +137,13 @@ NotifyInfo --> OrderInfo : "notifies"
```
**Diagram sources**
- [internal/models/init.go](file://internal/models/init.go#L48-L55)
- [internal/models/order/order_info.go](file://internal/models/order/order_info.go#L19-L68)
- [internal/models/merchant/merchant_info.go](file://internal/models/merchant/merchant_info.go#L12-L32)
**Section sources**
- [internal/models/init.go](file://internal/models/init.go#L48-L55)
## 数据库连接池管理
@@ -140,11 +151,13 @@ NotifyInfo --> OrderInfo : "notifies"
系统通过Beego ORM的`RegisterDataBase`函数建立数据库连接池使用标准的MySQL连接字符串格式。连接池配置支持自动连接恢复和连接复用提高了数据库访问效率。
连接字符串构造逻辑:
```
用户名:密码@tcp(主机:端口)/数据库名?charset=utf8&loc=Local&parseTime=true
```
关键参数说明:
- **charset=utf8**: 指定字符集为UTF-8支持中文存储
- **loc=Local**: 设置时区为本地时区
- **parseTime=true**: 启用时间类型自动解析
@@ -152,17 +165,20 @@ NotifyInfo --> OrderInfo : "notifies"
系统还通过`orm.Debug`开关控制SQL日志输出便于开发调试和性能分析。
**Section sources**
- [internal/models/init.go](file://internal/models/init.go#L32-L40)
## 实体模型结构
系统定义了多个实体模型来管理不同的业务数据每个模型对应数据库中的一张表。模型采用Go语言的结构体定义通过字段标签和Beego ORM约定实现数据库映射。
系统定义了多个实体模型来管理不同的业务数据每个模型对应数据库中的一张表。模型采用Go语言的结构体定义通过字段标签和Beego
ORM约定实现数据库映射。
### 订单信息模型
`OrderInfo`结构体定义了订单相关的核心数据字段,包括订单标识、金额信息、状态跟踪和时间戳等。
**Section sources**
- [internal/models/order/order_info.go](file://internal/models/order/order_info.go#L19-L68)
### 商户信息模型
@@ -170,6 +186,7 @@ NotifyInfo --> OrderInfo : "notifies"
`MerchantInfo`结构体管理商户的基本信息、认证凭证和业务配置,是系统权限控制和业务处理的基础。
**Section sources**
- [internal/models/merchant/merchant_info.go](file://internal/models/merchant/merchant_info.go#L12-L32)
## 数据操作实践
@@ -194,6 +211,7 @@ Service-->>调用方 : 返回业务数据
```
**Diagram sources**
- [internal/models/order/order_info.go](file://internal/models/order/order_info.go#L100-L120)
- [internal/models/merchant/merchant_info.go](file://internal/models/merchant/merchant_info.go#L90-L110)
@@ -202,6 +220,7 @@ Service-->>调用方 : 返回业务数据
系统通过`Insert``InsertWithCtx`方法实现数据插入,支持上下文传递以实现请求链路追踪。
**Section sources**
- [internal/models/order/order_info.go](file://internal/models/order/order_info.go#L150-L160)
- [internal/models/merchant/merchant_info.go](file://internal/models/merchant/merchant_info.go#L170-L180)
@@ -210,6 +229,7 @@ Service-->>调用方 : 返回业务数据
采用`Update``UpdateWithCtx`方法进行数据更新,支持批量更新和条件更新,确保了数据一致性。
**Section sources**
- [internal/models/order/order_info.go](file://internal/models/order/order_info.go#L130-L140)
- [internal/models/merchant/merchant_info.go](file://internal/models/merchant/merchant_info.go#L200-L210)
@@ -218,6 +238,7 @@ Service-->>调用方 : 返回业务数据
### 索引设计策略
根据业务查询模式,在高频查询字段上创建索引:
- 订单表:`bank_order_id`, `merchant_order_id`, `create_time`, `status`
- 商户表:`merchant_uid`, `login_account`, `belong_agent_uid`
- 通道表:`road_uid`, `pay_product_code`
@@ -225,6 +246,7 @@ Service-->>调用方 : 返回业务数据
### 连接池调优
建议根据实际并发量调整连接池参数:
- 设置合理的最大连接数MaxOpenConns
- 配置适当的空闲连接数MaxIdleConns
- 设置连接生命周期ConnMaxLifetime

View File

@@ -10,6 +10,7 @@
</cite>
## 目录
1. [Redis双重角色概述](#redis双重角色概述)
2. [单例初始化流程](#单例初始化流程)
3. [分布式缓存应用](#分布式缓存应用)
@@ -23,17 +24,22 @@
Redis在系统中扮演着双重关键角色作为分布式缓存层处理高频数据访问以及作为代理池的状态存储中心。这种双重架构设计实现了数据访问加速与代理资源管理的统一。
作为分布式缓存层Redis被广泛应用于订单状态缓存、商户配置缓存等高频读取场景有效降低了数据库的访问压力。同时Redis作为代理池的状态存储通过`internal/utils/dm_proxy_strategy.go`中的`DMProxyStrategy`结构体将代理IP、过期时间、最后使用时间等关键信息持久化存储在Redis中实现了代理资源的集中管理和状态追踪。
作为分布式缓存层Redis被广泛应用于订单状态缓存、商户配置缓存等高频读取场景有效降低了数据库的访问压力。同时Redis作为代理池的状态存储通过
`internal/utils/dm_proxy_strategy.go`中的`DMProxyStrategy`结构体将代理IP、过期时间、最后使用时间等关键信息持久化存储在Redis中实现了代理资源的集中管理和状态追踪。
**Section sources**
- [dm_proxy_strategy.go](file://internal/utils/dm_proxy_strategy.go#L38-L43)
- [proxy_pool.go](file://internal/utils/proxy_pool.go#L537-L553)
## 单例初始化流程
Redis客户端的初始化通过`internal/cache/redis.go`中的`Start()`函数实现,采用`sync.OnceFunc`确保线程安全的单例模式。该流程严格遵循一次性初始化原则,防止并发环境下重复创建连接实例。
Redis客户端的初始化通过`internal/cache/redis.go`中的`Start()`函数实现,采用`sync.OnceFunc`
确保线程安全的单例模式。该流程严格遵循一次性初始化原则,防止并发环境下重复创建连接实例。
初始化过程首先从配置中心获取Redis连接参数包括主机地址、密码和数据库编号。这些参数通过`config.GetConfig().GetRedisConfig()`方法从`conf/app.conf`文件中读取,其中默认配置为主机`127.0.0.1:6379`,数据库`db=0`,密码为空字符串。
初始化过程首先从配置中心获取Redis连接参数包括主机地址、密码和数据库编号。这些参数通过
`config.GetConfig().GetRedisConfig()`方法从`conf/app.conf`文件中读取,其中默认配置为主机`127.0.0.1:6379`,数据库`db=0`
,密码为空字符串。
```mermaid
sequenceDiagram
@@ -50,11 +56,13 @@ cache-->>main : 初始化完成
```
**Diagram sources**
- [redis.go](file://internal/cache/redis.go#L13-L25)
- [cfg_model.go](file://internal/config/cfg_model.go#L127-L133)
- [app.conf](file://conf/app.conf#L60-L63)
**Section sources**
- [redis.go](file://internal/cache/redis.go#L13-L30)
- [cfg_model.go](file://internal/config/cfg_model.go#L127-L133)
@@ -62,21 +70,27 @@ cache-->>main : 初始化完成
Redis作为分布式缓存层在系统中支撑着多个核心业务场景的数据访问。通过`GetRedisClient()`函数提供的全局访问点,各业务模块可以高效地进行缓存操作。
在订单管理场景中系统利用Redis的List数据结构实现订单队列管理通过`LPush``RPopUnmarshal`等方法进行订单的入队和出队操作。对于商户配置信息采用Hash结构进行存储实现配置项的快速读取和更新。高频访问的静态数据如支付通道信息通过`Set``Get`方法进行缓存,显著提升了接口响应速度。
在订单管理场景中系统利用Redis的List数据结构实现订单队列管理通过`LPush``RPopUnmarshal`
等方法进行订单的入队和出队操作。对于商户配置信息采用Hash结构进行存储实现配置项的快速读取和更新。高频访问的静态数据如支付通道信息通过
`Set``Get`方法进行缓存,显著提升了接口响应速度。
缓存策略采用合理的过期时间设置,避免数据长期驻留导致内存浪费。同时,通过`Exists`方法实现缓存预热和缓存击穿的预防,确保系统在高并发场景下的稳定性。
**Section sources**
- [redis.go](file://internal/cache/redis.go#L70-L100)
- [redis.go](file://internal/cache/redis.go#L134-L183)
## 代理池状态存储
Redis在代理池管理中发挥着核心作用通过与`internal/utils/proxy_pool.go``internal/utils/dm_proxy_strategy.go`的深度集成,实现了代理资源的智能化管理。
Redis在代理池管理中发挥着核心作用通过与`internal/utils/proxy_pool.go``internal/utils/dm_proxy_strategy.go`
的深度集成,实现了代理资源的智能化管理。
`DMProxyStrategy`结构体直接持有`*redis.Client`引用将Redis作为代理状态的唯一事实来源。代理IP、过期时间、最后使用时间等信息以`DMProxyInfo`结构体形式序列化后存储在Redis中键名为`proxy:{ip}`。这种设计实现了代理状态的持久化,即使服务重启也能恢复代理池状态。
`DMProxyStrategy`结构体直接持有`*redis.Client`引用将Redis作为代理状态的唯一事实来源。代理IP、过期时间、最后使用时间等信息以
`DMProxyInfo`结构体形式序列化后存储在Redis中键名为`proxy:{ip}`。这种设计实现了代理状态的持久化,即使服务重启也能恢复代理池状态。
代理池的清理机制通过定时任务实现,`startCleanupRoutine`协程每分钟执行一次`cleanupExpiredProxies`,清除过期的代理记录。同时,`ensureProxyPool`方法确保代理池中始终有足够的可用代理,当数量低于阈值时自动从代理服务商拉取新代理。
代理池的清理机制通过定时任务实现,`startCleanupRoutine`协程每分钟执行一次`cleanupExpiredProxies`,清除过期的代理记录。同时,
`ensureProxyPool`方法确保代理池中始终有足够的可用代理,当数量低于阈值时自动从代理服务商拉取新代理。
```mermaid
classDiagram
@@ -105,34 +119,44 @@ DMProxyStrategy --> redis.Client : "状态存储"
```
**Diagram sources**
- [dm_proxy_strategy.go](file://internal/utils/dm_proxy_strategy.go#L38-L43)
- [dm_proxy_strategy.go](file://internal/utils/dm_proxy_strategy.go#L11-L18)
**Section sources**
- [dm_proxy_strategy.go](file://internal/utils/dm_proxy_strategy.go#L35-L377)
- [proxy_pool.go](file://internal/utils/proxy_pool.go#L537-L553)
## 键值设计与序列化
系统采用规范化的键值设计原则确保Redis数据的可维护性和可追溯性。键名遵循`domain:subdomain:identifier`的命名规范,如订单相关的键以`order:`为前缀,代理相关的键以`proxy:`为前缀。
系统采用规范化的键值设计原则确保Redis数据的可维护性和可追溯性。键名遵循`domain:subdomain:identifier`的命名规范,如订单相关的键以
`order:`为前缀,代理相关的键以`proxy:`为前缀。
序列化策略根据数据类型和使用场景进行优化。对于结构体数据采用JSON序列化通过`json.Marshal``json.Unmarshal`实现对象的持久化。对于简单类型数据直接使用Redis原生的字符串存储。在List和Stream操作中系统自动将复杂对象序列化为JSON字符串后存储确保数据的完整性和可读性。
序列化策略根据数据类型和使用场景进行优化。对于结构体数据采用JSON序列化通过`json.Marshal``json.Unmarshal`
实现对象的持久化。对于简单类型数据直接使用Redis原生的字符串存储。在List和Stream操作中系统自动将复杂对象序列化为JSON字符串后存储确保数据的完整性和可读性。
特殊数据类型如时间戳采用Unix时间戳存储避免时区问题。布尔值通过`"true"``"false"`字符串表示,确保跨语言兼容性。这种统一的序列化策略简化了数据访问逻辑,降低了维护成本。
特殊数据类型如时间戳采用Unix时间戳存储避免时区问题。布尔值通过`"true"``"false"`
字符串表示,确保跨语言兼容性。这种统一的序列化策略简化了数据访问逻辑,降低了维护成本。
**Section sources**
- [redis.go](file://internal/cache/redis.go#L134-L183)
- [redis.go](file://internal/cache/redis.go#L224-L275)
## 连接池与性能配置
Redis客户端配置了合理的连接池参数以平衡性能和资源消耗。虽然当前实现中未显式设置连接池大小但依赖于`github.com/redis/go-redis/v9`库的默认连接池行为。
Redis客户端配置了合理的连接池参数以平衡性能和资源消耗。虽然当前实现中未显式设置连接池大小但依赖于
`github.com/redis/go-redis/v9`库的默认连接池行为。
连接超时、读取超时和写入超时均设置为合理的默认值,确保在网络异常时能够快速失败,避免请求堆积。通过`Ping()`方法在初始化时进行连接测试确保Redis服务的可用性。
连接超时、读取超时和写入超时均设置为合理的默认值,确保在网络异常时能够快速失败,避免请求堆积。通过`Ping()`
方法在初始化时进行连接测试确保Redis服务的可用性。
在高并发场景下系统利用Redis管道Pipeline和事务管道TxPipeline功能通过`Pipeline()``TxPipeline()`方法减少网络往返次数,提升批量操作的性能。发布/订阅模式通过`Publish()``Subscribe()`方法实现,支持实时消息通知。
在高并发场景下系统利用Redis管道Pipeline和事务管道TxPipeline功能通过`Pipeline()``TxPipeline()`
方法减少网络往返次数,提升批量操作的性能。发布/订阅模式通过`Publish()``Subscribe()`方法实现,支持实时消息通知。
**Section sources**
- [redis.go](file://internal/cache/redis.go#L35-L50)
- [redis.go](file://internal/cache/redis.go#L290-L300)
@@ -140,13 +164,17 @@ Redis客户端配置了合理的连接池参数以平衡性能和资源消耗
系统实现了多层次的缓存防护机制,有效应对缓存穿透、缓存击穿和缓存雪崩等常见问题。
对于缓存穿透,系统在数据访问层进行参数校验,拒绝非法请求。同时,对于查询结果为空的情况,设置短时间的空值缓存,避免相同请求频繁穿透到数据库。通过`Exists`方法预先检查键的存在性,减少不必要的查询操作。
对于缓存穿透,系统在数据访问层进行参数校验,拒绝非法请求。同时,对于查询结果为空的情况,设置短时间的空值缓存,避免相同请求频繁穿透到数据库。通过
`Exists`方法预先检查键的存在性,减少不必要的查询操作。
缓存击穿通过合理的过期时间设置和互斥锁机制防范。虽然当前代码中未显式实现分布式锁,但`sync.OnceFunc`的单例模式为关键初始化操作提供了线程安全保证。在热点数据更新时,采用先更新缓存后更新数据库的策略,确保数据一致性。
缓存击穿通过合理的过期时间设置和互斥锁机制防范。虽然当前代码中未显式实现分布式锁,但`sync.OnceFunc`
的单例模式为关键初始化操作提供了线程安全保证。在热点数据更新时,采用先更新缓存后更新数据库的策略,确保数据一致性。
监控方面,系统通过`GetSize()``GetSizeByPrefix()`方法定期采集Redis内存使用情况为容量规划提供数据支持。错误日志通过`otelTrace.Logger`记录,便于问题排查和性能分析。
监控方面,系统通过`GetSize()``GetSizeByPrefix()`方法定期采集Redis内存使用情况为容量规划提供数据支持。错误日志通过
`otelTrace.Logger`记录,便于问题排查和性能分析。
**Section sources**
- [redis.go](file://internal/cache/redis.go#L102-L115)
- [redis.go](file://internal/cache/redis.go#L117-L125)
@@ -156,10 +184,12 @@ Redis客户端配置了合理的连接池参数以平衡性能和资源消耗
健康检查通过`Ping()`方法实现定期验证Redis连接的可用性。在`Start()`函数中,连接失败时会记录详细的错误日志,包括错误类型和堆栈信息,为故障排查提供依据。
运维方面,系统提供了`Delete()`方法用于手动清理缓存,`Expire()`方法用于动态调整键的过期时间。通过`Keys()`方法配合前缀查询,可以统计特定类型数据的数量,为容量规划和性能优化提供数据支持。
运维方面,系统提供了`Delete()`方法用于手动清理缓存,`Expire()`方法用于动态调整键的过期时间。通过`Keys()`
方法配合前缀查询,可以统计特定类型数据的数量,为容量规划和性能优化提供数据支持。
定期的清理任务通过协程实现,如代理池的`startCleanupRoutine`,确保过期数据能够及时清理,避免内存泄漏。这种主动式运维策略提高了系统的自愈能力,降低了人工干预的需求。
**Section sources**
- [redis.go](file://internal/cache/redis.go#L13-L30)
- [dm_proxy_strategy.go](file://internal/utils/dm_proxy_strategy.go#L251-L263)

View File

@@ -12,6 +12,7 @@
</cite>
## 目录
1. [项目结构](#项目结构)
2. [MySQL 数据库集成与 ORM 配置](#mysql-数据库集成与-orm-配置)
3. [Beego ORM 模型定义与自动迁移](#beego-orm-模型定义与自动迁移)
@@ -27,7 +28,8 @@
## 项目结构
本项目采用分层架构设计,核心数据持久化与缓存组件集中于 `internal` 目录下。`models` 目录存放所有数据库实体模型,`cache` 目录封装 Redis 客户端,`proxy``utils` 目录分别实现代理池的两种状态存储方案。
本项目采用分层架构设计,核心数据持久化与缓存组件集中于 `internal` 目录下。`models` 目录存放所有数据库实体模型,`cache` 目录封装
Redis 客户端,`proxy``utils` 目录分别实现代理池的两种状态存储方案。
```mermaid
graph TD
@@ -57,13 +59,15 @@ Cache --> Utils
```
**图源**
- [conf/app.conf](file://conf/app.conf)
- [internal/models/init.go](file://internal/models/init.go)
- [internal/cache/redis.go](file://internal/cache/redis.go)
## MySQL 数据库集成与 ORM 配置
系统通过 Beego ORM 框架集成 MySQL 数据库,使用 `github.com/go-sql-driver/mysql` 作为底层驱动。数据库连接信息从 `conf/app.conf` 文件中读取,并在程序启动时完成初始化。
系统通过 Beego ORM 框架集成 MySQL 数据库,使用 `github.com/go-sql-driver/mysql` 作为底层驱动。数据库连接信息从
`conf/app.conf` 文件中读取,并在程序启动时完成初始化。
```mermaid
sequenceDiagram
@@ -84,16 +88,19 @@ ORM->>MySQL : 建立连接
```
**图源**
- [internal/models/init.go](file://internal/models/init.go#L21-L55)
- [conf/app.conf](file://conf/app.conf#L15-L21)
**本节源码**
- [internal/models/init.go](file://internal/models/init.go#L21-L55)
- [conf/app.conf](file://conf/app.conf#L15-L21)
## Beego ORM 模型定义与自动迁移
`models` 目录下的各子包(如 `accounts`, `merchant`, `order` 等)定义了具体的数据库实体。每个实体结构体通过 `orm.RegisterModel()``init()` 函数中注册Beego ORM 会根据结构体字段自动生成对应的数据库表。
`models` 目录下的各子包(如 `accounts`, `merchant`, `order` 等)定义了具体的数据库实体。每个实体结构体通过
`orm.RegisterModel()``init()` 函数中注册Beego ORM 会根据结构体字段自动生成对应的数据库表。
```mermaid
classDiagram
@@ -148,6 +155,7 @@ PayforInfo --> BankCardInfo : "关联银行卡"
```
**图源**
- [internal/models/user/user_info.go](file://internal/models/user/user_info.go)
- [internal/models/merchant/merchant_info.go](file://internal/models/merchant/merchant_info.go)
- [internal/models/order/order_info.go](file://internal/models/order/order_info.go)
@@ -157,7 +165,8 @@ PayforInfo --> BankCardInfo : "关联银行卡"
## 事务处理模式
系统通过 Beego ORM 的 `orm.NewOrm()` 方法获取事务对象,支持显式事务控制。在需要保证数据一致性的业务场景(如订单创建、资金变动)中,使用 `Begin()``Commit()``Rollback()` 方法管理事务。
系统通过 Beego ORM 的 `orm.NewOrm()` 方法获取事务对象,支持显式事务控制。在需要保证数据一致性的业务场景(如订单创建、资金变动)中,使用
`Begin()``Commit()``Rollback()` 方法管理事务。
```mermaid
flowchart TD
@@ -172,11 +181,13 @@ Rollback --> End
```
**本节源码**
- [internal/models/init.go](file://internal/models/init.go#L21-L55)
## Redis 缓存系统架构
Redis 在系统中扮演双重角色:作为 `internal/cache` 的分布式缓存层,加速高频数据读取;作为 `internal/utils/proxy_pool.go` 中代理池的状态存储管理代理IP的生命周期。
Redis 在系统中扮演双重角色:作为 `internal/cache` 的分布式缓存层,加速高频数据读取;作为 `internal/utils/proxy_pool.go`
中代理池的状态存储管理代理IP的生命周期。
```mermaid
graph TB
@@ -198,12 +209,14 @@ ProxyPool --> |状态存储| RedisServer
```
**图源**
- [internal/cache/redis.go](file://internal/cache/redis.go)
- [internal/utils/proxy_pool.go](file://internal/utils/proxy_pool.go)
## Redis 初始化与连接池管理
Redis 客户端通过 `cache.Start()` 函数进行单例初始化。该函数使用 `sync.OnceFunc` 确保全局唯一实例,从配置中读取连接参数,并建立与 Redis 服务器的连接。
Redis 客户端通过 `cache.Start()` 函数进行单例初始化。该函数使用 `sync.OnceFunc` 确保全局唯一实例,从配置中读取连接参数,并建立与
Redis 服务器的连接。
```mermaid
sequenceDiagram
@@ -226,16 +239,19 @@ end
```
**图源**
- [internal/cache/redis.go](file://internal/cache/redis.go#L18-L28)
- [internal/config/cfg_model.go](file://internal/config/cfg_model.go#L128-L138)
**本节源码**
- [internal/cache/redis.go](file://internal/cache/redis.go#L18-L28)
- [internal/config/cfg_model.go](file://internal/config/cfg_model.go#L128-L138)
## 分布式缓存层设计
`internal/cache` 包提供了一个封装的 `RedisClient` 结构体,对 `github.com/redis/go-redis/v9` 客户端进行了二次封装,提供了更便捷的 API。该层支持字符串、列表、Stream 等多种数据结构的操作,并内置了序列化/反序列化功能。
`internal/cache` 包提供了一个封装的 `RedisClient` 结构体,对 `github.com/redis/go-redis/v9` 客户端进行了二次封装,提供了更便捷的
API。该层支持字符串、列表、Stream 等多种数据结构的操作,并内置了序列化/反序列化功能。
```mermaid
classDiagram
@@ -258,11 +274,14 @@ RedisClient 的使用 --> ProxyPool : "代理状态"
```
**图源**
- [internal/cache/redis.go](file://internal/cache/redis.go#L35-L37)
## 代理池状态存储机制
`internal/utils/proxy_pool.go` 实现了一个基于订单号和通道的代理策略(`OrderBasedProxyStrategy`)。它使用内存中的 `map[string]*ProxyInfo` 来缓存代理IP并通过 `StartProxyPool()` 函数启动。该策略支持跨通道复用代理IP以满足 `OrderPerIP` 配置的需求。
`internal/utils/proxy_pool.go` 实现了一个基于订单号和通道的代理策略(`OrderBasedProxyStrategy`)。它使用内存中的
`map[string]*ProxyInfo` 来缓存代理IP并通过 `StartProxyPool()` 函数启动。该策略支持跨通道复用代理IP以满足 `OrderPerIP`
配置的需求。
```mermaid
flowchart TD
@@ -279,47 +298,57 @@ ReturnNew --> End
```
**图源**
- [internal/utils/proxy_pool.go](file://internal/utils/proxy_pool.go#L100-L150)
**本节源码**
- [internal/utils/proxy_pool.go](file://internal/utils/proxy_pool.go#L100-L150)
## 键值设计规范
系统遵循清晰的键值命名规范以确保数据的可维护性和可读性。Redis 键通常采用 `domain:subdomain:identifier` 的格式。
| 键前缀 | 用途 | 示例 | TTL |
| :--- | :--- | :--- | :--- |
| `nuclear_random_ids:` | 核弹卡密的随机ID与指纹映射 | `nuclear_random_ids:abc123` | 永久 (0) |
| `channel_orderID` | 代理池缓存键 | `appleCard_order_123` | 50-55秒 |
| `customer_order_pool:` | 用户订单池 | `customer_order_pool:road123:100.00` | 动态 |
| `produce_order_pool:` | 供应订单池 | `produce_order_pool:road123:100.00` | 动态 |
| 键前缀 | 用途 | 示例 | TTL |
|:-----------------------|:---------------|:-------------------------------------|:-------|
| `nuclear_random_ids:` | 核弹卡密的随机ID与指纹映射 | `nuclear_random_ids:abc123` | 永久 (0) |
| `channel_orderID` | 代理池缓存键 | `appleCard_order_123` | 50-55秒 |
| `customer_order_pool:` | 用户订单池 | `customer_order_pool:road123:100.00` | 动态 |
| `produce_order_pool:` | 供应订单池 | `produce_order_pool:road123:100.00` | 动态 |
**本节源码**
- [internal/service/supplier/third_party/pool/card_sender/nuclear.go](file://internal/service/supplier/third_party/pool/card_sender/nuclear.go#L48)
- [internal/utils/proxy_pool.go](file://internal/utils/proxy_pool.go#L100)
## 数据库连接管理
数据库连接由 Beego ORM 在 `init()` 函数中统一管理。通过 `RegisterDataBase()` 注册默认数据库连接,并使用全局的 `orm.Debug` 标志控制SQL日志输出。所有数据访问操作都通过 `orm.NewOrm()` 获取的 ORM 实例进行。
数据库连接由 Beego ORM 在 `init()` 函数中统一管理。通过 `RegisterDataBase()` 注册默认数据库连接,并使用全局的 `orm.Debug`
标志控制SQL日志输出。所有数据访问操作都通过 `orm.NewOrm()` 获取的 ORM 实例进行。
**本节源码**
- [internal/models/init.go](file://internal/models/init.go#L21-L55)
## 缓存穿透与击穿应对策略
系统通过以下策略应对缓存问题:
- **缓存穿透**:对于 `nuclear_random_ids` 这类键即使Redis中不存在也会生成一个临时ID和指纹作为兜底避免大量请求直接打到数据库。
- **缓存击穿**`OrderBasedProxyStrategy` 使用 `sync.RWMutex``proxies` 映射进行读写保护在获取新代理时加写锁确保并发安全防止同一时间大量请求穿透到代理服务API。
- **缓存击穿**`OrderBasedProxyStrategy` 使用 `sync.RWMutex``proxies`
映射进行读写保护在获取新代理时加写锁确保并发安全防止同一时间大量请求穿透到代理服务API。
**本节源码**
- [internal/service/supplier/third_party/pool/card_sender/nuclear.go](file://internal/service/supplier/third_party/pool/card_sender/nuclear.go#L81)
- [internal/utils/proxy_pool.go](file://internal/utils/proxy_pool.go#L100)
## 性能监控指标
系统通过 `otelTrace` 模块集成 OpenTelemetry对关键操作进行监控。日志中记录了代理获取、Redis操作、数据库查询等耗时可用于分析性能瓶颈。例如`GetProxy` 函数被标记为一个Span可以追踪其执行时间。
系统通过 `otelTrace` 模块集成 OpenTelemetry对关键操作进行监控。日志中记录了代理获取、Redis操作、数据库查询等耗时可用于分析性能瓶颈。例如
`GetProxy` 函数被标记为一个Span可以追踪其执行时间。
**本节源码**
- [internal/utils/proxy_pool.go](file://internal/utils/proxy_pool.go#L301)
- [internal/cache/redis.go](file://internal/cache/redis.go)

View File

@@ -13,6 +13,7 @@
</cite>
## 目录
1. [引言](#引言)
2. [核心组件与架构](#核心组件与架构)
3. [消息消费者启动流程](#消息消费者启动流程)
@@ -25,10 +26,14 @@
10. [事件解耦与业务流程](#事件解耦与业务流程)
## 引言
本文档详细描述了 `kami_gateway` 系统中基于 RabbitMQ 的异步任务处理机制。系统通过 `github.com/go-stomp/stomp/v3` 客户端实现消息队列通信,采用消息队列解耦核心支付流程,提升系统响应性与可扩展性。重点分析订单通知、渠道查询等关键事件的异步处理流程,并深入探讨队列管理器、任务处理器与工作协程之间的协作机制。
本文档详细描述了 `kami_gateway` 系统中基于 RabbitMQ 的异步任务处理机制。系统通过 `github.com/go-stomp/stomp/v3`
客户端实现消息队列通信,采用消息队列解耦核心支付流程,提升系统响应性与可扩展性。重点分析订单通知、渠道查询等关键事件的异步处理流程,并深入探讨队列管理器、任务处理器与工作协程之间的协作机制。
## 核心组件与架构
系统通过 ActiveMQ兼容 STOMP 协议)作为消息中间件,结合 Redis 队列与 Go 协程池实现多层级异步任务处理。主要组件包括:
- **消息生产者**:通过 `SendMessage` 发布订单通知与查询请求
- **消息消费者**`CreateOrderNotifyConsumer``CreateSupplierOrderQueryCuConsumer` 监听队列
- **队列管理器**`QueueManager` 统一管理 Redis 队列生命周期
@@ -62,28 +67,38 @@ BusinessLogic --> |结果| OrderNotify
BusinessLogic --> |结果| SupplierQuery
```
**图示来源**
**图示来源**
- [manager.go](file://internal/service/supplier/third_party/queue/manager.go)
- [handlers.go](file://internal/service/supplier/third_party/queue/handlers.go)
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go)
## 消息消费者启动流程
系统在初始化阶段启动多个消息消费者,分别监听不同的主题队列。
### 订单通知消费者
`CreateOrderNotifyConsumer` 函数负责启动订单回调消息的监听。首先获取 ActiveMQ 连接,订阅 `order_notify` 队列,采用 `stomp.AckClient` 模式手动确认消息。一旦接收到消息,立即通过 `sendNotifyPool.Go` 提交至异步协程池处理,并立即执行 `Ack` 确认,确保消息不丢失。
`CreateOrderNotifyConsumer` 函数负责启动订单回调消息的监听。首先获取 ActiveMQ 连接,订阅 `order_notify` 队列,采用
`stomp.AckClient` 模式手动确认消息。一旦接收到消息,立即通过 `sendNotifyPool.Go` 提交至异步协程池处理,并立即执行 `Ack`
确认,确保消息不丢失。
### 渠道查询消费者
`CreateSupplierOrderQueryCuConsumer` 启动订单查询任务的消费者,订阅 `order_query` 队列。接收到消息后,解析 `bankOrderId`,创建包含定时器的 `OrderQueryTask`,并将其加入延迟查询队列,随后立即确认消息。
**章节来源**
`CreateSupplierOrderQueryCuConsumer` 启动订单查询任务的消费者,订阅 `order_query` 队列。接收到消息后,解析 `bankOrderId`
,创建包含定时器的 `OrderQueryTask`,并将其加入延迟查询队列,随后立即确认消息。
**章节来源**
- [order_notify.go](file://internal/service/notify/order_notify.go#L185-L217)
- [supplier_query.go](file://internal/schema/query/supplier_query.go#L89-L116)
## 消息监听与处理机制
消费者采用 `for range``select` 循环持续监听消息通道。所有消息体为 `bankOrderId` 字符串,消费者不进行复杂解析,仅提取订单号后触发后续异步流程。
消息处理采用“快速确认 + 异步执行”模式:
1. 消费者接收到消息
2. 解析出 `bankOrderId`
3. 提交任务至协程池或 Redis 队列
@@ -92,20 +107,27 @@ BusinessLogic --> |结果| SupplierQuery
该模式有效避免因业务处理耗时导致消息积压或重复消费。
**章节来源**
**章节来源**
- [order_notify.go](file://internal/service/notify/order_notify.go#L185-L217)
- [supplier_query.go](file://internal/schema/query/supplier_query.go#L89-L116)
## 队列管理器与任务处理器协作机制
系统通过 `QueueManager` 实现对 Redis 队列的统一管理,其核心结构包含 `redisClient``queues` 映射、`registry` 处理器注册表、`taskFactory` 任务工厂等。
系统通过 `QueueManager` 实现对 Redis 队列的统一管理,其核心结构包含 `redisClient``queues` 映射、`registry` 处理器注册表、
`taskFactory` 任务工厂等。
### 队列创建与获取
`GetOrCreateQueue` 方法根据队列名与通道 ID 生成唯一键,若队列不存在则创建新的 `RedisQueue` 实例,并注册到 `queues` 映射中。
### 任务注册与调度
`HandlerRegistry` 接口定义处理器注册与获取机制。`DefaultHandlerRegistry` 使用 `taskType:channelID` 作为键存储 `HandlerFunc``DefaultTaskHandler` 通过注册表查找对应处理器并执行。
`HandlerRegistry` 接口定义处理器注册与获取机制。`DefaultHandlerRegistry` 使用 `taskType:channelID` 作为键存储
`HandlerFunc``DefaultTaskHandler` 通过注册表查找对应处理器并执行。
### 任务入队流程
`EnqueueTask` 方法首先获取或创建队列,然后调用 `RedisQueue.Enqueue` 将任务序列化后通过 `RPush` 写入 Redis 列表。
```mermaid
@@ -131,16 +153,20 @@ Handler->>Handler : 执行业务逻辑
end
```
**图示来源**
**图示来源**
- [manager.go](file://internal/service/supplier/third_party/queue/manager.go#L104-L113)
- [handlers.go](file://internal/service/supplier/third_party/queue/handlers.go#L70-L103)
- [queue.go](file://internal/service/supplier/third_party/queue/queue.go#L286-L310)
## 工作协程池设计
`WorkerPool` 是一个通用的协程池实现,用于并发执行任务。
### 核心结构
`WorkerPool` 包含以下关键字段:
- `workers`:协程数量
- `taskChan`:任务通道
- `results`:结果通道
@@ -148,6 +174,7 @@ end
- `metrics`:性能指标
### 任务执行流程
`Start` 方法启动指定数量的 `worker` 协程。每个 `worker` 持续监听 `taskChan`,一旦接收到任务即调用 `task.Execute(ctx)` 执行。
```mermaid
@@ -168,22 +195,27 @@ Continue --> ListenTask
end
```
**图示来源**
**图示来源**
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go#L65-L71)
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go#L74-L89)
## 连接管理与消息确认
系统通过 `message.GetActiveMQConn()` 获取全局唯一的 ActiveMQ 连接实例,确保连接复用与资源节约。
所有消费者均采用 `stomp.AckClient` 模式,即客户端手动确认。消费者在成功提交异步任务后立即调用 `conn.Ack(v)` 确认消息。若确认失败,系统会记录错误日志但不会重新入队,依赖上游重试机制保障可靠性。
所有消费者均采用 `stomp.AckClient` 模式,即客户端手动确认。消费者在成功提交异步任务后立即调用 `conn.Ack(v)`
确认消息。若确认失败,系统会记录错误日志但不会重新入队,依赖上游重试机制保障可靠性。
该模式平衡了性能与可靠性,避免因处理失败导致消息堆积。
**章节来源**
**章节来源**
- [order_notify.go](file://internal/service/notify/order_notify.go#L185-L217)
- [send_message.go](file://internal/service/message/send_message.go#L1-L25)
## 重试策略与死信队列
当前系统未显式配置死信队列DLX/DLQ但通过多层队列机制实现软性重试
1. **ActiveMQ 层**:消费者未确认时消息保持在队列中
@@ -193,6 +225,7 @@ end
对于订单通知,系统通过 `GetNotifyInfosNotSuccess` 查询未成功通知的记录,由定时任务重新触发,形成补偿机制。
## 消费性能调优
系统通过以下方式优化消费性能:
- **并发消费**:每个消费者可启动多个工作协程
@@ -204,17 +237,22 @@ end
`QueueManager` 支持为不同队列配置不同工作协程数,实现精细化性能控制。
## 事件解耦与业务流程
系统通过消息队列实现核心支付流程的解耦:
### 订单通知事件
支付完成后,系统调用 `SendMessage``order_notify` 队列发送 `bankOrderId`。消费者异步调用 `SendOrderNotify` 执行回调,更新 `notify_info` 表状态。该机制避免因第三方回调超时阻塞主流程。
支付完成后,系统调用 `SendMessage``order_notify` 队列发送 `bankOrderId`。消费者异步调用 `SendOrderNotify` 执行回调,更新
`notify_info` 表状态。该机制避免因第三方回调超时阻塞主流程。
### 渠道查询事件
对于需要轮询结果的渠道,系统将 `bankOrderId` 发送至 `order_query` 队列。消费者创建带定时器的查询任务,实现延迟查询与自动重试。
`notify_info.go` 定义了通知记录的数据结构与数据库操作,包括插入、查询、更新等方法,是异步通知持久化的基础。
**章节来源**
**章节来源**
- [notify_info.go](file://internal/models/notify/notify_info.go#L1-L76)
- [mq_config.go](file://internal/config/mq_config.go#L1-L59)
- [send_message.go](file://internal/service/message/send_message.go#L1-L25)

View File

@@ -9,6 +9,7 @@
</cite>
## 目录
1. [引言](#引言)
2. [核心数据结构](#核心数据结构)
3. [商户状态与生命周期管理](#商户状态与生命周期管理)
@@ -22,9 +23,12 @@
## 引言
本文档旨在深入解析`kami_gateway`项目中的商户模型,以`merchant_info.go`文件中的`MerchantInfo`结构体为核心,全面阐述商户系统的设计理念与实现细节。文档将详细解析商户的关键字段,包括状态、密钥、代理归属和支付回调配置等,阐明商户与负载信息之间的关联关系,以及这些设计如何支持多通道轮询和单通道指定等业务场景。同时,文档将结合`pay_resp.go``PayBaseResp``MerchantInfo`字段说明商户信息在API响应中的使用方式并涵盖商户的创建、启用/禁用、密钥轮换等管理流程。
本文档旨在深入解析`kami_gateway`项目中的商户模型,以`merchant_info.go`文件中的`MerchantInfo`
结构体为核心,全面阐述商户系统的设计理念与实现细节。文档将详细解析商户的关键字段,包括状态、密钥、代理归属和支付回调配置等,阐明商户与负载信息之间的关联关系,以及这些设计如何支持多通道轮询和单通道指定等业务场景。同时,文档将结合
`pay_resp.go``PayBaseResp``MerchantInfo`字段说明商户信息在API响应中的使用方式并涵盖商户的创建、启用/禁用、密钥轮换等管理流程。
**Section sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L12-L32)
## 核心数据结构
@@ -57,21 +61,28 @@ class MerchantInfo {
```
**Diagram sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L12-L32)
**Section sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L12-L32)
## 商户状态与生命周期管理
### 状态字段设计
`Status`字段是`MerchantInfo`结构体中的一个关键字符串类型字段,用于标识商户的当前状态。该字段的设计意图是实现对商户生命周期的精细化管理,通过不同的状态值来控制商户在系统中的可用性。
### 状态管理流程
商户的生命周期管理主要通过`UpdateMerchant`函数来实现。该函数接收一个`MerchantInfo`对象作为参数,并将其更新到数据库中。通过修改`Status`字段的值(例如,从"active"改为"inactive"),可以实现商户的启用或禁用操作。此操作通常在商户管理后台的审核流程中被调用。
商户的生命周期管理主要通过`UpdateMerchant`函数来实现。该函数接收一个`MerchantInfo`对象作为参数,并将其更新到数据库中。通过修改
`Status`字段的值(例如,从"active"改为"inactive"),可以实现商户的启用或禁用操作。此操作通常在商户管理后台的审核流程中被调用。
### 创建与删除
商户的创建由`InsertMerchantInfo`函数负责,该函数将一个新的`MerchantInfo`对象插入数据库。当需要永久移除一个商户时,则调用`DeleteMerchantByUid`函数,通过`merchant_uid`作为唯一标识进行删除。
商户的创建由`InsertMerchantInfo`函数负责,该函数将一个新的`MerchantInfo`对象插入数据库。当需要永久移除一个商户时,则调用
`DeleteMerchantByUid`函数,通过`merchant_uid`作为唯一标识进行删除。
```mermaid
sequenceDiagram
@@ -93,6 +104,7 @@ Service-->>Admin : 返回操作成功
```
**Diagram sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L14-L14)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L157-L165)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L188-L197)
@@ -100,6 +112,7 @@ Service-->>Admin : 返回操作成功
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L199-L207)
**Section sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L14-L14)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L157-L165)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L188-L197)
@@ -109,13 +122,20 @@ Service-->>Admin : 返回操作成功
## 商户密钥与安全机制
### 密钥字段设计
`MerchantKey``MerchantSecret`是两个至关重要的安全字段。`MerchantKey`作为商户的公开标识用于在API调用中识别商户身份。而`MerchantSecret`则是商户的私有密钥用于生成和验证API请求的签名确保通信的完整性和防篡改性。
`MerchantKey``MerchantSecret`是两个至关重要的安全字段。`MerchantKey`作为商户的公开标识用于在API调用中识别商户身份。而
`MerchantSecret`则是商户的私有密钥用于生成和验证API请求的签名确保通信的完整性和防篡改性。
### 密钥轮换流程
密钥轮换是保障系统安全的重要措施。当商户需要更新其密钥时,系统会调用`UpdateMerchant`函数,将新的`MerchantKey``MerchantSecret`值写入数据库。此操作会立即生效,旧的密钥将失效。在实际业务中,密钥轮换通常需要商户在管理后台主动触发,并可能涉及通知下游系统更新配置。
密钥轮换是保障系统安全的重要措施。当商户需要更新其密钥时,系统会调用`UpdateMerchant`函数,将新的`MerchantKey`
`MerchantSecret`值写入数据库。此操作会立即生效,旧的密钥将失效。在实际业务中,密钥轮换通常需要商户在管理后台主动触发,并可能涉及通知下游系统更新配置。
### 密钥使用场景
在支付回调等关键业务流程中,系统会通过`GetMerchantByUid`获取商户信息,然后使用其`MerchantSecret`来验证第三方回调请求的签名。例如,在多个第三方支付渠道(如`StarSilenceImpl`, `NinjaCardImpl`, `KuaiFuImpl`等)的`PayNotify`方法中,都包含了通过`merchant.GetMerchantByUid`获取商户信息并使用`MerchantSecret`进行验签的逻辑。
在支付回调等关键业务流程中,系统会通过`GetMerchantByUid`获取商户信息,然后使用其`MerchantSecret`
来验证第三方回调请求的签名。例如,在多个第三方支付渠道(如`StarSilenceImpl`, `NinjaCardImpl`, `KuaiFuImpl`等)的`PayNotify`
方法中,都包含了通过`merchant.GetMerchantByUid`获取商户信息并使用`MerchantSecret`进行验签的逻辑。
```mermaid
sequenceDiagram
@@ -134,6 +154,7 @@ end
```
**Diagram sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L19-L20)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L157-L165)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L188-L197)
@@ -142,6 +163,7 @@ end
- [kuaifu.go](file://internal/service/supplier/third_party/kuaifu.go#L122-L183)
**Section sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L19-L20)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L157-L165)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L188-L197)
@@ -149,16 +171,20 @@ end
## 代理归属与通道配置
### 代理归属设计
`BelongAgentUid`字段用于建立商户与代理之间的从属关系。该字段存储了代理的唯一标识符,使得系统能够清晰地追踪每个商户是由哪个代理发展而来,这对于分润计算和渠道管理至关重要。
### 支付通道配置
商户模型支持两种支付通道选择策略:
- **单通道指定**:通过`SinglePayForRoadUid``SinglePayForRoadName`字段,商户可以被强制绑定到一个特定的支付通道。
- **多通道轮询**:通过`RollPayForRoadCode``RollPayForRoadName`字段,商户可以被配置为从一组通道中进行轮询或负载均衡。
这种设计提供了极大的灵活性,允许运营人员根据商户的风险等级、业务需求或通道稳定性来配置最合适的支付策略。
**Section sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L15-L15)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L30-L31)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L32-L33)
@@ -166,10 +192,14 @@ end
## 商户负载信息与多通道策略
### 负载信息模型
`merchant_load_info.go`文件定义了`MerchantLoadInfo`结构体,用于记录商户在各个支付通道上的负载情况。其核心字段包括`MerchantUid`(关联商户)、`RoadUid`(关联通道)和`LoadAmount`(负载金额)。
`merchant_load_info.go`文件定义了`MerchantLoadInfo`结构体,用于记录商户在各个支付通道上的负载情况。其核心字段包括
`MerchantUid`(关联商户)、`RoadUid`(关联通道)和`LoadAmount`(负载金额)。
### 业务场景支持
`MerchantLoadInfo``MerchantInfo`的关联,为实现复杂的支付路由策略提供了数据基础。
- **多通道轮询**:系统可以根据`MerchantLoadInfo`中记录的各通道负载金额,实现基于负载的轮询或加权轮询,将支付请求分配到负载较低的通道,从而实现流量的均衡分布。
- **单通道指定**:当`MerchantInfo``SinglePayForRoadUid`被设置时,系统会忽略负载信息,直接将所有请求路由到指定的通道,确保业务的确定性。
@@ -191,36 +221,47 @@ MerchantInfo "1" -- "0..*" MerchantLoadInfo : 通过MerchantUid关联
```
**Diagram sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L12-L32)
- [merchant_load_info.go](file://internal/models/merchant/merchant_load_info.go#L12-L22)
**Section sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L12-L32)
- [merchant_load_info.go](file://internal/models/merchant/merchant_load_info.go#L12-L22)
## API响应中的商户信息
### 响应结构集成
`pay_resp.go`文件中,`PayBaseResp`结构体包含了一个`MerchantInfo`类型的字段。这表明在支付相关的API响应中商户的完整信息会被直接嵌入到响应体中返回给客户端。
### 使用方式
这种设计使得客户端如商户的前端应用无需进行额外的API调用来获取商户信息。例如在一个支付成功的响应中客户端可以立即获取到商户名称、支付费率等信息用于展示或后续处理。这提高了API的效率和用户体验。
**Section sources**
- [pay_resp.go](file://internal/schema/response/pay_resp.go#L10-L10)
## 数据库设计与约束
### 表结构
`MerchantInfo`结构体映射到数据库中的`merchant_info`表。该表通过`MerchantUid`字段作为业务主键,确保了商户的唯一性。
### 唯一性约束
虽然在提供的代码片段中未直接体现,但根据`IsExistByMerchantUid``IsExistByMerchantName`等查询函数的存在,可以推断数据库层面必然存在针对`merchant_uid``merchant_name`的唯一索引,以防止创建重复的商户。
虽然在提供的代码片段中未直接体现,但根据`IsExistByMerchantUid``IsExistByMerchantName`等查询函数的存在,可以推断数据库层面必然存在针对
`merchant_uid``merchant_name`的唯一索引,以防止创建重复的商户。
### 负载信息表
`MerchantLoadInfo`结构体映射到`merchant_load_info`表,用于存储商户通道的负载数据。该表通过`MerchantUid``RoadUid`的组合来标识一条唯一的负载记录。
`MerchantLoadInfo`结构体映射到`merchant_load_info`表,用于存储商户通道的负载数据。该表通过`MerchantUid``RoadUid`
的组合来标识一条唯一的负载记录。
**Section sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L34-L34)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L40-L42)
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L44-L46)
@@ -230,22 +271,31 @@ MerchantInfo "1" -- "0..*" MerchantLoadInfo : 通过MerchantUid关联
## 敏感信息存储策略
### 加密处理
`LoginPassword`字段用于存储商户后台登录的密码。根据安全最佳实践,该字段在数据库中存储的绝不是明文密码。系统在调用`InsertMerchantInfo``UpdateMerchant`之前,会使用`utils`包中的加密工具(如`AES_ECB.go`)对密码进行加密。在验证密码时,同样需要先对用户输入的密码进行加密后再与数据库中的密文进行比对。
`LoginPassword`字段用于存储商户后台登录的密码。根据安全最佳实践,该字段在数据库中存储的绝不是明文密码。系统在调用
`InsertMerchantInfo``UpdateMerchant`之前,会使用`utils`包中的加密工具(如`AES_ECB.go`
)对密码进行加密。在验证密码时,同样需要先对用户输入的密码进行加密后再与数据库中的密文进行比对。
### 安全考量
这种设计确保了即使数据库发生泄露,攻击者也无法直接获取到商户的登录凭证,从而保护了商户账户的安全。
**Section sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L21-L21)
- [AES_ECB.go](file://internal/utils/AES_ECB.go)
## IP白名单验证机制
### 白名单字段
`WhiteIps`字段是一个字符串类型用于存储允许访问该商户API的IP地址列表。多个IP地址通常以逗号或分号分隔。
### 验证流程
在接收到商户的API请求时网关服务会首先获取请求来源的IP地址然后调用`GetMerchantByUid`获取商户信息。接着,系统会检查`WhiteIps`列表中是否包含该请求IP。如果不在白名单内请求将被立即拒绝返回相应的错误码。此验证逻辑通常在API网关的中间件或控制器的前置处理中实现。
在接收到商户的API请求时网关服务会首先获取请求来源的IP地址然后调用`GetMerchantByUid`获取商户信息。接着,系统会检查
`WhiteIps`列表中是否包含该请求IP。如果不在白名单内请求将被立即拒绝返回相应的错误码。此验证逻辑通常在API网关的中间件或控制器的前置处理中实现。
**Section sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L23-L23)

View File

@@ -12,6 +12,7 @@
</cite>
## 目录
1. [订单模型](#订单模型)
2. [商户模型](#商户模型)
3. [账户模型](#账户模型)
@@ -21,30 +22,44 @@
## 订单模型
订单模型是系统的核心,由 `OrderInfo` 结构体定义,位于 `internal/models/order/order_info.go` 文件中。该模型记录了支付订单的完整信息,包括订单金额、状态、商户信息、支付通道等关键字段。
订单模型是系统的核心,由 `OrderInfo` 结构体定义,位于 `internal/models/order/order_info.go`
文件中。该模型记录了支付订单的完整信息,包括订单金额、状态、商户信息、支付通道等关键字段。
### 核心字段与数据类型
`OrderInfo` 结构体包含以下主要字段:
- **主键与标识**: `Id` (int, 主键), `BankOrderId` (string, 系统订单ID), `MerchantOrderId` (string, 商户订单ID)
- **金额信息**: `OrderAmount` (float64, 提交金额), `ShowAmount` (float64, 待支付金额), `FactAmount` (float64, 实际支付金额)
- **状态与时间**: `Status` (string, 支付状态), `CreateTime` (*time.Time, 创建时间), `UpdateTime` (*time.Time, 更新时间), `PayTime` (*time.Time, 支付时间)
- **商户与代理**: `MerchantUid` (string, 商户ID), `MerchantName` (string, 商户名称), `AgentUid` (string, 代理ID), `AgentName` (string, 代理名称)
- **支付通道**: `RoadUid` (string, 通道标识), `PayProductCode` (string, 支付产品编码), `PayTypeName` (string, 支付产品名称)
- **金额信息**: `OrderAmount` (float64, 提交金额), `ShowAmount` (float64, 待支付金额), `FactAmount` (float64,
实际支付金额)
- **状态与时间**: `Status` (string, 支付状态), `CreateTime` (*time.Time, 创建时间), `UpdateTime` (*time.Time, 更新时间),
`PayTime` (*time.Time, 支付时间)
- **商户与代理**: `MerchantUid` (string, 商户ID), `MerchantName` (string, 商户名称), `AgentUid` (string, 代理ID),
`AgentName` (string, 代理名称)
- **支付通道**: `RoadUid` (string, 通道标识), `PayProductCode` (string, 支付产品编码), `PayTypeName` (string,
支付产品名称)
- **扩展信息**: `ExValue` (string, 扩展属性通常为JSON), `CardReturnData` (string, 卡片返回数据)
### 索引
根据代码中的查询方法,可以推断出以下索引的存在,以支持高效的查询:
- `bank_order_id` 字段上存在唯一索引或主键索引,用于通过 `GetOrderByBankOrderId` 方法快速查找。
- `merchant_order_id` 字段上存在索引,用于通过 `GetOrderByMerchantOrderId` 方法查找。
- `merchant_uid``road_uid` 字段上存在复合索引,用于通过 `GetByUidAndRoadUid` 方法查找。
- `status` 字段上存在索引,用于统计成功订单率等聚合操作。
### 关联模型
订单模型与多个其他模型存在紧密关联:
- **订单利润信息 (`OrderProfitInfo`)**: 通过 `BankOrderId``MerchantOrderId``OrderProfitInfo` 表关联。当创建订单时,会使用 `InsertOrderAndOrderProfit` 事务方法同时插入两条记录,确保数据一致性。`HiddenOrder` 函数在处理隐藏订单时,也会同步复制利润信息。
- **订单结算信息 (`OrderSettleInfo`)**: 当订单支付成功后,`SolvePaySuccess` 服务函数会创建一条 `OrderSettleInfo` 记录,用于标记该订单可以进行结算。两者通过 `BankOrderId` 进行关联。
- **订单利润信息 (`OrderProfitInfo`)**: 通过 `BankOrderId``MerchantOrderId``OrderProfitInfo` 关联。当创建订单时,会使用
`InsertOrderAndOrderProfit` 事务方法同时插入两条记录,确保数据一致性。`HiddenOrder` 函数在处理隐藏订单时,也会同步复制利润信息。
- **订单结算信息 (`OrderSettleInfo`)**: 当订单支付成功后,`SolvePaySuccess` 服务函数会创建一条 `OrderSettleInfo`
记录,用于标记该订单可以进行结算。两者通过 `BankOrderId` 进行关联。
**Section sources**
- [order_info.go](file://internal/models/order/order_info.go#L25-L68)
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L92-L112)
- [order_settle_info.go](file://internal/models/order/order_settle_info.go#L32-L45)
@@ -55,18 +70,26 @@
商户模型由 `MerchantInfo` 结构体定义,位于 `internal/models/merchant/merchant_info.go` 文件中,用于管理商户的基本信息和配置。
### 核心字段与数据类型
`MerchantInfo` 结构体包含以下主要字段:
- **主键与标识**: `Id` (int, 主键), `MerchantUid` (string, 商户唯一ID), `MerchantName` (string, 商户名称)
- **认证信息**: `LoginAccount` (string, 登录账号), `LoginPassword` (string, 登录密码), `MerchantKey` (string, 支付密钥), `MerchantSecret` (string, 支付密钥)
- **认证信息**: `LoginAccount` (string, 登录账号), `LoginPassword` (string, 登录密码), `MerchantKey` (string, 支付密钥),
`MerchantSecret` (string, 支付密钥)
- **代理关系**: `BelongAgentUid` (string, 所属代理ID), `BelongAgentName` (string, 所属代理名称)
- **支付配置**: `SinglePayForRoadUid` (string, 单一支付通道ID), `RollPayForRoadCode` (string, 轮询支付通道编码)
- **状态与时间**: `Status` (string, 状态), `CreateTime` (time.Time, 创建时间), `UpdateTime` (time.Time, 更新时间)
### 关联模型
商户模型与以下模型存在关联:
- **商户加载信息 (`MerchantLoadInfo`)**: 通过 `MerchantUid` 字段关联。`MerchantLoadInfo` 记录了商户在特定支付通道上的押款信息。`MerchantAbleAmount` 服务函数在解款时会查询此信息,而 `settle` 函数在结算时会根据商户的押款配置创建新的 `MerchantLoadInfo` 记录。
- **商户加载信息 (`MerchantLoadInfo`)**: 通过 `MerchantUid` 字段关联。`MerchantLoadInfo` 记录了商户在特定支付通道上的押款信息。
`MerchantAbleAmount` 服务函数在解款时会查询此信息,而 `settle` 函数在结算时会根据商户的押款配置创建新的
`MerchantLoadInfo` 记录。
**Section sources**
- [merchant_info.go](file://internal/models/merchant/merchant_info.go#L12-L32)
- [merchant_load_info.go](file://internal/models/merchant/merchant_load_info.go#L11-L20)
@@ -75,17 +98,26 @@
账户模型由 `AccountInfo` 结构体定义,位于 `internal/models/accounts/account.go` 文件中,用于管理商户账户的资金状态。
### 核心字段与数据类型
`AccountInfo` 结构体包含以下主要字段:
- **主键与标识**: `Id` (int, 主键), `AccountUid` (string, 账户ID), `AccountName` (string, 账户名称)
- **资金信息**: `Balance` (float64, 账户总余额), `SettleAmount` (float64, 已结算金额), `WaitAmount` (float64, 待结算资金), `FreezeAmount` (float64, 冻结金额), `LoanAmount` (float64, 押款金额), `PayforAmount` (float64, 代付在途金额)
- **资金信息**: `Balance` (float64, 账户总余额), `SettleAmount` (float64, 已结算金额), `WaitAmount` (float64,
待结算资金), `FreezeAmount` (float64, 冻结金额), `LoanAmount` (float64, 押款金额), `PayforAmount` (float64,
代付在途金额)
- **状态与时间**: `Status` (string, 状态), `CreateTime` (time.Time, 创建时间), `UpdateTime` (time.Time, 更新时间)
### 与代理商利润的联动
尽管 `agent_profit.go` 文件中 `AgentProfit` 结构体为空,但从 `OrderProfitInfo` 模型中可以清晰地看到其设计意图。`OrderProfitInfo` 包含了 `AgentProfit` (代理利润) 和 `AgentRate` (代理费率) 字段。这表明代理商的利润信息是通过订单利润来计算和累积的。
当一笔订单成功结算时,`SolvePaySuccess` 服务函数会更新 `AccountInfo``Balance``WaitAmount`。同时,`OrderProfitInfo` 中计算出的 `AgentProfit` 会作为平台利润的一部分,最终体现在平台的总收益中。因此,代理商的利润并非直接存储在独立的 `AgentProfit` 表中,而是作为 `OrderProfitInfo` 的一个计算结果字段存在,并通过聚合查询来统计代理商的总利润
尽管 `agent_profit.go` 文件中 `AgentProfit` 结构体为空,但从 `OrderProfitInfo` 模型中可以清晰地看到其设计意图
`OrderProfitInfo` 包含了 `AgentProfit` (代理利润) 和 `AgentRate` (代理费率) 字段。这表明代理商的利润信息是通过订单利润来计算和累积的。
当一笔订单成功结算时,`SolvePaySuccess` 服务函数会更新 `AccountInfo``Balance``WaitAmount`。同时,`OrderProfitInfo`
中计算出的 `AgentProfit` 会作为平台利润的一部分,最终体现在平台的总收益中。因此,代理商的利润并非直接存储在独立的
`AgentProfit` 表中,而是作为 `OrderProfitInfo` 的一个计算结果字段存在,并通过聚合查询来统计代理商的总利润。
**Section sources**
- [account.go](file://internal/models/accounts/account.go#L12-L26)
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L12-L39)
- [settle_service.go](file://internal/service/settle_service.go#L42-L141)
@@ -186,6 +218,7 @@ MERCHANT_INFO }o--|| MERCHANT_INFO : "belongs_to_agent"
```
**Diagram sources**
- [order_info.go](file://internal/models/order/order_info.go#L19-L68)
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L12-L39)
- [order_settle_info.go](file://internal/models/order/order_settle_info.go#L12-L28)
@@ -198,17 +231,23 @@ MERCHANT_INFO }o--|| MERCHANT_INFO : "belongs_to_agent"
订单的数据生命周期从创建到最终结算,经历多个状态流转。
### 订单状态流转
1. **创建 (Created)**: 当商户发起支付请求时,系统调用 `CreateOrderInfo` 服务函数,创建一条 `OrderInfo` 记录,初始状态为 `created`
2. **等待支付 (WAIT)**: 订单信息生成后,状态更新为 `wait`,等待用户完成支付。
3. **支付成功 (SUCCESS)**: 当上游支付通道返回成功通知时,`SolvePaySuccess` 服务函数被触发。该函数执行一个数据库事务,将 `OrderInfo` 的状态更新为 `success`,同时更新 `OrderProfitInfo` 的状态,并创建一条 `OrderSettleInfo` 记录
4. **结算完成**: `OrderSettleInfo``is_complete_settle` 字段从 `no` 变为 `yes`,标志着该订单已完成结算
1. **创建 (Created)**: 当商户发起支付请求时,系统调用 `CreateOrderInfo` 服务函数,创建一条 `OrderInfo` 记录,初始状态为
`created`
2. **等待支付 (WAIT)**: 订单信息生成后,状态更新为 `wait`,等待用户完成支付
3. **支付成功 (SUCCESS)**: 当上游支付通道返回成功通知时,`SolvePaySuccess` 服务函数被触发。该函数执行一个数据库事务,将
`OrderInfo` 的状态更新为 `success`,同时更新 `OrderProfitInfo` 的状态,并创建一条 `OrderSettleInfo` 记录。
4. **结算完成**: `OrderSettleInfo``is_complete_settle` 字段从 `no` 变为 `yes`,标志着该订单已完成结算。
### 关键业务规则
- **数据一致性**: 在创建订单和利润信息时,必须使用 `InsertOrderAndOrderProfit` 事务方法,确保两条记录同时成功或失败。
- **幂等性**: `SolvePaySuccess` 函数在处理支付成功通知时,会先检查订单状态,防止重复处理。
- **资金安全**: 在结算 (`settle`) 操作中,会使用 `SELECT ... FOR UPDATE` 语句锁定 `account_info``order_settle_info` 记录,防止并发操作导致的资金错误。
- **资金安全**: 在结算 (`settle`) 操作中,会使用 `SELECT ... FOR UPDATE` 语句锁定 `account_info``order_settle_info`
记录,防止并发操作导致的资金错误。
**Section sources**
- [order_info.go](file://internal/models/order/order_info.go#L108-L115)
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L92-L112)
- [settle_service.go](file://internal/service/settle_service.go#L42-L141)
@@ -219,18 +258,23 @@ MERCHANT_INFO }o--|| MERCHANT_INFO : "belongs_to_agent"
系统使用 Beego ORM 进行数据库的 CRUD 操作。
### CRUD 操作示例
- **创建 (Create)**: 使用 `orm.NewOrm().Insert()` 方法插入单条记录。例如,`InsertOrder` 函数用于创建订单。
- **读取 (Read)**: 使用 `QueryTable().Filter().All()` 模式进行查询。例如,`GetOrderByBankOrderId` 函数通过 `bank_order_id` 查询订单。
- **读取 (Read)**: 使用 `QueryTable().Filter().All()` 模式进行查询。例如,`GetOrderByBankOrderId` 函数通过 `bank_order_id`
查询订单。
- **更新 (Update)**: 使用 `QueryTable().Filter().Update()` 方法更新记录。例如,`UpdateOrderStatus` 函数用于更新订单状态。
- **删除 (Delete)**: 使用 `QueryTable().Filter().Delete()` 方法删除记录。例如,`DeleteMerchantByUid` 函数用于删除商户。
### 查询性能优化策略
- **索引优化**: 如上所述,对 `bank_order_id`, `merchant_order_id`, `status` 等高频查询字段建立索引。
- **分页查询**: 对于列表查询,使用 `Limit(display, offset)` 进行分页,避免一次性加载过多数据。例如,`GetOrderByMap` 函数。
- **批量操作**: 在需要处理多条记录时,使用原生 SQL 或 ORM 的批量方法,减少数据库交互次数。
- **缓存**: 对于不经常变动的配置信息(如商户信息),可以在应用层引入 Redis 缓存,减少数据库压力。代码中的 `internal/cache` 目录表明系统已具备缓存能力。
- **缓存**: 对于不经常变动的配置信息(如商户信息),可以在应用层引入 Redis 缓存,减少数据库压力。代码中的 `internal/cache`
目录表明系统已具备缓存能力。
**Section sources**
- [order_info.go](file://internal/models/order/order_info.go#L108-L115)
- [order_info.go](file://internal/models/order/order_info.go#L203-L217)
- [order_info.go](file://internal/models/order/order_info.go#L344-L351)

View File

@@ -9,6 +9,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [订单核心模型](#订单核心模型)
3. [订单状态生命周期](#订单状态生命周期)
@@ -19,40 +20,48 @@
8. [高并发数据一致性保障](#高并发数据一致性保障)
## 简介
本文档详细阐述了支付网关系统中的订单模型设计,重点分析`OrderInfo`结构体及其相关联的利润、结算等模型。文档涵盖了订单字段定义、状态流转、数据关系、操作示例及性能优化策略,为开发人员提供全面的订单系统参考。
本文档详细阐述了支付网关系统中的订单模型设计,重点分析`OrderInfo`
结构体及其相关联的利润、结算等模型。文档涵盖了订单字段定义、状态流转、数据关系、操作示例及性能优化策略,为开发人员提供全面的订单系统参考。
## 订单核心模型
`OrderInfo`结构体是订单系统的核心数据模型,定义了订单的完整信息。以下是对关键字段的详细解释:
### 基础信息字段
- **MerchantOrderId**: 字符串类型商户订单ID由商户系统生成用于商户侧订单追踪。
- **BankOrderId**: 字符串类型本系统订单ID作为系统内订单的唯一标识。
- **BankTransId**: 字符串类型上游流水ID记录上游支付通道返回的交易流水号。
### 金额相关字段
- **OrderAmount**: float64类型订单提交的金额即用户下单时的原始金额。
- **ShowAmount**: float64类型待支付的金额可能因优惠活动等与原始金额不同。
- **FactAmount**: float64类型用户实际支付金额是订单最终完成支付的金额对账和结算以此为准。
### 通道与路由字段
- **RoadUid**: 字符串类型,通道标识,唯一标识一个支付通道,是路由和结算的关键字段。
- **RoadName**: 字符串类型,通道名称,用于展示和日志记录。
- **RollPoolCode**: 字符串类型,轮询池编码,标识订单所属的通道轮询池。
- **PayProductCode**: 字符串类型,上游支付公司的编码代号,用于标识支付服务提供商。
### 状态与时间字段
- **Status**: 字符串类型,订单支付状态,控制订单的生命周期流转。
- **CreateTime**: *time.Time类型订单创建时间。
- **PayTime**: *time.Time类型用户支付时间订单成功时更新。
- **UpdateTime**: *time.Time类型订单最后更新时间。
### 扩展与安全字段
- **ExValue**: 字符串类型扩展属性可用于存储JSON格式的额外信息。
- **IsIpRestricted**: 整型IP限制状态标识。
- **IsReplace**: 整型,标识订单是否已被替换(如隐藏订单场景)。
**Section sources**
- [order_info.go](file://internal/models/order/order_info.go#L19-L68)
## 订单状态生命周期
@@ -60,6 +69,7 @@
订单状态经历了从创建到最终结算的完整生命周期,各状态之间的流转逻辑如下:
### 状态流转图
```mermaid
graph TD
A[wait] --> |支付成功| B[success]
@@ -72,6 +82,7 @@ E --> |退款完成| G[refunded]
```
### 状态说明
- **wait**: 订单创建后的初始状态,等待用户支付。
- **success**: 用户支付成功,订单完成。
- **fail**: 支付失败,可能因余额不足、密码错误等原因。
@@ -82,9 +93,12 @@ E --> |退款完成| G[refunded]
- **unfreeze**: 冻结订单已解冻,恢复结算。
### 状态变更操作
状态变更通过`UpdateOrderStatus`函数实现,该函数确保订单状态和卡片返回数据的原子性更新。对于需要同步更新多个关联表的场景(如订单和利润表),系统使用`SwitchOrderAndOrderProfitStatus`函数在事务中完成操作,保证数据一致性。
状态变更通过`UpdateOrderStatus`函数实现,该函数确保订单状态和卡片返回数据的原子性更新。对于需要同步更新多个关联表的场景(如订单和利润表),系统使用
`SwitchOrderAndOrderProfitStatus`函数在事务中完成操作,保证数据一致性。
**Section sources**
- [order_info.go](file://internal/models/order/order_info.go#L40-L40)
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L120-L147)
@@ -93,6 +107,7 @@ E --> |退款完成| G[refunded]
订单系统包含多个关联模型,共同构成完整的订单数据生态。
### 实体关系图(ER图)
```mermaid
erDiagram
ORDER_INFO {
@@ -144,17 +159,22 @@ ORDER_PROFIT_INFO }o--|| PLATFORM_PROFIT : "一对多"
```
**Diagram sources**
- [order_info.go](file://internal/models/order/order_info.go#L19-L68)
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L12-L39)
- [order_settle_info.go](file://internal/models/order/order_settle_info.go#L12-L28)
- [platform_profit.go](file://internal/models/order/platform_profit.go#L2-L11)
### 模型关系说明
- **OrderInfo 与 OrderProfitInfo**: 一对一关系。`OrderInfo`存储订单基础信息,`OrderProfitInfo`存储订单的利润分润信息。两者通过`BankOrderId`关联,通常在订单创建时同时插入。
- **OrderInfo 与 OrderProfitInfo**: 一对一关系。`OrderInfo`存储订单基础信息,`OrderProfitInfo`存储订单的利润分润信息。两者通过
`BankOrderId`关联,通常在订单创建时同时插入。
- **OrderInfo 与 OrderSettleInfo**: 一对一关系。`OrderSettleInfo`存储订单的结算信息,包括结算金额、是否允许结算等状态。
- **OrderProfitInfo 与 PlatformProfit**: 一对多关系。`PlatformProfit`是聚合统计表,由多个`OrderProfitInfo`记录汇总生成,用于平台级的利润报表。
- **OrderProfitInfo 与 PlatformProfit**: 一对多关系。`PlatformProfit`是聚合统计表,由多个`OrderProfitInfo`
记录汇总生成,用于平台级的利润报表。
**Section sources**
- [order_info.go](file://internal/models/order/order_info.go#L19-L68)
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L12-L39)
- [order_settle_info.go](file://internal/models/order/order_settle_info.go#L12-L28)
@@ -165,14 +185,18 @@ ORDER_PROFIT_INFO }o--|| PLATFORM_PROFIT : "一对多"
系统使用Beego ORM进行数据库操作以下为关键操作的代码示例。
### 订单创建
订单创建需保证`OrderInfo``OrderProfitInfo`的原子性插入,通过事务实现:
```go
// 使用InsertOrderAndOrderProfit函数
success := InsertOrderAndOrderProfit(ctx, orderInfo, orderProfitInfo)
```
### 订单查询
提供多种查询方式,支持按不同条件过滤:
```go
// 按银行订单ID查询
order := GetOrderByBankOrderId(ctx, "BANK123456")
@@ -189,7 +213,9 @@ orders := GetOrderByMap(ctx, params, 10, 0)
```
### 订单更新
更新订单状态等关键信息:
```go
// 更新订单状态
err := UpdateOrderStatus(ctx, "BANK123456", "success", "{\"cardNo\":\"1234\"}")
@@ -199,6 +225,7 @@ success := InsertPayTime(ctx, "MCH789012")
```
**Section sources**
- [order_info.go](file://internal/models/order/order_info.go#L118-L124)
- [order_info.go](file://internal/models/order/order_info.go#L305-L313)
- [order_info.go](file://internal/models/order/order_info.go#L344-L351)
@@ -209,16 +236,19 @@ success := InsertPayTime(ctx, "MCH789012")
为保障高频查询的性能,系统对关键字段建立了数据库索引。
### 高频查询字段
- **MerchantOrderId**: 商户系统最常用的查询条件,必须保证唯一性和高效查询。
- **BankOrderId**: 系统内部核心标识,所有内部操作的基础查询条件。
- **Status**: 状态筛选是报表和对账的基础,查询频率极高。
### 索引设计
- **唯一索引**: 在`BankOrderId``MerchantOrderId`上建立唯一索引,防止重复订单。
- **复合索引**: 针对常见查询组合建立复合索引,例如`(merchant_uid, status, create_time)`用于商户订单列表查询。
- **状态索引**: 在`Status`字段上建立普通索引,加速状态筛选。
**Section sources**
- [order_info.go](file://internal/models/order/order_info.go#L19-L68)
## 数据归档与清理机制
@@ -226,10 +256,12 @@ success := InsertPayTime(ctx, "MCH789012")
为控制数据库规模,系统实施数据归档与清理策略。
### 归档策略
- **时间分区**: 订单表按月或按季度进行分区,历史数据自动归档到对应分区。
- **冷热分离**: 近期活跃数据如3个月内保留在主库历史数据迁移至归档库或数据仓库。
### 清理机制
- **TTL策略**: 通过定时任务清理已归档且超过保留期限如1年的数据。
- **软删除**: 对于需要保留审计痕迹的场景,采用`IsDeleted`标记而非物理删除。
@@ -238,11 +270,14 @@ success := InsertPayTime(ctx, "MCH789012")
在高并发场景下,系统通过多种机制保障数据一致性。
### 事务控制
关键操作(如创建订单、更新状态)均在数据库事务中执行,确保原子性。
### 乐观锁与悲观锁
- **乐观锁**: 在非关键路径使用版本号或时间戳进行乐观并发控制。
- **悲观锁**: 在资金变动等关键操作中,使用`SELECT ... FOR UPDATE`语句加行锁。
### 分布式锁
对于跨服务的复杂业务流程使用Redis实现分布式锁防止并发冲突。

View File

@@ -13,6 +13,7 @@
</cite>
## 目录
1. [账户模型设计](#账户模型设计)
2. [代理商模型与层级关系](#代理商模型与层级关系)
3. [账户资金字段详解](#账户资金字段详解)
@@ -46,15 +47,18 @@ class AccountInfo {
}
```
**图表来源**
**图表来源**
- [account.go](file://internal/models/accounts/account.go#L12-L26)
**本节来源**
**本节来源**
- [account.go](file://internal/models/accounts/account.go#L12-L26)
## 代理商模型与层级关系
系统通过 `AgentInfo` 结构体管理代理商信息,与商户账户形成层级管理关系。每个代理商可管理多个商户账户,通过 `AgentUid` 字段建立关联。代理商与商户之间通过 `AgentName``AgentUid` 字段在订单利润表中建立分润关系。
系统通过 `AgentInfo` 结构体管理代理商信息,与商户账户形成层级管理关系。每个代理商可管理多个商户账户,通过 `AgentUid`
字段建立关联。代理商与商户之间通过 `AgentName``AgentUid` 字段在订单利润表中建立分润关系。
```mermaid
classDiagram
@@ -85,12 +89,14 @@ AgentInfo --> OrderProfitInfo : "关联"
AccountInfo --> OrderProfitInfo : "生成"
```
**图表来源**
**图表来源**
- [agent_info.go](file://internal/models/agent/agent_info.go#L12-L27)
- [account.go](file://internal/models/accounts/account.go#L12-L26)
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L12-L39)
**本节来源**
**本节来源**
- [agent_info.go](file://internal/models/agent/agent_info.go#L12-L27)
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L12-L39)
@@ -107,16 +113,19 @@ AccountInfo --> OrderProfitInfo : "生成"
这些字段共同构成了账户资金的完整状态机,确保资金流动的准确追踪。
**本节来源**
**本节来源**
- [account.go](file://internal/models/accounts/account.go#L17-L22)
## 账户状态管理机制
账户状态通过 `Status` 字段进行管理,采用字符串枚举方式表示账户的生命周期状态。系统通过 `GetAccountByUid``UpdateAccount` 等方法实现状态的查询和更新,所有状态变更操作均通过事务保证数据一致性。
账户状态通过 `Status` 字段进行管理,采用字符串枚举方式表示账户的生命周期状态。系统通过 `GetAccountByUid``UpdateAccount`
等方法实现状态的查询和更新,所有状态变更操作均通过事务保证数据一致性。
账户状态变更遵循严格的业务流程,如结算、冻结、解冻等操作都需要经过完整的业务验证和事务处理,确保状态转换的正确性和安全性。
**本节来源**
**本节来源**
- [account.go](file://internal/models/accounts/account.go#L14-L14)
- [account.go](file://internal/models/accounts/account.go#L95-L103)
@@ -142,10 +151,12 @@ class AccountHistoryInfo {
}
```
**图表来源**
**图表来源**
- [account_history_info.go](file://internal/models/accounts/account_history_info.go#L12-L25)
**本节来源**
**本节来源**
- [account_history_info.go](file://internal/models/accounts/account_history_info.go#L12-L25)
## 代理商利润计算逻辑
@@ -168,11 +179,13 @@ participant 代理商利润
订单服务-->>支付系统 : 处理完成
```
**图表来源**
**图表来源**
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L36-L36)
- [pay_solve.go](file://internal/service/pay_solve.go#L131-L163)
**本节来源**
**本节来源**
- [order_profit_info.go](file://internal/models/order/order_profit_info.go#L36-L36)
- [pay_solve.go](file://internal/service/pay_solve.go#L131-L163)
@@ -182,13 +195,15 @@ participant 代理商利润
资金流水记录包含完整的上下文信息,包括订单号、变动类型、金额、手续费、变动后余额等,确保每一笔资金流动都可追溯。所有流水记录操作都与主业务操作在同一个数据库事务中完成,保证数据一致性。
**本节来源**
**本节来源**
- [account_history_info.go](file://internal/models/accounts/account_history_info.go#L27-L35)
- [pay_solve.go](file://internal/service/pay_solve.go#L131-L163)
## 账户余额原子性操作
账户余额更新采用数据库事务和行级锁机制确保原子性。在 `SolvePaySuccess` 方法中,通过 `SELECT ... FOR UPDATE` 语句获取账户记录的排他锁,防止并发场景下的超卖问题。
账户余额更新采用数据库事务和行级锁机制确保原子性。在 `SolvePaySuccess` 方法中,通过 `SELECT ... FOR UPDATE`
语句获取账户记录的排他锁,防止并发场景下的超卖问题。
所有余额更新操作都在事务中完成,包括账户余额更新、待结算资金更新、生成资金流水记录等,确保操作的原子性和一致性。系统还通过余额校验机制防止负余额的出现。
@@ -203,15 +218,18 @@ F --> G[提交事务]
E --> H[返回错误]
```
**图表来源**
**图表来源**
- [pay_solve.go](file://internal/service/pay_solve.go#L131-L163)
**本节来源**
**本节来源**
- [pay_solve.go](file://internal/service/pay_solve.go#L131-L163)
## 账户安全机制
账户安全通过多层机制保障:
- **密码存储**: 登录密码和支付密码均采用加密存储,防止明文泄露
- **并发控制**: 通过数据库事务和行级锁防止并发操作导致的数据不一致
- **操作审计**: 所有资金操作都记录到历史表,便于审计和追溯
@@ -219,13 +237,15 @@ E --> H[返回错误]
安全机制贯穿于账户操作的各个环节,从数据存储到业务处理都考虑了安全性要求。
**本节来源**
**本节来源**
- [agent_info.go](file://internal/models/agent/agent_info.go#L13-L14)
- [pay_solve.go](file://internal/service/pay_solve.go#L131-L163)
## 代理商分润定时结算
代理商分润结算由 `OrderSettle` 定时任务处理,通过 `settle_service.go` 中的 `OrderSettleInit` 方法启动。系统每隔2分钟检查待结算订单自动完成结算流程。
代理商分润结算由 `OrderSettle` 定时任务处理,通过 `settle_service.go` 中的 `OrderSettleInit`
方法启动。系统每隔2分钟检查待结算订单自动完成结算流程。
结算流程包括:更新订单结算状态、调整账户资金、处理押款逻辑等。对于需要押款的商户,系统还会根据配置的押款天数自动释放押款金额,整个过程完全自动化,确保代理商利润的及时分配。
@@ -245,8 +265,10 @@ J --> K[继续检查]
B --> |无订单| L[等待下次执行]
```
**图表来源**
**图表来源**
- [settle_service.go](file://internal/service/settle_service.go#L63-L122)
**本节来源**
**本节来源**
- [settle_service.go](file://internal/service/settle_service.go#L12-L236)

View File

@@ -15,6 +15,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
@@ -26,9 +27,11 @@
9. [结论](#结论)
## 简介
本文档详细解析了任务调度系统中third_party/pool目录下的订单池与任务队列机制。重点阐述了OrderPoolService接口如何通过workerPool、eventBus和taskPool实现高并发下的订单处理能力。文档详细描述了NewOrderPoolService初始化过程中的资源配置策略包括工作协程池、Redis事件总线和GoPool连接池的配置。结合event_handlers.go中的事件处理器注册逻辑阐明了系统如何通过发布-订阅模式响应订单提交、状态变更等事件。分析了task.go中任务执行流程与order.go定时任务的协同机制并解释了getAllRoadUids等关键方法在路由选择中的作用。最后提供了workerCount调优、Redis连接复用和事件广播延迟控制等性能优化建议。
## 项目结构
任务调度系统的核心功能集中在third_party/pool目录下该目录实现了订单池与任务队列的核心机制。系统通过模块化设计将订单处理、事件管理、任务执行等职责分离形成了清晰的组件结构。
```mermaid
@@ -74,24 +77,29 @@ EventHandler --> OrderQueryHandler
```
**图表来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L26-L52)
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go#L15-L25)
- [event.go](file://internal/service/supplier/third_party/pool/event.go#L60-L66)
- [task.go](file://internal/service/supplier/third_party/pool/task.go#L15-L25)
**本节来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L1-L585)
- [project_structure](file://#L1-L200)
## 核心组件
订单池服务的核心组件包括OrderPoolService接口、OrderPoolServiceImpl实现类、WorkerPool工作协程池、EventBus事件总线和TaskPool任务池。这些组件协同工作实现了高并发下的订单处理能力。
**本节来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L26-L52)
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go#L15-L25)
- [event.go](file://internal/service/supplier/third_party/pool/event.go#L60-L66)
## 架构概述
任务调度系统采用发布-订阅模式和工作协程池架构实现了高效的订单处理流程。系统通过Redis作为消息队列和状态存储结合Go协程池技术实现了高并发、低延迟的订单处理能力。
```mermaid
@@ -128,13 +136,16 @@ end
```
**图表来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L55-L73)
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go#L36-L46)
- [event.go](file://internal/service/supplier/third_party/pool/event.go#L80-L88)
- [task.go](file://internal/service/supplier/third_party/pool/task.go#L15-L25)
## 详细组件分析
### OrderPoolService接口分析
OrderPoolService接口定义了订单池服务的核心功能包括服务的启动与停止、订单的推送与提交、以及订单ID的查询。该接口通过清晰的方法定义为订单处理提供了标准化的操作接口。
```mermaid
@@ -178,12 +189,15 @@ OrderPoolService <|-- OrderPoolServiceImpl
```
**图表来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L26-L52)
**本节来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L26-L52)
### 初始化过程分析
NewOrderPoolService函数是订单池服务的初始化入口负责创建和配置所有核心组件。该函数通过依赖注入的方式将配置、Redis客户端等外部依赖注入到服务实现中并初始化工作协程池、事件总线和任务池等核心组件。
```mermaid
@@ -200,6 +214,7 @@ ReturnService --> End([初始化完成])
```
**图表来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L55-L73)
- [metrics.go](file://internal/service/supplier/third_party/pool/metrics.go#L50-L85)
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go#L36-L46)
@@ -207,9 +222,11 @@ ReturnService --> End([初始化完成])
- [card_sender/enums.go](file://internal/service/supplier/third_party/pool/card_sender/enums.go#L49-L72)
**本节来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L55-L73)
### 事件处理机制分析
系统通过事件总线EventBus实现了发布-订阅模式,将订单处理的各个阶段解耦。事件处理器负责监听特定类型的事件,并执行相应的业务逻辑,如订单创建、处理完成、查询等。
```mermaid
@@ -261,16 +278,19 @@ RedisEventBus --> EventHandler : "处理"
```
**图表来源**
- [event.go](file://internal/service/supplier/third_party/pool/event.go#L60-L66)
- [event_handlers.go](file://internal/service/supplier/third_party/pool/event_handlers.go#L15-L25)
- [events.go](file://internal/service/supplier/third_party/pool/events.go#L15-L25)
**本节来源**
- [event.go](file://internal/service/supplier/third_party/pool/event.go#L60-L177)
- [event_handlers.go](file://internal/service/supplier/third_party/pool/event_handlers.go#L15-L182)
- [events.go](file://internal/service/supplier/third_party/pool/events.go#L15-L136)
### 任务执行流程分析
任务执行流程是订单处理的核心,包括订单匹配、补充和刷新等操作。系统通过工作协程池并行处理这些任务,确保了高并发下的处理效率。
```mermaid
@@ -295,14 +315,17 @@ Note over OrderPoolService,WorkerPool : 订单补充流程
```
**图表来源**
- [task.go](file://internal/service/supplier/third_party/pool/task.go#L15-L165)
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go#L15-L90)
**本节来源**
- [task.go](file://internal/service/supplier/third_party/pool/task.go#L15-L165)
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go#L15-L90)
## 依赖分析
系统各组件之间的依赖关系清晰通过接口定义和依赖注入实现了松耦合。核心依赖包括配置管理、Redis客户端、工作协程池、事件总线和任务池。
```mermaid
@@ -336,6 +359,7 @@ OrderFailedHandler --> OrderPoolServiceImpl
```
**图表来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L40-L52)
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go#L15-L25)
- [event.go](file://internal/service/supplier/third_party/pool/event.go#L60-L66)
@@ -343,12 +367,15 @@ OrderFailedHandler --> OrderPoolServiceImpl
- [event_handlers.go](file://internal/service/supplier/third_party/pool/event_handlers.go#L15-L25)
**本节来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L40-L52)
- [worker.go](file://internal/service/supplier/third_party/pool/worker.go#L15-L25)
- [event.go](file://internal/service/supplier/third_party/pool/event.go#L60-L66)
## 性能考虑
### workerCount调优
workerCount参数决定了工作协程池的大小直接影响系统的并发处理能力。建议根据系统负载和硬件资源进行调优
- **低负载环境**设置为10-20避免资源浪费
@@ -358,6 +385,7 @@ workerCount参数决定了工作协程池的大小直接影响系统的并发
监控指标`worker_pool_size`可以帮助评估当前配置的合理性如果该指标经常达到上限说明需要增加workerCount。
### Redis连接复用
系统通过Redis客户端实现了连接复用减少了连接创建和销毁的开销。建议
- 保持长连接,避免频繁创建和销毁连接
@@ -365,6 +393,7 @@ workerCount参数决定了工作协程池的大小直接影响系统的并发
- 监控连接使用情况,及时发现连接泄漏
### 事件广播延迟控制
事件广播的延迟直接影响系统的响应速度。通过以下方式可以优化延迟:
- 调整事件处理协程池大小pool字段
@@ -372,7 +401,9 @@ workerCount参数决定了工作协程池的大小直接影响系统的并发
- 监控事件处理时间,及时发现性能瓶颈
## 故障排除指南
### 订单处理失败
当订单处理失败时,系统会记录相关错误并尝试重试。排查步骤:
1. 检查`order_pool_failed_total`监控指标
@@ -381,6 +412,7 @@ workerCount参数决定了工作协程池的大小直接影响系统的并发
4. 验证发卡任务类型的实现是否正确
### 事件处理器未响应
如果事件处理器未按预期工作,检查:
1. 事件是否正确发布到事件总线
@@ -389,6 +421,7 @@ workerCount参数决定了工作协程池的大小直接影响系统的并发
4. 事件处理器的实现是否存在错误
### 订单池大小异常
当订单池大小异常时:
1. 检查`order_pool_size`监控指标
@@ -397,9 +430,11 @@ workerCount参数决定了工作协程池的大小直接影响系统的并发
4. 查看是否有大量过期订单未被清理
**本节来源**
- [service.go](file://internal/service/supplier/third_party/pool/service.go#L300-L333)
- [task.go](file://internal/service/supplier/third_party/pool/task.go#L15-L165)
- [metrics.go](file://internal/service/supplier/third_party/pool/metrics.go#L50-L85)
## 结论
任务调度系统通过精心设计的架构和组件,实现了高效、可靠的订单处理能力。系统采用发布-订阅模式和工作协程池架构结合Redis作为消息队列和状态存储能够处理高并发的订单请求。通过合理的资源配置和性能优化系统能够在保证低延迟的同时处理大量的订单任务。建议在实际部署中根据具体负载情况进行参数调优并持续监控系统性能指标确保系统的稳定运行。

View File

@@ -11,6 +11,7 @@
</cite>
## 目录
1. [接口定义与统一标准](#接口定义与统一标准)
2. [适配器实现与继承关系](#适配器实现与继承关系)
3. [核心方法实现分析](#核心方法实现分析)
@@ -52,9 +53,11 @@ class ScanData {
```
**Diagram sources**
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go#L26-L36)
**Section sources**
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go#L1-L37)
### 接口方法说明
@@ -73,7 +76,8 @@ class ScanData {
## 适配器实现与继承关系
各供应商通过实现 `PayInterface` 接口来接入系统,同时继承 `web.Controller` 以获得Web请求处理能力。这种设计模式遵循了适配器模式将不同供应商的特定实现适配到统一的接口标准上。
各供应商通过实现 `PayInterface` 接口来接入系统,同时继承 `web.Controller`
以获得Web请求处理能力。这种设计模式遵循了适配器模式将不同供应商的特定实现适配到统一的接口标准上。
```mermaid
classDiagram
@@ -143,12 +147,14 @@ WalMartImpl ..|> PayInterface
```
**Diagram sources**
- [aibo.go](file://internal/service/supplier/third_party/aibo.go#L39-L41)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L36-L38)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L33-L35)
- [walmart.go](file://internal/service/supplier/third_party/walmart.go#L35-L37)
**Section sources**
- [aibo.go](file://internal/service/supplier/third_party/aibo.go#L1-L482)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L1-L434)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L1-L448)
@@ -189,12 +195,14 @@ ReturnSuccess --> End
```
**Diagram sources**
- [aibo.go](file://internal/service/supplier/third_party/aibo.go#L118-L149)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L114-L145)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L132-L165)
- [walmart.go](file://internal/service/supplier/third_party/walmart.go#L188-L219)
**Section sources**
- [aibo.go](file://internal/service/supplier/third_party/aibo.go#L118-L149)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L114-L145)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L132-L165)
@@ -244,12 +252,14 @@ WriteSuccess --> End
```
**Diagram sources**
- [aibo.go](file://internal/service/supplier/third_party/aibo.go#L168-L260)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L165-L212)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L167-L223)
- [walmart.go](file://internal/service/supplier/third_party/walmart.go#L233-L260)
**Section sources**
- [aibo.go](file://internal/service/supplier/third_party/aibo.go#L168-L260)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L165-L212)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L167-L223)
@@ -287,12 +297,14 @@ ReturnFalse --> End
```
**Diagram sources**
- [aibo.go](file://internal/service/supplier/third_party/aibo.go#L262-L323)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L214-L275)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L225-L287)
- [walmart.go](file://internal/service/supplier/third_party/walmart.go#L262-L322)
**Section sources**
- [aibo.go](file://internal/service/supplier/third_party/aibo.go#L262-L323)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L214-L275)
- [t_mall_game.go](file://internal/service/supplier/third_party/t_mall_game.go#L225-L287)
@@ -333,9 +345,11 @@ registerSupplier ..> PayInterface : "持有"
```
**Diagram sources**
- [init.go](file://internal/service/supplier/third_party/init.go#L151-L153)
**Section sources**
- [init.go](file://internal/service/supplier/third_party/init.go#L1-L197)
### 注册机制

View File

@@ -9,6 +9,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [核心组件](#核心组件)
3. [支付请求处理流程](#支付请求处理流程)
@@ -19,17 +20,22 @@
8. [幂等性保障](#幂等性保障)
## 简介
本文档详细阐述了支付服务的架构设计,重点分析了支付请求从扫码控制器进入系统后的完整处理流程。文档聚焦于`pay_service.go``pay_solve.go`的实现机制,涵盖了订单创建、状态管理、金额校验和结果回调等核心环节。
本文档详细阐述了支付服务的架构设计,重点分析了支付请求从扫码控制器进入系统后的完整处理流程。文档聚焦于`pay_service.go`
`pay_solve.go`的实现机制,涵盖了订单创建、状态管理、金额校验和结果回调等核心环节。
**Section sources**
- [pay_service.go](file://internal/service/pay_service.go#L1-L50)
- [pay_solve.go](file://internal/service/pay_solve.go#L1-L50)
## 核心组件
支付服务的核心组件包括扫码控制器(`scan_controller.go`)、支付服务(`pay_service.go`)、支付解决方案(`pay_solve.go`)以及订单信息模型(`order_info.go`)。这些组件协同工作,实现了完整的支付处理流程。
支付服务的核心组件包括扫码控制器(`scan_controller.go`)、支付服务(`pay_service.go`)、支付解决方案(`pay_solve.go`)
以及订单信息模型(`order_info.go`)。这些组件协同工作,实现了完整的支付处理流程。
**Section sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L1-L50)
- [pay_service.go](file://internal/service/pay_service.go#L1-L50)
- [pay_solve.go](file://internal/service/pay_solve.go#L1-L50)
@@ -61,12 +67,14 @@ ScanController->>Client : 返回支付结果
```
**Diagram sources **
- [scan_controller.go](file://internal/controllers/scan_controller.go#L69-L316)
- [pay_service.go](file://internal/service/pay_service.go#L305-L359)
- [pay_solve.go](file://internal/service/pay_solve.go#L37-L195)
- [order_info.go](file://internal/models/order/order_info.go#L370-L376)
**Section sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L69-L316)
- [pay_service.go](file://internal/service/pay_service.go#L305-L359)
@@ -91,14 +99,17 @@ UpdateRoad --> End([结束])
```
**Diagram sources **
- [pay_solve.go](file://internal/service/pay_solve.go#L37-L195)
**Section sources**
- [pay_solve.go](file://internal/service/pay_solve.go#L37-L195)
## 金额不一致处理逻辑
当实际支付金额与订单金额不一致时,系统通过`SolvePaySuccessByAmountDifferent`函数处理此情况。该函数首先检查通道是否允许金额不一致的重发,然后根据重发次数决定是否继续处理。
当实际支付金额与订单金额不一致时,系统通过`SolvePaySuccessByAmountDifferent`
函数处理此情况。该函数首先检查通道是否允许金额不一致的重发,然后根据重发次数决定是否继续处理。
```mermaid
flowchart TD
@@ -117,9 +128,11 @@ CallScan --> End([结束])
```
**Diagram sources **
- [pay_solve.go](file://internal/service/supplier/third_party/init.go#L173-L195)
**Section sources**
- [pay_solve.go](file://internal/service/supplier/third_party/init.go#L173-L195)
## 数据交互模式
@@ -157,12 +170,14 @@ PayService --> OrderInfo : "数据操作"
```
**Diagram sources **
- [scan_controller.go](file://internal/controllers/scan_controller.go#L69-L316)
- [pay_service.go](file://internal/service/pay_service.go#L305-L359)
- [pay_solve.go](file://internal/service/pay_solve.go#L37-L195)
- [order_info.go](file://internal/models/order/order_info.go#L370-L376)
**Section sources**
- [scan_controller.go](file://internal/controllers/scan_controller.go#L69-L316)
- [pay_service.go](file://internal/service/pay_service.go#L305-L359)
- [pay_solve.go](file://internal/service/pay_solve.go#L37-L195)
@@ -173,6 +188,7 @@ PayService --> OrderInfo : "数据操作"
系统实现了完善的异常处理机制,通过`SolvePayFail`函数处理支付失败的情况。该函数会更新订单状态为失败,并记录失败原因。同时,系统使用异步任务池处理回调通知,避免阻塞主流程。
**Section sources**
- [pay_solve.go](file://internal/service/pay_solve.go#L198-L254)
## 幂等性保障
@@ -180,4 +196,5 @@ PayService --> OrderInfo : "数据操作"
为了确保操作的幂等性,系统在关键操作上采用了数据库事务和状态检查机制。例如,在处理支付成功时,会先检查订单是否已经是成功状态,避免重复处理。
**Section sources**
- [pay_solve.go](file://internal/service/pay_solve.go#L37-L195)

View File

@@ -15,6 +15,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
@@ -26,10 +27,14 @@
9. [结论](#结论)
## 简介
本文档旨在深入分析支付服务payfor_service.go和支付解决方案payfor_solve.go的实现解释它们如何协同工作来处理支付请求。文档详细描述了供应商接口supplier_interface.go的设计模式这是一个关键的扩展点允许系统集成多种第三方支付渠道如Apple、京东卡、沃尔玛。同时文档说明了系统如何通过实现此接口来添加新的支付供应商并涵盖订单池和任务队列的处理机制。
## 项目结构
本项目采用分层架构设计,主要分为配置、部署、内部服务和模型等目录。核心业务逻辑位于`internal`目录下包括缓存、配置、常量、控制器、数据传输对象DTO、模型、OpenTelemetry追踪、代理、路由、模式、服务、Swagger文档、任务和工具等子模块。支付相关的核心逻辑集中在`internal/service/pay_for``internal/service/supplier`目录中。
本项目采用分层架构设计,主要分为配置、部署、内部服务和模型等目录。核心业务逻辑位于`internal`
目录下包括缓存、配置、常量、控制器、数据传输对象DTO、模型、OpenTelemetry追踪、代理、路由、模式、服务、Swagger文档、任务和工具等子模块。支付相关的核心逻辑集中在
`internal/service/pay_for``internal/service/supplier`目录中。
```mermaid
graph TD
@@ -61,7 +66,8 @@ PayForService --> MerchantInfo
PayForService --> RoadInfo
```
**图源**
**图源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go)
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go)
@@ -73,20 +79,24 @@ PayForService --> RoadInfo
- [merchant_info.go](file://internal/models/merchant/merchant_info.go)
- [account.go](file://internal/models/accounts/account.go)
**本节来源**
**本节来源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go)
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go)
## 核心组件
支付服务payfor_service.go负责处理支付请求的入口逻辑包括签名验证、金额检查、订单重复性检查和支付通道选择。支付解决方案payfor_solve.go则负责处理支付结果的更新包括支付成功和支付失败的处理。供应商接口supplier_interface.go定义了所有第三方支付供应商必须实现的方法确保了系统的可扩展性和一致性。
**本节来源**
**本节来源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go#L1-L330)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go#L1-L144)
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go#L1-L37)
## 架构概述
系统采用接口驱动的设计模式,通过定义清晰的接口来解耦核心业务逻辑和第三方支付供应商的实现。这种设计模式带来了显著的可维护性和可扩展性优势。当需要添加新的支付供应商时,只需实现供应商接口并注册到系统中,而无需修改核心业务逻辑。
```mermaid
@@ -102,7 +112,8 @@ PayForSolve --> Database[(数据库)]
PayForService --> Database
```
**图源**
**图源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go)
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go)
@@ -110,10 +121,13 @@ PayForService --> Database
- [jd.go](file://internal/service/supplier/third_party/jd.go)
## 详细组件分析
### 支付服务分析
支付服务组件负责处理支付请求的完整生命周期,从接收请求到最终的支付结果查询。它首先验证请求的签名和参数,然后检查订单的重复性,最后选择合适的支付通道并发起支付请求。
#### 支付服务类图
```mermaid
classDiagram
class PayForService {
@@ -149,15 +163,18 @@ SupplierInterface <|-- AppleImpl : "实现"
SupplierInterface <|-- WalmartImpl : "实现"
```
**图源**
**图源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go#L1-L330)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go#L1-L144)
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go#L1-L37)
### 供应商接口分析
供应商接口是系统的关键扩展点,它定义了所有第三方支付供应商必须实现的方法。通过这种接口驱动的设计,系统可以轻松集成新的支付渠道,而无需修改核心业务逻辑。
#### 供应商接口序列图
```mermaid
sequenceDiagram
participant Client as "客户端"
@@ -175,19 +192,22 @@ Supplier-->>PayForService : 返回支付结果
PayForService-->>Client : 返回支付响应
```
**图源**
**图源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go#L1-L330)
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go#L1-L37)
- [aibo.go](file://internal/service/supplier/third_party/aibo.go#L1-L481)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L1-L433)
**本节来源**
**本节来源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go#L1-L330)
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go#L1-L37)
- [aibo.go](file://internal/service/supplier/third_party/aibo.go#L1-L481)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L1-L433)
## 依赖分析
系统通过清晰的依赖关系实现了高内聚低耦合的设计。支付服务依赖于支付解决方案和供应商接口,而具体的供应商实现则依赖于供应商接口。数据模型层为所有服务提供数据支持,确保了数据的一致性和完整性。
```mermaid
@@ -207,7 +227,8 @@ SupplierInterface --> Walmart
PayforInfo --> Account
```
**图源**
**图源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go)
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go)
@@ -218,20 +239,25 @@ PayforInfo --> Account
- [road_info.go](file://internal/models/road/road_info.go)
- [account.go](file://internal/models/accounts/account.go)
**本节来源**
**本节来源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go)
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go)
## 性能考虑
系统在设计时充分考虑了性能因素。通过使用数据库事务确保数据一致性,采用缓存机制减少数据库访问,以及使用异步处理提高响应速度。此外,系统还实现了支付通道的轮询机制,确保在高并发场景下的稳定性和可靠性。
## 故障排除指南
当支付请求失败时首先检查请求的签名是否正确然后验证支付金额是否符合要求。如果问题仍然存在检查订单是否重复以及支付通道是否可用。对于第三方支付供应商的问题需要检查供应商的API文档和状态码说明。
**本节来源**
**本节来源**
- [payfor_service.go](file://internal/service/pay_for/payfor_service.go#L1-L330)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go#L1-L144)
## 结论
本文档详细分析了支付服务和支付解决方案的实现,以及供应商接口的设计模式。通过接口驱动的设计,系统实现了高度的可扩展性和可维护性。未来可以进一步优化支付通道的选择算法,提高支付成功率,并增强系统的监控和告警能力。

View File

@@ -18,6 +18,7 @@
</cite>
## 目录
1. [分布式追踪](#分布式追踪)
2. [日志诊断](#日志诊断)
3. [关键指标与Prometheus集成](#关键指标与prometheus集成)
@@ -27,9 +28,11 @@
## 分布式追踪
本系统使用OpenTelemetryotelTrace包实现分布式追踪通过`otelTrace.InitTracer()`函数初始化追踪器配置了生产环境优化的Trace导出器、资源标识、采样策略和批量处理器。追踪器使用gRPC协议将数据发送到指定的收集器支持gzip压缩以减少网络传输量。
本系统使用OpenTelemetryotelTrace包实现分布式追踪通过`otelTrace.InitTracer()`
函数初始化追踪器配置了生产环境优化的Trace导出器、资源标识、采样策略和批量处理器。追踪器使用gRPC协议将数据发送到指定的收集器支持gzip压缩以减少网络传输量。
系统通过`otelTrace.Middleware`中间件实现请求级别的追踪。该中间件在请求处理开始时创建一个Span从请求头中提取上游的trace context并注入到当前context中。Span记录了HTTP方法、URL、状态码、响应大小、处理时长等关键性能指标。当请求处理时间超过5秒时会自动标记为慢请求并记录警告日志。
系统通过`otelTrace.Middleware`中间件实现请求级别的追踪。该中间件在请求处理开始时创建一个Span从请求头中提取上游的trace
context并注入到当前context中。Span记录了HTTP方法、URL、状态码、响应大小、处理时长等关键性能指标。当请求处理时间超过5秒时会自动标记为慢请求并记录警告日志。
```mermaid
sequenceDiagram
@@ -47,10 +50,12 @@ Exporter->>Collector : 发送追踪数据
```
**Diagram sources**
- [init.go](file://internal/otelTrace/init.go#L29-L205)
- [middleware.go](file://internal/otelTrace/middleware.go#L21-L140)
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L1-L257)
- [middleware.go](file://internal/otelTrace/middleware.go#L1-L142)
@@ -58,7 +63,8 @@ Exporter->>Collector : 发送追踪数据
系统使用`otelTrace.Logger`进行日志记录该Logger集成了OpenTelemetry的日志导出功能。日志信息不仅会输出到标准输出还会通过OTLP协议发送到日志收集器实现日志的集中管理和分析。
`otelTrace.logs.go`文件定义了`CustomLogger`结构体它包装了Zap日志库并提供了`WithContext`方法可以将OpenTelemetry的context与日志记录关联起来。这使得在查看日志时可以轻松地关联到相应的追踪信息实现日志与追踪的联动分析。
`otelTrace.logs.go`文件定义了`CustomLogger`结构体它包装了Zap日志库并提供了`WithContext`
方法可以将OpenTelemetry的context与日志记录关联起来。这使得在查看日志时可以轻松地关联到相应的追踪信息实现日志与追踪的联动分析。
```mermaid
classDiagram
@@ -73,18 +79,22 @@ Logger --> CustomLogger : "全局实例"
```
**Diagram sources**
- [logs.go](file://internal/otelTrace/logs.go#L1-L29)
- [init.go](file://internal/otelTrace/init.go#L249-L256)
**Section sources**
- [logs.go](file://internal/otelTrace/logs.go#L1-L29)
- [init.go](file://internal/otelTrace/init.go#L249-L256)
## 关键指标与Prometheus集成
系统通过`otelTrace.InitTracer()`函数配置了Metrics导出器将关键指标通过OTLP协议发送到收集器。同时`internal/service/supplier/third_party/pool/metrics.go`文件定义了`Metrics`结构体,用于监控订单处理相关的指标。
系统通过`otelTrace.InitTracer()`函数配置了Metrics导出器将关键指标通过OTLP协议发送到收集器。同时
`internal/service/supplier/third_party/pool/metrics.go`文件定义了`Metrics`结构体,用于监控订单处理相关的指标。
这些指标包括:
- `order_pool_processed_total`: 已处理的订单总数
- `order_pool_failed_total`: 处理失败的订单总数
- `order_pool_processing_seconds`: 订单处理时间分布
@@ -111,10 +121,12 @@ I --> J[监控面板]
```
**Diagram sources**
- [init.go](file://internal/otelTrace/init.go#L29-L205)
- [metrics.go](file://internal/service/supplier/third_party/pool/metrics.go#L1-L163)
**Section sources**
- [init.go](file://internal/otelTrace/init.go#L29-L205)
- [metrics.go](file://internal/service/supplier/third_party/pool/metrics.go#L1-L163)
@@ -145,6 +157,7 @@ I --> J[监控面板]
3. 检查`CurrentChannelConcurrency``CurrentRoadConcurrency``CurrentFaceValueConcurrency`指标,查看当前并发数。
**Section sources**
- [order_controller.go](file://internal/controllers/order_controller.go#L1-L226)
- [payfor_solve.go](file://internal/service/pay_for/payfor_solve.go#L1-L144)
- [pay_service.go](file://internal/service/pay_service.go#L1-L448)
@@ -156,12 +169,14 @@ I --> J[监控面板]
对于慢查询的识别,系统在`otelTrace.Middleware`中设置了5秒的阈值。当请求处理时间超过5秒时会自动记录警告日志并标记为慢请求。开发者可以通过分析这些日志定位性能瓶颈。
优化建议:
1. 优化数据库查询,添加必要的索引。
2. 使用缓存减少数据库访问。
3. 优化上游供应商接口调用,减少网络延迟。
4. 调整订单池大小和队列长度,平衡性能和资源使用。
**Section sources**
- [middleware.go](file://internal/otelTrace/middleware.go#L21-L140)
- [metrics.go](file://internal/service/supplier/third_party/pool/metrics.go#L1-L163)
@@ -170,11 +185,13 @@ I --> J[监控面板]
系统通过`otelTrace.monitorExporterHealth`函数实现了导出器健康状态的监控。该函数每30秒检查一次导出失败次数如果失败次数超过10次会记录警告日志。
告警规则建议:
1.`exportFailures`超过10次时发送告警通知。
2.`order_query_failed_total`在5分钟内增加超过100次时发送告警通知。
3.`order_pool_processing_seconds`的95分位数超过5秒时发送告警通知。
4.`order_pool_size`接近最大值时,发送告警通知。
**Section sources**
- [utils.go](file://internal/otelTrace/utils.go#L58-L60)
- [metrics.go](file://internal/service/supplier/third_party/pool/metrics.go#L1-L163)

View File

@@ -11,6 +11,7 @@
</cite>
## 目录
1. [介绍](#介绍)
2. [核心接口定义](#核心接口定义)
3. [核心方法实现分析](#核心方法实现分析)
@@ -23,9 +24,11 @@
10. [常见陷阱与最佳实践](#常见陷阱与最佳实践)
## 介绍
本文档旨在为开发者提供一份详细的第三方支付渠道集成指南。通过分析现有支付供应商如Apple、京东的实现指导开发者如何创建新的支付供应商集成。文档涵盖了从接口实现、HTTP通信、错误处理到系统注册的完整流程。
## 核心接口定义
所有第三方支付供应商必须实现`PayInterface`接口,该接口定义了支付系统所需的核心功能。
```mermaid
@@ -57,13 +60,17 @@ class ScanData {
```
**图示来源**
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go#L26-L36)
**本节来源**
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go#L1-L37)
## 核心方法实现分析
### Scan方法实现
`Scan`方法是支付流程的起点负责处理扫码支付请求。通过对比Apple和京东的实现可以发现通用的处理模式。
```mermaid
@@ -82,14 +89,17 @@ Scan-->>开发者 : 返回支付结果
```
**图示来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L152-L183)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L114-L145)
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L152-L183)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L114-L145)
### PayNotify方法实现
`PayNotify`方法处理来自第三方供应商的支付回调通知,是确保交易状态同步的关键。
```mermaid
@@ -108,14 +118,17 @@ PayNotify-->>供应商 : 返回确认响应
```
**图示来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L202-L240)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L165-L212)
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L202-L240)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L165-L212)
### PayQuery方法实现
`PayQuery`方法用于查询订单状态,确保交易的最终一致性。
```mermaid
@@ -132,15 +145,19 @@ PayQuery-->>系统 : 返回查询结果
```
**图示来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L242-L305)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L214-L275)
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L242-L305)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L214-L275)
## HTTP请求与响应处理
### 请求构建与发送
支付供应商通过HTTP请求与第三方系统通信使用`httplib`库进行请求构建和发送。
```mermaid
@@ -156,14 +173,17 @@ Start([开始]) --> 构建参数["构建请求参数"]
```
**图示来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L77-L150)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L53-L112)
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L77-L150)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L53-L112)
### 响应解析与处理
收到响应后系统需要解析JSON数据并根据业务逻辑进行处理。
```mermaid
@@ -177,11 +197,14 @@ flowchart TD
```
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L77-L150)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L53-L112)
## 错误处理与重试机制
### 错误处理策略
系统采用分层的错误处理策略,确保异常情况下的稳定性。
```mermaid
@@ -200,17 +223,22 @@ flowchart TD
```
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L77-L150)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L53-L112)
### 重试机制实现
通过`req.Retries(3)`设置重试次数,确保网络不稳定时的请求可靠性。
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L77-L150)
## 供应商认证机制
### 签名生成
供应商通过特定算法生成签名,确保请求的完整性和安全性。
```mermaid
@@ -222,11 +250,14 @@ flowchart TD
```
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L77-L150)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L53-L112)
## API限流与数据格式
### 限流处理
系统通过配置和超时设置来应对API限流。
```mermaid
@@ -239,18 +270,23 @@ flowchart TD
```
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L77-L150)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L53-L112)
### 数据格式规范
所有请求和响应都遵循统一的JSON格式规范。
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L77-L150)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L53-L112)
## 新供应商集成步骤
### 创建供应商结构体
创建新的供应商实现结构体,继承必要的控制器。
```mermaid
@@ -261,31 +297,40 @@ flowchart TD
```
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L14-L16)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L14-L16)
### 实现核心方法
按照接口定义实现所有必需的方法。
**本节来源**
- [supplier_interface.go](file://internal/service/supplier/supplier_interface.go#L26-L36)
### 添加到系统
将新的供应商实现文件添加到third_party目录并在系统中注册。
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go)
- [jd.go](file://internal/service/supplier/third_party/jd.go)
## 单元测试编写
参考现有的`*_test.go`文件编写单元测试,确保代码质量。
**本节来源**
- [apple_test.go](file://internal/service/supplier/third_party/apple_test.go)
- [jd_test.go](file://internal/service/supplier/third_party/jd_test.go)
## 常见陷阱与最佳实践
### 交易幂等性保证
通过订单号和状态检查确保交易的幂等性。
```mermaid
@@ -300,10 +345,12 @@ flowchart TD
```
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go#L202-L240)
- [jd.go](file://internal/service/supplier/third_party/jd.go#L165-L212)
### 最佳实践总结
1. 始终记录详细的日志信息
2. 实现完整的错误处理
3. 遵循统一的代码风格
@@ -311,5 +358,6 @@ flowchart TD
5. 确保交易的幂等性
**本节来源**
- [apple.go](file://internal/service/supplier/third_party/apple.go)
- [jd.go](file://internal/service/supplier/third_party/jd.go)

View File

@@ -10,6 +10,7 @@
</cite>
## 目录
1. [多阶段构建详解](#多阶段构建详解)
2. [Docker Compose配置解析](#docker-compose配置解析)
3. [本地部署步骤](#本地部署步骤)
@@ -28,7 +29,8 @@
- `CGO_ENABLED=0`禁用CGO以生成静态二进制文件
- `GOOS=linux``GOARCH=amd64`:指定目标操作系统和架构
构建过程首先将整个项目代码复制到工作目录`/build`,然后执行`go mod tidy`清理依赖并使用`go build`命令生成经过优化的静态二进制文件`main`(使用`-ldflags="-s -w"`去除调试信息以减小体积)。
构建过程首先将整个项目代码复制到工作目录`/build`,然后执行`go mod tidy`清理依赖并使用`go build`命令生成经过优化的静态二进制文件
`main`(使用`-ldflags="-s -w"`去除调试信息以减小体积)。
### 运行阶段Runtime Stage
@@ -36,17 +38,19 @@
1. **环境变量设置**:配置了时区`TZ=Asia/Shanghai`及多个服务相关的环境变量
2. **系统配置**
- 配置阿里云Alpine镜像源以加速包下载
- 安装`tzdata``curl`等必要工具
- 设置中国时区
- 配置阿里云Alpine镜像源以加速包下载
- 安装`tzdata``curl`等必要工具
- 设置中国时区
3. **证书配置**
- 安装标准CA证书包
- 添加Comodo AAA证书以支持特定HTTPS连接
- 更新证书信任链
- 安装标准CA证书包
- 添加Comodo AAA证书以支持特定HTTPS连接
- 更新证书信任链
最后,从构建阶段复制编译好的二进制文件`main`、配置文件目录`conf/`和数据目录`data/`到运行环境,并设置容器启动命令为`./main`暴露端口12309。
最后,从构建阶段复制编译好的二进制文件`main`、配置文件目录`conf/`和数据目录`data/`到运行环境,并设置容器启动命令为`./main`
暴露端口12309。
**Section sources**
- [Dockerfile](file://deploy/Dockerfile#L1-L43)
## Docker Compose配置解析
@@ -57,25 +61,27 @@
- **构建配置**:指定构建上下文为项目根目录(`..`),使用`./deploy/Dockerfile`作为Dockerfile
- **容器配置**
- 容器名称:`kami_gateway`
- 镜像标签:`kami_gateway:$VERSION`(支持版本变量)
- 重启策略:`always`(确保容器异常退出后自动重启)
- 容器名称:`kami_gateway`
- 镜像标签:`kami_gateway:$VERSION`(支持版本变量)
- 重启策略:`always`(确保容器异常退出后自动重启)
- **网络配置**:连接到外部网络`1panel-network`,实现与其他服务的通信
- **端口映射**
- `127.0.0.1:22309:12309`:主服务端口,仅限本地访问
- `127.0.0.1:22390:12390`:监控服务端口,仅限本地访问
- `127.0.0.1:22309:12309`:主服务端口,仅限本地访问
- `127.0.0.1:22390:12390`:监控服务端口,仅限本地访问
- **卷挂载**
- 配置目录:`/data/kami/gateway/conf/``/app/conf/`
- 日志目录:`/data/kami/gateway/logs/``/app/logs/`
- 配置目录:`/data/kami/gateway/conf/``/app/conf/`
- 日志目录:`/data/kami/gateway/logs/``/app/logs/`
### 本地开发配置
`docker-compose-local.yaml`提供了简化的本地开发配置,主要差异包括:
- 使用固定标签`latest`
- 端口映射为`12309:12309`,允许外部访问
- 简化的服务名称`gateway_kami`
**Section sources**
- [docker-compose.yaml](file://deploy/docker-compose.yaml#L1-L23)
- [docker-compose-local.yaml](file://deploy/docker-compose-local.yaml#L1-L17)
@@ -91,6 +97,7 @@ cd kami_gateway
### 2. 准备配置文件
确保`conf/app.conf`中的配置正确特别是数据库、Redis和MQ连接信息。根据需要修改以下关键配置
- `httpport = 12309`:服务监听端口
- 数据库连接参数
- Redis连接参数
@@ -123,6 +130,7 @@ curl http://127.0.0.1:22309/health
```
**Section sources**
- [app.conf](file://conf/app.conf#L1-L77)
- [main.go](file://main.go#L23-L57)
@@ -133,11 +141,14 @@ curl http://127.0.0.1:22309/health
**症状**:容器无法启动,日志显示"port is already allocated"
**解决方案**
1. 检查端口占用情况:
```bash
netstat -tlnp | grep :22309
lsof -i :22309
```
2. 修改`docker-compose.yaml`中的主机端口映射,例如改为`127.0.0.1:32309:12309`
3. 重启服务
@@ -146,11 +157,14 @@ lsof -i :22309
**症状**`go mod tidy``go build`命令失败
**解决方案**
1. 确保网络连接正常,特别是对`goproxy.cn`的访问
2. 清理Go模块缓存
```bash
go clean -modcache
```
3. 检查`go.mod`文件完整性
4. 确保Docker有足够的内存资源
@@ -159,10 +173,13 @@ go clean -modcache
**症状**:容器启动后立即退出
**排查步骤**
1. 查看详细日志:
```bash
docker logs kami_gateway
```
2. 检查配置文件挂载是否正确
3. 验证数据库、Redis等依赖服务是否正常运行
4. 检查文件权限,确保容器内进程有足够权限访问挂载目录
@@ -172,10 +189,12 @@ docker logs kami_gateway
**症状**HTTPS连接失败证书验证错误
**解决方案**
1. 确认`Dockerfile`中证书安装步骤执行成功
2. 检查`update-ca-certificates`命令输出
3. 如需添加自定义证书,可将证书文件挂载到容器并手动添加
**Section sources**
- [Dockerfile](file://deploy/Dockerfile#L1-L43)
- [docker-compose.yaml](file://deploy/docker-compose.yaml#L1-L23)

View File

@@ -11,6 +11,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [预设环境变量](#预设环境变量)
3. [环境变量覆盖机制](#环境变量覆盖机制)
@@ -20,13 +21,17 @@
7. [总结](#总结)
## 简介
`kami_gateway` 项目通过环境变量实现灵活的运行时配置,支持开发、测试和生产等多环境部署。本项目利用 Docker 和 docker-compose 提供了完整的容器化部署方案,并通过环境变量机制实现配置的动态注入与覆盖。环境变量不仅用于服务地址配置,还涉及代理服务、认证信息等关键参数,是系统可配置性的核心组成部分。
`kami_gateway` 项目通过环境变量实现灵活的运行时配置,支持开发、测试和生产等多环境部署。本项目利用 Docker 和 docker-compose
提供了完整的容器化部署方案,并通过环境变量机制实现配置的动态注入与覆盖。环境变量不仅用于服务地址配置,还涉及代理服务、认证信息等关键参数,是系统可配置性的核心组成部分。
**Section sources**
- [Dockerfile](file://deploy/Dockerfile#L1-L44)
- [docker-compose.yaml](file://deploy/docker-compose.yaml#L1-L24)
## 预设环境变量
`Dockerfile` 中通过 `ENV` 指令预设了一系列环境变量,这些变量为系统提供了默认配置:
- `serverName`: 服务器名称,默认值为"默认"
@@ -42,31 +47,38 @@
这些环境变量在容器构建时被设置,为应用提供了基础的运行配置。
**Section sources**
- [Dockerfile](file://deploy/Dockerfile#L10-L19)
## 环境变量覆盖机制
`kami_gateway` 支持通过多种方式覆盖 Dockerfile 中预设的环境变量,实现不同环境的灵活配置。
`docker-compose.yaml` 文件中,虽然没有显式定义 `environment` 字段来覆盖环境变量,但通过外部卷挂载机制实现了配置的外部化:
```yaml
volumes:
- /data/kami/gateway/conf/:/app/conf/
- /data/kami/gateway/logs/:/app/logs/
```
这种设计允许通过挂载外部配置文件来间接影响环境变量的行为。
而在 `docker-compose-local.yaml` 中,通过简化配置实现了本地开发环境的快速部署,保留了构建上下文但简化了网络和端口配置。
运行时可以通过 `docker run` 命令的 `-e` 参数或在 `docker-compose.yml``environment` 字段中直接覆盖这些变量,例如:
```bash
docker run -e serverName="开发环境" -e gatewayAddr="http://dev.gateway:12309" kami_gateway
```
**Section sources**
- [docker-compose.yaml](file://deploy/docker-compose.yaml#L1-L24)
- [docker-compose-local.yaml](file://deploy/docker-compose-local.yaml#L1-L18)
## 构建脚本与版本控制
`build.sh` 脚本定义了项目的构建过程,其中包含了与环境变量相关的构建参数:
```bash
@@ -74,11 +86,13 @@ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
```
虽然该脚本本身不直接处理环境变量,但在 `docker-compose.yaml` 中使用了 `$VERSION` 环境变量来控制镜像标签:
```yaml
image: kami_gateway:$VERSION
```
这种设计允许在构建时通过传递不同的 `VERSION` 值来生成不同标签的镜像,实现版本控制。例如,可以通过以下命令构建特定版本的镜像:
```bash
VERSION=v1.2.0 docker-compose build
```
@@ -86,25 +100,33 @@ VERSION=v1.2.0 docker-compose build
这使得 CI/CD 流程能够根据 Git 标签或构建号自动创建相应版本的镜像,提高了部署的可追溯性和一致性。
**Section sources**
- [build.sh](file://build.sh#L1-L2)
- [docker-compose.yaml](file://deploy/docker-compose.yaml#L6-L7)
## 实际应用案例
环境变量在 `kami_gateway` 中有多种实际应用场景。
### 动态数据库配置
虽然数据库连接信息主要在 `app.conf` 中定义,但可以通过环境变量实现动态配置。例如,可以修改配置读取逻辑,从环境变量中获取数据库连接参数:
```go
dbhost := os.Getenv("MYSQL_HOST")
dbport := os.Getenv("MYSQL_PORT")
```
这样可以在不同环境中使用不同的数据库实例,而无需修改配置文件。
### 代理服务切换
通过 `proxyUrl``proxyAuthKey``proxyAuthPwd` 环境变量,可以轻松切换不同的代理服务提供商。在开发环境中可以使用测试代理,在生产环境中切换到付费的高稳定性代理服务。
### 多环境部署
通过组合使用不同的环境变量,可以实现一套代码在多个环境中的部署:
- 开发环境:`serverName="开发"``gatewayAddr="http://localhost:12309"`
- 测试环境:`serverName="测试"``gatewayAddr="http://test.gateway:12309"`
- 生产环境:`serverName="生产"``gatewayAddr="https://api.gateway.com"`
@@ -112,21 +134,29 @@ dbport := os.Getenv("MYSQL_PORT")
这种机制确保了配置的灵活性和安全性,敏感信息可以通过环境变量而非配置文件进行管理。
**Section sources**
- [app.conf](file://conf/app.conf#L1-L78)
- [Dockerfile](file://deploy/Dockerfile#L10-L19)
## 环境变量与配置文件优先级
`kami_gateway` 中,环境变量与 `app.conf` 配置文件之间存在明确的优先级关系。
根据 `internal/config/config.go` 中的配置读取逻辑,系统使用 beego 框架的 `web.AppConfig.String()` 方法读取配置值。beego 框架默认支持环境变量覆盖配置文件中的值,即当同名环境变量存在时,会优先使用环境变量的值。
根据 `internal/config/config.go` 中的配置读取逻辑,系统使用 beego 框架的 `web.AppConfig.String()` 方法读取配置值。beego
框架默认支持环境变量覆盖配置文件中的值,即当同名环境变量存在时,会优先使用环境变量的值。
例如,`app.conf` 中定义了 `httpport = 12309`,但可以通过设置 `httpport` 环境变量来覆盖此值。这种设计模式使得关键配置既可以在配置文件中提供默认值,又可以通过环境变量在部署时进行灵活调整。
例如,`app.conf` 中定义了 `httpport = 12309`,但可以通过设置 `httpport`
环境变量来覆盖此值。这种设计模式使得关键配置既可以在配置文件中提供默认值,又可以通过环境变量在部署时进行灵活调整。
对于 `shop::key` 这样的配置项,`GetMerchantKey()` 函数直接从配置中读取,这意味着可以通过环境变量 `shop__key`beego 将双下划线转换为配置节分隔符)来覆盖商户密钥,增强了配置的安全性。
对于 `shop::key` 这样的配置项,`GetMerchantKey()` 函数直接从配置中读取,这意味着可以通过环境变量 `shop__key`beego
将双下划线转换为配置节分隔符)来覆盖商户密钥,增强了配置的安全性。
**Section sources**
- [app.conf](file://conf/app.conf#L1-L78)
- [config.go](file://internal/config/config.go#L1-L45)
## 总结
`kami_gateway` 项目通过精心设计的环境变量机制,实现了高度灵活的配置管理。从 Dockerfile 中的预设变量,到 docker-compose 的部署配置,再到运行时的动态覆盖,形成了完整的配置管理体系。这种设计不仅支持多环境部署,还提高了系统的可维护性和安全性。通过将敏感信息和环境特定配置从代码中分离,项目能够更好地适应不同的部署场景,同时保持代码库的整洁和一致性。
`kami_gateway` 项目通过精心设计的环境变量机制,实现了高度灵活的配置管理。从 Dockerfile 中的预设变量,到 docker-compose
的部署配置,再到运行时的动态覆盖,形成了完整的配置管理体系。这种设计不仅支持多环境部署,还提高了系统的可维护性和安全性。通过将敏感信息和环境特定配置从代码中分离,项目能够更好地适应不同的部署场景,同时保持代码库的整洁和一致性。

View File

@@ -15,6 +15,7 @@
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
@@ -26,9 +27,11 @@
9. [结论](#结论)
## 简介
本文档提供基于Docker和Docker Compose的完整部署与配置指南涵盖本地开发和生产环境的设置。文档详细说明如何通过环境变量覆盖配置文件中的默认值并提供部署后的验证步骤。
## 项目结构
项目采用分层结构,主要包含配置、部署脚本、内部业务逻辑和主程序入口。部署相关文件集中于`deploy`目录,配置文件位于`conf`目录。
```mermaid
@@ -53,23 +56,28 @@ appConf --> main
buildScript --> main
```
**图示来源**
**图示来源**
- [Dockerfile](file://deploy/Dockerfile#L1-L44)
- [docker-compose.yaml](file://deploy/docker-compose.yaml#L1-L24)
- [app.conf](file://conf/app.conf#L1-L78)
**本节来源**
**本节来源**
- [deploy](file://deploy)
- [conf](file://conf)
## 核心组件
核心组件包括配置管理、代理池、消息队列和数据库连接。系统通过`main.go`初始化各项服务,包括代理池、缓存、消息消费者和队列系统。
**本节来源**
**本节来源**
- [main.go](file://main.go#L1-L59)
- [config.go](file://internal/config/config.go#L1-L45)
## 架构概述
系统采用微服务架构通过Beego框架提供HTTP服务使用Redis作为缓存MySQL作为持久化存储ActiveMQ作为消息队列。代理池用于外部请求的负载均衡。
```mermaid
@@ -83,16 +91,19 @@ ProxyPool --> ExternalAPI[外部API]
Gateway --> Backend[后端服务]
```
**图示来源**
**图示来源**
- [main.go](file://main.go#L1-L59)
- [cfg_model.go](file://internal/config/cfg_model.go#L1-L141)
## 详细组件分析
### 配置管理分析
系统配置通过`app.conf`文件和环境变量双重管理,环境变量优先级高于配置文件。
#### 配置类图
```mermaid
classDiagram
class Config {
@@ -129,15 +140,18 @@ Config --> ProxyInfo : "包含"
MQConfig --> Config : "依赖"
```
**图示来源**
**图示来源**
- [cfg_model.go](file://internal/config/cfg_model.go#L1-L141)
- [proxy.go](file://internal/config/proxy.go#L1-L18)
- [mq_config.go](file://internal/config/mq_config.go#L1-L60)
### 代理池分析
代理池在系统启动时初始化,支持通过环境变量动态配置代理服务器列表。
#### 代理池初始化流程图
```mermaid
flowchart TD
Start([程序启动]) --> InitProxyPool["proxy.InitProxyPool()"]
@@ -152,15 +166,18 @@ Initialize --> ReturnSuccess["返回nil"]
EmptyList --> Initialize
```
**图示来源**
**图示来源**
- [main.go](file://main.go#L1-L59)
- [proxy.go](file://internal/config/proxy.go#L1-L18)
- [mq_config.go](file://internal/config/mq_config.go#L1-L60)
### 消息队列分析
系统使用消息队列处理订单查询和通知通过独立的goroutine消费消息。
#### 消息处理序列图
```mermaid
sequenceDiagram
participant Main as "main()"
@@ -176,10 +193,12 @@ Main->>third_party : StartOrderPool()
Note over Notify,Queue : 启动消息消费者和队列系统
```
**图示来源**
**图示来源**
- [main.go](file://main.go#L1-L59)
## 依赖分析
系统依赖Go模块、Docker环境和外部服务。通过`go.mod`管理Go依赖通过Docker容器化部署。
```mermaid
@@ -192,22 +211,28 @@ B --> F[其他Beego组件]
C --> G[MySQL协议]
```
**图示来源**
**图示来源**
- [Dockerfile](file://deploy/Dockerfile#L1-L44)
- [go.mod](file://go.mod#L1-L20)
**本节来源**
**本节来源**
- [Dockerfile](file://deploy/Dockerfile#L1-L44)
## 性能考虑
系统通过缓存、代理池和消息队列提高性能。Redis缓存减少数据库访问代理池实现请求负载均衡消息队列解耦业务逻辑。
## 故障排除指南
常见问题包括数据库连接失败、Redis连接失败和代理池初始化失败。检查相应的配置项和网络连接。
**本节来源**
**本节来源**
- [main.go](file://main.go#L1-L59)
- [cache/redis.go](file://internal/cache/redis.go#L18-L28)
## 结论
本文档提供了完整的部署和配置指南,涵盖了从构建到运行的全过程。通过合理的配置管理和组件设计,系统具有良好的可维护性和扩展性。

View File

@@ -10,6 +10,7 @@
</cite>
## 目录
1. [应用配置](#应用配置)
2. [HTTP端口配置](#http端口配置)
3. [日志配置](#日志配置)
@@ -34,6 +35,7 @@ runmode = prod
- `runmode`:设置运行模式为`prod`(生产环境),系统根据此模式加载相应的配置和行为
**Section sources**
- [app.conf](file://conf/app.conf#L1-L2)
## HTTP端口配置
@@ -54,6 +56,7 @@ HTTPAddr = 0.0.0.0
这些配置共同定义了应用的网络访问入口,客户端通过`http://<服务器IP>:12309`访问服务。
**Section sources**
- [app.conf](file://conf/app.conf#L2-L6)
## 日志配置
@@ -74,6 +77,7 @@ maxdays=10
- `maxdays`日志文件最大保存天数为10天超过此期限的日志将被自动清理
日志级别0-7分别对应
- 0: emergency (紧急)
- 1: alert (警报)
- 2: critical (严重)
@@ -84,6 +88,7 @@ maxdays=10
- 7: debug (调试)
**Section sources**
- [app.conf](file://conf/app.conf#L9-L15)
## 数据库配置
@@ -110,6 +115,7 @@ debug = true
这些配置用于建立与MySQL数据库的连接支持系统数据的持久化存储。
**Section sources**
- [app.conf](file://conf/app.conf#L17-L24)
## Redis配置
@@ -130,6 +136,7 @@ password = ""
Redis主要用于缓存频繁访问的数据提高系统性能和响应速度。
**Section sources**
- [app.conf](file://conf/app.conf#L26-L30)
## 消息队列配置
@@ -148,6 +155,7 @@ port = 61613
消息队列用于实现系统组件间的异步通信,提高系统的可扩展性和可靠性。
**Section sources**
- [app.conf](file://conf/app.conf#L32-L35)
## 第三方服务API配置
@@ -155,6 +163,7 @@ port = 61613
配置文件中包含了多个第三方服务的API端点和回调地址配置。
### MF服务配置
```ini
[mf]
submit_card_url = http://test.shop.center.mf178.cn/userapi/card/submit_card
@@ -162,6 +171,7 @@ query_card_url = http://test.shop.center.mf178.cn/userapi/card/order_info
```
### Apple卡服务配置
```ini
[appleCard]
submit_card_url = http://kami_backend:12401/api/cardInfo/appleCard/submit
@@ -170,6 +180,7 @@ notify_url = http://kami_gateway:12309/appleCard/notify
```
### 京东卡服务配置
```ini
[jdCard]
submit_card_url = http://kami_backend:12401/api/cardInfo/jdCard/submit
@@ -178,6 +189,7 @@ notify_url = http://kami_gateway:12309/jdCard/notify
```
### 其他服务配置
```ini
[tMallGame]
submit_card_url=http://test.shop.center.mf178.cn/recharge/tMallGame/order/submit
@@ -186,11 +198,13 @@ query_card_url=http://test.shop.center.mf178.cn/userapi/card/order_info
```
这些配置定义了与各个第三方服务交互的API端点包括
- `submit_card_url`:提交卡密的接口地址
- `query_card_url`:查询卡密状态的接口地址
- `notify_url`:接收第三方服务回调通知的地址
**Section sources**
- [app.conf](file://conf/app.conf#L37-L77)
## 代理配置
@@ -207,6 +221,7 @@ proxies = []
代理配置与环境变量`proxyUrl`协同工作用于获取动态代理IP实现请求的IP轮换避免被目标网站封禁。
**Section sources**
- [app.conf](file://conf/app.conf#L77-L78)
## 环境变量与配置文件的协同
@@ -227,6 +242,7 @@ func GetProxyInfo() ProxyInfo {
- `proxyAuthKey``proxyAuthPwd`:代理认证密钥和密码,同样优先从环境变量获取
这种设计实现了配置的灵活性:
1. 生产环境中通过环境变量设置敏感信息,避免硬编码在配置文件中
2. 开发环境中使用默认值,简化配置
3. 可以根据不同环境动态调整代理服务
@@ -249,16 +265,19 @@ J --> K[请求完成]
```
**Diagram sources**
- [proxy.go](file://internal/config/proxy.go#L10-L17)
- [proxy_pool.go](file://internal/utils/proxy_pool.go#L108-L120)
**Section sources**
- [proxy.go](file://internal/config/proxy.go#L10-L17)
- [proxy_pool.go](file://internal/utils/proxy_pool.go#L108-L120)
## 生产与测试环境对比
### 生产环境配置特点
```ini
runmode = prod
dbpasswd = Woaizixkie!123
@@ -271,6 +290,7 @@ proxyUrl = https://secure.proxy.enterprise.com/api
- 日志级别可能调整为较低级别如3-4减少日志量
### 测试环境配置特点
```ini
runmode = dev
dbpasswd = test123
@@ -284,15 +304,16 @@ proxyUrl = https://test.proxy.service.com/api
### 配置项对系统行为的影响
| 配置项 | 影响范围 | 生产环境建议 | 测试环境建议 |
|--------|--------|------------|------------|
| `runmode` | 系统行为模式 | prod | dev |
| `level` | 日志详细程度 | 3-4 (error-warning) | 7 (debug) |
| `maxdays` | 存储空间占用 | 30-90天 | 7-14天 |
| `debug` | 性能与安全性 | false | true |
| `proxyUrl` | 请求IP轮换 | 高质量代理服务 | 测试代理服务 |
| 配置项 | 影响范围 | 生产环境建议 | 测试环境建议 |
|------------|--------|---------------------|-----------|
| `runmode` | 系统行为模式 | prod | dev |
| `level` | 日志详细程度 | 3-4 (error-warning) | 7 (debug) |
| `maxdays` | 存储空间占用 | 30-90天 | 7-14天 |
| `debug` | 性能与安全性 | false | true |
| `proxyUrl` | 请求IP轮换 | 高质量代理服务 | 测试代理服务 |
通过合理配置这些参数,可以确保系统在不同环境中以最优方式运行,既保证生产环境的稳定性和安全性,又满足测试环境的调试需求。
**Section sources**
- [app.conf](file://conf/app.conf#L1-L78)

View File

@@ -15,6 +15,7 @@
</cite>
## 目录
1. [项目简介](#项目简介)
2. [项目结构](#项目结构)
3. [核心功能](#核心功能)
@@ -25,41 +26,52 @@
8. [高级特性](#高级特性)
## 项目简介
kami_gateway 是一个基于 Go 语言和 Beego 框架构建的高性能支付网关系统。该项目旨在为各种支付方式(包括礼品卡和其他支付渠道)提供支付处理、订单管理和供应商集成服务。系统采用 MVC 模式进行架构设计,具有良好的可扩展性和可维护性。
kami_gateway 是一个基于 Go 语言和 Beego 框架构建的高性能支付网关系统。该项目旨在为各种支付方式(包括礼品卡和其他支付渠道)提供支付处理、订单管理和供应商集成服务。系统采用
MVC 模式进行架构设计,具有良好的可扩展性和可维护性。
**Section sources**
- [CLAUDE.md](file://CLAUDE.md#L1-L20)
## 项目结构
项目采用分层架构,主要分为以下几个目录:
- `conf`: 配置文件目录
- `deploy`: 部署相关文件
- `internal`: 核心业务逻辑
- `controllers`: HTTP 请求处理器
- `models`: 数据模型和数据库操作
- `service`: 业务逻辑层
- `dto`: 数据传输对象
- `routers`: 路由配置
- `config`: 配置管理
- `cache`: 缓存管理
- `otelTrace`: 分布式追踪
- `utils`: 工具函数
- `controllers`: HTTP 请求处理器
- `models`: 数据模型和数据库操作
- `service`: 业务逻辑层
- `dto`: 数据传输对象
- `routers`: 路由配置
- `config`: 配置管理
- `cache`: 缓存管理
- `otelTrace`: 分布式追踪
- `utils`: 工具函数
**Section sources**
- [main.go](file://main.go#L1-L57)
## 核心功能
kami_gateway 提供了以下核心功能:
1. **多渠道支付处理**: 支持多种支付渠道的集成和处理
2. **订单调度**: 实现订单的创建、查询和状态管理
3. **代付请求**: 处理代付相关的业务逻辑
4. **回调通知**: 实现订单状态变更的回调通知机制
**Section sources**
- [CLAUDE.md](file://CLAUDE.md#L50-L70)
## 系统架构
系统采用 MVC 模式,各层之间的关系如下:
- **控制器层 (Controllers)**: 负责处理 HTTP 请求,调用服务层进行业务处理
- **服务层 (Service)**: 实现核心业务逻辑,协调模型层进行数据操作
- **模型层 (Models)**: 负责与数据库交互,提供数据访问接口
@@ -74,11 +86,14 @@ C --> F[第三方供应商]
```
**Diagram sources**
- [internal/controllers/base_controller.go](file://internal/controllers/base_controller.go#L6-L8)
- [internal/models/init.go](file://internal/models/init.go#L0-L42)
## 技术栈
kami_gateway 采用了以下技术栈:
- **Beego v2**: Web 框架,提供 MVC 模式支持
- **OpenTelemetry**: 分布式追踪,用于监控系统性能和调试
- **Redis**: 缓存和会话管理,提高系统响应速度
@@ -88,10 +103,13 @@ kami_gateway 采用了以下技术栈:
这些技术的组合使得 kami_gateway 具有高性能、高可用性和良好的可扩展性。
**Section sources**
- [CLAUDE.md](file://CLAUDE.md#L90-L110)
## 启动流程
系统的启动流程从 `main.go` 文件开始,主要步骤如下:
1. 获取 MQ 地址配置
2. 初始化代理池
3. 初始化分布式追踪
@@ -113,10 +131,13 @@ H --> I[结束]
```
**Diagram sources**
- [main.go](file://main.go#L23-L57)
## 数据流示例
以一个支付请求为例,完整的数据流如下:
1. 客户端发送支付请求到控制器
2. 控制器调用服务层处理支付逻辑
3. 服务层调用模型层进行数据库操作
@@ -127,11 +148,14 @@ H --> I[结束]
8. 控制器返回响应给客户端
**Section sources**
- [internal/service/pay_solve.go](file://internal/service/pay_solve.go#L37-L195)
- [internal/service/notify/order_notify.go](file://internal/service/notify/order_notify.go#L41-L109)
## 高级特性
### 分布式追踪
系统集成了 OpenTelemetry实现了分布式追踪功能。通过在关键代码路径上添加追踪点可以监控系统的性能和调试问题。
```mermaid
@@ -146,9 +170,11 @@ G --> H[响应结束]
```
**Diagram sources**
- [internal/otelTrace/init.go](file://internal/otelTrace/init.go#L29-L205)
### 异步任务队列
系统使用基于 Redis 的异步任务队列来处理耗时操作,如订单查询和回调通知。这种机制提高了系统的响应速度和吞吐量。
```mermaid
@@ -160,10 +186,12 @@ D --> E[更新状态]
```
**Diagram sources**
- [internal/service/supplier/third_party/pool.go](file://internal/service/supplier/third_party/pool.go#L16-L25)
- [internal/schema/query/supplier_query.go](file://internal/schema/query/supplier_query.go#L89-L116)
### 定时任务
系统实现了多种定时任务,如订单结算和商户负载计算。这些任务通过定时器触发,确保系统数据的及时更新。
```mermaid
@@ -175,4 +203,5 @@ C --> E[更新商户信息]
```
**Diagram sources**
- [internal/service/settle_service.go](file://internal/service/settle_service.go#L219-L235)

File diff suppressed because one or more lines are too long

View File

@@ -4,11 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
This is a payment gateway system built with Go using the Beego framework. It handles payment processing, order management, and supplier integration for various payment methods including gift cards and other payment channels.
This is a payment gateway system built with Go using the Beego framework. It handles payment processing, order
management, and supplier integration for various payment methods including gift cards and other payment channels.
## Development Commands
### Building and Running
```bash
# Build the application
./build.sh
@@ -27,6 +29,7 @@ go test ./internal/service/supplier/third_party -run TestAppleSupplier
```
### Docker Operations
```bash
# Build Docker image
docker build -f deploy/Dockerfile -t kami-gateway .
@@ -45,25 +48,25 @@ docker run -p 12309:12309 \
### Core Components
1. **Controllers** (`internal/controllers/`): HTTP request handlers
- `base_controller.go`: Base controller with common functionality
- `payfor_controller.go`: Payment processing endpoints
- `base_controller.go`: Base controller with common functionality
- `payfor_controller.go`: Payment processing endpoints
2. **Services** (`internal/service/`): Business logic layer
- `backend/`: Backend management services
- `pay_for/`: Payment processing services
- `supplier/`: Third-party supplier integrations
- `notify/`: Order notification handling
- `backend/`: Backend management services
- `pay_for/`: Payment processing services
- `supplier/`: Third-party supplier integrations
- `notify/`: Order notification handling
3. **Models** (`internal/models/`): Data models and database operations
- `order/`: Order-related models
- `merchant/`: Merchant management
- `accounts/`: Account management
- `system/`: System configuration
- `order/`: Order-related models
- `merchant/`: Merchant management
- `accounts/`: Account management
- `system/`: System configuration
4. **Supplier Integration** (`internal/service/supplier/third_party/`):
- Multiple payment supplier implementations (Apple, Walmart, etc.)
- Pool-based order processing system
- Queue-based task management
- Multiple payment supplier implementations (Apple, Walmart, etc.)
- Pool-based order processing system
- Queue-based task management
### Key Dependencies
@@ -76,6 +79,7 @@ docker run -p 12309:12309 \
### Configuration
The application uses environment variables for configuration:
- `serverName`: Server identifier
- `gatewayAddr`: Gateway service address
- `portalAddr`: Portal service address
@@ -85,6 +89,7 @@ The application uses environment variables for configuration:
### Database Schema
The system uses MySQL with the following main entities:
- Orders and order settlements
- Merchant accounts and configurations
- Agent profit tracking
@@ -93,6 +98,7 @@ The system uses MySQL with the following main entities:
### Testing
The project includes unit tests for critical components:
- Supplier integration tests (`*_test.go` files)
- Utility function tests
- Service layer tests
@@ -110,6 +116,7 @@ The project includes unit tests for critical components:
## 开发命令
### 构建和运行
```bash
# 构建应用程序
./build.sh
@@ -128,6 +135,7 @@ go test ./internal/service/supplier/third_party -run TestAppleSupplier
```
### Docker 操作
```bash
# 构建 Docker 镜像
docker build -f deploy/Dockerfile -t kami-gateway .
@@ -146,25 +154,25 @@ docker run -p 12309:12309 \
### 核心组件
1. **控制器** (`internal/controllers/`): HTTP 请求处理器
- `base_controller.go`: 具有通用功能的基础控制器
- `payfor_controller.go`: 支付处理端点
- `base_controller.go`: 具有通用功能的基础控制器
- `payfor_controller.go`: 支付处理端点
2. **服务层** (`internal/service/`): 业务逻辑层
- `backend/`: 后台管理服务
- `pay_for/`: 支付处理服务
- `supplier/`: 第三方供应商集成
- `notify/`: 订单通知处理
- `backend/`: 后台管理服务
- `pay_for/`: 支付处理服务
- `supplier/`: 第三方供应商集成
- `notify/`: 订单通知处理
3. **模型** (`internal/models/`): 数据模型和数据库操作
- `order/`: 订单相关模型
- `merchant/`: 商户管理
- `accounts/`: 账户管理
- `system/`: 系统配置
- `order/`: 订单相关模型
- `merchant/`: 商户管理
- `accounts/`: 账户管理
- `system/`: 系统配置
4. **供应商集成** (`internal/service/supplier/third_party/`):
- 多个支付供应商实现Apple、Walmart 等)
- 基于池的订单处理系统
- 基于队列的任务管理
- 多个支付供应商实现Apple、Walmart 等)
- 基于池的订单处理系统
- 基于队列的任务管理
### 主要依赖项
@@ -177,6 +185,7 @@ docker run -p 12309:12309 \
### 配置
应用程序使用环境变量进行配置:
- `serverName`: 服务器标识符
- `gatewayAddr`: 网关服务地址
- `portalAddr`: 门户服务地址
@@ -186,6 +195,7 @@ docker run -p 12309:12309 \
### 数据库架构
系统使用 MySQL包含以下主要实体
- 订单和订单结算
- 商户账户和配置
- 代理商利润跟踪
@@ -194,6 +204,7 @@ docker run -p 12309:12309 \
### 测试
项目包含关键组件的单元测试:
- 供应商集成测试(`*_test.go` 文件)
- 工具函数测试
- 服务层测试

10
go.mod
View File

@@ -8,16 +8,15 @@ require (
github.com/PuerkitoBio/goquery v1.10.3
github.com/beego/beego/v2 v2.3.8
github.com/bytedance/gopkg v0.1.3
github.com/bytedance/sonic v1.14.1
github.com/bytedance/sonic v1.14.2
github.com/dubonzi/otelresty v1.6.0
github.com/duke-git/lancet/v2 v2.3.7
github.com/duke-git/lancet/v2 v2.3.8
github.com/forgoer/openssl v1.8.0
github.com/go-resty/resty/v2 v2.16.5
github.com/go-sql-driver/mysql v1.9.3
github.com/go-stomp/stomp/v3 v3.1.3
github.com/google/uuid v1.6.0
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/prometheus/client_golang v1.23.2
github.com/redis/go-redis/v9 v9.14.0
github.com/shopspring/decimal v1.4.0
@@ -41,10 +40,9 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
@@ -79,7 +77,5 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

24
go.sum
View File

@@ -1,7 +1,5 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
@@ -16,10 +14,10 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -33,8 +31,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dubonzi/otelresty v1.6.0 h1:Oi9fY9bj3/J5R9+FZVMOk+10HtLs6S57fy8nQY5X3C0=
github.com/dubonzi/otelresty v1.6.0/go.mod h1:H+lNImIXAOhK4bk2bZBPDDdp5xGYvtnrUX8F8uVNE30=
github.com/duke-git/lancet/v2 v2.3.7 h1:nnNBA9KyoqwbPm4nFmEFVIbXeAmpqf6IDCH45+HHHNs=
github.com/duke-git/lancet/v2 v2.3.7/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/duke-git/lancet/v2 v2.3.8 h1:dlkqn6Nj2LRWFuObNxttkMHxrFeaV6T26JR8jbEVbPg=
github.com/duke-git/lancet/v2 v2.3.8/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -88,8 +86,6 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
@@ -111,9 +107,11 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -269,10 +267,6 @@ google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXn
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -73,4 +73,5 @@ func init() {
web.Router("/careless/notify", &third_party.CarelessImpl{}, "*:PayNotify")
web.Router("/nuclear/notify", &third_party.NuclearImpl{}, "*:PayNotify")
web.Router("/origin/notify", &third_party.OriginImpl{}, "*:PayNotify")
web.Router("/lianIns/notify", &third_party.LianInsImpl{}, "*:PayNotify")
}

View File

@@ -60,6 +60,7 @@ var supplierCode2Name = map[string]string{
"NUCLEAR": "玖隆核销平台",
"SDPAY": "闪电核销平台",
"ORIGIN": "山禾",
"LIANINS": "燕子核销平台",
}
var registerSupplier = make(map[string]supplier.PayInterface)
@@ -150,6 +151,8 @@ func init() {
logs.Notice(CheckSupplierByCode("UP"))
registerSupplier["ORIGIN"] = new(OriginImpl)
logs.Notice(CheckSupplierByCode("ORIGIN"))
registerSupplier["LIANINS"] = new(LianInsImpl)
logs.Notice(CheckSupplierByCode("LIANINS"))
}
func GetPaySupplierByCode(code string) supplier.PayInterface {

View File

@@ -0,0 +1,286 @@
package third_party
import (
"context"
"encoding/json"
"gateway/internal/config"
"gateway/internal/models/merchant"
"gateway/internal/models/merchant_deploy"
"gateway/internal/models/order"
"gateway/internal/models/payfor"
"gateway/internal/models/road"
"gateway/internal/models/supply_model"
"gateway/internal/otelTrace"
"gateway/internal/service"
"gateway/internal/service/client"
"gateway/internal/service/supplier"
"gateway/internal/service/supplier/third_party/pool/card_sender"
"strconv"
"github.com/beego/beego/v2/client/httplib"
"github.com/duke-git/lancet/v2/convertor"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/beego/beego/v2/server/web"
"github.com/widuu/gojson"
)
type LianInsImpl struct {
web.Controller
}
// HasDependencyHTML 是否有单独的支付页面
func (c *LianInsImpl) HasDependencyHTML() bool {
return false
}
func (c *LianInsImpl) SendCard(ctx context.Context, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string, merchantInfo merchant.MerchantInfo, roadInfo road.RoadInfo) (bool, string) {
orderAmount, _ := (&cardTypeQuery{
OrderNo: attach,
QueryType: gojson.Json(roadInfo.Params).Get("queryType").Tostring(),
CardNo: cardInfo.CardNo,
CardPwd: cardInfo.Data,
Balance: cardInfo.GetFaceTypeFloat(ctx),
}).GetBalance(ctx)
cardInfo.FaceType = strconv.FormatFloat(orderAmount, 'f', 2, 64)
otelTrace.Logger.WithContext(ctx).Info("获取订单金额", zap.Any("orderAmount", cardInfo))
merchantDeploy := merchant_deploy.GetMerchantDeployByUidAndRoadUid(ctx, merchantInfo.MerchantUid, roadInfo.RoadUid)
if merchantDeploy.MerchantUid == "" {
return false, "商户不存在"
}
if merchantDeploy.SubmitStrategy == merchant_deploy.SUBMIT_STRATEGY_POOL {
if err := orderPoolService.PushOrder(ctx, card_sender.SendCardTask{
CardInfo: cardInfo,
RoadUid: roadInfo.RoadUid,
LocalOrderID: attach,
SendCardTaskType: card_sender.SendCardTaskTypeEnumLianIns,
NeedQuery: gojson.Json(roadInfo.Params).Get("needQuery").Tostring() == "1",
}); err != nil {
return false, err.Error()
}
return true, "订单已提交"
}
if err := orderPoolService.SubmitOrder(ctx, card_sender.SendCardTask{
CardInfo: cardInfo,
RoadUid: roadInfo.RoadUid,
LocalOrderID: attach,
SendCardTaskType: card_sender.SendCardTaskTypeEnumLianIns,
NeedQuery: gojson.Json(roadInfo.Params).Get("needQuery").Tostring() == "1",
}); err != nil {
return false, err.Error()
}
return true, "订单已提交"
}
func (c *LianInsImpl) Scan(ctx context.Context, orderInfo order.OrderInfo, roadInfo road.RoadInfo, merchantInfo merchant.MerchantInfo) supplier.ScanData {
ctx, span := otelTrace.Span(ctx, "LianInsImpl", "LianInsImpl.Scan", trace.WithAttributes(
attribute.String("bankOrderId", orderInfo.BankOrderId),
attribute.String("merchantUid", orderInfo.MerchantUid),
))
defer span.End()
cdata := supplier.RedeemCardInfo{}
err := json.Unmarshal([]byte(orderInfo.ExValue), &cdata)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("格式化数据失败", zap.String("ExValue", orderInfo.ExValue))
return supplier.ScanData{Status: "-1", Msg: "订单有误,请稍后再试"}
}
ok, str := c.SendCard(ctx, roadInfo.Params, cdata, orderInfo.BankOrderId, merchantInfo, roadInfo)
var scanData supplier.ScanData
if !ok {
scanData = supplier.ScanData{
Status: "-1",
Msg: "失败:" + str,
BankNo: orderInfo.MerchantOrderId,
OrderNo: orderInfo.BankOrderId,
ReturnData: str,
}
return scanData
}
scanData.Status = "00"
scanData.OrderNo = orderInfo.BankOrderId
scanData.BankNo = orderInfo.MerchantOrderId
scanData.OrderPrice = strconv.FormatFloat(orderInfo.OrderAmount, 'f', 2, 64)
scanData.ReturnData = str
return scanData
}
func (c *LianInsImpl) PayNotify() {
ctx := c.Ctx.Request.Context()
req := struct {
OrderNo string `json:"order_no"`
TradeNo string `json:"trade_no"`
Amount string `json:"amount"`
MerchantNo string `json:"merchant_no"`
Status int `json:"status"`
ApiKey string `json:"api_key"`
}{}
if err := c.Bind(&req); err != nil {
otelTrace.Logger.WithContext(ctx).Error("参数错误", zap.Error(err))
c.Ctx.WriteString("参数错误")
return
}
if req.Status == 2 {
c.Ctx.WriteString("处理中的订单不要回调!!!")
return
}
otelTrace.Logger.WithContext(ctx).Info("回调通知", zap.Any("req", req))
localId, err := orderPoolService.GetLocalIdByOrderId(ctx, req.TradeNo)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("获取本地订单ID失败", zap.Error(err))
c.Ctx.WriteString("fail")
return
}
orderInfo := order.GetOrderByBankOrderId(ctx, localId)
if orderInfo.BankOrderId == "" || len(orderInfo.BankOrderId) == 0 {
otelTrace.Logger.WithContext(ctx).Error("回调的订单号不存在,订单号", zap.String("tradeNo", req.TradeNo))
c.Ctx.WriteString("fail")
return
}
if orderInfo.Status != "wait" {
otelTrace.Logger.WithContext(ctx).Info("订单已经回调,不进行回调")
c.Ctx.WriteString("success")
return
}
roadInfo := road.GetRoadInfoByRoadUid(ctx, orderInfo.RoadUid)
if roadInfo.RoadUid == "" || len(roadInfo.RoadUid) == 0 {
otelTrace.Logger.WithContext(ctx).Error("支付通道已经关系或者删除,不进行回调")
c.Ctx.WriteString("fail")
return
}
price, err := strconv.ParseFloat(req.Amount, 64)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("金额转换失败", zap.Error(err))
c.Ctx.WriteString("fail")
return
}
if req.Status == 1 {
isOk := false
if price == orderInfo.OrderAmount {
isOk = service.SolvePaySuccess(ctx, orderInfo.BankOrderId, orderInfo.OrderAmount, orderInfo.MerchantUid, "支付成功")
} else {
SolvePaySuccessByAmountDifferent(ctx, orderInfo.BankOrderId, price, orderInfo.BankOrderId, convertor.ToString(req))
}
if isOk {
c.Ctx.WriteString("success")
} else {
c.Ctx.WriteString("fail")
}
} else {
cdata := supplier.RedeemCardInfo{}
_ = json.Unmarshal([]byte(orderInfo.ExValue), &cdata)
if gojson.Json(roadInfo.Params).Get("jvnkaQuery").Tostring() == "1" {
_, _ = client.NewHeePayClient().ToString(ctx, &client.QueryCardInput{
OrderNo: orderInfo.BankOrderId,
CardNo: cdata.CardNo,
CardPassword: cdata.Data,
})
}
isOk := service.SolvePayFail(ctx, orderInfo.BankOrderId, orderInfo.BankOrderId, "支付失败")
if isOk {
c.Ctx.WriteString("success")
} else {
c.Ctx.WriteString("fail")
}
}
}
func (c *LianInsImpl) PayQuery(orderInfo order.OrderInfo, roadInfo road.RoadInfo) bool {
return false
}
func (c *LianInsImpl) PayQueryV2(orderInfo order.OrderInfo, roadInfo road.RoadInfo) supply_model.MsgModel {
return supply_model.RemoteDataErr
}
func (c *LianInsImpl) PayFor(info payfor.PayforInfo) string {
return ""
}
func (c *LianInsImpl) PayForQuery(payFor payfor.PayforInfo) (string, string) {
cfg := config.Config{}
url1, err := cfg.GetMFCardQueryUrl()
if err != nil {
return config.PAYFOR_FAIL, ""
}
ctx := context.Background()
params := map[string]string{}
params["order_id"] = payFor.BankOrderId
params["app_key"] = gojson.Json("").Get("appKey").Tostring()
req := httplib.Post(url1)
marshal, err := json.Marshal(params)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Map转化为byte数组失败,异常。", zap.Error(err))
return config.PAYFOR_FAIL, "内部错误请稍后再试试"
}
otelTrace.Logger.WithContext(ctx).Info("请求参数" + string(marshal))
req.Header("Content-Type", "application/json")
req.Body(marshal)
req.Header("Accept-Charset", "utf-8")
response, err := req.String()
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("MF GetToken 请求失败:", zap.Error(err))
return config.PAYFOR_FAIL, ""
}
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据", zap.String("response", response))
if gojson.Json(response).Get("code").Tostring() == "" {
otelTrace.Logger.WithContext(ctx).Error("远程调用失败")
return config.PAYFOR_BANKING, ""
}
if gojson.Json(response).Get("code").Tostring() == "0" {
type data struct {
OrderID int64 `json:"order_id"`
CardNo string `json:"card_no"`
CardPwd string `json:"card_pwd"`
Status int `json:"status"`
RspInfo string `json:"rsp_info"`
FaceVal int `json:"face_val"`
Amount int `json:"amount"`
Discount string `json:"discount"`
}
var d data
err2 := json.Unmarshal([]byte(gojson.Json(response).Get("data").Tostring()), &d)
if err2 != nil {
return config.PAYFOR_FAIL, ""
}
if d.Status == 9 {
return config.PAYFOR_SUCCESS, ""
}
if d.Status == 4 {
return config.PAYFOR_BANKING, ""
}
if d.Status == 7 || d.Status == 8 {
return config.PAYFOR_FAIL, ""
}
}
otelTrace.Logger.WithContext(ctx).Error("远程调用失败")
return config.PAYFOR_BANKING, ""
}
func (c *LianInsImpl) BalanceQuery(roadInfo road.RoadInfo) float64 {
return 0.00
}
func (c *LianInsImpl) PayForNotify() string {
return ""
}

View File

@@ -46,6 +46,7 @@ const (
SendCardTaskTypeEnumNuclear SendCardTaskEnum = "NUCLEAR"
SendCardTaskTypeEnumSdPay SendCardTaskEnum = "SDPAY"
SendCardTaskTypeEnumOrigin SendCardTaskEnum = "ORIGIN"
SendCardTaskTypeEnumLianIns SendCardTaskEnum = "LIANINS"
)
func GetAllSendCardTaskType() []SendCardTaskEnum {
@@ -71,6 +72,7 @@ func GetAllSendCardTaskType() []SendCardTaskEnum {
SendCardTaskTypeEnumNuclear,
SendCardTaskTypeEnumSdPay,
SendCardTaskTypeEnumOrigin,
SendCardTaskTypeEnumLianIns,
}
}
@@ -120,6 +122,8 @@ func (sendCardTaskTypeEnum SendCardTaskEnum) GetSendCardTaskType() sendCardTaskT
return &SendCardTaskTypeNuclear{}
case SendCardTaskTypeEnumOrigin:
return &SendCardTaskTypeOrigin{}
case SendCardTaskTypeEnumLianIns:
return &SendCardTaskTypeLianIns{}
}
return nil
}

View File

@@ -0,0 +1,166 @@
package card_sender
import (
"context"
"encoding/json"
"errors"
"fmt"
"gateway/internal/config"
"gateway/internal/models/road"
"gateway/internal/otelTrace"
"gateway/internal/utils"
"github.com/bytedance/sonic"
"net/url"
"sort"
"strings"
"time"
"github.com/dubonzi/otelresty"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/maputil"
"github.com/duke-git/lancet/v2/pointer"
"github.com/go-resty/resty/v2"
"github.com/widuu/gojson"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
type SendCardTaskTypeLianIns struct {
sendCardTaskTypeSendCardTaskBase
}
func (s *SendCardTaskTypeLianIns) CreateOrder(ctx context.Context, roadUid string, faceValue float64) (OrderPoolItem, error) {
orderPoolItem := OrderPoolItem{}
roadInfo := road.GetRoadInfoByRoadUid(ctx, roadUid)
orderId := utils.GenerateId()
reqBody := struct {
Amount string `json:"amount"`
MerNo string `json:"mer_no"`
TradeNo string `json:"trade_no"`
NotifyUrl string `json:"notify_url"`
ReturnUrl string `json:"return_url"`
ApiKey string `json:"api_key"`
ChannelId string `json:"channel_id"`
}{
Amount: fmt.Sprintf("%.0f", faceValue),
MerNo: orderId,
TradeNo: utils.GenerateId(),
NotifyUrl: config.GetConfig().GatewayAddr() + "/lianIns/notify",
ReturnUrl: "https://baidu.com",
ApiKey: gojson.Json(roadInfo.Params).Get("api_key").Tostring(),
ChannelId: gojson.Json(roadInfo.Params).Get("channel_id").Tostring(),
}
webClient := resty.New().SetTimeout(time.Second * 10).SetRetryCount(3)
otelresty.TraceClient(webClient)
response, err := webClient.R().SetBody(reqBody).Post("https://pay.lianins.top/openapi/sub_order")
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("请求订单失败", zap.Error(err))
return orderPoolItem, err
}
respData := struct {
Code int `json:"code"`
Status string `json:"status"`
Message string `json:"message"`
Data struct {
OrderNo string `json:"order_no"`
TradeNo string `json:"trade_no"`
MerchantNo string `json:"merchant_no"`
Amount string `json:"amount"`
Remark string `json:"remark"`
PayUrl string `json:"pay_url"`
Status int `json:"status"`
VerifyStatus int `json:"verify_status"`
} `json:"data,omitempty"`
}{}
if err = json.Unmarshal(response.Body(), &respData); err != nil {
otelTrace.Logger.WithContext(ctx).Error("解析失败", zap.Error(err))
return orderPoolItem, err
}
orderPoolItem = OrderPoolItem{
OrderID: orderId,
PayURL: respData.Data.PayUrl,
RoadUid: roadUid,
CreateTime: time.Now(),
FaceValue: faceValue,
DispatchCount: 0,
SendCardTaskType: SendCardTaskTypeEnumLianIns,
}
return orderPoolItem, nil
}
func (s *SendCardTaskTypeLianIns) channelOne(ctx context.Context, orderItem OrderPoolItem, task SendCardTask) error {
webClient := resty.New().SetTimeout(time.Second * 30).SetRetryCount(3)
otelresty.TraceClient(webClient)
payUrl, err := url.Parse(orderItem.PayURL)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("解析失败", zap.Error(err))
return errors.New("解析提交地址失败")
}
reqBody := struct {
TradeNo string `json:"trade_no"`
CardNo string `json:"card_no"`
CardPwd string `json:"card_pwd"`
}{
TradeNo: payUrl.Query().Get("trade_no"),
CardPwd: task.CardInfo.Data,
CardNo: task.CardInfo.CardNo,
}
response, err := webClient.R().SetBody(reqBody).Post("https://api.gjifu.cn/openapi/order/sub")
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("请求失败", zap.Error(err))
return err
}
respData := struct {
Code int `json:"code"`
Status string `json:"status"`
Message string `json:"message"`
}{}
otelTrace.Logger.WithContext(ctx).Info("请求结果", zap.Any("response", response.String()))
err = sonic.Unmarshal(response.Body(), &respData)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("解析失败", zap.Error(err))
return err
}
if respData.Code != 1000 {
otelTrace.Logger.WithContext(ctx).Error("提交失败")
return errors.New(respData.Message)
}
return nil
}
func (s *SendCardTaskTypeLianIns) HandleSendCardTask(ctx context.Context, orderItem OrderPoolItem, task SendCardTask) error {
ctx, span := otelTrace.Span(ctx, "SendCardTaskTypeLuban", "SendCardTaskTypeLuban.HandleSendCardTask", trace.WithAttributes(
attribute.String("bankOrderId", task.LocalOrderID),
attribute.String("cardNo", task.CardInfo.CardNo),
attribute.String("cardPassword", task.CardInfo.Data),
attribute.String("orderId", orderItem.OrderID),
))
defer span.End()
if strings.Contains(orderItem.PayURL, "api.xxsbm.com") {
err2 := (&SendCardTaskTypeFatSix{}).HandleSendCardTask(ctx, orderItem, task)
return err2
}
return s.channelOne(ctx, orderItem, task)
}
func (s *SendCardTaskTypeLianIns) sign(ctx context.Context, params map[string]any, key string) string {
keys := maputil.Keys(params)
sort.Strings(keys)
signD := ""
for _, subKey := range keys {
if pointer.IsNil(params[subKey]) || convertor.ToString(params[subKey]) == "" {
continue
}
signD += subKey + "=" + convertor.ToString(params[subKey]) + "&"
}
signD += "key=" + key
return strings.ToUpper(utils.EncodeMd5Str(signD))
}
func (s *SendCardTaskTypeLianIns) QueryOrder(ctx context.Context, orderItem OrderPoolItem, task SendCardTask) error {
return nil
}

View File

@@ -0,0 +1,55 @@
package card_sender
import (
"encoding/json"
"gateway/internal/utils"
"github.com/dubonzi/otelresty"
"github.com/go-resty/resty/v2"
"testing"
"time"
)
func TestSendCardTaskTypeLianIns_CreateOrder(t *testing.T) {
reqBody := struct {
Amount string `json:"amount"`
MerNo string `json:"mer_no"`
TradeNo string `json:"trade_no"`
NotifyUrl string `json:"notify_url"`
ReturnUrl string `json:"return_url"`
ApiKey string `json:"api_key"`
ChannelId string `json:"channel_id"`
}{
Amount: "10",
MerNo: "860078486326972834222944",
TradeNo: utils.GenerateId(),
NotifyUrl: "https://baidu.com",
ReturnUrl: "https://baidu.com",
ApiKey: "7XoWOExJxytVCfNTGxfFNoeIqo4OT2fO",
ChannelId: "888",
}
webClient := resty.New().SetTimeout(time.Second * 10).SetRetryCount(3)
otelresty.TraceClient(webClient)
response, err := webClient.R().SetBody(reqBody).Post("https://pay.lianins.top/openapi/sub_order")
if err != nil {
t.Error(err)
}
respData := struct {
Code int `json:"code"`
Status string `json:"status"`
Message string `json:"message"`
Data struct {
OrderNo string `json:"order_no"`
TradeNo string `json:"trade_no"`
MerchantNo string `json:"merchant_no"`
Amount string `json:"amount"`
Remark string `json:"remark"`
PayUrl string `json:"pay_url"`
Status int `json:"status"`
VerifyStatus int `json:"verify_status"`
} `json:"data,omitempty"`
}{}
if err = json.Unmarshal(response.Body(), &respData); err != nil {
t.Error(err)
}
t.Log(respData)
}