背景:从 PHP (Laravel) 到 Go 的模式迁移

•原 PHP (Laravel) 实现思路:核心模式: “行为管道” (Behavior Pipeline)。如何工作: 将订单创建拆分成多个独立的小任务 (如:请求限流、素材验证、创建订单、调用支付、埋点)。每个任务是一个实现了特定接口 (BehaviorOrderCreateInterface) 的类。执行过程: 将这些任务类注册到一个列表中。创建订单时,按顺序执行列表里每个任务的 handle 方法。状态共享: 每个任务在执行时,能访问和修改同一个“订单创建服务”对象 (OrderCreateService),通过它传递数据和状态(比如订单创建后,埋点任务需要用到订单ID)。异常处理: 使用 try-catch 捕获异常并进行事务回滚。

makeOrder 创建订单方法

/**
* 生成订单
* @param $params
* @return string[]
* @date 2023/6/6 13:30
* @throws \Exception
*/
public function makeOrder($params): array
{
$db = DB::connection(DbEnum::MYSQL_RELEBOOK);
$db->beginTransaction(); try {
$data = (new OrderCreateService($params))->registerBehavior(
function () {
return new RequestLimit();
},
function () {
return new ResValidate();
},
function () {
return new Order();
},
function () {
return new Payment();
},
function () {
return new OrderBuried();
},
)->create(); $db->commit(); return $data;
} catch (\LogicException $logicException) {
$db->rollback();
// 异常code码(CODE_117006008)返回素材信息,不直接抛异常
if ($logicException->getCode() == RelebookUserCodeEnum::CODE_117006008) {
$data = collect(json_decode($logicException->getMessage()))->toArray();
$data['order_no'] = '';
$data['pay_url'] = '';
return $data;
}
throw new \LogicException($logicException->getMessage(), $logicException->getCode());
} catch (\Exception $exception) {
$db->rollback();
throw new \Exception($exception->getMessage(), intval($exception->getCode()));
}
}

BehaviorOrderCreateInterface 接口

interface BehaviorOrderCreateInterface
{
public function handle(OrderCreateService $orderCreateService); }

OrderCreateService 订单创建服务

class OrderCreateService
{ public array $params; public array $params;
/** 素材信息 **/
public array $res;
/**订单信息 **/
public array $order_info;
/**子订单信息 **/
public array $subOrders;
/**购物车信息 **/
public array $cartItem;
/**支付服务 **/
public PaymentInterface $paymentService;
/**支付信息 **/
public array $payment; /**
* @var array|\Closure[]
*/
private array $behaviorList = []; public function __construct($params)
{
$this->params = $params;
} /**
* 订单创建行为注册
* @param \Closure ...$callback
* @return $this
* @date 2023/6/29 13:58
*/
public function registerBehavior(\Closure ...$callback): static
{
$this->behaviorList = array_merge($this->behaviorList,$callback);
return $this;
} /**
* 创建订单
* @return mixed
* @date 2023/6/29 14:21
*/
public function create()
{
$this->params['order_type'] = PayEnum::ORDER_TYPE_RES; // A、执行订单创建行为
foreach ($this->behaviorList as $behavior) {
$behavior()->handle($this);
} // B、订单返回数据
$this->data = array_merge($this->data, $this->payment);
$this->data['order_type'] = $this->params['order_type'];
$this->data['cash_type'] = $this->params['cash_type'];
$this->data['order_no'] = $this->order_info['order_no'];
$this->data['pay_url'] = $this->payment['pay_url'];
$this->data['type'] = RelebookUserOrder::ORDER_CREATE_STATUS_SUCCESS;
$this->data['res'] = []; return $this->data;
} }

Go 重构的挑战与解决方案

•挑战 1:状态共享 (Go 没有类属性)问题: Go 没有类的概念,不同的小任务(函数/方法)不能直接共享一个“服务对象”的状态。解决方案: 引入 DTO (Data Transfer Object)。DTO 是什么? 一个专门用来在不同部分之间传递数据的结构体。它就像是一个共享的“数据盒子”。作用: 将所有小任务需要共享的数据(请求参数、中间结果、最终响应、错误信息)都定义在这个结构体 (OrderBehaviorDTO) 里。如何工作: 每个小任务接收这个 DTO 指针作为参数,从中读取需要的数据,并将自己产生的结果写回到 DTO 中。这样,后续的任务就能访问到前面任务写入的数据。

•挑战 2:异常处理 (Go 没有 try-catch)问题: Go 通常通过函数返回 error 值来处理错误,不能像 PHP 那样在任意地方 throw 并被外层 catch。解决方案:在 DTO 中加入专门的错误字段 (Err)。每个小任务在执行过程中,如果遇到错误,不直接 panic 或抛异常,而是将错误信息设置到 DTO 的 Err 字段 (或 Response.Code/ErrMsg) 中,然后返回 DTO。主流程 (Handle 方法) 检查每个任务执行后 DTO 中的错误状态。一旦发现错误,立即终止后续任务并返回错误响应。

Go 实现的核心逻辑 (简化说明)

1.定义 DTO 结构体 (OrderBehaviorDTO): 包含所有需要流转的数据(输入参数、支付结果、订单信息、错误码等)。

2.定义任务接口 (BehaviorOrderCreateInterface): 要求所有小任务都必须实现一个 Handle(*OrderBehaviorDTO) *OrderBehaviorDTO 方法。

3.创建主服务 (MakeOrderService):初始化时创建 DTO。将需要执行的小任务 (如 OrderValidate, OrderCreate, OrderPayment, OrderBuried) 添加到任务列表 (Behavior 切片)。

4.执行主流程 (Handle 方法):按顺序遍历任务列表,调用每个任务的 Handle 方法,并传入 同一个 DTO 指针。每个任务从 DTO 读数据,处理业务,把结果写回 DTO。检查每个任务执行后 DTO 中的错误状态,遇错即停。

5.组装最终响应: 所有任务成功后,从 DTO 中提取所需数据,组装成最终的订单创建响应 (OrderMakeResponse)。

关键优势 (使用 DTO + 接口模式)

•解耦清晰: 每个小任务只依赖 DTO 接口,不直接依赖其他任务的具体实现。

•状态管理: DTO 作为唯一的、明确的数据交换通道,解决了 Go 中跨函数/方法状态共享的问题。

•灵活可扩展: 添加新任务只需实现接口并注册到列表即可。

•错误处理适配 Go 习惯: 通过返回值和 DTO 中的错误状态显式处理错误,符合 Go 的 error 处理哲学。

实现代码:

1.定义DTO结构体内容

// OrderBehaviorDTO 用于解耦在不同结构体(服务)里面传递的数据结构体

type OrderBehaviorDTO struct {

Params *order.OrderMakeRequest // 请求参数

Response *order.OrderMakeResponse // 响应参数

UserId int // 用户id

Uid string // uid

CartItem []relebook.LlRelebookCartItem // 子服务查询产生的购物车信息

Res map[int32]relebook.LlRelebookRes // 子服务查询产生的素材信息

Orders OrderInfos // 子服务创建的订单信息 -用于埋点

PayResult payment.PayResult // 支付子服务产生的支付信息 -用于埋点

Err Errer

}

// 响应参数的proto

message OrderMakeResponse {

string order_no = 1;

string pay_url = 2;

string payment_intent_id = 3;

string client_secret = 4;

int32 order_type = 5;

int32 cash_type = 6;

int32 type = 7;

repeated Empty res = 8;

// 异常码和返回消息

int32 code = 9;

string err_msg = 10;

}

1.将订单限流功能抽离成限流组件

2.执行 orderService.NewMakeOrder方法 实例化订单创建服务并注入DTO结构体然后调用.Handle方法

func (OrderLogic) OrderMake(ctx context.Context, in order.OrderMakeRequest) (order.OrderMakeResponse, error) {

// 订单请求限制

if utils.SlidingWindowRateLimiter(utils.GetLimitKey(ctx, "make_order"), 1, time.Second
1) != nil {

result := &order.OrderMakeResponse{}

result.Code = code.CODE_117001011

result.ErrMsg = lang.OperatingLimit

return result, nil

}

// 开始执行业务

return orderService.NewMakeOrder(&dto.OrderBehaviorDTO{

Params: in,

Response: &order.OrderMakeResponse{},

UserId: cast.ToInt(trace.GetUserId(ctx)),

Uid: in.Uid,

}).Handle()

}

MakeOrderService结构体内容

type MakeOrderService struct {
payService payment.PaymentInterface // 支付服务接口
resInfos map[int32]relebook.LlRelebookRes // 订单支付的素材
resDAOImpl dao.ResDAOImpl // 素材表DAO封装的查询集合
userId int // 用户id
Dto *dto.OrderBehaviorDTO // DTO
Behavior []BehaviorOrderCreateInterface // 行为单元
} type BehaviorOrderCreateInterface interface {
Handle(Dto *dto.OrderBehaviorDTO) *dto.OrderBehaviorDTO
} func (m *MakeOrderService) SetBehavior(behavior BehaviorOrderCreateInterface) *MakeOrderService {
m.Behavior = append(m.Behavior, behavior)
return m
} func NewMakeOrder(Dto *dto.OrderBehaviorDTO) *MakeOrderService {
service, _ := payment.GetPaymentService(int(Dto.Params.CashType))
return &MakeOrderService{
payService: service,
Dto: Dto,
}
}
func (m *MakeOrderService) Handle() (*order.OrderMakeResponse, error) {
// 支付验签
if err := m.payService.Validate(payment.PaymentParams{}); err != nil {
m.Dto.Response.Code = code.CODE_117001001
m.Dto.Response.ErrMsg = err.Error()
return m.Dto.Response, nil
}
//设置需要写入的服务
m.SetBehavior(new(behavior_create.OrderValidate)).
SetBehavior(new(behavior_create.OrderCreate)).
SetBehavior(new(behavior_create.OrderPayment)).
SetBehavior(new(behavior_create.OrderBuried))
// 执行服务
for _, behavior := range m.Behavior {
if behavior.Handle(m.Dto).Response.Code != 0 {
return m.Dto.Response, nil
}
}
// 执行完成后
m.Dto.Response.ClientSecret = m.Dto.PayResult.ClientSecret
m.Dto.Response.PayUrl = m.Dto.PayResult.PaymentIntentId
m.Dto.Response.PayUrl = m.Dto.PayResult.PayUrl
m.Dto.Response.OrderType = m.Dto.Params.OrderType
m.Dto.Response.CashType = m.Dto.Params.CashType
m.Dto.Response.OrderNo = m.Dto.Orders.OrderInfo.OrderNo
m.Dto.Response.Type = relebook.ORDER_CREATE_STATUS_SUCCESS return m.Dto.Response, nil
}

Go 重构案例分享:订单创建逻辑重构的更多相关文章

  1. [实战]MVC5+EF6+MySql企业网盘实战(16)——逻辑重构3

    写在前面 本篇文章将新建文件夹的逻辑也进行一下修改. 系列文章 [EF]vs15+ef6+mysql code first方式 [实战]MVC5+EF6+MySql企业网盘实战(1) [实战]MVC5 ...

  2. [实战]MVC5+EF6+MySql企业网盘实战(14)——逻辑重构

    写在前面 上篇文章关于修改文件夹和文件名称导致的找不到物理文件的问题,这篇文章将对其进行逻辑的修改. 系列文章 [EF]vs15+ef6+mysql code first方式 [实战]MVC5+EF6 ...

  3. 【案例分享】使用ActiveReports报表工具,在.NET MVC模式下动态创建报表

    提起报表,大家会觉得即熟悉又陌生,好像常常在工作中使用,又似乎无法准确描述报表.今天我们来一起了解一下什么是报表,报表的结构.构成元素,以及为什么需要报表. 什么是报表 简单的说:报表就是通过表格.图 ...

  4. [实战]MVC5+EF6+MySql企业网盘实战(15)——逻辑重构2

    写在前面 上篇文章修改文件上传的逻辑,这篇修改下文件下载的逻辑. 系列文章 [EF]vs15+ef6+mysql code first方式 [实战]MVC5+EF6+MySql企业网盘实战(1) [实 ...

  5. 谈软件-Java重构案例之Switch_Statements

    1.软件重构,大量swich语句如何重构 2.使用 ide 使用 快捷键ctrl+alt+shift+T调出重构菜单,选择method对之前的for循环重构一个method 3.得到一个新的方法,使用 ...

  6. ENode 2.6 架构与设计简介以及全新案例分享

    前言 ENode是一个应用开发框架,为开发人员提供了一整套基于DDD+CQRS+ES+EDA架构风格的解决方案.ENode从发布1.0开始到现在的差不多两年时间,我几乎每周都在更新设计或实现代码.以至 ...

  7. Office 2010 KMS激活原理和案例分享

    Office 2010 KMS激活原理和案例分享     为了减低部署盗版(可能包含恶意软件.病毒和其他安全风险)的可能性,Office 2010面向企业客户推出了新的批量激活方式:KMS和MAK.这 ...

  8. Office 2010 KMS激活原理和案例分享 - Your Office Solution Here - Site Home - TechNet Blogs

    [作者:葛伟华.张玉工程师 ,  Office/Project支持团队, 微软亚太区全球技术支持中心 ] 为了减低部署盗版(可能包含恶意软件.病毒和其他安全风险)的可能性,Office 2010面向企 ...

  9. 3D语音天气球(源码分享)——创建可旋转的3D球

    开篇废话: 在9月份时参加了一个网站的比赛,比赛的题目是需要使用第三方平台提供的服务做出创意的作品. 于是我选择使用语音服务,天气服务,Unity3D,Android来制作一个3D语音天气预报,我给它 ...

  10. 老李案例分享:定位JAVA内存溢出

    老李案例分享:定位JAVA内存溢出   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.在poptest的loadrunner的培 ...

随机推荐

  1. 多态的引入--java进阶day02

    1.多态的介绍 总的来说就是一句话,使用多态,所有的子类都可以根据父类这个桥梁来连接它们各自的成员方法,从而调用方法,减少多次的代码重写,使代码更加简单便捷 我们以之前说的公司写业务为例子来理解多态, ...

  2. 小白快速了解的Java知识!

    Java初学习 1.Java的诞生与崛起 1972年,c语言诞生,其高效率,运行速度快让大批程序员为之倾倒,但是c语言的指针及其内存管理需要程序员自行操作,浪费了大量的时间以及精力,再加上c语言需要尽 ...

  3. 【C语言】转义字符及其对应英文

    对于很多人来说,用转义字符都是熟能生巧,而不清楚为什么是那样的转义字符,所以我在这列了一个表,翻译了其对应的英文. 转义字符分为一般转义字符.八进制转义字符.十六进制转义字符. 一般转义字符:\0. ...

  4. 详细介绍FutureTask类

    一.详细介绍FutureTask类 FutureTask 未来将要执行的任务对象,继承 Runnable.Future 接口,用于包装 Callable 对象,实现任务的提交 public stati ...

  5. java基础之File、流

    一.File类 我们可以使用File类的方法 创建一个文件/文件夹 删除文件/文件夹 获取文件/文件夹 判断文件/文件夹是否存在 对文件夹进行遍历 获取文件的大小 构造方法: 1.public Fil ...

  6. 82.9K star!全平台AI助手神器,一键部署轻松搞定!

    嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 项目介绍 轻量极速的AI对话平台,支持Web/iOS/Mac/Android多端运行,轻松接 ...

  7. 119K star!无需GPU轻松本地部署多款大模型,DeepSeek支持!这个开源神器绝了

    嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 "只需一行命令就能在本地运行Llama 3.DeepSeek-R1等前沿大模型,支 ...

  8. Octotree插件 - 可以列出github项目的目录结构

    Octotree - GitHub code tree

  9. 代码随想录第九天 | Leecode 151.翻转字符串里的单词、Leecode 28. 找出字符串中第一个匹配项的下标、Leecode 459.重复的子字符串

    Leecode 151.翻转字符串里的单词 题目链接:https://leetcode.cn/problems/reverse-words-in-a-string/description/ 题目描述 ...

  10. 【HUST】网安|计算机网络安全实验|实验一 TCP协议漏洞及利用

    写在最前: 实验指导书已经写得非常好了,这是我个人的实验记录,并没有认真整理和记录容易出问题的地方.只是免得以后忘了什么是netwox还得翻学习通. 文章目录 涉及代码的仓库地址 docker使用 建 ...