# 业务逻辑层架构

<cite>
**本文档中引用的文件**
- [main.go](file://main.go)
- [logic.go](file://internal/logic/logic.go)
- [sys_user.go](file://internal/service/sys_user.go)
- [sys_user.go](file://internal/dao/v_1_sys_user.go)
- [sys_user.go](file://internal/model/sys_user.go)
- [sys_user.go](file://internal/logic/sys_user/sys_user.go)
- [card_apple_order.go](file://internal/logic/card_apple_order/order.go)
- [card_redeem_order.go](file://internal/logic/card_redeem_order/order.go)
- [merchant_order.go](file://internal/logic/merchant_order/order.go)
- [restriction.go](file://internal/logic/restriction/restriction.go)
- [card_t_mall_order.go](file://internal/logic/card_t_mall_order/order.go)
- [card_apple_order.go](file://internal/service/card_apple_order.go)
- [card_redeem_order.go](file://internal/service/card_redeem_order.go)
- [merchant_order.go](file://internal/service/merchant_order.go)
- [restriction.go](file://internal/service/restriction.go)
- [card_t_mall_order.go](file://internal/service/card_t_mall_order.go)
- [config.yaml](file://manifest/config/config.yaml)
- [order.go](file://internal/logic/jd_cookie/order.go) - *在提交77aff4289e7b60617a6b97873721f586af264581中更新*
- [jd_cookie.go](file://internal/service/jd_cookie.go) - *在提交0fdae6a89fef78f6368400b1fe726d987adda7b1中更新*
- [order_create.go](file://internal/logic/jd_cookie/order_create.go) - *在提交bc2d58753b8fe0b4f7150657bf40cfe756ce48de中重构*
- [order_utils.go](file://internal/logic/jd_cookie/order_utils.go) - *在提交8bc2d51c8ba46582286270b3dd85e166f2562e51中优化*
- [order_jd.go](file://internal/logic/jd_cookie/order_jd.go) - *在提交d1b7f907caa0c3c59628241250901cb7239b25a8中新增功能*
</cite>

## 更新摘要
**已做更改**
- 更新了京东Cookie订单创建和支付链接刷新功能的文档
- 增加了京东Cookie账户变更历史记录功能的说明
- 修订了京东Cookie相关组件的序列图和流程描述
- 更新了受影响文件的引用来源标记
- 新增了京东订单支付状态定时检查与卡密提取功能的文档
- 更新了订单创建逻辑的并发控制机制说明

## 目录
1. [引言](#引言)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构概述](#架构概述)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排除指南](#故障排除指南)
9. [结论](#结论)

## 引言
kami_backend 是一个用于管理卡密充值、订单处理和账户管理的后端服务系统。该系统采用分层架构设计，包含 controller、service 和 logic 三层，分别负责请求处理、业务逻辑和数据访问。系统支持多种卡密类型，包括 Apple、京东、沃尔玛等，并提供了灵活的配置和扩展机制。本文档旨在描述该系统的业务逻辑层架构，包括高阶设计、架构模式、系统边界、核心业务流程、组件交互、数据流、集成模式、技术决策、基础设施要求、可扩展性考虑、部署拓扑、安全性、监控和灾难恢复等横切关注点。

## 项目结构
kami_backend 项目采用模块化设计，主要分为 api、internal、manifest、utility 等目录。api 目录包含各业务模块的 API 定义，internal 目录包含核心业务逻辑，manifest 目录包含部署配置，utility 目录包含工具类和辅助功能。

```mermaid
graph TD
subgraph "API层"
A[api/authority]
B[api/card_info_apple]
C[api/card_info_jd]
D[api/card_info_walmart]
E[api/order]
F[api/sysUser]
end
subgraph "内部层"
G[internal/controller]
H[internal/service]
I[internal/logic]
J[internal/dao]
K[internal/model]
end
subgraph "工具层"
L[utility/config]
M[utility/integration]
N[utility/monitor]
end
A --> G
B --> G
C --> G
D --> G
E --> G
F --> G
G --> H
H --> I
I --> J
J --> K
L --> H
M --> I
N --> H
```

**图源**
- [main.go](file://main.go#L1-L50)
- [logic.go](file://internal/logic/logic.go#L1-L37)

## 核心组件
kami_backend 的核心组件包括用户管理、卡密充值、订单处理、权限控制等。用户管理组件负责用户账户的创建、修改、删除和权限分配。卡密充值组件支持多种卡密类型，包括 Apple、京东、沃尔玛等，提供卡密的充值、查询、回调等功能。订单处理组件负责订单的创建、查询、状态更新和统计。权限控制组件基于 Casbin 实现，支持基于角色的访问控制。

**组件源**
- [sys_user.go](file://internal/service/sys_user.go#L1-L102)
- [card_apple_order.go](file://internal/service/card_apple_order.go#L1-L127)
- [card_redeem_order.go](file://internal/service/card_redeem_order.go#L1-L80)
- [merchant_order.go](file://internal/service/merchant_order.go#L1-L44)
- [restriction.go](file://internal/service/restriction.go#L1-L67)

## 架构概述
kami_backend 采用典型的三层架构，包括 controller、service 和 logic 层。controller 层负责接收和处理 HTTP 请求，调用 service 层提供的接口。service 层是业务逻辑的核心，封装了具体的业务规则和流程，调用 logic 层进行数据访问和处理。logic 层负责与数据库交互，执行 CRUD 操作，并提供数据访问的抽象。

```mermaid
graph TD
A[客户端] --> B[Controller]
B --> C[Service]
C --> D[Logic]
D --> E[DAO]
E --> F[数据库]
```

**图源**
- [main.go](file://main.go#L1-L50)
- [logic.go](file://internal/logic/logic.go#L1-L37)

## 详细组件分析
### 用户管理组件分析
用户管理组件负责用户账户的全生命周期管理，包括创建、修改、删除、查询和权限分配。该组件基于 GoFrame 框架实现，使用 MySQL 作为持久化存储。

#### 类图
```mermaid
classDiagram
class SysUser {
+string Id
+string Username
+int IsAdmin
+string Phone
+*gtime.Time CreatedAt
+*gtime.Time UpdatedAt
+*gtime.Time DeletedAt
+GetCasBinUserPrefix() string
+NotCheckAuthAdminIds(ctx context.Context) *gset.Set
+GetAdminUserByUsernamePassword(ctx context.Context, req *model.UserLoginInput) (user *model.LoginUserOutput, err error)
+GetUserByUsername(ctx context.Context, userName string) (sysUser *model.LoginUserOutput, err error)
+GetUserById(ctx context.Context, id string) (sysUser *model.LoginUserOutput, err error)
+LoginLog(ctx context.Context, params *model.LoginLogParams)
+GetAdminRules(ctx context.Context, userId string) (menuList []*model.UserMenus, permissions []string, err error)
+GetAdminRole(ctx context.Context, userId string, allRoleList []*entity.V1SysRole) (roles []*entity.V1SysRole, err error)
+GetAdminRoleIds(ctx context.Context, userId string) (roleIds []uint, err error)
+GetAllMenus(ctx context.Context) (menus []*model.UserMenus, err error)
+GetAdminMenusByRoleIds(ctx context.Context, roleIds []uint) (menus []*model.UserMenus, err error)
+GetMenusTree(menus []*model.UserMenus, pid uint) []*model.UserMenus
+GetPermissions(ctx context.Context, roleIds []uint) (userButtons []string, err error)
+List(ctx context.Context, req *model.UserSearchInput) (total int, userList []*entity.V1SysUser, err error)
+GetByUserName(ctx context.Context, username string) (userList []*entity.V1SysUser, err error)
+ListWithPayment(ctx context.Context, req *model.UserSearchInput) (total int, userList []*model.UserSearchWithPaymentOutput, err error)
+Add(ctx context.Context, req *model.UserAddInput) (userId string, err error)
+Edit(ctx context.Context, req *model.UserEditInput) (err error)
+EditUserRole(ctx context.Context, roleIds []int64, userId int64) (err error)
+UserNameOrMobileExists(ctx context.Context, userName string, id ...string) (isExist bool, err error)
+GetEditUser(ctx context.Context, id string) (res *model.UserGetEditOutput, err error)
+ChangePwd(ctx context.Context, input *model.UserChangePwdInput) (err error)
+ChangeUserStatus(ctx context.Context, input *model.UserStatusInput) (err error)
+DeleteById(ctx context.Context, id string) (err error)
+Delete(ctx context.Context, ids []string) (err error)
+GetUsers(ctx context.Context, ids []string) (users []*model.SysUserSimpleOutput, err error)
+GetUsersAll(ctx context.Context) (users []*model.SysUserSimpleOutput, err error)
+GetUserFromToken(ctx context.Context) (needAuth bool, user *entity.V1SysUser, err error)
+CheckUserNormal(ctx context.Context, userId string) (isNormal bool, err error)
+GetAllUserNormal(ctx context.Context, tx gdb.TX) (user []*entity.V1SysUser, err error)
+GetAllSimpleUser(ctx context.Context) (data []model.UserSimpleUsersOutput, err error)
+GetCurrentOneFromCache(ctx context.Context, channelName consts.CardRedeemAccountCategory) (data *entity.V1SysUser, err error)
+GetOneNormalOneWithPayment(ctx context.Context, userId string, tx gdb.TX) (output *model.UserSearchWithPaymentOutput, err error)
+GetAllNormalWithPayment(ctx context.Context, minAmount float64, tx gdb.TX) (output []*model.UserSearchWithPaymentOutput, err error)
+GetAllNormalWithPaymentAndChannelCfg(ctx context.Context, minAmount float64, tx gdb.TX) (output []*model.UserSearchWithPaymentAndCfgOutput, err error)
+GetNextOne(ctx context.Context, userId string, amount float64, isNext bool, tx gdb.TX) (data *entity.V1SysUser, err error)
+SetTotp(ctx context.Context, data *entity.V1SysUser, key string, secret string) (err error)
+ResetTotp(ctx context.Context, data *entity.V1SysUser) (err error)
}
class SysUserDao {
+V1SysUser *v1SysUserDao
+Get(ctx context.Context, id string) (*entity.V1SysUser, error)
+Create(ctx context.Context, user *entity.V1SysUser) error
+Update(ctx context.Context, user *entity.V1SysUser) error
+Delete(ctx context.Context, id string) error
}
class SysUserModel {
+User *model.User
+UserSearchInput *model.UserSearchInput
+UserSearchWithPaymentOutput *model.UserSearchWithPaymentOutput
+UserSearchWithPaymentAndCfgOutput *model.UserSearchWithPaymentAndCfgOutput
+UserAddInput *model.UserAddInput
+UserEditInput *model.UserEditInput
+UserGetEditOutput *model.UserGetEditOutput
+UserChangePwdInput *model.UserChangePwdInput
+UserStatusInput *model.UserStatusInput
+SysUserSimpleOutput *model.SysUserSimpleOutput
+UserLoginOutInput *model.UserLoginOutInput
+LoginUserOutput *model.LoginUserOutput
+UserLoginOutOutput *model.UserLoginOutOutput
+UserSimpleUsersOutput *model.UserSimpleUsersOutput
}
SysUser --> SysUserDao : "uses"
SysUser --> SysUserModel : "uses"
SysUserDao --> SysUserModel : "returns"
```

**图源**
- [sys_user.go](file://internal/logic/sys_user/sys_user.go#L1-L767)
- [sys_user.go](file://internal/dao/v_1_sys_user.go#L1-L28)
- [sys_user.go](file://internal/model/sys_user.go#L1-L92)

### 卡密充值组件分析
卡密充值组件支持多种卡密类型，包括 Apple、京东、沃尔玛等，提供卡密的充值、查询、回调等功能。该组件基于 GoFrame 框架实现，使用 MySQL 作为持久化存储。

#### 序列图
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Controller as "Controller"
participant Service as "Service"
participant Logic as "Logic"
participant DAO as "DAO"
participant DB as "数据库"
Client->>Controller : POST /api/card_info_apple/v1/recharge_submit
Controller->>Service : AddRechargeOrder(input)
Service->>Logic : AddRechargeOrder(input)
Logic->>DAO : InsertAndGetId(do.V1CardAppleRechargeInfo)
DAO->>DB : INSERT INTO v1_card_apple_recharge_info
DB-->>DAO : 返回ID
DAO-->>Logic : 返回ID
Logic->>DAO : Insert(do.V1CardAppleHistoryInfo)
DAO->>DB : INSERT INTO v1_card_apple_history_info
DB-->>DAO : 返回结果
DAO-->>Logic : 返回结果
Logic-->>Service : 返回订单号
Service-->>Controller : 返回订单号
Controller-->>Client : 返回订单号
```

**图源**
- [card_apple_order.go](file://internal/logic/card_apple_order/order.go#L1-L343)
- [card_apple_order.go](file://internal/service/card_apple_order.go#L1-L127)

### 订单处理组件分析
订单处理组件负责订单的创建、查询、状态更新和统计。该组件基于 GoFrame 框架实现，使用 MySQL 作为持久化存储。

#### 流程图
```mermaid
flowchart TD
Start([开始]) --> ValidateInput["验证输入参数"]
ValidateInput --> InputValid{"输入有效?"}
InputValid --> |否| ReturnError["返回错误响应"]
InputValid --> |是| CheckOrder["检查订单是否存在"]
CheckOrder --> OrderExists{"订单存在?"}
OrderExists --> |是| UpdateOrder["更新订单状态"]
OrderExists --> |否| CreateOrder["创建新订单"]
CreateOrder --> SaveOrder["保存订单到数据库"]
SaveOrder --> UpdateStatus["更新订单状态"]
UpdateStatus --> ReturnResult["返回结果"]
UpdateOrder --> ReturnResult
ReturnResult --> End([结束])
ReturnError --> End
```

**图源**
- [merchant_order.go](file://internal/logic/merchant_order/order.go#L1-L48)
- [merchant_order.go](file://internal/service/merchant_order.go#L1-L44)

### 京东Cookie组件分析
京东Cookie组件负责京东订单的创建、支付链接刷新以及Cookie账户的管理。根据最新的代码变更，该组件已重构了订单创建和支付流程的错误处理机制，并增加了Cookie变更历史记录功能。

#### 序列图
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Controller as "Controller"
participant Service as "Service"
participant Logic as "Logic"
participant DAO as "DAO"
participant DB as "数据库"
Client->>Controller : POST /api/jd_cookie/v1/create_order
Controller->>Service : CreateOrder(input)
Service->>Logic : CreateOrder(input)
Logic->>DAO : InsertAndGetId(do.V1JdCookieOrder)
DAO->>DB : INSERT INTO v1_jd_cookie_order
DB-->>DAO : 返回订单ID
DAO-->>Logic : 返回订单ID
Logic->>DAO : Insert(do.V1JdCookieOrderChangeHistory)
DAO->>DB : INSERT INTO v1_jd_cookie_order_change_history
DB-->>DAO : 返回结果
DAO-->>Logic : 返回结果
Logic->>Service : 返回订单信息
Service-->>Controller : 返回订单信息
Controller-->>Client : 返回订单信息
Client->>Controller : GET /api/jd_cookie/v1/get_payment_url?order_id=123
Controller->>Service : GetPaymentUrl(orderId)
Service->>Logic : GetPaymentUrl(orderId)
Logic->>DAO : Update(do.V1JdCookieOrder)
DAO->>DB : UPDATE v1_jd_cookie_order SET payment_url = ?
DB-->>DAO : 返回结果
DAO-->>Logic : 返回结果
Logic->>DAO : Insert(do.V1JdCookieOrderChangeHistory)
DAO->>DB : INSERT INTO v1_jd_cookie_order_change_history
DB-->>DAO : 返回结果
DAO-->>Logic : 返回结果
Logic-->>Service : 返回支付链接
Service-->>Controller : 返回支付链接
Controller-->>Client : 返回支付链接
```

**图源**
- [order.go](file://internal/logic/jd_cookie/order.go#L1-L287) - *在提交77aff4289e7b60617a6b97873721f586af264581中更新*
- [jd_cookie.go](file://internal/service/jd_cookie.go#L1-L156) - *在提交0fdae6a89fef78f6368400b1fe726d987adda7b1中更新*

### 权限控制组件分析
权限控制组件基于 Casbin 实现，支持基于角色的访问控制。该组件负责用户权限的验证和管理。

#### 类图
```mermaid
classDiagram
class Restriction {
+GetAllProvince(ctx context.Context) []string
+CheckIPIsAllowed(ctx context.Context, ip string, area []string) (isAllowed bool, err error)
+AddToRestrictIP(ctx context.Context, input model.AddRestrictionRecordInput) (err error)
+SetCurrentOrderDisable(ctx context.Context, orderNo string, status consts.RestrictStatus, tx gdb.TX) (err error)
+GetLocationWithProxyByIP(ctx context.Context, ip string) (data *model.RestrictionIPRecordOutput, err error)
+IsInProvince(ctx context.Context, ip string, province []string) (isin bool, m *model.RestrictionIPRecordOutput, err error)
+GetRestrictRecordByCardPass(ctx context.Context, cardPass string) (dataList []entity.V1RestrictIpOrderAccess, err error)
+GetRestrictRecordByIP(ctx context.Context, ip string) (dataList []model.RestrictWithIPRecordOutput, err error)
+GetIPOrderAccessByCardPass(ctx context.Context, cardPass string) (data []entity.V1RestrictIpOrderAccess, err error)
+IsRestrictExistByCardPass(ctx context.Context, cardPass string) (isExist bool)
+IsRestrictExistByIP(ctx context.Context, ip string) (isExist bool)
+IsRestrictDevice(ctx context.Context, deviceId string) (isRestrict bool)
}
class RestrictionDao {
+V1RestrictIpRecord *v1RestrictIpRecordDao
+V1RestrictIpOrderAccess *v1RestrictIpOrderAccessDao
+GetOrSetIPRecord(ctx context.Context, ip string, tx gdb.TX) (m *model.RestrictionIPRecordOutput, err error)
+GetRestrictRecordByCardPass(ctx context.Context, cardPass string) (dataList []entity.V1RestrictIpOrderAccess, err error)
+GetRestrictRecordByIP(ctx context.Context, ip string) (dataList []model.RestrictWithIPRecordOutput, err error)
+GetIPOrderAccessByCardPass(ctx context.Context, cardPass string) (data []entity.V1RestrictIpOrderAccess, err error)
}
class RestrictionModel {
+AddRestrictionRecordInput *model.AddRestrictionRecordInput
+RestrictionIPRecordOutput *model.RestrictionIPRecordOutput
+RestrictWithIPRecordOutput *model.RestrictWithIPRecordOutput
}
Restriction --> RestrictionDao : "uses"
Restriction --> RestrictionModel : "uses"
RestrictionDao --> RestrictionModel : "returns"
```

**图源**
- [restriction.go](file://internal/logic/restriction/restriction.go#L1-L174)
- [restriction.go](file://internal/service/restriction.go#L1-L67)

## 依赖分析
kami_backend 依赖于多个外部组件和库，包括 GoFrame 框架、MySQL 数据库、Redis 缓存、OpenTelemetry 监控系统等。GoFrame 框架提供了 Web 服务、数据库访问、配置管理等基础功能。MySQL 数据库用于持久化存储用户、订单、卡密等数据。Redis 缓存用于提高系统性能，减少数据库访问压力。OpenTelemetry 监控系统用于收集和分析系统运行时的性能数据。

```mermaid
graph TD
A[kami_backend] --> B[GoFrame]
A --> C[MySQL]
A --> D[Redis]
A --> E[OpenTelemetry]
B --> F[gin]
B --> G[gorm]
C --> H[InnoDB]
D --> I[内存存储]
E --> J[Jaeger]
E --> K[Prometheus]
```

**图源**
- [main.go](file://main.go#L1-L50)
- [config.yaml](file://manifest/config/config.yaml#L1-L10)

## 性能考虑
kami_backend 在设计时充分考虑了性能因素，采用了多种优化措施。首先，系统使用 Redis 缓存热点数据，如用户信息、配置信息等，减少数据库访问次数。其次，系统采用连接池技术，复用数据库连接，减少连接创建和销毁的开销。此外，系统还使用了异步处理机制，将耗时操作放入后台任务队列中执行，提高响应速度。最后，系统通过 OpenTelemetry 监控系统实时监控性能指标，及时发现和解决性能瓶颈。

## 故障排除指南
当 kami_backend 出现问题时，可以按照以下步骤进行排查：
1. 检查系统日志，查看是否有错误信息。
2. 检查数据库连接是否正常，是否有慢查询。
3. 检查 Redis 缓存是否正常，是否有缓存穿透或雪崩。
4. 检查 OpenTelemetry 监控系统，查看性能指标是否异常。
5. 检查配置文件，确认配置项是否正确。
6. 如果问题仍然无法解决，可以联系技术支持团队。

**组件源**
- [main.go](file://main.go#L1-L50)
- [config.yaml](file://manifest/config/config.yaml#L1-L10)

## 结论
kami_backend 是一个功能完善、性能优良的后端服务系统。通过采用分层架构设计，系统实现了良好的模块化和可维护性。通过使用多种优化措施，系统能够高效地处理大量并发请求。通过集成 OpenTelemetry 监控系统，系统具备了强大的可观测性。未来，系统可以进一步优化性能，增加更多功能，提高用户体验。