ECShop 是一款经典的 PHP 开源网店系统,虽然其官方更新已放缓,但由于其稳定、易用和庞大的用户基础,至今仍有大量网站在使用,二次开发可以让你在不破坏其核心逻辑的前提下,添加新功能、修改现有行为,以满足个性化需求。

第一部分:ECShop 开发环境准备
在开始之前,你需要一个本地开发环境,推荐使用集成环境包,它们可以一键配置好 PHP、MySQL、Web 服务器等。
软件安装
- Web 服务器: Apache (ECShop 对 Apache 的 Rewrite 规则支持更好) 或 Nginx。
- PHP 版本: 强烈推荐 PHP 7.1 或 7.2,ECShop 的早期版本对 PHP 8.x 的支持不佳,会出现大量兼容性问题,新项目应尽量使用高版本 PHP,但 ECShop 二次开发通常需要迁就旧版本。
- 数据库: MySQL 5.6+ 或 MariaDB。
- 集成环境包:
- Windows: XAMPP, WampServer, phpStudy
- macOS: MAMP, XAMPP
- Linux: 可以使用
apt(Ubuntu/Debian) 或yum(CentOS) 安装 LAMP (Linux, Apache, MySQL, PHP) 环境。
安装 ECShop
- 从 ECShop 官方网站或可靠源下载 ECShop 安装包(
ecshop4.1.0版本)。 - 将下载的文件解压,并将
upload文件夹内的所有文件和文件夹通过 FTP 或 SFTP 工具上传到你的 Web 服务器根目录(htdocs或www)。 - 创建一个新的数据库和数据库用户,并记录下数据库名、用户名和密码。
- 在浏览器中访问你的域名,
http://localhost/。 - 按照安装向导的提示进行操作,填写数据库信息、管理员信息等。
- 安装成功后,务必删除
install文件夹,这是出于安全考虑。
代码编辑器
选择一款好的代码编辑器能极大提高开发效率,推荐:
- Visual Studio Code (VS Code): 免费、强大、插件丰富。
- Sublime Text: 轻量、快速。
- PhpStorm: 专业 PHP IDE,功能最全面,但收费。
第二部分:ECShop 核心架构与目录结构
理解 ECShop 的“骨架”是二次开发的关键。
目录结构解析
ECShop 的目录结构清晰,遵循 MVC 思想的雏形。

ecshop/
├── admin/ # 后台管理程序目录
│ ├── templates/ # 后台模板文件
│ └── ... # 后台 PHP 控制文件
├── upload/ # 核心业务逻辑目录 (重要!)
│ ├── includes/ # 核心函数库和类库
│ │ ├── lib_base.php # 基础函数库 (常用函数)
│ │ ├── lib_goods.php # 商品相关函数
│ │ ├── lib_user.php # 用户相关函数
│ │ ├── cls_ecshop.php # ECSHOP 核心类
│ │ └── ... # 其他核心库
│ ├── modules/ # 各个功能模块的 PHP 控制器
│ │ ├── goods/ # 商品模块
│ │ ├── user.php # 用户模块
│ │ └── ... # 其他模块
│ └── languages/ # 语言包目录
│ ├── zh_cn/ # 简体中文语言包
│ └── ... # 其他语言
├── data/ # 缓存和数据目录
│ ├── cache/ # 系统缓存
│ └── ... # 其他数据文件
├── images/ # 图片资源目录
├── themes/ # 前端模板目录
│ └── default/ # 默认模板
├── api/ # API 接口目录
├── index.php # 前台入口文件
├── admin.php # 后台入口文件
└── ... # 其他文件
核心运行流程
- 入口文件:
index.php或admin.php是程序的入口。 - 初始化: 入口文件会加载
includes/init.php,这个文件是整个系统的初始化脚本,负责加载核心类、常量、数据库连接等。 - 路由分发: 系统会根据 URL 参数(如
act=user,op=login)来决定调用哪个模块的哪个方法。act(Action): 通常对应modules/下的一个文件,如act=user->modules/user.php。op(Operation): 对应user.php文件中的某个函数,如op=login->user.php中的login()函数。
- 模型-视图-控制器 (MVC雏形):
- C (Controller - 控制器):
modules/下的 PHP 文件,负责接收请求、调用业务逻辑、处理数据。 - M (Model - 模型): 主要指
includes/lib_*.php中的函数和类,负责数据的增删改查和业务逻辑计算。 - V (View - 视图):
themes/下的模板文件,负责展示数据。
- C (Controller - 控制器):
第三部分:二次开发实战教程
下面通过几个常见的二次开发场景,来讲解具体的开发方法。
修改首页的某个区域(如“热卖商品”)
目标: 将首页的“热卖商品”模块修改为“新品上架”模块。
步骤:
-
找到模板文件:
(图片来源网络,侵删)- 首页模板位于
themes/default/index.dwt。 - 用编辑器打开这个文件,搜索
{foreach from=$hot_goods item=goods},这个foreach循环就是渲染“热卖商品”的代码块。
- 首页模板位于
-
找到数据来源:
- 在
index.dwt中,通常在文件顶部或{insert_scripts}标签之前,会有一句类似{insert name='top10'}的代码,这就是插入“热卖商品”数据的钩子。 - 这个
insert标签对应的处理函数在includes/lib_insert.php文件中,找到function insert_top10()函数,你会发现它查询的是ecs_goods表中is_hot = 1的商品。
- 在
-
修改数据逻辑:
-
我们不直接修改
lib_insert.php,因为这样会影响到其他可能使用“热卖商品”的地方,更好的方法是复制并重命名这个函数。 -
在
lib_insert.php中,复制insert_top10()函数,重命名为insert_new10()。 -
修改新函数中的 SQL 查询条件,将
is_hot = 1改为is_new = 1。// 在 lib_insert.php 中 function insert_new10($arr) { $time = gmtime(); $sql = "SELECT goods_id, goods_name, goods_img, shop_price, market_price " . "FROM " . $GLOBALS['ecs']->table('goods') . " WHERE is_new = 1 AND is_delete = 0 AND is_on_sale = 1 AND goods_id > 0 " . "ORDER BY goods_id DESC LIMIT " . $arr['num']; $res = $GLOBALS['db']->getAll($sql); $goods_list = array(); foreach ($res AS $idx => $row) { $goods_list[$idx]['id'] = $row['goods_id']; $goods_list[$idx]['name'] = $row['goods_name']; $goods_list[$idx]['thumb'] = get_image_path($row['goods_id'], $row['goods_img']); $goods_list[$idx]['url'] = build_uri('goods', array('gid'=>$row['goods_id']), $row['goods_name']); $goods_list[$idx]['price'] = price_format($row['shop_price']); } return $goods_list; }
-
-
修改模板文件:
- 回到
themes/default/index.dwt。 - 将
{foreach from=$hot_goods item=goods}改为{foreach from=$new_goods item=goods}。 - 将
{insert name='top10' assign='hot_goods'}改为{insert name='new10' assign='new_goods'}。
- 回到
-
清除缓存:
- 访问网站后台 -> 商店设置 -> 清除缓存,或者直接删除
data/cache/目录下的所有文件。 - 刷新首页,你就能看到“新品上架”了。
- 访问网站后台 -> 商店设置 -> 清除缓存,或者直接删除
添加一个新的数据库表并管理它
目标: 添加一个“优惠券”表,并在后台管理它。
步骤:
-
创建数据表:
- 通过 phpMyAdmin 或其他数据库管理工具,在你的 ECShop 数据库中执行以下 SQL 语句:
CREATE TABLE `ecs_coupons` ( `coupon_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '优惠券ID', `coupon_name` varchar(255) NOT NULL COMMENT '优惠券名称', `coupon_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '类型:0-满减,1-折扣', `coupon_value` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠值(满减金额或折扣率)', `min_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '最低消费金额', `start_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '开始时间', `end_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '结束时间', `is_enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用', PRIMARY KEY (`coupon_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
- 通过 phpMyAdmin 或其他数据库管理工具,在你的 ECShop 数据库中执行以下 SQL 语句:
-
创建后台管理文件:
- 在
admin/目录下创建一个新文件coupon.php。 coupon.php的基本结构:<?php define('IN_ECS', true); require(dirname(__FILE__) . '/includes/init.php'); $action = !empty($_REQUEST['act']) ? trim($_REQUEST['act']) : 'list';
// //-- 列表页面 // if ($action == 'list') { $smarty->assign('ur_here', $_LANG['coupon_list']); $smarty->assign('action_link', array('text' => $_LANG['add_coupon'], 'href' => 'coupon.php?act=add'));
// 从数据库获取优惠券列表 $coupons = get_coupon_list(); $smarty->assign('coupons', $coupons); $smarty->display('coupon_list.htm');} // //-- 添加优惠券 // elseif ($action == 'add') { if (isset($_POST['submit'])) { // 处理表单提交,插入数据库 $sql = "INSERT INTO " . $ecs->table('coupons') . " (coupon_name, coupon_type, coupon_value, min_amount, start_time, end_time) VALUES ('".$_POST['coupon_name']."', ".$_POST['coupon_type'].", ".$_POST['coupon_value'].", ".$_POST['min_amount'].", ".$_POST['start_time'].", ".$_POST['end_time'].")"; $db->query($sql); // 跳转到列表页 ecs_header("Location: coupon.php?act=list\n"); exit; } $smarty->assign('ur_here', $_LANG['add_coupon']); $smarty->assign('action_link', array('text' => $_LANG['coupon_list'], 'href' => 'coupon.php?act=list')); $smarty->display('coupon_info.htm'); } // //-- 获取优惠券列表的函数 // function get_coupon_list() { $sql = "SELECT * FROM " . $GLOBALS['ecs']->table('coupons') . " ORDER BY coupon_id DESC"; return $GLOBALS['db']->getAll($sql); } ?>
- 在
-
创建后台模板文件:
- 在
admin/templates/目录下创建coupon_list.htm(列表页) 和coupon_info.htm(添加/编辑页)。 coupon_list.htm(列表页):{include file="pageheader.htm"} <div class="list-div" id="listDiv"> <table cellpadding="3" cellspacing="1"> <tr> <th>ID</th> <th>优惠券名称</th> <th>类型</th> <th>优惠值</th> <th>最低消费</th> <th>操作</th> </tr> {foreach from=$coupons item=coupon} <tr> <td>{$coupon.coupon_id}</td> <td>{$coupon.coupon_name}</td> <td>{if $coupon.coupon_type == 0}满减{else}折扣{/if}</td> <td>{$coupon.coupon_value}</td> <td>{$coupon.min_amount}</td> <td><a href="coupon.php?act=edit&id={$coupon.coupon_id}">编辑</a> | <a href="javascript:;" onclick="if(confirm('确定删除吗?')) location.href='coupon.php?act=drop&id={$coupon.coupon_id}'">删除</a></td> </tr> {/foreach} </table> </div> {include file="pagefooter.htm"}coupon_info.htm(添加页): 需要创建一个表单,包含优惠券名称、类型、优惠值等字段。
- 在
-
添加菜单项:
- 登录后台,在数据库的
ecs_admin_menu表中添加一条记录,指向你的新管理页面。 parent_id为 2 (商品相关),link为coupon.php?act=list,text为优惠券管理。
- 登录后台,在数据库的
修改前台用户注册流程
目标: 在用户注册时增加一个“手机号”字段,并验证其唯一性。
步骤:
-
修改数据库表:
- 在
ecs_users表中增加一个mobile字段。ALTER TABLE `ecs_users` ADD `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号' AFTER `user_name`;
- 在
-
修改注册模板:
- 打开
themes/default/user_passport.dwt。 - 在用户名和密码输入框之间,添加手机号输入框。
<li> <label for="mobile">手机号:</label> <input type="text" name="mobile" id="mobile" size="25" /> </li>
- 打开
-
修改注册处理逻辑:
- 打开
modules/passport.php。 - 找到
register()函数,在函数中,处理完$_POST数据后,在插入数据库之前,增加手机号的验证逻辑。// 在 passport.php 的 register() 函数中 if (isset($_POST['mobile']) && !empty($_POST['mobile'])) { // 验证手机号格式 if (!preg_match('/^1[3-9]\d{9}$/', $_POST['mobile'])) { show_message('手机号格式不正确', '', '', 'error'); } // 验证手机号是否已存在 $sql = "SELECT user_id FROM " . $GLOBALS['ecs']->table('users') . " WHERE mobile = '" . $_POST['mobile'] . "'"; if ($GLOBALS['db']->getOne($sql)) { show_message('该手机号已被注册', '', '', 'error'); } // 将手机号存入 $user 数组,准备插入数据库 $user['mobile'] = $_POST['mobile']; } // ... 后续的 $db->autoExecute 代码会将 $user 数组中的数据插入到 users 表
- 打开
第四部分:高级技巧与注意事项
使用钩子
ECShop 的 insert 标签就是一个简单的钩子系统,你可以利用它在不修改核心文件的情况下,在模板的指定位置插入自定义内容。
覆盖核心文件
ECShop 的文件加载顺序是:先加载 themes/当前模板名/ 下的文件,如果没有再加载根目录下的同名文件。
- 应用: 你想修改
includes/lib_goods.php中的某个函数,但又不想动核心文件,你可以直接在themes/default/includes/目录下创建一个lib_goods.php文件,并把需要修改的函数复制过去进行修改,系统会优先加载你的版本。
安全性
- SQL 注入: 永远不要直接拼接 SQL 语句,ECShop 提供了
$GLOBALS['db']->getOne(),$GLOBALS['db']->getAll()等安全查询方法,如果必须拼接,请使用$ecs->table()来获取表名,并始终对用户输入进行过滤。 - XSS 跨站脚本: 在输出用户到页面的内容时(如商品名、评论),使用
htmlspecialchars()函数进行转义,ECShop 的$_CFG['lang']中有时会定义转义函数。 - 文件上传: 严格控制上传目录的执行权限,并对上传的文件类型、大小、内容进行严格检查。
缓存机制
ECShop 大量使用缓存(data/cache/ 目录下的 .php 文件),修改了数据或配置后,如果前台或后台没有立即更新,请务必清除缓存,这是 ECShop 开发中最常见的问题之一。
调试技巧
- 开启错误报告: 在
includes/init.php的开头添加error_reporting(E_ALL);和ini_set('display_errors', 'On');,可以显示 PHP 的所有错误和警告,对排查问题非常有帮助。 - 使用
print_r()和die(): 在 PHP 代码中,使用print_r($variable); die();来打印变量的内容并停止脚本执行,是快速查看变量值的简单方法。
第五部分:学习资源与社区
- ECShop 官方论坛: 虽然官方已不主推,但论坛里仍有大量历史问题和解答。
- 各种 ECShop 二次开发博客/教程站: 搜索“ECShop 二次开发 教程”可以找到很多个人博客的经验分享。
- GitHub: 搜索
ecshop,可以找到一些基于 ECShop 的二次开发项目或补丁。
ECShop 的二次开发核心在于理解其目录结构、MVC雏形和数据流向,从修改模板开始,到操作数据库,再到编写复杂的业务逻辑,遵循“先看,再改,后重构”的原则,多练习,多分析现有代码,你就能逐渐掌握它。
在修改任何核心文件之前,先备份!这是所有开发工作的黄金法则。
