简介草莓CDMS是一套基于ThinkPHP5.1、EasyWechat4.0、Swoole与Redis开发的原创内容分销系统面向希望搭建微信小说平台、开展内容分销业务的开发者与运营团队。系统支持微信开放平台扫码授权与公众平台手动对接两种模式并设置管理员、版权商、合作商、渠道商、代理商五种角色可分别管理资源上传、渠道拓展与数据统计。内容层面覆盖小说、漫画、有声、影视、视频等版块各版块可独立开关统计模块提供订单、用户、下线、内容、扣量、平台及版权商等多维数据便于业务分析。资源包共5个文件包含zip框架源码、gz压缩包、html使用说明、so扩展文件与txt安装必读整体约37.86MB结构紧凑便于部署与二次开发。目前已有147人学习下载适合需要快速搭建内容分销平台、研究多角色权限与智能推送实现的技术人员参考。1. 草莓CDMS内容分销系统到底解决什么问题微信小说平台从0到1的架构判断如果你正在琢磨用草莓CDMS搭一套微信小说平台系统v1.0大概率已经遇到过这个局面手里有内容源但不知道怎么把它变成可分销、可追踪、可结算的生意。草莓CDMS独创的内容分销系统核心不是帮你写小说而是把「章节内容 → 分销渠道 → 用户付费 → 佣金结算」这条链路串起来。它适合三类人手里有小说版权想快速铺渠道的运营方、想靠分销赚佣金的流量主、以及需要一套可二次开发的微信小说平台系统的技术团队。v1.0这个版本号意味着它是个起点版本功能骨架在但细节需要你自己补。我见过太多人一上来就纠结UI好不好看结果分销链路跑不通用户付了钱佣金算错那才是真的翻车。这一章先把架构判断讲清楚后面再动手。2. 草莓CDMS内容分销系统的核心链路拆解从章节入库到佣金到账2.1 内容分销系统的最小闭环由哪四个模块组成一套能跑起来的内容分销系统不管界面多花哨底层一定是四个模块咬合内容管理、分销关系、订单支付、佣金结算。草莓CDMS的独创性在于它把分销关系绑定在了「章节」这个粒度上而不是常见的「整本小说」粒度。这意味着同一个用户通过不同渠道点进同一本书的不同章节可以归属到不同的分销员。这个设计对微信小说平台系统来说很关键因为微信生态里的分享往往是单章分享用户点进来直接看某一章而不是从封面开始。内容管理模块负责把小说元数据、章节正文、章节顺序、免费/付费标记存好。分销关系模块维护「谁分享了哪一章给谁」的映射通常用一张带过期时间的绑定表来实现。订单支付模块对接微信支付生成预支付单、处理回调、标记订单状态。佣金结算模块在订单支付成功后根据分销关系计算佣金并写入待结算流水。这四个模块的数据库表设计如果一开始没做好后面改起来就是血泪经验。我一般会建议在v1.0阶段把分销关系表设计成三列核心distributor_id、chapter_id、user_id再加一个bind_time和expire_time。用户首次通过某分销员的链接进入某章节时写入绑定后续该用户所有付费行为都归这个分销员。注意这里有个坑如果用户先通过A分销员看了第1章又通过B分销员看了第2章到底算谁的草莓CDMS的常见做法是「首触归因」即第一次绑定生效后续不覆盖。这个规则要在代码里写死否则结算时扯皮。2.2 微信小说平台系统的技术选型为什么我推荐PHPMySQL起步v1.0阶段最怕过度设计。我见过有人上来就微服务、消息队列、分库分表结果三个月没上线。微信小说平台系统的特点是读多写少、并发集中在章节阅读和支付回调用PHPMySQL完全扛得住初期量级。草莓CDMS本身也是这个技术栈的产物二次开发成本低招人也好招。具体选型上Web框架用ThinkPHP或Laravel都行前者国内资料多后者工程化更好。数据库MySQL 5.7字符集用utf8mb4因为小说内容里可能有生僻字和emoji。缓存用Redis主要缓存章节正文和分销绑定关系减少数据库压力。队列在v1.0可以先用Redis的list模拟处理支付回调后的佣金计算避免回调超时。这里给一个分销绑定关系的建表语句直接抄CREATE TABLE distributor_bind ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, distributor_id int(11) NOT NULL COMMENT 分销员ID, chapter_id int(11) NOT NULL COMMENT 章节ID, user_id int(11) NOT NULL COMMENT 被绑定的用户ID, bind_time int(11) NOT NULL COMMENT 绑定时间戳, expire_time int(11) NOT NULL COMMENT 过期时间戳一般7天, order_id int(11) DEFAULT NULL COMMENT 首次产生订单的ID, PRIMARY KEY (id), UNIQUE KEY uk_user_chapter (user_id,chapter_id), KEY idx_distributor (distributor_id), KEY idx_expire (expire_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT分销绑定关系表;逻辑说明uk_user_chapter唯一索引保证同一用户对同一章节只绑定一次首触归因靠这个索引实现。expire_time用于清理过期绑定一般设7天因为微信场景下用户决策周期短。order_id字段在首次付费时回填方便对账。参数上distributor_id和chapter_id的类型要根据你的实际量级选初期int够用量大了再改bigint。2.3 章节内容入库与付费标记的实操步骤内容入库是分销的起点。草莓CDMS的内容分销系统支持批量导入但v1.0的导入模板往往比较简陋我一般会自己写个脚本处理。步骤是先把小说元数据书名、作者、封面、简介插入novel表拿到novel_id再按章节顺序插入chapter表每章标记is_free1免费0付费和price章节价格单位分。import pymysql import re # 连接数据库 conn pymysql.connect(host127.0.0.1, userroot, passwordyourpass, dbnovel_platform, charsetutf8mb4) cursor conn.cursor() # 假设小说文件按章节分割每章以第X章开头 with open(novel.txt, r, encodingutf-8) as f: content f.read() # 按章节标题分割 chapters re.split(r(第[零一二三四五六七八九十百千\d]章), content) # chapters[0]是前言之后成对出现标题和正文 novel_id 1 # 假设已插入小说元数据并拿到ID sort_order 0 for i in range(1, len(chapters), 2): title chapters[i].strip() body chapters[i1].strip() if i1 len(chapters) else sort_order 1 # 前3章免费之后付费单价10分 is_free 1 if sort_order 3 else 0 price 0 if is_free else 10 sql INSERT INTO chapter (novel_id, title, content, sort_order, is_free, price) VALUES (%s,%s,%s,%s,%s,%s) cursor.execute(sql, (novel_id, title, body, sort_order, is_free, price)) conn.commit() cursor.close() conn.close() print(f导入完成共{sort_order}章)逻辑说明正则第[零一二三四五六七八九十百千\d]章覆盖了常见的中文章节标题格式。sort_order保证章节顺序前3章免费是行业惯例用来吸引用户继续读。price单位是分避免浮点数精度问题。参数上novel_id需要你先插入小说元数据后获取实际项目中可以用cursor.lastrowid。如果章节标题格式不统一建议先人工清洗一遍否则分割会乱。3. 分销关系绑定与佣金结算的代码实现把玄学变成可对账的流水3.1 用户进入章节时的绑定逻辑与参数设计用户通过分销链接进入章节时URL里通常带distributor_id和chapter_id。后端要做的事检查该用户对该章节是否已有绑定没有则写入有则忽略。同时要检查绑定是否过期过期则重新绑定。这里的关键参数是过期时间我一般设7天太短分销员没动力太长归因混乱。// ThinkPHP示例用户进入章节页 public function chapter($chapterId) { $distributorId input(distributor_id, 0, intval); $userId session(user_id); // 假设已登录 if (!$userId) { // 未登录用户先引导登录登录后回跳 session(redirect_url, request()-url()); return redirect(/login); } if ($distributorId 0) { $bind Db::name(distributor_bind) -where(user_id, $userId) -where(chapter_id, $chapterId) -find(); $now time(); if (!$bind) { Db::name(distributor_bind)-insert([ distributor_id $distributorId, chapter_id $chapterId, user_id $userId, bind_time $now, expire_time $now 7 * 86400, ]); } elseif ($bind[expire_time] $now) { // 过期则更新绑定 Db::name(distributor_bind)-where(id, $bind[id])-update([ distributor_id $distributorId, bind_time $now, expire_time $now 7 * 86400, ]); } } // 查询章节内容返回 $chapter Db::name(chapter)-where(id, $chapterId)-find(); return view(chapter, [chapter $chapter]); }逻辑说明先判断用户是否登录未登录则记录回跳地址。绑定检查用user_id chapter_id查唯一记录不存在则插入存在但过期则更新。注意这里没有覆盖未过期的绑定符合首触归因。参数上7 * 86400是7天秒数你可以改成配置项。distributor_id为0表示自然流量不绑定。3.2 支付回调后佣金计算的幂等处理支付回调是分销系统最容易翻车的地方。微信支付回调可能重复推送如果佣金计算不幂等分销员会拿到多倍佣金。草莓CDMS的常见做法是在订单表加commission_status字段0未计算、1已计算回调时用UPDATE ... WHERE commission_status0来保证只算一次。// 微信支付回调处理 public function notify() { $xml file_get_contents(php://input); $data xml_to_array($xml); // 验签省略实际项目必须验签 $orderNo $data[out_trade_no]; $order Db::name(order)-where(order_no, $orderNo)-find(); if (!$order || $order[status] 1) { return xml_return(SUCCESS); // 已处理直接返回成功 } Db::startTrans(); try { // 更新订单状态条件status0保证幂等 $affected Db::name(order)-where(order_no, $orderNo)-where(status, 0)-update([ status 1, pay_time time(), transaction_id $data[transaction_id], ]); if ($affected) { // 查绑定关系 $bind Db::name(distributor_bind) -where(user_id, $order[user_id]) -where(chapter_id, $order[chapter_id]) -find(); if ($bind $bind[expire_time] time()) { // 佣金比例10% $commission intval($order[amount] * 0.1); Db::name(commission_log)-insert([ distributor_id $bind[distributor_id], order_id $order[id], amount $commission, status 0, // 待结算 create_time time(), ]); } } Db::commit(); } catch (\Exception $e) { Db::rollback(); return xml_return(FAIL); } return xml_return(SUCCESS); }逻辑说明where(status, 0)是幂等关键只有未处理的订单才会被更新重复回调时$affected为0跳过佣金计算。佣金比例10%是示例实际按你的分销规则来。commission_log的status0表示待结算后续提现时改为1。参数上amount单位是分佣金计算用intval取整避免小数。3.3 佣金结算与提现的流水设计佣金不能直接加到分销员余额就完事必须有流水可查。我一般设计三张表commission_log记录每笔佣金来源withdraw_log记录提现申请distributor表里放balance和frozen_balance。用户申请提现时从balance扣减加到frozen_balance审核通过后扣减frozen_balance驳回则退回balance。-- 分销员表增加余额字段 ALTER TABLE distributor ADD balance int(11) NOT NULL DEFAULT 0 COMMENT 可提现余额单位分; ALTER TABLE distributor ADD frozen_balance int(11) NOT NULL DEFAULT 0 COMMENT 冻结余额单位分; -- 提现申请表 CREATE TABLE withdraw_log ( id int(11) NOT NULL AUTO_INCREMENT, distributor_id int(11) NOT NULL, amount int(11) NOT NULL COMMENT 提现金额单位分, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0待审核1已通过2已驳回, create_time int(11) NOT NULL, audit_time int(11) DEFAULT NULL, PRIMARY KEY (id), KEY idx_distributor (distributor_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明balance和frozen_balance分开避免用户提现期间还能继续消费余额。提现审核通过后实际打款走微信企业付款这里不展开。参数上提现门槛一般设1000分10元太低手续费不划算。4. 草莓CDMS微信小说平台系统v1.0的避坑与排查清单4.1 分销绑定不生效现象、原因与解决现象用户通过分销链接进入章节但后台查不到绑定记录。原因通常是URL参数丢失或session未登录。微信内打开链接时如果经过多次跳转distributor_id可能被截断。解决在入口处用$_GET直接取参不要依赖路由解析未登录用户先存cookie再跳登录登录后从cookie恢复参数。4.2 佣金算多了重复回调与并发导致的双倍佣金现象同一订单产生了两条佣金流水。原因微信回调重复推送且代码没有幂等控制。解决如3.2节所示用where(status, 0)更新订单只有影响行数为1时才计算佣金。另外高并发下建议给订单号加Redis锁锁过期时间设5秒。4.3 章节内容乱码编码与特殊字符处理现象导入小说后部分章节显示乱码或截断。原因源文件编码不是UTF-8或内容里有\0等特殊字符。解决导入前用iconv转码入库前用str_replace过滤\0。MySQL连接字符集必须设utf8mb4否则emoji会报错。4.4 支付回调超时队列与重试机制缺失现象用户支付成功但订单状态未变佣金未生成。原因回调处理逻辑太重超过微信5秒超时。解决回调里只更新订单状态和写队列佣金计算放队列异步处理。v1.0可以用Redis list模拟队列一个cron每分钟消费一次。4.5 分销员提现后余额对不上冻结与解冻逻辑错误现象提现驳回后余额没退回。原因驳回逻辑只改了withdraw_log状态没操作distributor表。解决提现申请时balance - amount; frozen_balance amount通过时frozen_balance - amount驳回时frozen_balance - amount; balance amount。三步必须在一个事务里。5. 进阶技巧用对账脚本验证分销系统是否跑通v1.0上线后别急着推广先跑一周对账。我一般写个脚本每天凌晨核对三件事订单总额是否等于微信账单、佣金总额是否等于订单总额乘以比例、分销员余额是否等于佣金流水减去提现流水。任何一项对不上立刻排查。import pymysql from datetime import date, timedelta conn pymysql.connect(host127.0.0.1, userroot, passwordyourpass, dbnovel_platform, charsetutf8mb4) cursor conn.cursor() yesterday date.today() - timedelta(days1) start int(yesterday.strftime(%s)) end start 86400 # 1. 订单总额 cursor.execute(SELECT SUM(amount) FROM order WHERE status1 AND pay_time%s AND pay_time%s, (start, end)) order_total cursor.fetchone()[0] or 0 # 2. 佣金总额 cursor.execute(SELECT SUM(amount) FROM commission_log WHERE create_time%s AND create_time%s, (start, end)) commission_total cursor.fetchone()[0] or 0 # 3. 分销员余额总和 cursor.execute(SELECT SUM(balance) FROM distributor) balance_total cursor.fetchone()[0] or 0 # 4. 佣金流水减去提现流水 cursor.execute(SELECT SUM(amount) FROM commission_log WHERE status1) commission_paid cursor.fetchone()[0] or 0 cursor.execute(SELECT SUM(amount) FROM withdraw_log WHERE status1) withdraw_paid cursor.fetchone()[0] or 0 print(f订单总额: {order_total}分) print(f佣金总额: {commission_total}分比例: {commission_total/order_total if order_total else 0:.2%}) print(f分销员余额总和: {balance_total}分) print(f应余: {commission_paid - withdraw_paid}分) if balance_total ! commission_paid - withdraw_paid: print(警告余额对不上请检查冻结逻辑) else: print(余额核对通过) cursor.close() conn.close()逻辑说明脚本按天统计start和end是时间戳。订单总额和佣金总额的比例应该等于你设置的分销比例如果偏差超过1%说明有订单没算佣金或有重复计算。余额核对是最后一道防线balance_total必须等于已结算佣金减去已提现。参数上commission_log的status1表示已结算实际项目中你可能用其他状态值按你的设计改。这个脚本我一般挂cron每天跑输出发到工作群。有一次就是靠它发现某分销员的绑定关系被覆盖了导致佣金算给了别人及时追回。对账脚本是分销系统的后悔药别省。希望帮到你。本文还有配套的精品资源点击获取