当前位置:文档之家› shopex4.85二次开发

shopex4.85二次开发

ShopExShopExShopExShopEx完全手册
author : ShopEx 开发团队
since : 2009-12-03 $Rev: 308 $
网店系统shopex 的完整项目介绍,架构,及数据库说明,数据库结构飞天小神整理2010-12-16
1.前言和导读
2.安装和使用
o 2.1. 安装shopex
. 2.1.1. 如何选择主机
o 2.2. 初始化配置系统
o 2.3. 系统调优
. 2.3.1. url rewrite
. 2.3.2. 搜索引擎优化(SEO)
. 2.3.3. 服务器配置
o 2.4. 操作技巧
. 2.4.1. 使用快捷键
. 2.4.2. 使用条码扫描器
o 2.5. 业务成长之后...
o 2.6. 升级方法
3.扩展shopex
o 3.1. 插件体系
. 3.1.1. 用户登录插件(passport)
. 3.1.2. 图片存储方式插件(storager)
. 3.1.3. 支付方式插件(payment)
. 3.1.4. 网店机器人动作插件(actions)
. 3.1.5. 数据导入导出插件(dataio)
. 3.1.6. 用户消息插件(messenger)
. 3.1.7. 单独页面布局插件(layout)
. 3.1.8. 地区数据插件(location)
. 3.1.9. 网页挂件(widgets)
. 3.1.10. 商品插件(schema)
. 3.1.11. 前台功能插件(shop)
. 3.1.12. 后台功能插件(admin)
o 3.2. 软件功能包(app)
o 3.3. 使用二次开发接口
. 3.3.1. 案例A: 更改友情链接页面显示个数
o 3.4. 自定义核心流程页面模板
4.系统探秘
o 4.1. 系统结构
. 4.1.1. 数据库结构定义文件
. 4.1.2. 网店对象
. 4.1.3. 业务模型(model)
o 4.2. 运行过程

. 4.2.1. 前台流程
. 4.2.2. 后台流程
o 4.3. 配置信息的存储
o 4.4. 模板系统
o 4.5. 缓存机制
. 4.5.1. 基于http 协议的浏览器缓存
. 4.5.2. 前台全页缓存-控制器
. 4.5.3. 前台全页缓存-存储器
. 4.5.4. 模板缓存
5.shopex 对外数据接口
6.附录
o 6.1. setting
o 6.2. 数据库手册
o 6.3. 结构图
1. 前言和导读
本手册对应版本shopex 4.8.5
本手册部分内容由shopex 系统源代码直接生成,因此会持续保持更新
. html 版本: https://www.doczj.com/doc/1417982570.html,/shopex-b2c/
. pdf 版本: https://www.doczj.com/doc/1417982570.html,/shopex-b2c/shopex-book.pdf
相关文档
. 模板手册: https://www.doczj.com/doc/1417982570.html,/shopex-b2c/tplbook/
本手册适合谁来阅读:
. 网店店主:
查看包括怎样获取shopex。如何选择主机。怎样安装和调优你的网店系统。
. 开发者:
希望扩展shopex 功能的朋友,本手册将为你展示多种扩展系统的方法,你将发现
shopex 已经为你很多准备工作。手册后面的附录里包含了shopex 系统的数据对象
和数据库的定义细节。
2. 安装和使用
ShopExV4.8 网店系统是一套基于网上快速建店的标准化B2C 电子商务系统。系统集成了最基
本最普通最常用的电子商务运作流程及使用功能,可以满足正常的开店需求。

2.1. 安装shopex
2.1.1. 如何选择主机
shopex 提供了一个服务器性能检测探针文件,位于install/svinfo.php。你可以单独上传
这个文件到被测的服务器环境上。这个探针所探测的数据和你在系统安装时或

者后台看到的
服务器状态是完全一样的。下面就是个典型的服务器检测报告:
$Rev: 308 $
ShopEx 服务器测评
================================================================
服务器基本信息
================================================================
操作系统WINNT
服务器软件Apache/2.0.59 (Win32) DAV/2 SVN/1.4.0 PHP/5.2.3
php 运行方式apache2handler
================================================================
php 基本信息
================================================================
php 版本5.2.3
程序最多允许使用内存量memory_limit 128M
POST 最大字节数post_max_size 8M
允许最大上传文件upload_max_filesize 2M
程序最长运行时间max_execution_time 30
被禁用的函数disable_functions 无
================================================================
基本需求
================================================================
PHP4 以上5.2.3
zend.ze1_compatibility_mode 关闭Off
支持文件锁(flock) 支持
php 可以解析xml 文件支持
MySQL 函数库可用5.0.37
数据库Mysql 3.2.23 以上127.0.0.1:3306
ZEND Optimizer2.5.7 以上未安装
DNS 配置完成,本机上能通过域名访问网络成功
================================================================
推荐配置

================================================================
unix/linux 主机WINNT
php 版本5.2.0 以上5.2.3
MySQL 版本4.1.2 以上5.0.27
GD 支持freetype,gif,jpg,png,bmp
Zlib 支持支持
Json 支持支持
mbstring 支持支持
fsockopen 支持支持
iconv 支持支持
register_globals 关闭已关闭
allow_url_include 关闭(php5.2.0 以上) 已关闭
高速缓存模块(apc,memcached) Memcached
小技巧:
. 你可以在ie 浏览器里直接ctrl+a,ctrl+c 复制出来,不需要担心格式问题
. 这是个独立的文件,同时也会检测php 环境和zend opt。
2.2. 初始化配置系统
2.3. 系统调优
2.3.1.url rewrite
. 如果开启rewrite,过去的网址将依然有效,用户可以同时通过新老两种url 访问你的
系统。同时我们会通过发送301 头信息更新搜索引擎的记录为新地址
. 该转换规则存在于plugins/functions/urlmap.php 文件中。其他系统转换过来的用
户可以通过修改该文件,使得旧地址依然有效
. 新版本url 地址映射关系是由plugins/functions/actmapper.php 文件所定义, 熟
悉php 的用户可以自己设计独特的url 方式
Apache 的rewrite 规则
RewriteEngine on
# 设置RewriteBase 的值为你的商店目录地址
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule ^(.*)$ index.php?$1 [L]
rewrite 启用方法,就是把root.htaccess 改名成.htaccess。更改.htaccess 文件中
rewritebase 为你url 的前缀如果感觉麻烦直接在后台设置->基本设置->启用伪静态。由程
序自动完成上面两个

工作。
IIS 下的配置方法
iis 下需要isapi_rewrite 第三版,老版本的rewrite 不支持RewriteCond 语法。下载地址
https://www.doczj.com/doc/1417982570.html,/download-isapi_rewrite3.htm 下载那个ISAPI_Rewrite
Lite for Windows NT4/2000/XP/2003 (Freeware!)即可,免费软件! 安装好后,规则和
apache 类似。直接复制到httpd.conf 文件中即可,有的系统也可能叫httpd.ini。
# 设置RewriteBase 的值为你的商店目录地址
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?$1 [L]
管理工具-> internet 信息服务-> 网站点右键. -> 属性ISAPI 筛选器看到下面那个
ISAPI_Rewrite3 了吧。转到rewrite 的选项卡可以看到里面的rewrite 规则

设置mime 类型,启用javascript 加速

Nginx 请修改PATH 为你的安装路径
location /PATH/ {
if (!-e $request_filename) {
rewrite ^/PATH/(.*)$ /PATH/index.php?$1 last;
}
}
webserver 众多,我们仅列出具有代表性的几款
2.3.2. 搜索引擎优化(SEO)
2.3.3. 服务器配置
2.4. 操作技巧
2.4.1. 使用快捷键

快捷键功能
alt+1 商品
alt+2 订单
alt+3 会员
alt+4 营销推广
alt+5 站点管理
alt+6 统计报表
alt+5 站点管理
esc 关闭对话框/ 关闭列表详细
R 刷新主区域
U 切换左侧导航
H 打开使用向导
/ 定位到搜索框
按下Ctrl 点击鼠标拖动列表页将航向滚动列表到拖动方向
2.4.2. 使用条码扫描器
2.5. 业务成长之后...
当你需要丰富的权限分配,工作流,来规范你的业务流程的时候。可以选用shopex 提供的订
单处理中心系统。
当你需要扩展网店系统功能来支撑精细化的销售运作的时候。你可以寻找一些有技术实例的团队
来解决这个问题。当然你也可以组织自己的技术部门把本手册第三章后面的部分扔给他们就好
了。shopex 内建的插件体系足够让他们满足你的要求。
2.6. 升级方法
现在的升级逻辑很简单
先跑php 然后调用diff
先跑php 是让数据库里的东西可以跟着变过来
调用数据库diff 去处理php 没搞完的杂事
3. 扩展shopex

扩展有两种办法,一种是通过增加插件。另一种是采用我们称之为"第三方开发方案的方
式。
. shopex 对插件的设计目标是语法简单,并且可以很方便的将成果发布,传递出去。
. 第三方开发方案的特点是,无所不能,功能强大。但实现稍微复杂。
除了一些基本的模板改动之外,其他都要求开发者要基本的懂得PHP 语法,并且有一些最基础
的面向对象编程的知识。函数不熟可以去查手册,大多数时候,只要会改的技能就可以了。
3.1. 插件体系
shopex 系统支持13 种插件,其中促销方式可能在会后面的版本有大的改动。
插件名类型标示名默认路径
用户登录插件-passport plugins/passport

图片存储方式插件-storager plugins/storager
支付方式插件-payment plugins/payment
促销方式插件-pmtScheme plugins/pmtScheme
网店机器人动作插件-action plugins/action
数据导入导出插件-dataio plugins/dataio
用户消息插件-messenger plugins/messenger
单独页面布局插件-layout plugins/layout
地区数据插件-location plugins/location
网页挂件-widgets plugins/widgets
商品插件-schema plugins/schema
前台功能插件-shop -
后台功能插件-admin -
从插件的文件组织方式上可以分成目录型和文件型两大类插件。
. 文件型插件:
每个单独的php 文件就是一个插件,文件名和其中的类的名称要符合一下规则:
o 文件名: <标示名>.插件名.php
o 类名:<标示名>_插件名
例如: 支付插件taobao 的文件名是:payment.taobao.php, 对应的文
件内容是:

class payment_taobao {
var $name = ... //插件名称
var $version = ... //版本号
...
}
?>
可以被放置在除目录型插件里面的任何plugins 下的任何位置。到后台刷新
插件数据库时,就会被发现。
. 目录型插件:
因为有些自己的资源所以被设计成独立的一个目录其中必须放置一个主插件php
文件,文件的命名规则和类的内容都和文件型插件相同。一个目录下只能放置一
个主文件。其余文件都作为这个主文件的资源存在。可以被放置在除目录型插件里
面的任何plugins 下的任何位置。
在4.8.5 版本之后,不同类型插件不需要放在各自类型的目录里。只要放在plugins 下面就都
可以被扫描到。这是为下一步app 机制作的准备。
3.1.1. 用户登录插件(passport)
文件型
可以集合msn passport, google account, openid 等统一登录系统
class passport_phpwind extends modelFactory {
var $passport_name = "PhpWind 论坛V6.3.2"; //整合论坛名称
var $passport_memo = "描述"; //整合论坛描述内容
var $_config = null; //初始化配置变量
var $forward = 0; //初始化跳转链接变量
var $name = "phpwind"; //用户登陆插件名称
/**
* setConfig,发送时调用
* 必有方法,config 参数为getOptions 取得的所有项的配置结果
*/
function setConfig($config) {
$this->_config = $config;
}

/**
* verifylogin ,用户登陆验证
* 可选方法,login 参数为用户名,passwd 参数为用户密码
*/
function verifylogin($login,$passwd){
}
/**
*decode,解码
*可选方法
*/
function decode($responseData){
}
/**
* getoptions
* 必有方法,取得的所有项的配置结果
* 返回一个配置结果的数组
*/
function getoptions(){
return $options;
}
/**
* login ,用户登陆
* 必有方法,userId 参数为用户Id 号,rurl 参数为返回的跳转链接
*/
function login($userId, $rurl) {
}
/**
* regist ,用户注册
* 必有方法,userId 参数为用户I

d 号,rurl 参数为返回的跳转链接
*/
function regist($userId,$rurl) {
}
/**
* logout ,用户退出

* 必有方法,userId 参数为用户Id 号,rurl 参数为返回的跳转链接
*/
function logout($userId,$rurl) {
}
/**
* _StrCode ,编码/ 解码
* 可选方法,string 参数为需要进行处理的字符串,
* action 参数表示对数据进行ENCODE 编码还是DECODE 解码
*/
function _StrCode($string,$action='ENCODE'){
}
/**
* ClientUserAction
* 必有方法,action 参数为作为客户端操作动作,
* userdb 参数为数据库名,forward 参数为跳转链接
*/
function ClientUserAction($action,$userdb,$forward=''){
}
/**
* StrCode ,编码/ 解码
* 可选方法,string 参数为需要进行处理的字符串,
* action 参数表示对数据进行ENCODE 编码还是DECODE 解码
*/
function StrCode($string,$action='ENCODE'){
}
/**
* ServerClient
* 必有方法,action 参数为作为服务器端操作动作
*/
function ServerClient($action){
}
/**
* getPlugCookie ,取得插件Cookie

* 可选方法,action 参数为作为服务器端操作动作
*/
function getPlugCookie(){
}
/**
* setPlugCookie,设置插件Cookie
* 可选方法,val 参数为设置插件Cookie 的值
*/
function setPlugCookie($val){
}
}
?>
3.1.2. 图片存储方式插件(storager)
文件型
如果你去查看shopex 系统里存放商品图片的数据表sdb_gimages,会发现图片存放是一个竖
线分割的字符串, 规则如下:
网址|资源定位|存储方式
比如:
images/goods/01/02.jpg|goods/01/02.jpg|fs_storager
为什么要这样设计?
我们把这个字符串叫storager 格式,他的好处是可以方便的使用多种存储后端,shopex 可以
把图片存在ftp 上,存在flickr 里,存在amazon 的s3 里,或者存在memcache 里。在此
分段解释一下各部分的使用方式:
. 第一个段是用来在前台访问的:就是url,可以是绝对地址,也可以是相对地址。
. 第二段是存储代码用来定位资源的:比如是flickr 里的图片id, ftp 服务器里的相对资

. 第三段是图片存储器的类型表示, 代码会定位到plugins/function/<存储类型
>.php

规则很简单,只取第一个竖线前面的部分,我们通过一个smarty 插件做storager 格式和url
的转换:

如果是远程图片,或者是从其他系统导入的数据,就不会有竖线。storager 过滤器就把整条数
据都当第一段来处理,以此保证这个结构的兼容性。第二段和第三段是用来管理资源的当对这
个图片进行删除, 修改等操作时, 系统会找到由第三段定义的存储插件, 然后用插件通过第二段
进行定位进而对资源进行操作。下面贴一个使用nginx + Tokyo Tyrant 进行存储的方案:
nginx 的NginxHttpMemcachedModule 有个

很牛X 的特性, 可以使用url 作为memcache
的key 进行资源访问。这使得建造分布式的图片存储集群变得简单,同时,性能也有大规模提
升。
使用内存做存储有两个缺点,一是有容量限制,二是不能永久存储。因此我们使用Tokyo Tyrant
做存储方案。Tokyo Tyrant 是Tokyo Cabinet 的网络接口,可以使用memcached 一样的
协议。同memcache 相比,Tokyo Cabinet 可以将资源存放在硬盘中,可以实现互备,可以
作为永久存储方案。
实施分两部分, 首先配置软件端, 我们增加一个shopex 的存储插件–tt_storager 在其中实
现向tt 服务器的存储部分,很简单:把它当memcache 用就可以了。
function save($file,&$url,$type,$addons){
$id = $this->_get_ident($file,$type,$addons,$url,$path);
if($path && $this->memcache->set($path,file_get_contents($file))){
$this->memcache->delete($file,10);
return $id;
}else{
return false;
}
}
接着配置nginx ,让他可以对那个访问的url 到tt 上取资源:
# 这里可以配置n 组tt_server
upstream tt_server1{ # 配置一个tt 服务器组
server 192.168.2.35:11211;
server 192.168.2.36:11211; # 双主互备
}
server {
server_name https://www.doczj.com/doc/1417982570.html,;

listen *:80;
location / {
set $memcached_key $uri;
memcached_pass tt_server1;
}
}
接下来,搭建一组这样的nginx 服务器,再配合lvs,nginx 集群+tt 集群,一个高性能,高可
用互备的图片集群就建立起来了。
3.1.3. 支付方式插件(payment)
文件型
require('paymentPlugin.php');
class pay_xxxx extends paymentPlugin{
var $name = 'xxx'; //支付方式名称
//支付方式logo 名称,
//logo 图片保存在\plugins\payment\images 下,
//命名规则比如:logourl.gif
var $logo = 'logourl';
var $version = 20050615; //支付方式版本
var $charset = 'gb2312'; //编码设置
//订单提交正式交易地址
var $submitUrl= 'https://https://www.doczj.com/doc/1417982570.html,/Payment.aspx';
//提交按钮的图片地址
var $submitButton = '提交按钮url';
//支持支付的货币类型,定义的数组中货币类
//型键值根据支付网关给出的技术文档来决定
var $supportCurrency = array("CNY"=>"RMB",
"USD"=>"02");
//支持支付的地区
var $supportArea = array('AREA_CNY','AREA_USD');

var $desc = ''; // 支付方式描述
var $intro = ''; // 支付方式介绍
var $M_Language = "1"; // 语言选择,表示商家使用的页面语言
// 在添加支付方式时,下拉列表的排列顺序(越小越靠前显示)
var $orderby = 29;
var $head_charset = "gb2312"; // 编码设置
// 支持真实的外币交易
var $cur_trading = true;
/**
* toSubmit ,向支付网关发出支付请求
* 必有方法,payment 参数为需要提交的订单详细信息
*/
function toSubmit($payment){
return $return;
}
/**
* callback ,支付网关向系统发出支付应答
* 必有方法,返回

四种状态:
* 1.PAY_ERROR :签名认证失败;
* 2.PAY_SUCCESS :支付成功;
* 3.PAY_FAILED :支付失败;
* 4.PAY_PROGRESS :交易处理中;
*/
function callback($in,&$paymentId,&$money,&$message,&$tradeno){
return PAY_ERROR;
}
/**
* getfields
* 必有方法,返回所需要配置的信息数组
*/
function getfields(){
return array(
'member_id'=>array(
'label'=>' 客户号', // 配置信息标签
'type'=>'string' // 配置信息类型
),
'PrivateKey'=>array(

'label'=>'私钥',
'type'=>'string'
)
);
}
/**
* applyForm,支付方式申请的表单
* 可选方法,agentfield 参数为申请表单需要提交的信息
* 返回一个支付方式申请的表单
*/
function applyForm($agentfield){
return $tmp_form;
}
}
?>
3.1.4. 网店机器人动作插件(actions)
文件型
class action_order{
var $name = '订单触发器可用动作'; //动作名称
var $action_for = 'trading/order'; //绑定的特殊模块
/**
* actions,机器人执行动作
* 必有方法,返回机器人执行动作数组
*/
function actions(){
return array(
'addPoint'=>array(
'label'=>'增加积分', //执行动作标签名称
//执行动作的内容,以及该内容的类型
'args'=>array('点数'=>array('type'=>'number'))),
'delPoint'=>array('label'=>'扣除积分'
,'args'=>array('点数'=>array('type'=>'number'))),
'addCoupon'=>array('label'=>'送订单优惠券'

,'args'=>array(' 优惠券'=>array(
'required'=>true,
// 引用对象的特殊模块
'type'=>'object:trading/coupon',
'filter'=>'cpns_type=1&ifvalid=1')
)),
);
}
/**
* addPoint ,根据actions 方法返回的动作命名的函数
* 必有方法,data 参数为订单信息,point 参数为变化的积分
*/
function addPoint($data,$point){
...
}
/**
* delPoint ,根据actions 方法返回的动作命名的函数
* 必有方法,data 参数为订单信息,point 参数为变化的积分
*/
function delPoint($data,$point){
...
}
/**
* addCoupon ,根据actions 方法返回的动作命名的函数
* 必有方法,data 参数为订单信息,coupon 参数为优惠券信息
*/
function addCoupon($data, $coupon){
...
}
}
actions 返回的数组,在网店后台工具箱-> 网店机器人-> 订单-> 添加规则中,“执行动作”的
下拉列表(见下图示), 下拉列表的选择项就是返回数组的'label' 标签名称。后面的输入框是根据
返回数组的键args 来判断,假如'type' 类型是number ,则显示文本框,假如'type' 类型是引
用特殊模块,则根据特殊模块来显示内容。

3.1.5. 数据导入导出插件(dataio)
文件型
class io_txt{
var $name = 'txt-制表符分隔的文本文件'; //数据导入导出插件名称
/**
* export_begin
* 必有方法,keys 参数为导出文件类型的键名数组,
* type 参数为导出文件类型,count 参数为导出数据数量
*/

function export_begin($keys,$type,$count){
//文件下载方法,其中第一个参数为文件名

download($type.'-'.date('Ymd').'('.$count.').txt');
//以制表符为间隔来输出每一个键名
echo implode("\t",$keys)."\r\n";
flush(); //刷新输出缓冲
}
/**
* export_rows
* 必有方法,rows 参数为需要导出所有行
*/
function export_rows($rows){
foreach($rows as $row){
foreach($row as $k=>$v){
$row[$k] = str_replace("\n",'\n',$v);
}
//以制表符为间隔来输入每一行的每一列,并且最后换行
echo implode("\t",$row)."\r\n";
}
flush();
}
/**
* export_finish
* 必有方法,导出结束
*/
function export_finish(){
}
}
?>
目前数据导入方法暂时不公布。
3.1.6. 用户消息插件(messenger)
目录型
class messenger_email{
var $name = '电子邮件'; //名称
var $iconclass="sysiconBtn email"; //操作区图标

var $name_show = ' 发邮件'; // 列表页操作区名称
var $version='$ver$'; // 版本
var $updateUrl=''; // 新版本检查地址
var $isHtml = true; // 是否html 消息
var $hasTitle = true; // 是否有标题
var $maxtime = 300; // 发送超时时间, 单位: 秒
var $maxbodylength =300; // 最多字符
var $allowMultiTarget=false; // 是否允许多目标
var $targetSplit = ','; // 目标分隔符
var $dataname='email'; // 数据库名
var $debug = false;
/**
* ready ,准备发送时触发
* 可选方法,config 参数为getOptions 取得的所有项的配置结果
*/
function ready($config){
...
}
/**
* finish ,结束发送时触发
* 可选方法,config 参数为getOptions 取得的所有项的配置结果
*/
function finish($config){
...
}
/**
* send ,发送时调用
* 必有方法,to 参数为发送对象,subject 参数为消息主题,
* body 参数为消息内容,config 参数为getOptions 取得的所有项的配置
结果
*/
function send($to, $subject, $body, $config){
...
}
/**
* getOptions ,取得配置信息
* 必有方法,返回配置信息的数组
*/
function getOptions(){

...
}
}
?>
在网店后台,会员->邮件短信设置->电子邮件下的配置,就可以根据用户消息插件中
getOptions 定义的配置信息来配置发送方式、发送地址等。
3.1.7. 单独页面布局插件(layout)
目录型
├─3-columns //三列
│ layout_3-columns.php //布局配置文件
│ preview.png //布局缩略图预览
│ layout.html //布局模板

├─2-columns-left //两列,左为主
│ preview.png
│ layout_2-columns-left.php
│ layout.html

├─2-columns-right //两列,右为主
│ preview.png
│ layout_2-columns-right.php
│ layout.html

└─1-column //单列
layout.html
preview.png
layout_1-column.php
layout.html
比如:在2-columns-left 目录下的布局模板,布局:两列,左为主。


<{widgets}>




<{widgets}>

layout_2_columns_left.php
比如:在2-columns-left 目录下的布局配置文件。
class layout_2_columns_left{
var $name='两列,左为主'; //布局插件名称
var $slotsNum = 2; //布局列数
}
?>
在网店后台页面管理->站点栏目->添加顶级栏目->选择单独页面->选择布局->设置布局(如
下图示),就可以根据自定义的单独页面布局插件,来选择布局。
3.1.8. 地区数据插件(location)
目录型
class local_difang{
var $name = '某地方'; //地区插件名称
var $desc = '地区描述'; //地区插件描述
var $maxdepth = 3; //地区最大深度为3 层
/**
* install

* 必有方法,安装时调用,将area.txt 文件内地区信息插入数据库表中。
*/
function install(){
....
}
}
3.1.9. 网页挂件(widgets)
3.1.9.1. 一步步做个widgets 基础篇
首先建立一个目录plugins/widgets/helloword 这个就是新的挂件目录系统的每个widgets
都是一个目录
里面放两个文件:
widgets.php:
$setting['author']='我'; //挂件作者
$setting['name']='Helloword'; //挂件名称
$setting['version']='0.0.1'; //挂件版本
$setting['catalog']='我的原创'; //挂件目录
$setting['description'] = '这是我自己的helloword 板块'; //挂件描述
?>
default.html:

HelloWorld



这时目录看起来这个样子:

我们到后台可视化模板编辑里面添加板块。
保存,前台就能看到了

添加一个输入框
下面我们来改进这个板块:在后台输入一串文字,在前台显示出来。
增加一个配置文件_config.html 这个文件名是定死的, 只要存在这个文件, 那么就会把这个文
件内容放到后台的版块编辑里面
_config.html
输入我的文字:
value="<{$setting.string_1}>" name="string_1" />
注意: 这个输入框的value 在这里设置的是<{$setting. string_1}> , 这样在版块编辑的时
候,才会作为把你上次填的文字放在输入框里我们在这里做的每个input/select 元素,都会作
为$setting 这个变量的一部分
编辑的时候就是这个样子:

下面把输入的字符串在前台显示出来:修改default.html

Hello:<{$setting.string_1}>


这时前台的样子:
加入控制程序
再在这个目录下添加一个widgets_helloword.php,这里的文件和php 函数,要用widget_+
目录的名字(helloword) 命名
widget_helloword.php
function widget_helloword(&$setting,&$system){
return ''.$setting['string_1'].'';
}
?>
下面修改default.html,让他输出通过php 修改过的文字。只要把<{$setting.string_1}> 改
成<{$data}> 即可。

default.html

Hello:<{$data}>


写到这里发现写错字了,helloword... 只好继续"你好单词"下去...

前台表现:
3.1.9.2. 一步步做个widgets 实战篇
很多人做挂件的动机一般都是看到一个比较好效果或者是对官方提供的功能进行更深入的修改。
比如看到了一个很好的效果,想把他用到自己的网店上,同时也可以在后台维护,下面的一个效
果挂件,就是出于这样的一个动机来制作的。
原始的效果:https://www.doczj.com/doc/1417982570.html,/demos/slider_class/ 感觉这个带有数字控制的
滑动的效果还是不错,想把它改成的ShopEx 的挂件,只要店主在后台设置了图片的路径和这
个图片上的链接就可以实现这个广告效果。
下面的图帮你来温习一下挂件的制作基础。

首先要想清楚,挂件里面有些什么东西是需要店主设置的:1、图片的路径,这个当然需要店
主自己改了, 否则就不能自己添加广告了。2、点击图片所链接的地址, 这个也是必须的。3、
每一个图片停留的时间,是让浏览者看3 秒还是看5 秒,这个也是需要店主自定义的4、图片
滑动的时间,就是从一张图片向另外一张图片切换的时候所需要的时间。5、数字的颜色和背
景, 因为是带有数字的, 所以就有当前的图片的数字和非当前的图片数字, 这两个地方的颜色都
需要用户可以设定的。
然后看看, 上面看到的那个效果的HTML 和js 是不是支持上面所理想中设定, 当然, 这个地方
需要你了解一些js 了。看了一下这个效果整个的js 和HTML 结构,ok ,没有问题,都是可以
支持的。把这些定下来以后,就可以去制作_config.html 了。
制作_config.htm 文件的时候,可以直接从一个类似的官方挂件中复制一个过来,这样有些现
在的html 和js 都是可以使用。比如从exchangeeffect 这个挂件中把_config 文件复制过来。
现在还是对这个文件动手了。在制作_config.htm 的时候,也不需要考虑太多,凡是需要店主
定义的地方,统统写成<{$setting.XXXXX}> 的格式。

制作好_config.htm 以后,在后台插入这个挂件的时候,就可以看到大概的样子了。

然后接下来制作default.html, 这个就是这个挂件的主区域。按部就班的把js 和html 调整好,
保证在静态页面的情况下(就是没有嵌入到ShopEx 系统中),可以正常使用。

上面的步骤全部做好以后,就可以使用这个挂件了,简单的预览如下:

3.1.10. 商品插件(schema)
目录型
─custom
│ schema.custom.php
│ icon-48x48.png

└─view
init.html

商品可以定义商品的发货函数可以实现点卡自动充值机票的代理自动化等等甚至向工厂自动传
送用户的下单信息。
schema.custom.php
class schema_custom{
var $name='自定义商品类型'; //商品插件名称
var $version='11689$'; //商品插件版本号
var $use_brand = true; /

/本类型商品是否有品牌
var $use_params = false; //本类型商品是否有参数表
var $use_props = true; //本类型商品是否有属性
var $use_minfo = false; //本类型商品是否有用户购买时的必填信息
/**
* init,自定义商品类型初始化
* 可选方法
*/
function init(&$post){
if(!isset($post['is_physical']))$post['is_physical'] = true;
return true;
}
}
?>
3.1.11. 前台功能插件(shop)
class shop_helloworld{
var $name = 'hello';
function index(){
echo 'hello';
}
}
3.1.12. 后台功能插件(admin)

class admin_helloworld{
var $name = 'hello';
var $workground = 'tools';
function index(){
echo 'hello';
}
}
3.2. 软件功能包(app)
即将出现在485 正式版
我们提供了一个很干净的机制让开发者扩展网店的功能,实现以下特性:
. 建立自己的数据库表
. 创建自己的控制器
. 在前后台增加栏目
. 用自己的控制器替换系统默认的(自定义业务流程)
. 添加事件侦听器,使得系统事件时调用自己的代码
可以看到,这基本相当提供给大家一个简单的php 框架了。下面说下技术细节。
每个程序包占一个目录。我们推荐将所有的包都放置在plugins/app 文件夹下。下面用个例
子, 阐述下功能包的各个控制点。假设这个文件夹的名字为:demo, 包里面必须有一个
app.demo.php。并且里面必须含有一个app_demo 的类。该类需要继承shopex_appdef
这个基类,例如:
plugins/app/demo/app.demo.php
class app_demo extends shopex_appdef{
var $ver = 0.8;
var $name='样例程序';
var $website = 'https://www.doczj.com/doc/1417982570.html,';
var $author = 'dev@https://www.doczj.com/doc/1417982570.html,';

// 可选函数
// 定义接管系统哪些流程,由自身的哪个类/ 方法去执行
// 本例表示,启用后前台将把所有访问购物车的控制器请求重定向
// 到本软件包内democtl 对象的cartidx 方法里。
function ctl_mapper(){
return array(
'shop:cart:index' => 'demo_ctl:cartidx',
);
}
// 可选函数
// 侦听系统哪些事件
// 此处可用关键字any 表示所有事件
// 本例表示:
// 侦听订单新建事件-> 调用event_handle 类的order_new 方法执行
// 侦听会员新建事件-> 调用event_handle 类的member_create 方法执行
function listener(){
return array(
'trading/order:create' =>
'demo_event_handler:order_new',
'member/account:register' =>
'demo_event_handler:member_create',
'any'=>'demo_event_handler:any',
);
}
// 可选函数, 返回需要建表的信息
// 本例是建立两个表, 系统会自动加前缀sdb_
function dbtables(){
$tables['table_2'] = array (
'columns' =>
array (
'controller' =>
array (
'type' => 'varchar(100)',
'required' => true,
'pkey' => true,
'editable' => false,
),
'plugin' =>

array (
'type' =>'varchar(100)',
'required' => true,
'editable' => false,
),
),
);
$t

ables['table_1'] = array (
'columns' =>
array (
'controller' =>
array (
'type' => 'varchar(100)',
'required' => true,
'pkey' => true,
'editable' => false,
),
'plugin' =>
array (
'type' =>'varchar(100)',
'required' => true,
'editable' => false,
),
),
);
return $tables;
}
// 我承认这是个非常邪恶的设计...
// 但你要承认它可以让你无所不能
function output_modifiers(){
return array(
'admin:goods/product:index'=>'demo_modifiers:product_edit'
);
}
// 重载安装时的方法...
// 同样可重载的还有:
// uninstall -> 卸载
// enable -> 程序启动
// disable -> 程序关闭
function install(){

// 别忘了调用父类的install
return parent::install();
}
}
为了防止命名冲突,请使用自己包的名字作为类的前缀。
plugins/app/demo/demo_event_handler.php
class demo_event_handler{
// 订单新建时本方法将被自动执行
// 此处event 被赋值为order:new
function order_new($event_type,$order_data){
...
}
// 会员新建时本方法将被自动执行
// 此处event 被赋值为order:new
function member_create($event_type,$member_data){
...
}
// 任何事件都将调用此函数
function any($event_type,$event_data){
...
}
}
建立一个类来重定义购物车页面
plugins/app/demo/demo_ctl.php
require('app_page.php');
class demo_ctl extends app_page{
function cartidx(){
...

// 输出模板:软件包文件夹里的cart.html
$this->system; // 可以调用系统入口
$this->db; // 可以直接使用数据库
// 载入包里的类...
require(dirname(__FILE__).'/demo_my_model_layer.php');
$obj = new demo_my_model_layer;
$this->output('view/cart.html');
}
}
建立一个类来重定义购物车页面用关键字<{$_BASE_PATH_}>可以定位到插件文件夹的url 。
plugins/app/demo/view/cart.html


例如输出

plugins/app/demo/images/cart.png



过滤器,将商品列表页面的“新建商品”按钮上的文字换掉。
plugins/app/demo/demo_modifiers.php
class demo_modifiers{
function product_edit( &$content ){
return str_replace(' 添加商品',' 别看我是只羊!',$content);
}
}
在第一次运行时会进行初始化。本例初始化时出现的信息是:
欢迎来到样例程序初始化向导

即将初始化样例程序0.8 到你的网店系统中。
注意!本应用程序将:
. 向数据库里安装2 个表: sdb_demo_table_2 , sdb_demo_table_1 。
. 接管你的查看购物车流程。
. 监督订单新建事件。
. 监督会员注册事件。
. 监督所有事件。
是否继续安装流程?
3.3. 使用二次开发接口
定制可以根据客户的需求对网站进行相应功能的添加修改或者删除,同时定制也存在一定的弊
端。ShopExV4.8 以前版本的定制是在原来的程序上修改的所以定制过的网站就不能

使用该版
本后发布的相关补丁。ShopExV4.8 版本采用MVC 开发模式,二次开发解决了定制在原程序
上进行修改导致程序不能升级的问题,使新的程序模块可以很好的融合到ShopExV4.8 系统中
同时也可以继承原有程序的所有功能。
本着不与原程序冲突的原则,需要新建一个目录去存放二次开发所用的程序,这就要求在
ShopExV4.8 的配置文件中定义一个存放二次开发程序目录的常量。同时为了使二次开发程序
能够兼容原程序的所有功能也要求要包含原来的控制器文件或模型层文件通过类继承和函数重
载的方式实现原有功能的保留、修改和新功能的开发当然如果该功能完全与原有功能没有联系
则只需继承控制器文件或模型层文件的基类。
1. 配置config.php 文件:
2.define(‘CUSTOM_CORE_DIR’,’自定义文件路径’)
自定义文件路径建议和core 同级
3. 后台菜单新增规则(customSchema.php):
格式参照原有后台菜单文件的书写格式但数组名必须为$cusmenu
新增菜单项
此处格式参照adminSchema.php 即可,数组名称注意应为$cusmenu
在已有菜单项中添加

根据菜单出现的位置添加不同的参数如在“统计报表”下新增二级菜单“测试二次开
发”
$cusmenu['analytics']=array(
'items'=>array(
array(
'type'=>'group',
'label'=>' 测试二次开发',
'position'=>'after|begin|end|before',
'reference'=>' 访问统计',
'items'=>array(
array(
'type'=>'menu',
'label'=>' 测试二次开发1',
'link'=>'index.php?ctl=vip/vote&act=index'
),
array(
'type'=>'menu','label'=>' 测试二次开发2',
'link'=>'index.php?ctl=vip/vote&act=index'
)
)
)
)
);
position 值及说明:
o after :在某个菜单项的后面,此时reference 必须为一个同级已存在的菜
单项。如上述例子中为“访问统计”则“测试二次开发”出现在”访问统计”的下

o before :同I,只是位置在前
o begin :在同级目录的最前面,refernce 可以为空
o end :在同级目录的最后面,refernce 可以为空
4. 在三级菜单中插入某菜单项同上述二级菜单的操作
5. 控制器文件:控制器文件必须以cct 命名
6. 继承原有控制器文件,前提是原控制器文件必须存在
https://www.doczj.com/doc/1417982570.html,t_ 原控制器名称extends ctl_ 原控制名称{
8. function 新增函数名(){
9. // 新增功能函数
10. }

11. function 原有函数名(){
12. // 函数重载
13. }
14. }
15. 新建控制器后台控制器文件
16. cct_control extends adminPage{
17. function __construct(){
18. // 自定义操作
19. }
20. // 自定义函数
21. }
或者
cct_control extends objectPage{
function __construct(){
// 自定义操作
}
// 自定义函数
}
前台控制器文件
cct_control extends shopPage{
function __construct(){
// 自定义操作
}
// 自定义函


}
22. 模型层文件:
模型层文件必须以cmd 开头命名,继承原有模型层文件,前提是原模型层文件必须
存在
cmd_ 原模型层名称extends mdl_ 模型层名称{
function 新增函数(){

//新增函数
}
function 原函数名(){
//重载函数
}
}
新建模型层文件
cmd_model extends shopObject{
function __construct(){
//自定义操作
}
//自定义函数
...
}

cmd_model extends modlFactory{
function __construct(){
//自定义操作
}
//自定义函数
...
}
3.3.1. 案例A: 更改友情链接页面显示个数
前台友情链接页面默认每页显示的友情链接为10 个大多数店主会觉得这个数字太少导致页面
太空,所以想修改此处的数字为30 或者更多。要实现这个修改,首先设置好二次开发目录。然
后在二次开发目录下新建目录。shop/controller
然后在此目录下建立文件cct.link.php 继承原始的ctl_link 类,之后重载控制器函数。
内容为:
class cct_link extends ctl_link{

function showList($page=1){
$sitemapts=$this->system->loadModel('content/sitemap');
$title=$sitemapts->getTitleByAction('link:showList');
$title=$title['title']?$title['title']:'友情链接';
$this->path[]=array('title'=>$title);
$this->title = $title;
$pageLimit = 100;
$oLink=$this->system->loadModel('content/frendlink');
$result=$oLink->getList('*', '',
($page-1)*$pageLimit,$pageLimit,$linkCount);
$this->pagedata['pager'] = array(
'current'=>$page,
'total'=>ceil($linkCount/$pageLimit),
'link'=>$this->system->mkUrl('link',
'showList',array(($tmp = time()))),
'token'=>$tmp);
if($page > $this->pagedata['pager']['total']){
trigger_error('查询数为空',E_USER_NOTICE);
}
$this->pagedata['data'] = $result;
$this->output();
}
}
将其中第九行$pageLimit = 100; 改为您要的数字就行了。
3.4. 自定义核心流程页面模板
相比大家都感觉48 只能修改框架而感到束手束脚了吧很多东西只有修改core/shop/view 下
的html 文件才可以实现,而这些都是核心的东西。模板又无法控制~~
其实有办法的:可以在模板目录下建一个view 文件夹。然后按照core/shop/view 同样的
路径,建立新的html 文件。系统输出的时候就会优先调用模板里自定义的核心业务html 部分

例如:修改会员后台的样式需要修改core/shop/view/member/main.html 才能实现。
现在自己做了一个模板叫myth,模板目录在themes/myth。就可以建立一个,把原来的
core/shop/view/member/main.html 文件复制

themes/myth/view/member/main.html 到想要自定义的细节样式,通过修改新的
themes/myth/view/member/main.html 文件即可实现。
现在,你可以随心所欲的控制样式了。
注意!
这样的话,模板就不能跟随升级了,因为里面有自定义的核心业务所需的模板。
很可能你修改过的member/m

ain.html 里面的一些变量在新版本里已经失效。
于是,这需要你手动修改member/main.html
文件来跟随系统升级。
因此我们建议,你可以自用,但尽量不要使用自定义的核心业务区域html 去做
通用模板。
否则,需要你亲自升级。当然,你也可以为这个服务提高模板价格....
自定义导入导出介质,支付方式,配送方式,促销规则,促销页面模板等等扩展接口…
4. 系统探秘
4.1. 系统结构
以下文档均以最新的shopex 4.8.5 为准。
程序为mvc 3 层结构。模型-视图-控制器。
. 模型是前后台公用。完成业务逻辑,所有的数据库操作,文件等资源调用,都由此层
实现。
. 控制器则是前后台独立。负责业务模型的调用,拼接,变量的转换等等。
. 视图层采用类似smarty 的实现。
在任何时候都有一个全局变量$system 指向唯一的一个内核类:core/kernel.php
结构图

公共类图

公共函数库位于/core/func_ext.php,它在内核加载时首先被加载,对全系统有效。其中包含
若干php5 函数的php4 模拟版本:
. file_put_contents
. json_encode
. json_decode
. ftp_chmod
. array_diff_key
. http_build_query
这是一个结构图这里是前台的控制器,视图文件这里是后台的控制器,视图文件这里是后台的
smarty 插件文件这里是前后台公用的smarty 插件文件
我们的逻辑是这样的。
控制器负责所有的变量的转换,包括对外界的变量转换。像胶水一样粘合各个业务model,并
在其中的数据交互过程中做一些数据转换过程。最终将变量抛到模板层。模板层负责编译成html
输出,这里我们创建了大量的smarty 插件来减少开发者的重复工作量。
model 层负责所有的资源操作,包括数据库和文件系统以及读取网络等等。
业务模型层下面是schema,这些是对数据库表结构的最底层的描述。
我们将从底向上的介绍这些层次
4.1.1. 数据库结构定义文件
在485 之前,我们使用powerdesigner 来管理这些表结构,但是在一些项目的版本管理过程
中,有时需要维护多个版本。powerdesigner 的储存文件为一个单一的巨大的xml 格式pdm
文件。这在进行版本的合并分支时几乎无法处理。而在485 里,我们使用php 去描述表结构有
了以下几个优势:
. 数据库定义具有版本管理特性
o 各分支开发互不干扰
o 协同更改时,可以进行合并操作
o 针对单表的版本还原与操作日志
. 容易做自动化的表结构对比
. 更快速的getInsertSQL/getUpdateSQL: 不需要select * from xxx where 0=1
. 插件可以有自己的表定义文件
. 为model::getColumns 自动化做准备
. 按照用途来定义字段类型,不会出现同一种数据类型而字段定义不一致的情况
. 简单的外键实现,方便的

统计表的依赖关系,使得外键字段定义完全一致
. 更好的mysql 版本兼容:type = MyISAM DEFAULT CHARACTER SET 语法
. 很方便的实现gbk/utf8 多版本

现在这些数据库定义文件放置在core/schemas 下面,每个表一个php 文件,样例:
$db['table_name'] = array(
'columns'=>array(
'col_1'=>array('type'=>'int',
'pkey'=>true,'comment'=>'id 字段'), //主键
'col_p1'=>array('type'=>'int',
'pkey'=>true,'comment'=>'主键2'), //联合主键
'col_33'=>array('type'=>'mediumint unsigned'),
//当然也支持通常的type
'col_2'=>array('type'=>'email',
'comment'=>'id 字段'), //email = varchar(255)
'col_3'=>array('type'=>'money',
'notnull'=>true,'default'=>3),
//money = decimal(20,3), 非空字段,默认为3.000
'col_4'=>array('type'=>'html',
'comment'=>'id 字段'), //html = text
'col_4'=>array('type'=>'bool',
'comment'=>'id 字段'), //bool = enum('true','false')
'table2_c1'=>array('type'=>'table_2:c1',
'comment'=>'表table_2 的c1 字段'),
//类型和table_2 的c1 字段相同, 当外键处理,自动加索引
),
'index'=>array(
'...',//再定义
)
'engine'=>'heap'//可不填,默认为myisam
'option'=>''//其他扩展语法
);
好了,现在一切的表结构都可以被系统精确控制了。利用这个东西,我们实现了485 里的数据
表结构比较功能,并且以这个为基础建立了新的升级机制。并且在未来,这将是app 体系的一
个基石,
关于app 体系,我将在后来的章节讲解,这将是一个神奇的东西。
4.1.2. 网店对象
在model 里面,有一些特别特别的类是基于shopObject 这个类的。shopObject 用来表示
一些有共性和意义的对象,比如商品,订单,会员。有一些抽象的共用方法。

每个shopObject 都有一个绑定的表,并且标明了哪些字段是主键。因此我们可以做一些批量
的东西。后台的订单列表,商品列表,会员列表等等。我们给他取名为finder。在此向osx 致
以敬意。
函数名参数返回值说明
is_highlight($row)行数据bool 用来判断是否将该行高亮
因此如果你想修改这些默认行为比如在列上加些自定义的字段而这个字段可能完全不是数据
库里,而存在于其他某外部系统中。那么很简单,你只需要重载对应的方法即可。
. 你可以去思考将这个重载放在哪一层。如果希望所有调用这个数据的地方都变化,那
么就重载model。
. 如果只是希望后台的列表增加一个列,就重载他的控制器即可
shopObject 还有一个特性,就是支持事件广播。他有一个特殊的方法:
shopObject::fireEvent($eventName) 用来广播事件。其他对象采用观察者模式listen 到感
兴趣的对象上。
当订单新建时,系统会调用相应的$orderModel->firevent('create')方法,这时所有关
注order-create 的函数就全都被纷纷调用有

的负责发邮件通知用户有的负责记录的数据库,
有的可能是插件写的:将这些事件广播道某esb 系统里。
这时一条网店系统的数字神经,当订单新建,或者会员登录的时候。我们可以通过外挂的脚本做
一些触发事件。这也是485 网店机器人的基础。
4.1.3. 业务模型(model)
模型是前后台公用。完成业务逻辑,所有的数据库操作,文件等资源调用,都由
此层实现
模块调用方法: $systme->loadModel(path/to/a/model); 载入过程如图所示:

4.2. 运行过程
ShopEx 前后台调度都分别通过各自目录下一个名为index.php 的入口文件进行,入口文件接
受控制器、方法等的输入参数,调度给对应的控制器方法执行并输出结果。index.php 会调用
kernel。Kernel 基础类提供模型对象加载,插件入口,错误处理,设置管理,输入输出,第
三方类库加载等基础服务。
前后台及安装入口程序在初始化时,都会初始化一个对应的内核类,这个类在前台是
shopCore.php,后台时是adminCore.php。这两个文件都是继承core/kernel.php 文件。
该内核类首先会形成一个名为system 的指向自身的全局对象,然后根据传入参数调用对应的
控制器方法运行。控制器层与模型层都会产生对该全局system 对象的调用来使用基础服务。

并且控制器和模型层在自身被实例化之后都可以使用$this->system 来引用内核对象。
也就是说,构造函数中只能通过$GLOBALS['system']来访问。
4.2.1. 前台流程
对于前台来说,会有缓存的介入。
为方便rewrite,采用形如文件名的形式做为请求句柄,请求句柄包含了控制器名,控制器方法,类
型及传入参数信息请求句柄语法:
CTL[-ARG…][-METHOD].
其中CTL 为控制器. ARG 部分为传入参数组, 可省略. METHOD 为控制器方法,可省略,默认为
index 为方便更改请求句柄的编码方式,请求句柄的编码与解析采取读取
system.seo.parselink 中定义的外挂函数(见插件小节外挂函数部分)处理,默认函数为
actmapper:
前台控制器位/core/shop/controller 于前台的入口文件为index.php,该页面初始化
shopCore,初始化时运行父类kernel 的构造方法设定system 的指向自身的全局对象,最后
构造函数调用shopCore->run(),流程如下:

说明入口程序/index.php, 判断config.php 是否存在,不存在则转至安装目录重新设定超级
全局变量$_COOKIE 为$_COOKIE[COOKIE_PFIX]的值;COOKIE_PFIX 用以区分同一目录
下安装的不同ShopEX 实例。对传入URL 进行处理,返回数组$this->request
array(
//cache 是否公用, 用于用户登录或转换货币后界面
'cache'=>$cache,
// 请求句柄XXXX.html
'query'=>$query?$query:'index.html',
// 链接基地址( 带?m/)
'base_url'=>$url_prefix.($domain?$domain.'/':''

),
'url_prefix'=>$url_prefix, // 链接基地址
'domain'=>$domain?$domain.'/':'', // m/ 或空
'member'=>$memberInfo, // 用户信息数组
'cur'=>$cur, // 货币
'lang'=>$lang // 语言
);
注:在会员登录后,链接将变为index.php?/m/XXXX.html

判断传回的request['cache']:
if($request['cache']=='public' &&
!(defined('WITHOUT_CACHE') && WITHOUT_CACHE )){
//对公共cache 进行处理
}else($request['cache']=='private')
//对私有cache 进行处理
}else{
直接调用动态程序(流程5)
}
输出缓存或重新生成和输出
调用请求句柄解析方法进行处理if 请求句柄= ‘index.html’ 返回
array('controller'=>'index','method'=>'home','args'=>array(),'type'=>'html') else
读取system.seo.parselink 中定义的外挂函数(见插件小节外挂函数部分)解析处理: 默认函
数为actmapper->parse(处理逻辑见下节)
4.2.2. 后台流程
后台入口文件为/shopadmin/index.php 文件后台的文件链接形如
http://host/shopadmin/index.php?#ctl=<控制器>&act=<方法>[&p[0]...]
后台控制器位/core/admin/controller 于后台的入口文件为/shopadmin/index.php,
该页面初始化adminCore,初始化时运行父类kernel 的构造方法设定system 的指向自身的
全局对象,最后构造函数调用adminCore->run(),流程如下:

4.3. 配置信息的存储
网店的配置信息分为两种。
1. 安装数据:存放在config/config.php 里
通常存放软件安装配置,即当系统无法正常运行时也可以通过ftp 进行修改的参数。
安装配置文件在系统安装时依据config.sample.php 模板文件自动生成。秘诀:
你也可以通过修改这个文件来覆盖系统中的一些常量。
config.sample.php 内容如下:
/**
* 网店配置模板
*
* 版本$Id: config.sample.php 37215 2009-12-03 09:28:28Z
flaboy $
* 配置参数讨论专贴
https://www.doczj.com/doc/1417982570.html,/bbs/thread-61957-1-1.html
*/

// ** 数据库配置** //
define('DB_USER', 'usernamehere'); # 数据库用户名
define('DB_PASSWORD', 'yourpasswordhere'); # 数据库密码
define('DB_NAME', 'putyourdbnamehere'); # 数据库名
# 数据库服务器--99% 的情况下您不需要修改此参数
define('DB_HOST', 'localhost');
//define('DB_PCONNECT',1); # 是否启用数据库持续连接?
define ('STORE_KEY', ''); # 密钥
define('DB_PREFIX', 'sdb_');
define ('LANG', '');
define ('WITHOUT_CACHE',false);
# 启用触发器日志: home/logs/trigger.php
//define ('TRIGGER_LOG',true);
//define ('DISABLE_TRIGGER',true); # 禁用触发器
/* 以下为调优参数*/
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');
define('DEBUG_JS',false);
define('BASE_DIR', realpath(dirname(__FILE__).'/../'));
define('CORE_DIR', BASE_DIR.'/core');
// 安全模式启用后将禁用插件
define('SAFE_MODE',false);
# 您可以更改这个目录的位置来获得更高的安全性
define('HOME_DIR', BASE_DIR.'/home');
define('PLUG

IN_DIR', BASE_DIR.'/plugins');
define('THEME_DIR', BASE_DIR.'/themes');
define('MEDIA_DIR', BASE_DIR.'/images');
define('PUBLIC_DIR', BASE_DIR.'/public'); # 同一主机共享文件
define('CERT_DIR', BASE_DIR.'/cert');
define('DEFAULT_LOCAL','mainland');
define('SECACHE_SIZE','15M'); # 缓存大小, 最大不能超过1G
//define('TEMPLATE_MODE','database');
define("MAIL_LOG",false);
define('DEFAULT_INDEX','');
define('SERVER_TIMEZONE',8); # 服务器时区
//define('APP_ROOT_PHP','index.php'); #iis 5

@ini_set('memory_limit','32M');
define('WITHOUT_GZIP',false);
# 前台禁ip
//define('BLACKLIST','10.0.0.0/24 192.168.0.1/24');
# 数据库集群.
//define('DB_SLAVE_NAME',DB_NAME);
//define('DB_SLAVE_USER',DB_USER);
//define('DB_SLAVE_PASSWORD',DB_PASSWORD);
//define('DB_SLAVE_HOST',DB_HOST);
# 支持泛解的时候才可以用这个, 仅支持fs_storager
/*
* define('HOST_MIRRORS',
* 'https://www.doczj.com/doc/1417982570.html,,
* https://www.doczj.com/doc/1417982570.html,,
* https://www.doczj.com/doc/1417982570.html,');
*/
# 使用ftp 存放图片文件
//define('WITH_STORAGER','ftp_storager');
# 确定服务器支持htaccess 文件时, 可以打开下面两个参数获得加速。
//define ('GZIP_CSS',true);
//define ('GZIP_JS',true);
# 可以选择缓存方式apc 或者memcached
//define('CACHE_METHOD','cacheApc');
//======================================
//define('CACHE_METHOD','memcached');
//======================================
# 使用单个文件存放,稳定,但无法控制文件大小
//define('CACHE_METHOD','cachedir');
/* 日志*/
//define('LOG_LEVEL',E_ERROR);
# 按日期分目录,每个ip 一个日志文件。扩展名是php 防止下载。
//define('LOG_FILE',HOME_DIR.'/logs/{date}/{ip}.php');

#log 文件头部放上exit()保证无法下载。
//define('LOG_HEAD_TEXT','<'.'?php exit()?'.'>');
//define('LOG_FORMAT',"{gmt}\t{request}\t{code}");
//======================================
//define('WITH_MEMCACHE',true);
//define('MEMCACHED_HOST','192.168.0.230');
//define('MEMCACHED_PORT','11211');
//======================================
#禁止运行安装
//define('DISABLE_SYS_CALL',1);
#使用数据库存放改动过的模板
//define('THEME_STORAGE','db');
运行时的默认值存放在core/include/defined.php 中。
2. 业务数据:存放在系统的配置数据库里的
存放在数据库中,必须在系统可以正常运行时才能修改,大多是网店经营性质的配
置。这个机制有点像windows 的注册表,或者firefox 地址栏里输入: about:config
看到的东西。
ShopEx 通过参数表的方式存放业务环节中的常用参数。参数表采用文件与数据库
双重定义方式。默认值位于文件/core/include/setting.php,实际设定值通过
sdb_settings 表进行存储。对于每一个参数,存在一个存取用的key,对应它的有
类型、值、说明。类型分为:
define('SET_T_STR',0); //text
define('SET_T_INT',1); //number
define('SET

_T_ENUM',2); //select
define('SET_T_BOOL',3); //bool
define('SET_T_TXT',4); //textarea
define('SET_T_FILE',5); //file
系统方法$system->getConf() 负责取值,$system->setConf()保存值。系统
在取值时,首先在数据库中查找对应的key,如果未找到,则在默认值文件中进行
查找。
4.4. 模板系统

smarty 风格的模板引擎。语法参照smarty 的文档:https://www.doczj.com/doc/1417982570.html,/manual/en/
前后台的公用的模板插件路径为:
core/include/smartyplugins
后台的专有模板插件路径为:
core/admin/smartyplugin
特别之处:
在4.8.5 之后,我们重新编写了模板编译引擎。沿用一直以来的smarty 语法。我们做了以下
增强:
. 重写的正则表达式,编译时匹配效率更高
. 更严谨的字符串变量里的变量,避免语法模糊造成错误。
. 支持变量预绑定,我们把在很长时间内不变的变量,可以通过语法通知模板系统。使
其直接输出目标值,而不是php 代码。
. if 等条件语句,会自动判断条件,如果其中的条件不包括可变变量(非预绑定的变量)

则预先做条件判断。这几乎减少了一倍的目标代码量。
. 支持编译型的modifier 插件
. 重新设计的简单实用的errorhandler
. include 语法支持文件名后面加井号"#",来为同一模板生成不同的编译缓存
. 大量目标php 代码的优化
. 更改缓存文件名为md5hash 格式,使其在将来有希望归并到secache 体系。
这个优化的原则就是把更多的插件改为编译时插件然后通过精心设计的缓存控制系统去自动更
新模板缓存.
这是一个完全重写的引擎。我们把这个类smarty 的模板引擎取名为tramsy。实际检测,同样
的代码,我们的tramsy 生成的代码运行效率提高了50%以上。而如果用变量预绑定的方式传
入变量,则可以使目标代码减少50%,运行效率提高80%。
我们会在将来的版本中更多的利用变量预绑定机制来提高性能。
4.5. 缓存机制
缓存系统的设计,直接决定了系统的运行效能。系统的无数bug,也可能由缓存而生。我们在
此一起抽丝剥茧的看一下shopex 的缓存设计方案。
shopex 有3 层缓存结构,按顺序触发:

层次缓存名称存放位置
1 基于http 协议的浏览器缓存用户浏览器本地
2 前台全页缓存home/cache/cachedata.php
3 模板缓存home/cache/front_tmpl
4.5.1. 基于http 协议的浏览器缓存
浏览器都很聪明,他们懒的把同样的数据从网上下载两次。
如果用户访问过这个网站,那么我们会在系统的header 头里放一个Etag 标签。这个Etag 数
据是和页面的内容相关的,如果页面内容变了,Etag 也会不同。
第一次访问一个shopex 页面,服务器的返回情况, 使用抓包工具可以看到http 头如下所示。
HTTP/1.1 200 OK
Date: Tue, 24 Nov 20

相关主题
文本预览
相关文档 最新文档