简介这是一套基于PHP开发的借贷公司整站源码面向需要快速搭建网贷平台的开发者与创业团队涵盖用户注册、信用评估、借款申请、还款计划、逾期处理等完整借贷业务流程并集成在线审批、合同签署、资金转账与支付接口等线上化操作。源码由得得系统改编而来经过实际测试验证具备可运行性与二次开发价值。压缩包为rar格式大小约11.34MB内含借贷网整站源码包括前端HTML、CSS与JavaScript界面文件、后端PHP业务脚本、数据库SQL结构文件以及支付、短信、邮件等接口配置与类库便于理解整体架构并快速部署。目前已有2535人学习下载适合具备一定PHP基础、希望研究网贷系统实现逻辑或进行定制开发的读者参考使用。1. 从一份 PHP 借贷源码说起为什么很多团队还在用它搭网贷平台去年帮一个做金融科技的朋友看他们新接手的放贷系统打开代码仓库一看核心目录里躺着application/、thinkphp/、vendor/三个文件夹数据库配置文件里明晃晃写着 MySQL 的连接串。这套 PHP 借贷源码已经跑了三年多日活不高但业务逻辑极其复杂——从授信申请、风控审核、放款到还款计划生成全链路都在里面。很多人以为网贷平台源码早就该用 Java 或 Go 重写了但现实是大量中小型借贷公司源码项目依然基于 PHP 技术栈在稳定运行原因很简单开发快、部署轻、招人便宜而且这套业务模型已经被验证过无数遍。这份 PHP 借贷源码适合两类人一是想快速搭建一套可运行的网贷平台原型的技术负责人二是需要接手维护或二次开发存量借贷系统的开发者。它不是一个玩具 Demo而是一套包含用户端、后台管理、风控接口、还款计算等模块的完整工程。2. 技术栈与目录结构拿到源码先看这三层2.1 框架选型与运行环境这套借贷源码大概率基于 ThinkPHP 5.x 或 6.x 构建这是国内中小型金融类 PHP 项目最常见的选型。原因不复杂ThinkPHP 的中文文档齐全模型层封装了查询构造器写起借贷这种大量涉及金额计算和状态流转的业务来比裸写 PDO 舒服得多。运行环境方面常见配置是 PHP 7.2 到 7.4 之间搭配 MySQL 5.7 或 8.0Web 服务器用 Nginx 做反向代理加静态资源分发。如果你拿到的是压缩包解压后先别急着往服务器上传本地用 phpStudy 或者 Docker 起一个 PHP 环境跑一遍确认能正常访问首页再往下走。我一般会先检查三个东西composer.json里锁定的依赖版本、.env或config/database.php里的数据库配置、以及public/index.php这个入口文件。这三个文件决定了项目能不能跑起来以及跑起来之后连的是哪个库。# 典型的本地启动流程以 ThinkPHP 为例 cd /path/to/project composer install --no-dev # 安装生产依赖跳过开发包 cp .env.example .env # 复制环境配置文件 # 编辑 .env填入数据库地址、账号、密码 php think migrate:run # 执行数据库迁移如果项目带了 migration php -S localhost:8000 -t public # 用 PHP 内置服务器快速验证这段命令里composer install --no-dev是为了避免把 PHPUnit 之类的开发依赖装到生产环境php think migrate:run不是每个项目都有但如果database/migrations/目录存在说明作者用了迁移工具管理表结构跑一遍能省去手动导入 SQL 的麻烦。最后用内置服务器验证时注意入口文件在public/下所以-t public不能省。2.2 目录结构拆解业务代码藏在哪里一套典型的 PHP 借贷源码目录结构大致如下├── application/ # 应用逻辑层 │ ├── admin/ # 后台管理模块 │ │ ├── controller/ # 控制器审核、放款、用户管理 │ │ ├── model/ # 模型借款订单、还款计划、用户 │ │ └── view/ # 模板文件 │ ├── api/ # 对外接口模块 │ │ ├── controller/ # 供 App 或 H5 调用的接口 │ │ └── behavior/ # 钩子行为签名校验、日志记录 │ ├── index/ # 用户端页面模块 │ └── common/ # 公共函数、常量、枚举 ├── config/ # 配置文件 │ ├── database.php # 数据库连接 │ ├── app.php # 应用配置 │ └── loan.php # 借贷业务参数利率、期限、额度 ├── public/ # Web 根目录 │ ├── index.php # 入口文件 │ └── static/ # 前端静态资源 ├── extend/ # 第三方扩展库 ├── runtime/ # 运行时缓存、日志 └── vendor/ # Composer 依赖重点看application/common/和config/loan.php。前者通常放着金额格式化、利息计算、状态码映射这些公共方法后者定义了产品的核心参数——比如日利率范围、可选的借款期限、最低和最高额度。改业务规则的时候先翻这两个地方往往比在控制器里大海捞针快得多。2.3 数据库表设计借贷业务的核心几张表网贷平台源码的数据库设计直接反映了业务模型。不管前端怎么变核心表就那么几张表名作用关键字段user用户基本信息id, mobile, real_name, id_card, credit_scoreloan_order借款订单id, user_id, amount, period, interest_rate, statusrepayment_plan还款计划id, order_id, period_no, due_date, principal, interest, statususer_account用户资金账户user_id, balance, frozen_amount, total_borrowedadmin_user后台管理员id, username, role_id, last_login_timeloan_product借款产品配置id, name, min_amount, max_amount, min_period, max_period, rateloan_order的status字段是整个系统的状态机核心常见取值包括待审核、审核通过、已放款、还款中、已结清、逾期、坏账。每次状态变更都要同步更新repayment_plan和user_account这是最容易出 bug 的地方。我见过一个项目因为放款时没锁住账户余额导致同一笔订单被重复放款两次最后靠对账才查出来。3. 核心模块运行链路从借款申请到还款计划生成3.1 借款申请接口的完整处理流程用户在前端点击“立即借款”后请求会打到application/api/controller/Loan.php的apply方法。这个方法的处理链路通常包括参数校验、用户状态检查、风控规则过滤、生成订单、返回结果。// application/api/controller/Loan.php public function apply() { $userId $this-request-userId; // 从 token 中解析出的用户 ID $amount $this-request-post(amount, 0, floatval); $period $this-request-post(period, 0, intval); // 1. 基础参数校验 if ($amount 100 || $amount 50000) { return json([code 400, msg 借款金额超出范围]); } if (!in_array($period, [7, 14, 30])) { return json([code 400, msg 借款期限不合法]); } // 2. 检查用户是否有未结清订单 $hasActive LoanOrderModel::where(user_id, $userId) -whereIn(status, [repaying, overdue]) -count(); if ($hasActive 0) { return json([code 400, msg 存在未结清借款]); } // 3. 风控规则过滤调用内部风控服务或本地规则 $riskResult $this-checkRisk($userId, $amount); if (!$riskResult[pass]) { return json([code 403, msg 风控未通过 . $riskResult[reason]]); } // 4. 生成借款订单 $orderNo date(YmdHis) . mt_rand(1000, 9999); $rate LoanProductModel::getRateByPeriod($period); // 根据期限取利率 $order LoanOrderModel::create([ order_no $orderNo, user_id $userId, amount $amount, period $period, interest_rate $rate, status pending, created_at time(), ]); return json([code 200, msg 申请已提交, data [order_no $orderNo]]); }这段代码的关键点在于金额和期限的校验必须放在最前面避免无效请求打到风控服务检查未结清订单时用了whereIn而不是逐个状态判断这样新增状态时不容易漏风控调用被抽成了独立方法checkRisk方便后续替换成外部风控接口。订单号生成用了时间戳加随机数生产环境建议换成更严格的唯一 ID 生成器比如雪花算法或 Redis 自增序列。3.2 还款计划生成等额本息与先息后本的实现差异借款订单审核通过并放款后系统需要生成还款计划。这一步的算法选择直接影响用户的还款体验和平台的资金回笼节奏。常见的两种模式是等额本息和先息后本。// application/common/service/RepaymentService.php class RepaymentService { /** * 生成还款计划 * param int $orderId 订单 ID * param string $type 还款方式equal等额本息| interest_first先息后本 */ public function generatePlan($orderId, $type equal) { $order LoanOrderModel::get($orderId); $principal $order[amount]; $periods $order[period] / 7; // 假设按周还款 $rate $order[interest_rate] / 100; $plans []; if ($type equal) { // 等额本息每期还款额相同 $monthlyRate $rate / $periods; $payment $principal * $monthlyRate * pow(1 $monthlyRate, $periods) / (pow(1 $monthlyRate, $periods) - 1); $remaining $principal; for ($i 1; $i $periods; $i) { $interest $remaining * $monthlyRate; $principalPart $payment - $interest; $remaining - $principalPart; $plans[] [ order_id $orderId, period_no $i, due_date strtotime({$i} week, $order[loan_time]), principal round($principalPart, 2), interest round($interest, 2), total round($payment, 2), status unpaid, ]; } } else { // 先息后本前 N-1 期只还利息最后一期还本金加利息 $weeklyInterest $principal * $rate / $periods; for ($i 1; $i $periods; $i) { $isLast ($i $periods); $plans[] [ order_id $orderId, period_no $i, due_date strtotime({$i} week, $order[loan_time]), principal $isLast ? $principal : 0, interest round($weeklyInterest, 2), total $isLast ? round($principal $weeklyInterest, 2) : round($weeklyInterest, 2), status unpaid, ]; } } // 批量写入还款计划表 Db::name(repayment_plan)-insertAll($plans); return $plans; } }等额本息的计算公式里$monthlyRate实际上是每期利率这里按周还款所以用总期数做分母。pow(1 $monthlyRate, $periods)是复利终值系数整个公式保证了每期还款额固定。先息后本则简单得多前几期只还利息最后一期一次性还本金。注意round的精度问题——金额计算必须保留两位小数否则累计误差会导致最后一期对不上账。我一般会在生成计划后加一个校验所有期数的本金之和必须等于借款金额利息之和与预期总利息的偏差不能超过 0.01 元。3.3 后台审核与放款的状态流转后台管理员在application/admin/controller/Loan.php里操作审核和放款。状态流转必须严格按顺序走不能跳步。// application/admin/controller/Loan.php public function approve() { $orderId input(post.order_id/d); $action input(post.action); // pass | reject $order LoanOrderModel::get($orderId); if (!$order || $order[status] ! pending) { return json([code 400, msg 订单状态不允许审核]); } Db::startTrans(); try { if ($action pass) { $order-status approved; $order-approve_time time(); $order-save(); // 生成还款计划 (new RepaymentService())-generatePlan($orderId, equal); } else { $order-status rejected; $order-reject_reason input(post.reason, ); $order-save(); } Db::commit(); return json([code 200, msg 操作成功]); } catch (\Exception $e) { Db::rollback(); return json([code 500, msg 操作失败 . $e-getMessage()]); } }这里用Db::startTrans()开启了事务因为审核通过后要同时更新订单状态和插入还款计划两个操作必须原子化。如果生成还款计划时抛异常订单状态会回滚到pending不会出现“订单已通过但没有还款计划”的脏数据。放款操作类似但还需要更新user_account表的total_borrowed字段并记录资金流水。常见做法是在放款成功后给用户发一条站内信或短信通知这部分通常通过队列异步处理避免阻塞主流程。4. 避坑与常见问题接手借贷源码后最容易翻车的几个点4.1 金额计算精度丢失现象还款计划里最后一期的本金加利息和借款总额对不上差几分钱。原因PHP 浮点数运算本身存在精度问题0.1 0.2 ! 0.3在金额计算里是致命的。解决所有金额字段在数据库里用decimal(10,2)存储PHP 层计算时用bcmath扩展或round强制保留两位小数。生成还款计划后加一步校验array_sum(array_column($plans, principal))必须等于借款本金。4.2 并发放款导致重复出款现象同一笔订单被两个管理员同时点击放款用户账户收到两笔钱。原因放款接口没有做幂等控制也没有对订单行加锁。解决在放款前用SELECT ... FOR UPDATE锁住订单行或者用 Redis 分布式锁以订单号为 key。更简单的做法是在loan_order表上加唯一索引放款时先更新状态为disbursing更新影响行数为 0 则说明已被处理。4.3 风控接口超时拖垮主流程现象借款申请接口响应时间忽高忽低高峰期大量请求超时。原因风控校验是同步调用外部 HTTP 接口对方响应慢时直接阻塞了 PHP 进程。解决给风控调用设置合理的超时时间比如 2 秒超时后走降级策略——要么直接拒绝要么标记为“人工复审”。我一般会把风控结果缓存 5 分钟同一用户短时间内重复申请直接读缓存。4.4 还款计划日期计算跨月出错现象1 月 31 日借款按周还款的计划里出现了 2 月 31 日这种不存在的日期。原因用strtotime(1 week)在月末附近会跳到下个月但业务上可能需要固定为每月同一天。解决明确还款周期的定义——是按自然周还是按固定日。如果是按月还款用DateTime::modify(1 month)并处理月末溢出如果是按周直接用时间戳加7 * 86400更可控。4.5 后台权限校验缺失现象普通管理员能操作放款和修改利率越权操作没人发现。原因控制器里只校验了登录状态没有校验角色权限。解决在application/admin/behavior/里加一个权限检查钩子或者在基类控制器的_initialize方法里统一拦截。权限节点建议存到数据库用admin_role和admin_rule两张表管理别硬编码在代码里。5. 二次开发与改造从能跑到好用的几个关键动作5.1 把业务参数从代码里抽到配置中心接手项目后我做的第一件事通常是把config/loan.php里的硬编码参数搬到数据库或配置中心。利率、期限、额度范围这些参数运营随时可能调整每次改都让开发发版不现实。改造思路是建一张loan_product表后台加一个产品管理页面接口层通过缓存读取产品配置。// 改造后的利率获取方式 public function getRateByPeriod($period) { $products Cache::get(loan_products); if (!$products) { $products LoanProductModel::where(status, 1)-select(); Cache::set(loan_products, $products, 300); // 缓存 5 分钟 } foreach ($products as $p) { if ($period $p[min_period] $period $p[max_period]) { return $p[rate]; } } throw new \Exception(未找到匹配的借款产品); }这样运营在后台改完利率最多 5 分钟后所有接口自动生效不需要重启服务。5.2 加一层接口签名与防重放对外 API 如果裸奔很容易被刷。我一般会在application/api/behavior/里加一个签名校验行为要求每个请求带上timestamp、nonce、sign三个参数。sign的计算方式是md5(参数按字典序拼接 密钥)服务端校验时间戳偏差不超过 5 分钟并用 Redis 记录nonce防止重复使用。// application/api/behavior/ApiSign.php public function run($params) { $timestamp input(timestamp/d); $nonce input(nonce); $sign input(sign); if (abs(time() - $timestamp) 300) { throw new \Exception(请求已过期); } if (Cache::get(nonce_ . $nonce)) { throw new \Exception(重复请求); } Cache::set(nonce_ . $nonce, 1, 300); $secret config(api.secret); $expected md5($this-buildSignStr($params) . $secret); if ($sign ! $expected) { throw new \Exception(签名校验失败); } }这套机制能挡住大部分脚本刷接口的行为但密钥管理要小心——别把secret写死在代码里提交到仓库用环境变量注入。5.3 还款提醒与逾期处理的任务化原始代码里还款提醒可能是手动触发的或者干脆没有。二次开发时我会用 ThinkPHP 的命令行工具加两个定时任务一个每天上午扫描即将到期的还款计划发短信或推送提醒另一个扫描已逾期的计划更新状态并计算罚息。# 在 crontab 里配置 0 9 * * * cd /path/to/project php think repayment:remind 0 1 * * * cd /path/to/project php think repayment:overdue对应的命令类放在application/common/command/下逻辑不复杂但很关键。逾期罚息的计算规则要跟业务方确认清楚——是按日计息还是按复利有没有上限。我见过一个项目因为罚息没有封顶用户逾期三个月后罚息超过了本金最后闹到投诉。5.4 日志与对账给系统留一颗后悔药借贷系统最怕的就是钱对不上。我习惯在关键节点——放款、还款、逾期——都写一条资金流水到fund_log表字段包括用户 ID、订单号、变动类型、变动金额、变动前余额、变动后余额、时间戳。这样一旦用户投诉或者对账发现差异直接查流水就能定位到具体哪一步出了问题。// 记录资金流水的公共方法 function logFundChange($userId, $orderNo, $type, $amount, $before, $after) { Db::name(fund_log)-insert([ user_id $userId, order_no $orderNo, change_type $type, // disburse | repay | overdue_fee amount $amount, balance_before $before, balance_after $after, created_at time(), ]); }这个表只增不改定期归档到历史库。有了它任何资金问题都能追溯到源头比翻 PHP 日志高效得多。从那以后我每次接手一套新的借贷源码都会先把资金流水和对账逻辑跑一遍确认每一笔钱的来龙去脉都能对上再动业务代码。这个习惯帮我挡掉了至少三次潜在的线上事故。希望帮到你。本文还有配套的精品资源点击获取