小额借贷系统源码搭建:还款计划、推广下载页与本地部署实践 📅 发布时间:2026/9/15 5:40:46 👁 浏览次数: 简介新版小额借贷贷款系统源码是一套经过二开修复的PHP借贷平台程序定位面向需要快速搭建线上小额贷款、信用分期业务的开发者与站长。系统经过多轮版本迭代页面更精致核心流程更稳定并新增了推广APP下载页面方便产品推广同时前台用户不可再自行修改提现银行卡号只能由后台修改降低了资金操作风险。资源共2507个文件约67MB以PHP业务代码、HTML/CSS/JS交互页面、GIF/PNG界面素材和LOG运行日志为主压缩包内还包含完整搭建教程与数据库文件。功能方面涵盖八种订单状态、短信审核通知默认短信宝、在线收款二维码、会员禁登、在线客服、邀请码结算、信用评分、收银台及推广下载页等模块几乎覆盖借贷平台常见业务场景。目前已有619人学习下载适合具备PHPMySQL基础、希望基于成熟源码进行二次开发或研究借贷系统架构的技术人员。1. 小额借贷系统源码怎么选先分清新版和二次开发版小额借贷系统源码在资源站和技术社区里流通量不小但能在半小时内一装就跑的版本不到一半。常见情况是包里藏着旧版ThinkPHP、数据库结构和PHP 7.4对不上、放款回调写死本机地址用户密码还是MD5裸存。新版和旧版最大的差异不在界面而在两处推广APP下载页面做成了独立分流模块Android、iOS、渠道码、下载埋点分开处理放款还款从单表堆字段改成订单加还款计划两张表联动对账和逾期计算都按这个结构走。下面围绕这三点展开怎么判断源码新旧、核心表结构和还款理算怎么写、推广下载页怎么落地以及最后怎么在本地一次跑通。2. 核心业务表与还款理算小额借贷系统的数据底座拿到一套源码先别急着配数据库先看它的目录和表结构基本就能判断这套代码是不是新版以及值不值得继续往下搭。2.1 新版源码的模块划分与识别方法常见的源码包结构分为两种一种是单一入口的传统结构application、public、runtime目录平铺另一种是 FastAdmin 风格在application上加了一层admin、api、index的模块区分。新版小额借贷系统大多沿用第二种因为运营后台、用户端API、推广页三块逻辑分开改动时不会互相牵连。判断是不是新版我一般只看三个特征application/下有没有独立的download模块或promotion模块保底也要有一个download.php入口。旧版通常把下载页直接写成静态HTML没有UA判断。数据库里有没有channel_download或download_log这类表。没有这张表说明推广渠道根本没法统计也就谈不上“新增推广APP下载页面”。配置文件里是否引用了bcmath或bcmatch扩展。还款计划里等额本息的计算只要涉及幂运算和浮点就绕不开这个扩展。这三个特征同时命中基本可以认定是改过推广模块的新版。如果只命中前两个说明推广页是新加的但理算逻辑还是老的后面改起来要额外注意。2.2 四张核心表用户、订单、还款计划、下载埋点小额借贷系统不管怎么改版核心表逃不出这四张用户表、借款订单表、还款计划表、渠道下载表。下面这份SQL是精简过的版本覆盖了最关键的字段也保持了新版两表联动的结构。CREATE TABLE users ( id int(11) unsigned NOT NULL AUTO_INCREMENT, phone varchar(20) NOT NULL COMMENT 手机号, password_hash varchar(100) NOT NULL COMMENT 密码哈希, channel varchar(32) NOT NULL DEFAULT direct COMMENT 注册渠道码, register_time datetime NOT NULL COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE loan_order ( id int(11) unsigned NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号业务唯一, user_id int(11) unsigned NOT NULL, principal decimal(10,2) NOT NULL COMMENT 放款本金, term tinyint(4) NOT NULL COMMENT 期数, annual_rate decimal(6,3) NOT NULL COMMENT 年化利率百分比, repay_method tinyint(4) NOT NULL DEFAULT 1 COMMENT 1等额本息 2先息后本, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0待初审 1待放款 2还款中 3已结清 4逾期, create_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT借款订单表; CREATE TABLE repay_plan ( id int(11) unsigned NOT NULL AUTO_INCREMENT, order_id int(11) unsigned NOT NULL, period tinyint(4) NOT NULL COMMENT 第几期, due_date date NOT NULL COMMENT 应还日期, principal_part decimal(10,2) NOT NULL COMMENT 当期本金, interest_part decimal(10,2) NOT NULL COMMENT 当期利息, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0未还 1已还, paid_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_order (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT还款计划表; CREATE TABLE channel_download ( id int(11) unsigned NOT NULL AUTO_INCREMENT, channel varchar(32) NOT NULL COMMENT 渠道码, ua varchar(255) NOT NULL COMMENT 原始UA, ip varchar(45) NOT NULL COMMENT 客户端IP, create_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_channel_time (channel, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT推广下载埋点表;金额字段统一用decimal(10,2)不要用float否则小额贷款这种利率精度要求高的场景跑几个月对账就会出分位差。order_no必须建唯一索引放款回调、还款回调都是拿这个字段做幂等判断的。repay_plan里的status只表示“这一期还没还”用户的整体逾期状态由loan_order.status冗余维护查询列表时不用临时聚合。2.3 还款计划计算等额本息的PHP实现小额借贷常见的还款方式有两种等额本息和先息后本。先息后本逻辑简单每期只还利息最后一期还本金加当期利息两行SQL就能生成。等额本息麻烦在月供公式和最后一期的尾差修正代码如下。?php /** * 等额本息还款计划生成 * param float $principal 本金 * param float $annualRate 年化利率传百分比数值如 18 表示18% * param int $months 期数 * return array */ function calcEqualInstallment(float $principal, float $annualRate, int $months): array { $monthRate $annualRate / 12 / 100; // 月供公式P * r * (1r)^n / ((1r)^n - 1) $pow pow(1 $monthRate, $months); $monthly $principal * $monthRate * $pow / ($pow - 1); $list []; $balance $principal; for ($i 1; $i $months; $i) { $interest round($balance * $monthRate, 2); $principalPart round($monthly - $interest, 2); $balance round($balance - $principalPart, 2); $list[] [ period $i, principal $principalPart, interest $interest, balance $balance, ]; } // 最后一期修正尾差把余额里剩下的一分钱并入本金 $last count($list) - 1; if ($balance 0) { $list[$last][principal] round($list[$last][principal] $balance, 2); $list[$last][balance] 0.00; } return $list; }这里有两个地方容易出错。第一interest和principalPart不能先round再累加否则每期舍入误差会滚雪球到最后一期差出几毛钱正确做法是每期先算利息并单独保留最后统一修正尾差。第二balance到最后一期可能不是0因为月供反推本金时每一步都做了四舍五入所以修正逻辑必须放在循环外面只改最后一期。生产环境我建议顺手把money相关的计算改成用bcadd、bcmul处理虽然pow还是得用原生函数但加减乘除的精度损失能完全规避。2.4 订单状态机避免“已结清”和“逾期”打架订单状态处理不好最典型的问题是把“逾期”当成一个终态导致用户还完款后状态永远卡在逾期里。小额借贷系统的状态流转一般按下面的规则设计状态值状态含义触发时机0待初审用户提交借款申请1待放款初审通过等待资金方回调2还款中放款成功还款计划已生成3已结清全部期数正常还完4逾期中任一期末还且已过宽限期注意状态值4不是终态用户逾期后把欠款结清应该回到3如果逾期超过一定期限进入坏账才单独走一个关闭状态而不是把状态值改到4就完事。还款回调进来时只更新repay_plan对应期次的状态然后重新查一次所有期次是否都还完再决定要不要把loan_order.status改成3。这种“从下往上推算”的做法比“事件驱动直接改写订单状态”要稳得多。3. 推广APP下载页面新版分流模块的实现与埋点标题里提到的“新增推广APP下载页面”本质上是把原来一个静态的APK下载链接改造成动态分流入口。这一步做得好不好直接影响渠道投放的转化率和结算对账。3.1 下载页要解决的核心问题不是落地页是分流页很多运营会把推广下载页设计成一张充满产品卖点的落地页但技术实现上真正要紧的是两件事一是识别访问者设备给Android用户下发APK给iOS用户跳转App Store或签名渠道二是记录每个访问者来自哪个渠道这样用户注册时才能把渠道归因写进users.channel后续按渠道对账。旧版代码一般只放一个app.apk直链iOS用户点了没反应渠道统计只能翻Nginx访问日志。新版做法是引入一个download.php做UA判断同时把渠道码种到cookie里注册接口优先读这个cookie完成归因。这个页面的技术含量不高但和业务绑得很紧值得仔细写。3.2 UA识别与渠道码落地的分流实现下面是一份完整可用的download.php核心逻辑是白名单校验、埋点入库、按UA分流三步?php // 推广APP下载分流入口download.php // 渠道白名单避免任意参数写入统计表造成垃圾数据 $allowChannels [wx, douyin, xiaohongshu, direct]; $channel $_GET[from] ?? ($_COOKIE[dl_ch] ?? direct); if (!in_array($channel, $allowChannels, true)) { $channel direct; } // cookie有效期30天用户从下载到注册往往隔几小时甚至几天 setcookie(dl_ch, $channel, time() 86400 * 30, /); $ua strtolower($_SERVER[HTTP_USER_AGENT] ?? ); $ip $_SERVER[REMOTE_ADDR] ?? ; // 先写埋点再跳转防止并发下丢记录 $stmt $pdo-prepare( INSERT INTO channel_download (channel, ua, ip, create_time) VALUES (?, ?, ?, NOW()) ); $stmt-execute([$channel, $ua, $ip]); if (strpos($ua, iphone) ! false || strpos($ua, ipad) ! false) { // iOS端走App Store企业签证书经常过期维护成本高 header(Location: https://apps.apple.com/cn/app/你的应用ID); } else { // Android端给真机下载地址文件名带版本号方便排查线上问题 header(Location: /download/app_v2.1.0.apk); } exit;第一个参数from是渠道投放链接上带的抖音、微信、小红书分开给不同链接第二个参数是cookie兜底用户第一次访问没带参数时用上次记录的渠道。in_array一定要带第三个参数严格比较否则0会被当成direct处理。跳转前先写库防止header之后还没执行SQL就被浏览器断开连接。3.3 二维码场景下的下载链路注意事项线下物料经常印二维码链接里带fromwx这类参数会缩短后变糊。常见做法是把渠道参数折叠到短链里短链服务解析后再302到download.php?fromxxx。这里有个坑很多短链服务默认加了301缓存渠道参数变了也不会重新跳所以短链服务上要强制302。Android端还有一个高版本适配问题如果APK放在/download/目录下直接被浏览器访问Android 7.0以上会拦截安装需要在APK的AndroidManifest.xml里配置好FileProvider否则用户下载完点击安装包没反应转化率直接腰斩。实际业务里我甚至见过纯前端写的下载页安卓点击后跳到一个intent://协议结果在微信内置浏览器里被拦截。微信里下载APK最好先用提示引导用户“复制链接到浏览器打开”别跟内置浏览器的白名单机制较劲。3.4 下载埋点的对账SQL埋点数据入库后运营每天看的报表就是下面这条SQL的变体SELECT channel, DATE(create_time) AS day, COUNT(*) AS download_count, COUNT(DISTINCT ip) AS unique_ip FROM channel_download WHERE create_time CURDATE() - INTERVAL 7 DAY GROUP BY channel, DATE(create_time) ORDER BY day DESC, download_count DESC;COUNT(DISTINCT ip)用来粗略去重因为同一个用户短时间重试下载会被记多次。更准确的做法是在channel_download表加一个fingerprint字段存设备ID或IDFA但小额借贷场景下这个字段经常拿不到用IP去重是性价比最高的方案。这张表和users.channel对得上说明渠道归因链路是通的。4. 搭建教程从源码包到本地跑通的最小步骤标题里写了“内附搭建教程”源码包里确实会附带一份环境说明。但实际照教程装几乎都会卡在伪静态配置、PHP扩展缺失、runtime目录权限这三个地方。4.1 源码包结构和搭建教程通常不会写的内容标准的源码包结构一般是sql/install.sql放数据库初始化脚本application/放业务代码public/放前端入口runtime/留给运行时缓存和日志。教程文档通常写到“导入数据库、改配置、访问域名”三句话就完了但漏掉了三件必做的事runtime目录要写入权限PHP-FPM运行用户www-data需要能写进去否则直接白屏。PHP要求安装bcmath、fileinfo、mbstring三个扩展少任何一个登录或还款计划接口就会报错。MySQL建库时字符集必须指定utf8mb4用默认的latin1建库只要表里存了emoji就乱码手机号带区号也会截断。把这三件事在动手前处理好安装失败率能降低一半以上。4.2 环境准备与数据库导入命令下面以 Ubuntu 20.04 PHP 7.4 MySQL 5.7 为例是这类源码最常见的运行环境。PHP 8.0以上兼容性反而差很多老代码的each()和mysql_real_escape_string用不了。# 安装基础环境 sudo apt update sudo apt install -y nginx mysql-server php7.4-fpm php7.4-mysql \ php7.4-bcmath php7.4-xml php7.4-mbstring php7.4-gd unzip # 解压源码到站点目录 unzip loan_system_v2.1.zip -d /var/www/loan # 创建数据库并导入初始化SQL sudo mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS loan_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; sudo mysql -uroot -p loan_system /var/www/loan/sql/install.sql # 设置runtime目录权限 sudo chown -R www-data:www-data /var/www/loan/runtimeinstall.sql导入时如果报错多半是SQL文件里带了数据库名或者用了版本更高的语法。先打开文件看开头有没有CREATE DATABASE有就去掉只保留建表语句。runtime目录权限如果设错后面改代码不会立即生效排查时会非常痛苦所以一定先设好再动nginx配置。4.3 Nginx站点配置伪静态、安全目录、PHP解析站点配置文件放在/etc/nginx/sites-available/loan核心是伪静态规则和两个安全location。这套配置对ThinkPHP和FastAdmin风格都通用。server { listen 80; server_name loan.local; root /var/www/loan/public; index index.php index.html; # 禁止访问隐藏文件、备份文件和env配置 location ~ /\. { deny all; } location ~* \.(sql|md|log|bak|env|git)$ { deny all; } # 前端控制器伪静态 location / { try_files $uri $uri/ /index.php?s$uri; } location ~ \.php$ { include fastcgi_params; fastcgi_pass unix:/run/php/php7.4-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }try_files里的/index.php?s$uri是兼容pathinfo模式的兜底规则。如果源码用的是FastAdmin路由走的是index.php?s/admin/xxx这条规则能正常解析。第三个location里SCRIPT_FILENAME必须写$document_root$fastcgi_script_name直接写$request_filename在个别环境会解析到错误路径。改完配置执行sudo nginx -t检查语法再sudo systemctl reload nginx生效。4.4 PHP和MySQL的关键参数核对清单搭建完成后不要只看到首页能开就结束要顺手核对下面几个参数参数推荐值作用upload_max_filesize20M上传身份证照片、合同附件需要post_max_size20M必须大于upload上限否则上传大图直接报错memory_limit256MExcel导入导出和还款计划批量生成时防OOMopcache.enableOn生产环境必须开PHP响应速度能提升一倍innodb_buffer_pool_size128M以上MySQL内存表缓存数据量小可默认改PHP参数在/etc/php/7.4/fpm/php.ini里改完执行sudo systemctl restart php7.4-fpm。MySQL参数在/etc/mysql/mysql.conf.d/mysqld.cnf。这些参数不动小流量跑着没问题一旦做还款计划批量生成或运营后台导出就会出现接口超时。4.5 搭建失败排查对照表本地搭建最常见的报错和对应处理方式整理成一份对照表报错现象原因处理方法首页白屏无报错runtime目录无写入权限chown -R www-data:www-data runtime后重试Call to undefined function bcaddPHP bcmath扩展未装apt install php7.4-bcmath并重启fpm数据库连接失败config.php里host写的localhost但MySQL走sockethost改为127.0.0.1所有页面404try_files伪静态规则不匹配确认路由格式改用/index.php?s$uri兜底能开首页但后台登录跳转404伪静态没生效检查nginx是否加载了/etc/nginx/sites-enabled/loanAPK下载地址404站点根目录设成了public下载目录在上级把下载目录移到public下或加location指向真实目录最后一行是最容易忽略的下载APK的目录被Nginx的location规则挡住返回404但页面却正常。这类问题排查时先看Nginx错误日志/var/log/nginx/error.log基本都能定位到具体原因。5. 上线前的参数校准与风控边界搭建跑通只是开始真正决定这套系统能不能稳定运转的是上线前的几处细节调整。这三块做完再放量接渠道不迟。5.1 下载页防刷与UA校验推广链接一旦上线刷量的就来了。最常见的刷法是用脚本直接请求download.php伪造UA但不产生真实下载。防刷可以从两个点入手一是同一IP单日请求超过20次直接拒绝跳转二是UA里完全没有浏览器特征词的直接跳过埋点写库。$todayCount $pdo-prepare( SELECT COUNT(*) FROM channel_download WHERE ip ? AND DATE(create_time) CURDATE() ); $todayCount-execute([$ip]); if ((int)$todayCount-fetchColumn() 20) { exit(download limit exceeded); }IP防刷只能挡住低级脚本拦不住IP池。真要精确防要接设备指纹服务但对于小额借贷系统的推广场景IP频次限制已经够用因为渠道对账看的是注册转化率不是单纯的下载量。5.2 计息精度金额一律用分计算PHP的浮点计算在涉及利率时非常不可靠经典例子就是0.1 0.2 ! 0.3。等额本息里的月供计算虽然用了pow但后续principalPart和interestPart的加减如果直接用浮点累计误差会在第12期后显现。推荐做法是金额全部转成整数分计算最后的round在存入数据库前执行一次。function moneyToFen(float $amount): int { return (int)round($amount * 100); } function fenToMoney(int $fen): string { return number_format($fen / 100, 2, ., ); }利息计算时用分做单位本金12000元就是1200000分月利率1.5%就是1.5/100每一步用intdiv取整尾差单独处理。这个方案能让对账误差归零代价是代码稍啰嗦但对金融系统来说是值得的。5.3 上线验证清单与日志核对正式开放前按下面这份清单过一遍比翻源码更有效验证项验证方法预期结果还款计划生成调用后台生成接口比对3期利息手工计算分毫不差下载分流curl -A iPhone http://loan.local/download.php -ILocation指向App Store安卓下载curl -A Android http://loan.local/download.php -ILocation指向APK文件渠道归因用fromwx访问下载页后注册查users.channel值为wx放款回调幂等同一回调参数发送两次第二次被忽略且无报错数据库备份执行mysqldump并gzip生成备份文件且可恢复放款回调幂等这一项最容易被忽略。小额借贷系统对接资金方时回调接口可能因网络超时被重复推送如果接口没有按order_no做幂等判断会造成重复入账。核对完这六项再把runtime下的debug日志关闭PHP的display_errors置为Off推广下载页的接口响应时间控制在500毫秒以内。本文还有配套的精品资源点击获取