YAOTU INSIGHTS

Discuz精仿闲鱼二手交易插件开发实战

Discuz精仿闲鱼二手交易插件开发实战
简介本资源是一套专为Discuz!站长与PHP开发者设计的二手交易功能扩展插件精准复刻闲鱼式商品发布体验解决传统论坛缺乏轻量级C2C交易模块的痛点适用于希望提升用户活跃度与社区粘性的中小型Discuz站点。压缩包共34个文件含19个核心PHP业务逻辑文件、4个XML插件配置与语言包、4个HTML前端模板页、3个GIF界面元素及字体、CSS与图片等配套资源整体仅62KB结构精简、部署轻量。已有777人学习下载表明其在实战场景中具备较高参考价值。读者可直接获取完整可运行的插件源码体系涵盖后台管理入口、商品发布/展示/搜索全流程、多编码语言包GBK/UTF8/BIG5、AJAX交互逻辑、安全防护基础防SQL注入、以及安装/卸载/升级全生命周期脚本便于快速集成、二次开发或逆向学习Discuz插件架构。1. Discuz精仿闲鱼二手发布不是套壳是把「闲鱼式交易流」塞进DZ老内核里你手头有个跑着Discuz! X3.4的老论坛用户活跃但转化低想加二手交易功能又不想推倒重来、重写整套用户体系和权限逻辑。这时候“Discuz精仿闲鱼二手发布”就不是一句营销话术——它是一套在DZ原生架构上复刻闲鱼核心交互链路的工程方案从发布页的多图上传自定义属性弹窗到列表页的卡片式瀑布流价格/距离/新旧度三排序维度再到详情页的“我想要”按钮私聊入口信用标识全部用DZ插件机制实现不改核心文件、不引入Vue/React前端框架、不依赖外部API网关。它解决的不是“有没有二手功能”而是“如何让老论坛用户零学习成本地完成从浏览→询价→成交的闭环”。适合中小社区运营者、本地生活类站点站长、教育机构内部资源流转平台搭建者——你不需要懂Laravel或SpringBoot但得会看DZ的source/plugin/目录结构、能改模板变量、敢动common.inc.php里的钩子调用顺序。2. 拆解“精仿”二字为什么必须绕开DZ默认主题用独立插件定制模板重构交易流2.1 “精仿”的本质是业务流重定向而非UI像素级复制Discuz默认的“分类信息”模块如portal或forum下的分类版块本质是单帖模型一个标题一段描述一张图几个自定义字段。而闲鱼的核心是商品实体模型主图轮播、规格参数表、卖家信用快照、动态库存状态、议价历史折叠区。强行在DZ原有帖子里塞这些字段会导致数据库冗余每个二手帖要额外建10个custom_字段、模板逻辑爆炸viewthread.htm里嵌套5层if判断、后台管理混乱管理员要在“帖子管理”和“分类信息管理”两个入口来回切换。所以“精仿”的第一步是用DZ插件机制注册全新入口路由把/plugin.php?idershou:publish作为发布起点所有数据存入独立表pre_plugin_ershou_goods彻底隔离于pre_forum_post。2.2 插件结构设计三层分离确保可维护性我们采用标准DZ插件三层结构但关键路径做了强化install.php不只是建表还注入全局钩子。例如在global_footer钩子里插入JS检测当前页面是否为商品详情页动态加载地图SDK用于显示“距您X公里”admincp.php提供独立后台含商品审核队列带“一键通过/驳回/下架”快捷操作、敏感词过滤开关针对二手交易高频违规词如“代充”“外挂”、发货状态机配置待发货→已发货→已签收→退款中template/目录不复用DZ默认模板而是新建ershou_list.htm瀑布流、ershou_detail.htm详情页、ershou_publish.htm发布页每个模板只加载必需CSS/JS避免与论坛主站样式冲突。提示DZ插件模板无法直接使用{eval}执行PHP逻辑所有动态数据必须通过插件的discuz_application::instance()-init()阶段预加载到$GLOBALS[ershou_data]再在模板中用{$_G[ershou_data][xxx]}调用。这是新手最容易卡住的点——别试图在.htm里写?php echo ... ?DZ会直接报错。2.3 数据库设计用“扩展表状态机”替代DZ原生字段硬编码pre_plugin_ershou_goods表结构精简但覆盖全链路字段名类型说明示例值gidint(10) PK AI商品ID12873uidint(10)发布者UID关联pre_common_member4521titlevarchar(100)商品标题带防刷限长iPhone 13 Pro 256G 银色 99新pricedecimal(10,2)标价支持小数单位元4299.00statustinyint(1)状态机0草稿/1待审核/2已上架/3已售罄/4已下架/5已删除2locationvarchar(50)城市区域结构化存储非纯文本上海:浦东新区:张江picstextJSON数组存储图片URL兼容DZ附件系统[http://xxx/1.jpg,http://xxx/2.jpg]attrstext序列化属性键值对如“成色”:“99新”,“购买渠道”:“官网”a:2:{s:6:成色;s:6:99新;s:12:购买渠道;s:6:官网;}这个设计规避了DZ原生customfield的三大缺陷字段不可搜索、无法建立联合索引、修改字段需全量重建缓存。比如按“成色95新且价格3000”筛选直接WHERE attrs LIKE %95新% AND price 3000即可无需触发DZ的customfield慢查询。3. 发布页实现如何用DZ原生组件搭出闲鱼式多步骤表单3.1 多图上传复用DZ附件系统但改造上传逻辑Discuz自带的swfupload组件只支持单图而闲鱼要求至少3张图主图细节图实拍图。解决方案是拦截DZ的ajax.php?actionswfupload请求在服务端做多图合并处理// source/plugin/ershou/ajax.php if($_GET[action] swfupload) { $uid $_G[uid]; $file $_FILES[Filedata]; // 1. 验证文件类型和大小限制JPG/PNG单图≤5MB if(!in_array(strtolower(pathinfo($file[name], PATHINFO_EXTENSION)), [jpg,jpeg,png])) { exit({error:仅支持JPG/PNG格式}); } if($file[size] 5242880) { exit({error:单图不能超过5MB}); } // 2. 调用DZ原生附件上传函数但指定自定义目录 $attach C::t(forum_attachment_n)-insert(array( uid $uid, dateline TIMESTAMP, filename $file[name], filesize $file[size], attachment ershou/.date(Ym)./.random(8)..jpg, // 自定义路径 isimage 1, width 0, height 0, thumb 0, remote 0 ), true); // 3. 返回JSON格式URL供前端JS拼接 $url getforumimg($attach[aid], 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,......逻辑说明C::t(forum_attachment_n)是DZ 3.4的附件表操作类insert()返回新记录IDaidgetforumimg($aid, 1, ...)生成带防盗链参数的图片URL1表示启用缩略图前端JS收到URL后存入数组window.ershou_pics []点击“发布”时作为JSON字符串提交给插件主逻辑。3.2 自定义属性弹窗用DZ的dialog组件实现动态字段组闲鱼发布页的“手机成色”“是否带票”“配件齐全度”等字段不是固定列表而是根据商品类目动态加载。我们复用DZ后台的dialog弹窗机制// 在 ershou_publish.htm 中 function showAttrDialog() { var dialog $.dialog({ title: 填写商品属性, content: div idattr-content/div, width: 600, height: 400, ok: function() { // 收集所有input值序列化为JSON var attrs {}; $(#attr-content input).each(function(){ attrs[$(this).data(key)] $(this).val(); }); $(#attrs-json).val(JSON.stringify(attrs)); // 存入隐藏域 } }); // 异步加载对应类目的属性模板 $.get(plugin.php?idershou:attrcid$(#category).val(), function(html){ $(#attr-content).html(html); }); }后端source/plugin/ershou/function/attr.php根据cid类目ID返回HTML片段例如手机类目返回div classattr-item label成色/label select namechengse>// source/plugin/ershou/list.php $lastid intval($_GET[lastid]); $limit min(20, intval($_GET[limit])); // 最大20条防恶意limit10000 $where status2; // 只查已上架 if($_GET[city]) $where . AND location LIKE {$_GET[city]}%; if($_GET[price_min]) $where . AND price {$_GET[price_min]}; if($lastid) { $where . AND gid {$lastid}; // 注意是 不是 } $query DB::query(SELECT * FROM .DB::table(plugin_ershou_goods). WHERE {$where} ORDER BY gid DESC LIMIT {$limit}); $goods array(); while($row DB::fetch($query)) { $goods[] $row; } // 返回JSON供前端追加DOM echo json_encode(array( list $goods, next_lastid !empty($goods) ? end($goods)[gid] : 0, has_more count($goods) $limit ));逻辑说明gid {$lastid}确保结果严格递减避免ID重复或跳过next_lastid是本页最后一条的gid前端下次请求时填入lastidhas_more布尔值控制是否显示“加载更多”按钮。4.2 详情页性能优化用DZ的memory缓存静态化预生成闲鱼详情页需实时显示“距您X公里”依赖用户GPS、“卖家在线状态”需查pre_common_session但大部分内容标题、图片、描述是静态的。我们采用混合缓存策略静态部分商品基础信息title,pics,attrs存入memory缓存Key为ershou_detail_{$gid}TTL3600秒动态部分用户位置、在线状态、最新询价数在模板中用AJAX单独请求plugin.php?idershou:detail_dynamicgid12873防爬补充对/plugin.php?idershou:detailgidxxx路径增加Referer白名单校验只允许来自本站域名并在Nginx层配置limit_req zoneershou_detail burst5 nodelay。注意DZ的memory缓存依赖config/config_memory.php配置若未启用Memcached或Redis会自动降级为文件缓存data/cache/目录此时需确保data/可写且磁盘充足。5. 避坑指南在DZ里做闲鱼仿站这5个坑让我重装了3次环境5.1 现象发布成功后商品不显示在列表页后台审核队列也为空原因插件安装时未正确执行install.php中的数据库建表语句或pre_plugin_ershou_goods表的ENGINE被设为MyISAMDZ 3.4默认用InnoDB。MyISAM不支持事务当发布流程中某步失败如图片上传超时会导致商品记录插入但附件未关联状态机卡在“待审核”而审核队列查询条件WHERE status1却因表引擎问题无法命中。解决登录phpMyAdmin选中pre_plugin_ershou_goods表 → “操作” → “存储引擎” → 改为InnoDB再运行source/plugin/ershou/install.php中的建表SQL手动补全。5.2 现象多图上传后详情页只显示第一张图其余图URL为空原因DZ附件系统在swfupload回调中对attachment字段的路径拼接有长度限制默认255字符而自定义路径ershou/202405/xxxxxx.jpg加上域名后可能超长导致C::t(forum_attachment_n)-insert()返回空aid。解决修改source/class/table/table_forum_attachment_n.php第120行左右将attachment字段类型从varchar(255)改为text并同步更新数据库表结构。5.3 现象按“距离排序”时列表页报错Unknown column distance in order clause原因“距您X公里”是前端JS计算的伪字段不能直接用于SQLORDER BY。很多开发者误在list.php里写ORDER BY distance ASC而distance根本不存在于数据库。解决距离排序必须由前端实现——先用ORDER BY gid DESC拉取20条再用JS遍历每条记录的location字段如“上海:浦东新区:张江”调用百度地图API的getDistance()计算直线距离最后用Array.sort()重排DOM顺序。5.4 现象后台审核通过后商品状态变为2已上架但列表页仍不显示原因DZ的discuz_application::instance()-init()阶段插件钩子global_footer被其他插件如SEO优化插件提前终止导致商品状态变更后的缓存刷新逻辑未执行。解决在source/plugin/ershou/index.php顶部添加define(IN_DISCUZ, true);并在common.inc.php末尾显式调用require_once DISCUZ_ROOT../source/plugin/ershou/cache_refresh.php;绕过钩子依赖。5.5 现象手机端访问详情页图片轮播失效控制台报Uncaught ReferenceError: swiper is not defined原因DZ移动端模板template/default/touch/未引入Swiper JS而插件模板ershou_detail.htm直接写了new Swiper(...)。PC端正常是因为template/default/common/header.htm已加载Swiper但Touch模板走的是另一套资源链。解决在ershou_detail.htm顶部添加条件判断!--{if $_G[mobile]}-- script src{STATICURL}js/swiper.min.js/script !--{else}-- script src{STATICURL}js/swiper-bundle.min.js/script !--{/if}--并确保static/js/目录下存放了适配移动端的精简版Swiper。6. 进阶技巧用DZ的cron任务微信模板消息把二手交易做成闭环运营6.1 每日自动清理超期草稿防止数据库垃圾堆积Discuz的定时任务cron是处理后台无人值守任务的黄金方案。我们创建source/plugin/ershou/cron/clean_drafts.php?php // 删除7天前的草稿status0 DB::delete(plugin_ershou_goods, status0 AND dateline .(TIMESTAMP - 604800)); // 同时清理对应附件需关联pre_forum_attachment表 $draft_gids DB::fetch_all(SELECT gid FROM .DB::table(plugin_ershou_goods). WHERE status0 AND dateline .(TIMESTAMP - 604800)); foreach($draft_gids as $g) { $attach_ids DB::fetch_all(SELECT aid FROM .DB::table(forum_attachment). WHERE tid{$g[gid]} AND pid0); foreach($attach_ids as $a) { C::t(forum_attachment)-delete($a[aid]); // 调用DZ原生删除函数自动清理文件 } } ?在DZ后台 → 管理中心 → 工具 → 定时任务 → 添加新任务名称清理二手草稿脚本文件plugin/ershou/cron/clean_drafts.php执行周期每天凌晨2点0 2 * * *提示DZ的cron任务依赖服务器crontab若你的虚拟主机不支持可改用“伪定时”——在global_footer钩子里检查$_G[timestamp] % 86400 300每天前5分钟执行但精度较低。6.2 微信模板消息通知让“有人想要您的商品”真正触达用户闲鱼的核心体验是“即时反馈”。我们利用DZ的uc_apiUCenter打通微信服务号模板ID触发场景关键参数AT001商品被“我想要”thing1: 商品标题,number2: 价格,phrase3: 卖家昵称AT002商品已售罄thing1: 商品标题,date2: 下架时间实现逻辑当用户点击“我想要”按钮前端提交到plugin.php?idershou:wantgid12873后端除了插入pre_plugin_ershou_want记录还调用// 获取卖家的微信OpenID需提前在用户中心绑定 $seller C::t(common_member)-fetch($_G[uid]); if($seller[wx_openid]) { $template_data array( first 您发布的商品有人感兴趣啦, keyword1 array(value $goods[title], color #173177), keyword2 array(value ¥.$goods[price], color #173177), keyword3 array(value $buyer[username], color #173177), remark 快去私聊了解详情吧~ ); // 调用微信模板消息API需提前配置AppID/AppSecret $url https://api.weixin.qq.com/cgi-bin/message/template/send?access_token.$access_token; $res dfsockopen($url, 0, json_encode(array( touser $seller[wx_openid], template_id AT001, data $template_data ))); }参数说明dfsockopen()是DZ封装的cURL函数比原生curl_exec()更兼容老版本PHPaccess_token需通过https://api.weixin.qq.com/cgi-bin/token接口获取建议缓存到data/cache/wx_access_token.php模板消息需在微信公众平台后台申请且用户必须关注服务号并完成绑定。6.3 一个血泪经验永远在install.php里写回滚脚本上线前我自信满满地删掉了uninstall.php结果测试时发现商品属性弹窗的CSS冲突了论坛首页。紧急修复需要手动删除pre_plugin_ershou_goods表清空pre_common_plugin中ershou相关记录删除source/plugin/ershou/整个目录清除data/cache/下所有cache_ershou_*文件。现在我的每个插件都带uninstall.php开头就是// source/plugin/ershou/uninstall.php DB::query(DROP TABLE IF EXISTS .DB::table(plugin_ershou_goods)); DB::query(DELETE FROM .DB::table(common_plugin). WHERE identifierershou); // 删除模板缓存 unlink(DISCUZ_ROOT../data/cache/cache_ershou_*); // 删除附件目录谨慎 // rmdir(DISCUZ_ROOT../data/attachment/ershou/);——哪怕只是开发阶段也要把卸载当成必经流程。毕竟线上环境没有后悔药只有备份和回滚。希望帮到你。本文还有配套的精品资源点击获取