简介
本文件面向DouPHP项目的Service层开发与封装规范,聚焦以下目标:
- 明确Service在MVC中的职责边界:业务编排、数据访问封装、事务与一致性保障、跨域能力组合。
- 说明如何继承BaseService基类,统一依赖解析方式(DB、ORM、配置、日志、语言等)。
- 给出完整的Service方法设计范式:签名、参数校验、返回值、异常与错误码。
- 覆盖常见业务场景:数据库操作、缓存使用、第三方API调用、支付与订单状态机、售后退款分账等。
- 提供单元测试、性能优化与错误处理的实践建议。
项目结构
本项目采用分层与领域服务组织方式:
- core/service:核心领域服务与通用基础设施(如支付、用户、钱包、订单状态机等)。
- admin/service、front/service、api/service:面向前后台与API的壳层服务,负责请求组装、响应格式化与展示逻辑。
- model:数据模型与查询器(Reader/Query/Core),通过静态门面或DI注入到Service中。
- controller:薄控制器,仅做参数收集、鉴权、路由与视图/JSON返回。
graph TB
Controller["控制器<br/>参数收集/响应"] --> Service["Service层<br/>业务编排/事务"]
Service --> Model["Model/ORM<br/>静态门面/查询器"]
Service --> DB["数据库<br/>事务/锁"]
Service --> Cache["缓存/外部资源"]
Service --> ThirdParty["第三方服务<br/>支付/短信/AI"]
章节来源
- BaseService.php:21-60
核心组件
- BaseService:所有Service的抽象基类,约定运行时依赖通过Facade/Helper就近解析,ORM以静态门面访问,复杂查询拆分为独立Reader/Query服务并通过构造注入。
- UserService:会员核心服务的“薄编排门面”,将资料读模型、扩展身份、收货联系人、推广关系等子能力解耦为独立服务,对外暴露稳定签名。
- PaymentService:单笔支付尝试的台账服务,实现幂等、状态机推进、混合支付收尾、退款分账与对账兜底。
- OrderStatusTransition:订单状态机与付款后联动(库存、积分、分销、会员升级等)集中管理,保证状态迁移安全。
- CashierService:前台收银台业务层,聚合订单、支付、状态机与用户统计,完成支付页与混合支付流程编排。
- CategoryService:后台文档分类业务服务,体现表单构建、附件上传、审计日志与删除保护等典型CRUD编排。
章节来源
- UserService.php:23-69
- PaymentService.php:30-72
- OrderStatusTransition.php:31-56
- CashierService.php:32-49
- CategoryService.php:31-39
架构总览
Service层在MVC中的定位:
- 控制器只负责HTTP输入输出、鉴权与视图渲染;业务规则、事务、跨系统协作全部下沉至Service。
- Service通过构造注入或静态门面获取依赖,避免直接耦合具体实现。
- 复杂读模型与写模型分离:读路径可引入Reader/Query服务,写路径保持简洁并保证原子性。
sequenceDiagram
participant C as "控制器"
participant S as "Service"
participant M as "Model/ORM"
participant D as "数据库"
participant T as "第三方服务"
C->>S : 调用业务方法(参数)
S->>S : 参数校验/权限检查
S->>D : 开启事务
S->>M : 读取/写入数据
M-->>S : 结果集/影响行数
alt 需要外部交互
S->>T : 调用第三方API
T-->>S : 回调/结果
end
S->>D : 提交/回滚
S-->>C : 标准化返回
图表来源
- PaymentService.php:154-225
- CashierService.php:32-49
章节来源
- BaseService.php:21-60
详细组件分析
会员核心服务(UserService)
- 角色:薄编排门面,按变化原因拆分只读/工具能力到子服务,对外保持稳定签名。
- 关键能力:
- 资料读模型:构建当前会员基础资料、等级名称、按字段查找user_id、标准化行。
- 扩展身份:工作端/VIP/分销身份详情与布尔判定。
- 收货联系人:列表与完整地址拼装。
- 推广关系:解析直推/间推、登记关系树、生成不重复user_sn与头像文件名。
- 设计要点:
- 通过构造注入UserProfileQuery、UserMembershipQuery、UserContactQuery、UserPromotionService,降低耦合。
- 对外方法只做参数透传与简单包装,复杂逻辑下沉到子服务。
classDiagram
class BaseService
class UserService {
+buildUserProfile(row, field) array
+levelName(level_id) string
+findUserId(keyword) mixed|null
+currentLevel(userId) array|null
+format(userOrId) array|false
+work(user_id) array|null
+vip(user_id) array|bool
+distribution(user_id) array|bool
+isVip(userId) bool
+isWork(userId) bool
+isDistribution(userId) bool
+contactList(user_id, current_contact_id) array
+addressFull(contact) string
+resolvePromotionLineage(userSn, selfUserId) array
+recordPromotionRelation(newUserId, directUserId) void
+randUserSn() string
+createAvatarFilename() string
}
class UserProfileQuery
class UserMembershipQuery
class UserContactQuery
class UserPromotionService
UserService --> UserProfileQuery : "组合"
UserService --> UserMembershipQuery : "组合"
UserService --> UserContactQuery : "组合"
UserService --> UserPromotionService : "组合"
UserService <|-- BaseService
图表来源
- UserService.php:39-69
- UserService.php:71-289
章节来源
- UserService.php:23-69
- UserService.php:71-289
支付台账服务(PaymentService)
- 角色:单笔支付尝试的台账服务,维护order_payment表,处理webhook幂等、混合支付收尾、退款分账。
- 关键能力:
- createForOrder:创建pending状态的支付记录。
- markSucceeded:推进payment到成功,累计网关已付金额,尝试收尾订单。
- tryFinalizeOrder:当网关+钱包累计金额覆盖订单金额时推进订单到PAID。
- markSucceededByWallet:钱包支付腿扣款、落账、累计wallet_paid,并在同一事务内防并发超扣。
- refundOrder:售后退款分账,gateway优先、wallet在后,区分succeeded/pending退款。
- markRefundSucceeded:收尾pending的网关退款,更新支付腿状态。
- 设计要点:
- 严格的状态机约束(PaymentStatus::canTransit)。
- 幂等:已成功的payment直接返回true,不再重复联动。
- 事务与锁:钱包支付使用FOR UPDATE确保并发安全。
- 容差:AMOUNT_EPSILON避免浮点误差导致“已付满”漏判。
flowchart TD
Start(["开始"]) --> CheckParam["校验参数"]
CheckParam --> CreatePending{"是否创建待支付?"}
CreatePending --> |是| InsertPayment["插入order_payment(PENDING)"]
CreatePending --> |否| NextStep["继续"]
InsertPayment --> NextStep
NextStep --> MarkSuccess{"收到成功回调?"}
MarkSuccess --> |是| UpdatePayment["更新为SUCCEEDED"]
UpdatePayment --> Accumulate["累计网关已付金额"]
Accumulate --> TryFinalize["尝试收尾订单"]
TryFinalize --> FinalizeCheck{"订单金额是否凑满?"}
FinalizeCheck --> |是| ChangeOrderPaid["推进订单到PAID"]
FinalizeCheck --> |否| KeepPending["保持PENDING(部分支付)"]
MarkSuccess --> |否| End(["结束"])
ChangeOrderPaid --> End
KeepPending --> End
图表来源
- PaymentService.php:84-102
- PaymentService.php:172-225
- PaymentService.php:237-296
章节来源
- PaymentService.php:30-72
- PaymentService.php:84-102
- PaymentService.php:172-225
- PaymentService.php:237-296
- PaymentService.php:311-391
- PaymentService.php:490-592
- PaymentService.php:601-642
订单状态机(OrderStatusTransition)
- 角色:订单状态变更与付款后联动(库存、积分、分销、会员升级等)集中管理。
- 设计要点:
- 状态值采用字符串枚举,迁移走表驱动校验。
- 付款后的会员侧效应按features.*闸按需触发,未启用模块时跳过,保持解耦。
- 关键事件在DB::commit()之后派发,避免下游失败回滚状态变更。
章节来源
- OrderStatusTransition.php:31-56
前台收银台(CashierService)
- 角色:收银台/支付页/余额+网关混合支付/线下凭证/货到付款的业务编排。
- 职责:控制器仅负责参数收集与响应格式化,支付台账与状态推进在此完成。
- 依赖:OrderCore、PaymentService、OrderStatusTransition、UserStatsService等。
章节来源
- CashierService.php:32-49
后台文档分类(CategoryService)
- 角色:后台文档分类业务服务,对应CategoryController与路由。
- 能力:表单默认数据构建、创建/编辑数据装配、新增/更新/删除、附件上传、审计日志、删除保护(占用/子类拦截)、二次确认。
- 错误处理:格式校验上移至FormRequest,此处仅做纯业务规则判断,失败抛出DomainException由全局处理器统一输出提示。
章节来源
- CategoryService.php:31-39
- CategoryService.php:45-126
- CategoryService.php:137-164
- CategoryService.php:174-218
API登录示例(WeixinController)
- 场景:微信UnionID绑定、登录计数更新、手机号回填与默认联系人创建。
- 事务:使用DB::beginTransaction包裹多表写入,保证一致性。
- 集成:调用userService->isWork进行身份判定,结合contactQuery->upsertDefaultContact完成联系人初始化。
章节来源
- WeixinController.php:213-237
依赖关系分析
- 低耦合:Service通过构造注入或静态门面获取依赖,避免硬编码。
- 高内聚:每个Service专注单一领域(用户、支付、订单、文档分类等)。
- 可测试性:子服务可单独由容器注入或通过Module::make取实例,便于单元测试替换。
graph LR
A["UserService"] --> B["UserProfileQuery"]
A --> C["UserMembershipQuery"]
A --> D["UserContactQuery"]
A --> E["UserPromotionService"]
F["PaymentService"] --> G["OrderStatusTransition"]
F --> H["WalletService"]
I["CashierService"] --> F
I --> G
图表来源
- UserService.php:39-69
- PaymentService.php:45-72
- CashierService.php:32-49
章节来源
- BaseService.php:21-60
性能考虑
- 读模型分离:复杂查询抽取到Reader/Query服务,减少Service臃肿。
- 批量与分页:使用ORM的get()/paginate(),避免一次性加载大结果集。
- 事务范围最小化:仅在必要时开启事务,缩短持有锁的时间。
- 幂等与去重:利用DB唯一键与Service内部幂等逻辑,避免重复处理。
- 缓存策略:热点数据(如配置、字典)可通过缓存层加速,注意失效与一致性。
- 外部调用超时与重试:第三方API调用需设置合理超时与退避策略,避免阻塞主流程。
故障排查指南
- 参数校验失败:优先在FormRequest或Service入口进行强类型与规则校验,失败抛出DomainException。
- 事务异常:捕获异常并rollback,记录上下文信息(订单号、用户ID、金额等)以便追踪。
- 状态机拒绝:检查当前状态与目标状态是否允许迁移,参考PaymentStatus/OrderStatus的canTransit。
- 幂等冲突:确认唯一键约束(如gateway_txn)与服务内部幂等逻辑是否正确生效。
- 第三方回调异常:记录raw_callback与错误日志,支持对账与人工介入。
章节来源
- PaymentService.php:172-225
- PaymentService.php:311-391
- PaymentService.php:490-592
- CategoryService.php:174-218
结论
DouPHP的Service层以BaseService为基座,强调:
- 清晰的职责边界:控制器薄、Service厚、Model专注数据访问。
- 稳定的对外签名:UserService作为编排门面,组合子服务能力。
- 严谨的一致性保障:PaymentService与OrderStatusTransition共同维护支付与订单状态机。
- 可测试与可扩展:通过构造注入与模块化,便于替换与扩展。 遵循本规范可实现高内聚、低耦合、易维护的业务代码体系。
附录:Service开发规范清单
- 继承BaseService,使用Facade/Helper就近解析依赖。
- 方法签名设计:
- 入参:强类型与必要校验,非法参数抛出DomainException。
- 出参:标准化数组或对象,包含code/message/data结构。
- 数据访问:
- 写:新增用Xxx::create($data),更新用hydrated实例fill()->save()或whereKey()->update()。
- 读:Xxx::with('relation')->find()/first()/get()/paginate()。
- 事务管理:
- 使用DB::beginTransaction()/commit()/rollback()包裹多步写操作。
- 关键路径使用FOR UPDATE防止并发超扣。
- 缓存使用:
- 热点配置/字典缓存,注意失效策略与一致性。
- 第三方API:
- 超时、重试、降级与日志记录,raw_callback留底。
- 错误处理:
- 统一抛出DomainException,全局处理器输出友好提示。
- 记录上下文信息,便于问题定位。
- 单元测试:
- 子服务可单独注入,Mock外部依赖,验证状态机与幂等逻辑。
- 性能优化:
- 读模型分离、批量与分页、最小化事务范围、幂等与去重。