LikeShop 二开代码规范:目录结构、钩子使用与版本升级兼容方案

做电商系统二开的团队,几乎都踩过同一个坑:拿到源码直接改,功能上线时很爽,等官方发新版本要升级时,全是冲突,改到崩溃。

LikeShop 全系列产品(单商户、多商户、垂直行业版)在设计时已经预留了扩展机制,但很多二开团队没有顺着架构来,直接硬改核心文件,最后把可升级的源码改成了「一次性版本」。后续官方修复漏洞、加新功能,都没法平滑同步,技术债越积越重。

这篇文章从目录结构、钩子机制、升级兼容三个维度,整理一套可落地的二开代码规范。无论是 PHP 版还是 Java 版,核心思路都是一致的:最小侵入、物理隔离、扩展优先、兼容升级。

一、目录结构规范:官方源码与二开代码物理隔离

所有规范的第一步,都是先把「官方的」和「自己改的」分开。混在一起写,短期快,长期全是维护成本。

1.1 PHP 版(ThinkPHP 架构)目录约定

LikeShop PHP 版基于 ThinkPHP 框架,标准 MVC 结构。二开最忌讳直接在官方模块里改控制器、改模型、改模板。

禁止直接修改的目录

这些是官方核心代码,升级时会全量覆盖,直接修改会导致升级即丢失:

  • vendor/:框架与第三方依赖
  • app/admin/、app/api/:官方后台与前台接口模块
  • app/common/:公共模型、服务层
  • public/static/admin/、public/static/api/:官方静态资源
推荐的二开目录结构

在不破坏官方结构的前提下,新增独立目录承载二开代码:

复制代码
项目根目录
├── app/
│   ├── extra/          # 二开业务模块(新增)
│   │   ├── controller/ # 自定义控制器
│   │   ├── model/      # 自定义模型
│   │   ├── service/    # 自定义业务服务
│   │   └── validate/   # 自定义验证器
│   └── common/
│       └── hook/       # 自定义钩子实现(推荐)
├── addons/             # 插件化二开(推荐,完全隔离)
│   └── your_addon/     # 自定义插件
├── public/
│   └── static/extra/   # 二开静态资源(js/css/images)
├── theme/
│   └── extra/          # 二开自定义模板
└── config/
    └── extra.php       # 二开自定义配置
正确的扩展姿势:继承优先

需要修改官方逻辑时,优先继承官方类,在子类里重写方法,而不是直接改原文件。

php 复制代码
<?php
// ✅ 推荐:继承官方服务类,重写指定方法
namespace app\extra\service;

use app\common\service\OrderService as BaseOrderService;

class OrderService extends BaseOrderService
{
    // 只重写需要扩展的方法
    public function createOrder(array $data)
    {
        // 前置扩展逻辑
        $this->beforeCreate($data);
        
        // 调用父类原生逻辑
        $result = parent::createOrder($data);
        
        // 后置扩展逻辑
        $this->afterCreate($result);
        
        return $result;
    }
}

这样官方升级后,只要父类方法签名不变,二开代码几乎不用改。

1.2 Java 版(SpringBoot 架构)目录约定

Java 版基于 SpringBoot,分层清晰。二开核心原则是:不修改官方包内的类,自定义包独立维护。

推荐包结构
复制代码
com.likeshop
├── official/           # 官方代码包(升级时全量替换)
│   ├── controller/
│   ├── service/
│   ├── mapper/
│   └── entity/
└── custom/             # 二开自定义包(完全独立)
    ├── controller/     # 自定义接口
    ├── service/        # 自定义业务逻辑
    ├── listener/       # 事件监听器(核心扩展方式)
    ├── dto/
    └── config/         # 自定义配置类
配置文件扩展

不要直接改 application.yml,新增 application-custom.yml,在主配置里引入:

yaml 复制代码
spring:
  profiles:
    include: custom

升级时官方配置文件全量替换,自定义配置不受影响。

二、钩子/事件机制:非侵入式扩展的核心

二开最高级的做法,是完全不碰官方业务代码,只通过钩子/事件注入自己的逻辑。LikeShop 在核心交易链路上都预留了事件点,这是官方推荐的扩展方式。

2.1 为什么优先用钩子

  • 零侵入:不修改官方源码,升级零冲突;
  • 解耦:二开逻辑和主流程分离,互不影响;
  • 可插拔:不需要时直接移除钩子,不影响主流程;
  • 可维护:所有二开逻辑集中在钩子/监听器里,一目了然。

2.2 PHP 版钩子与行为的正确用法

ThinkPHP 的行为(Hook)机制是 PHP 版扩展的核心。官方在下单、支付、退款、注册等关键节点都预埋了钩子。

常用内置钩子点
  • order_paid:订单支付成功后
  • order_cancel:订单取消后
  • user_register:用户注册后
  • goods_save:商品保存后
  • refund_success:退款成功后
示例:支付成功后发放积分

不要去改 PayService.php,而是注册一个钩子监听:

php 复制代码
<?php
// 1. 定义钩子行为
namespace app\common\hook;

class IntegralHook
{
    /**
     * 订单支付成功钩子
     */
    public function orderPaid($params)
    {
        $order = $params['order'];
        $userId = $order['user_id'];
        $points = intval($order['pay_amount'] * 1);
        
        // 二开逻辑:发放积分
        integral_service()->add($userId, $points, '购物返积分');
    }
}
php 复制代码
<?php
// 2. 注册钩子(tags.php 中配置)
return [
    'order_paid' => [
        'app\\common\\hook\\IntegralHook',
        // 可叠加多个钩子,互不影响
    ],
];

官方主流程完全不用动,二开逻辑通过钩子注入。后续官方升级支付流程,只要钩子点不变,二开代码就兼容。

2.3 Java 版事件监听扩展方式

Java 版基于 Spring 事件机制,官方核心节点都会发布对应事件,二开通过 @EventListener 监听即可。

示例:支付成功后触发分销结算
java 复制代码
/**
 * 二开监听器:支付成功处理分销佣金
 * 完全不侵入官方支付代码
 */
@Component
public class DistributionListener {

    @Autowired
    private CommissionService commissionService;

    @EventListener
    public void onOrderPaid(OrderPaidEvent event) {
        Order order = event.getOrder();
        
        // 二开业务逻辑
        if (order.getDistributorId() != null) {
            commissionService.calculateAndSettle(order);
        }
    }
}

官方的 OrderPaidEvent 是稳定的扩展点,只要事件对象不变,升级就不会断。

2.4 钩子使用的边界规范

  1. 不要在钩子中修改主流程状态:钩子只做后置扩展,不要回改订单、库存等核心数据,避免破坏主事务;
  2. 耗时操作异步处理:发短信、生成报表、统计分析等操作,扔到队列里异步执行,不要阻塞主流程;
  3. 异常隔离:钩子逻辑报错,不要影响主流程正常结束,做好异常捕获和日志;
  4. 一个钩子只做一件事:不同业务拆不同钩子,不要堆在一个钩子里面。

三、版本升级兼容方案:最小化升级冲突

官方版本迭代是必然的(修复漏洞、性能优化、新增功能),二开项目能不能平滑升级,取决于前期侵入程度。

3.1 四层扩展优先级

从优到劣依次是,越靠前升级成本越低:

  1. 钩子/事件扩展 → 完全无侵入,升级零成本;
  2. 继承重写 → 官方方法签名不变就兼容,成本极低;
  3. 新增文件 → 只加不减,升级不会覆盖,成本低;
  4. 修改官方源码 → 升级必冲突,成本最高,尽量避免。

原则:能用上一层,就不用下一层。 比如能通过钩子实现的,就不要继承;能通过继承实现的,就不要改官方源码。

3.2 数据库变更的兼容管理

数据库是升级里另一个重灾区。二开加字段、加表,不要直接改官方表结构。

规范做法:

  1. 二开字段加前缀 :比如 ext_xxx,和官方字段区分开;
  2. 二开表加前缀 :比如 ls_ext_xxx,和官方表区分;
  3. SQL 单独管理 :二开的建表、改表语句,单独放在 sql/extra/ 目录,和官方升级 SQL 分开;
  4. 禁止删除官方字段:宁可冗余字段,也不要删官方字段,否则升级必报错。

3.3 完整升级步骤

按照这个流程升级,可以把冲突降到最低:

  1. 全量备份:代码、数据库、静态资源全部备份,留回滚方案;
  2. 官方代码替换:用新版本官方目录覆盖旧版官方目录,保留二开目录不动;
  3. 数据库升级:先执行官方升级 SQL,再执行二开增量 SQL;
  4. 冲突排查:对比变更日志,检查二开用到的钩子、继承的父类是否有变动;
  5. 回归测试:重点测二开功能和核心交易链路;
  6. 灰度发布:先在测试环境验证,再生产环境灰度切换。

3.4 冲突处理策略

  • 如果是官方修复 bug 导致的方法变动,同步修改二开继承类对应方法;
  • 如果是官方新增了同类功能,优先替换为官方能力,逐步下线二开实现;
  • 如果是官方调整了钩子参数,同步修改钩子接收参数。

四、二开避坑与最佳实践

4.1 几条绝对红线

  1. 不要修改框架核心文件:ThinkPHP / SpringBoot 框架源码、官方核心类,动了后续永远没法升级;
  2. 不要在官方方法里硬编码业务:比如在下单方法里直接加一段分销逻辑,后续升级合并不想死;
  3. 不要删除官方字段和方法:哪怕暂时用不上,也留着,否则其他依赖的地方会炸;
  4. 不要改官方表结构的字段类型 :比如把 varchar 改 text,很容易导致官方 SQL 执行异常。

4.2 团队协作规范

  • 二开代码统一命名空间、统一前缀,一眼就能区分官方和自定义;
  • 每个二开功能写注释:「为什么改、改了哪、对应需求」,后人接盘不懵;
  • 建立二开变更文档:记录每个版本改了哪些文件、加了哪些钩子、对应官方哪个基线版本。

4.3 静态资源与前端二开规范

  • 二开前端组件单独目录,不要混入官方组件库;
  • 主题样式用覆盖方式,不要直接改官方样式文件;
  • 前端二开同样遵循:扩展组件 > 修改组件 > 重写页面。

结语

二开这件事,短期看「直接改最快」,长期看「按规范扩展最省」。

LikeShop 无论是 PHP 版还是 Java 版,在设计时都预留了钩子、继承、插件等扩展机制,本质就是希望二开团队不用硬改源码,能跟着官方版本一起迭代。顺着架构做扩展,而不是逆着架构硬改,前期多花一点时间做结构设计,后续每次升级都能省下几倍的时间。

对团队来说,代码规范从来不是束缚,是长期效率的保障。

相关推荐
深圳老胡3 小时前
STM32 开发中 Trae 内置模型怎么选:Seed、MiniMax、GLM、DeepSeek、Kimi、Qwen 全对比
笔记·stm32·单片机·嵌入式硬件·代码规范·1024程序员节
喵个咪19 小时前
AI 时代的危与机:当潮水退去,技术人如何自处
ai编程·代码规范
阿俊-全栈开发1 天前
LikeShop 单商户标准版 UniApp 端二开:页面组件复用与自定义主题适配
uni-app·likeshop开源商城·likeshop单商户标准版·多商户源码
六个蒙多都说少之一拳下去那得是1 天前
Git 与 GitHub 从零到协作:核心操作完全指南
github·代码规范
hunter_0761 天前
App 被苹果 4.3 拒绝怎么办?从官方条款到整改、沟通与申诉
面试·代码规范
深圳老胡1 天前
STM32 工程在 VSCode 中用 AI 搭建软件骨架架构:从零到可运行的分层实践
笔记·stm32·单片机·代码规范
深圳老胡1 天前
STM32 工程在 VS Code 中的编译与调试:AI 辅助工具实战指南
笔记·stm32·单片机·代码规范
这个DBA有点耶2 天前
索引建了查询却更慢——优化器选错索引与Index Merge的隐藏代价
数据库·架构·代码规范
深圳老胡2 天前
STM32CubeMX 2 与 STM32CubeMX 的主要区别
笔记·stm32·单片机·代码规范