简介:本资源是一套基于ThinkPHP5框架开发的家庭财务收支管理网站完整源码案例,面向PHP初学者与Web开发入门者,解决个人及家庭日常记账、预算控制与消费分析等实际需求。压缩包共1412个文件,涵盖320个HTML页面、277个JavaScript交互脚本、245个PNG图标资源、238个CSS样式文件、173个PHP核心逻辑文件,以及SQL数据库脚本、配置文件和前端UI组件(如Bootstrap、EasyUI、UEditor、Font Awesome等),整体大小为14.77MB,结构清晰体现标准MVC分层设计。目前已有189人学习下载,适合通过真实项目理解用户认证、多账户管理、收支分类统计、可视化报表生成及安全防护等Web开发关键能力。读者可直接部署运行,深入研读application目录下的模型-控制器-视图实现,结合public静态资源与config配置体系,掌握ThinkPHP5工程化开发全流程。
1. 这不是又一个“记账小工具”,而是用 ThinkPHP5 搭建可落地的家庭财务收支管理网站的完整实践路径
你在网上搜“家庭财务收支管理”,大概率会看到一堆 Excel 模板、手机 App 推荐,或者几行 PHP 代码拼凑的单页记账页。但真正能部署在自己服务器上、支持多成员协作、有清晰权限分层、数据可导出可统计、界面不简陋还能二次扩展的 Web 系统——极少有开源案例把「ThinkPHP5」这个成熟框架和「家庭财务」这个具体场景深度绑定来呈现。本标题指向的.zip源码包,本质是一套经过真实需求反推、结构完整、路由清晰、模型分层合理、且未过度封装的家庭级财务系统参考实现。它不追求企业级复杂度,但严格遵循 MVC 分层逻辑:收支分类用数据库字典驱动,流水记录带时间戳+金额+账户+分类+备注+附件(可选),报表页用原生 SQL 聚合而非前端硬算,权限控制到「仅查看配偶账目」粒度。适合 PHP 初学者理解 TP5 的 Request/Model/View/Validate 四件套如何协同,也适合中小团队快速 fork 出内部费用登记系统——关键在于,它把「家庭」这个最小财务单元的业务规则(如:房贷月供固定、水电费按月结算、红包收入无分类)编码进了验证器和控制器逻辑里,而不是堆砌功能。
2. 用 ThinkPHP5 在本地跑通家庭财务系统的最小命令与目录结构还原
2.1 解压后必须确认的三个核心目录与文件作用
拿到ThinkPHP5家庭财务收支管理网站源码案例设计.zip后,解压得到的根目录结构通常包含application/、public/、think可执行脚本及runtime/(可能为空)。这并非标准 TP5 官方安装包,而是基于 TP5.0.24 或 TP5.1.39 的定制化应用包,需特别注意:
application/下的common.php是全局函数入口,其中定义了money_format()等财务专用格式化函数;application/index/是默认模块,其controller/Index.php中的index()方法返回首页视图,而controller/Account.php才是收支核心控制器;public/目录下必须存在index.php入口文件,且其顶部define('APP_PATH', __DIR__ . '/../application/');路径需与实际解压位置匹配。
提示:若直接双击
index.php打开空白页,说明未启用 PHP 内置服务器或 Apache/Nginx 配置错误。TP5 5.0+ 版本要求 PHP >= 5.6.0,且pdo_mysql、mbstring、openssl扩展必须启用。
2.2 用 PHP 内置服务器启动并验证基础路由
无需配置 Apache 或 Nginx,用 PHP 自带的开发服务器即可快速验证。进入public/目录执行:
php -S localhost:8000 -t . router.php其中router.php是 TP5 官方推荐的路由转发脚本,内容如下(若缺失请手动创建):
<?php // router.php if (preg_match('/\.(?:png|jpg|jpeg|gif|ico|txt|woff|woff2|css|js|html|htm)$/i', $_SERVER['REQUEST_URI'])) { return false; } else { include __DIR__ . '/index.php'; }执行后访问http://localhost:8000,应看到登录页;访问http://localhost:8000/index/account/index应跳转至收支列表页(需先注册账号)。若提示No input file specified.,检查php -v输出的 PHP 版本是否低于 5.6;若报错Class 'think\Db' not found,说明vendor/目录缺失——此源码包未包含 Composer 依赖,需手动补全。
2.3 补全 Composer 依赖与数据库初始化的两步强制操作
该源码包通常不打包vendor/,需通过 Composer 安装核心依赖。在项目根目录(即含composer.json的目录)执行:
composer install --no-dev --optimize-autoloader--no-dev参数排除测试类库,--optimize-autoloader加速类加载,符合生产环境习惯。成功后检查vendor/topthink/thinkphp/是否存在,确认 TP5 核心已载入。
数据库初始化分三步:
- 创建 MySQL 数据库(如
family_finance),字符集设为utf8mb4; - 导入
application/database.php中指定的 SQL 文件(常见路径为data/family_finance.sql),该文件包含finance_account(账户表)、finance_category(分类表)、finance_record(流水表)、finance_user(用户表)四张核心表; - 修改
application/database.php中的数据库配置:
return [ 'type' => 'mysql', 'hostname' => '127.0.0.1', 'database' => 'family_finance', // 此处必须与你创建的库名一致 'username' => 'root', 'password' => 'your_password', 'hostport' => '3306', 'charset' => 'utf8mb4', 'prefix' => 'finance_', // 表前缀,需与 SQL 文件中建表语句匹配 ];注意:
prefix参数必须与 SQL 文件中CREATE TABLE finance_account的前缀完全一致,否则模型查询将找不到表。若 SQL 文件使用无前缀建表,则此处设为空字符串''。
2.4 验证登录与基础功能的四个关键请求路径
启动服务后,用浏览器访问以下路径验证各模块连通性:
| 路径 | 用途 | 预期响应 |
|---|---|---|
/index.php?s=/index/user/login | 用户登录页 | 显示用户名/密码输入框,提交后跳转至首页 |
/index.php?s=/index/account/index | 收支列表页 | 显示近30天流水,含「新增」按钮 |
/index.php?s=/index/category/index | 分类管理页 | 列出「餐饮」「交通」「工资」等预设分类,支持增删改 |
/index.php?s=/index/report/monthly | 月度报表页 | 柱状图显示当月收入/支出对比,下方表格列明细 |
若任一路径返回 404,检查application/route.php中是否定义了对应路由规则。典型配置如下:
// application/route.php Route::get('index/user/login', 'index/User/login'); Route::get('index/account/index', 'index/Account/index'); Route::get('index/category/index', 'index/Category/index'); Route::get('index/report/monthly', 'index/Report/monthly');这些路由映射到控制器方法,是理解业务流程的起点。例如Account/index方法内调用FinanceRecordModel::where('user_id', $uid)->order('create_time desc')->paginate(15),即按用户 ID 查询并分页,体现了典型的家庭多账户隔离逻辑。
3. 收支核心模型与验证器的三层校验逻辑拆解
3.1FinanceRecord模型中的字段约束与关联定义
application/index/model/FinanceRecord.php是收支流水的核心模型,其定义直接决定数据写入的合法性。关键代码段如下:
<?php namespace app\index\model; use think\Model; class FinanceRecord extends Model { // 指定数据表(不含前缀) protected $name = 'record'; // 自动写入时间戳 protected $autoWriteTimestamp = true; protected $createTime = 'create_time'; protected $updateTime = false; // 字段类型转换 protected $type = [ 'amount' => 'float', 'account_id' => 'integer', 'category_id' => 'integer', 'is_income' => 'boolean', ]; // 关联账户表 public function account() { return $this->belongsTo('FinanceAccount', 'account_id', 'id'); } // 关联分类表 public function category() { return $this->belongsTo('FinanceCategory', 'category_id', 'id'); } }这段代码揭示了三个关键设计点:
第一,$name = 'record'表明该模型操作finance_record表(前缀finance_+ 名称record),与数据库实际表名严格对应;
第二,$type数组将amount强制转为浮点型,避免整数存储导致小数丢失,这是财务数据精度的生命线;
第三,belongsTo关联声明让FinanceRecord::with('account,category')->find(1)可一次性查出流水+账户名+分类名,省去多次查询。
3.2FinanceRecordValidate验证器中的业务规则嵌入
单纯数据库约束不够,家庭财务有独特业务规则。application/index/validate/FinanceRecordValidate.php将规则前置到应用层:
<?php namespace app\index\validate; use think\Validate; class FinanceRecordValidate extends Validate { protected $rule = [ 'amount' => 'require|number|between:0.01,9999999.99', 'account_id' => 'require|integer|gt:0', 'category_id' => 'require|integer|gt:0', 'is_income' => 'require|in:0,1', 'remark' => 'max:200', 'create_time' => 'date', ]; protected $message = [ 'amount.require' => '金额不能为空', 'amount.number' => '金额必须为数字', 'amount.between' => '金额应在0.01至9999999.99之间', 'account_id.gt' => '请选择有效账户', 'category_id.gt' => '请选择有效分类', 'is_income.in' => '收支类型只能是0(支出)或1(收入)', ]; }这里between:0.01,9999999.99限制单笔金额范围,防止误输0.001(分币单位)或10000000(千万级异常值);in:0,1强制is_income字段二值化,确保报表统计时SUM(CASE WHEN is_income=1 THEN amount ELSE 0 END)可靠执行。验证失败时,控制器中$validate->batch(false)->check($data)返回false,并携带getError()消息,前端可直接展示。
3.3 控制器中对「家庭成员可见性」的权限拦截逻辑
application/index/controller/Account.php的add()方法不仅处理新增,还注入了家庭维度权限控制:
public function add() { if ($this->request->isPost()) { $data = $this->request->post(); $data['user_id'] = session('user_id'); // 强制绑定当前登录用户 // 若开启家庭共享,检查目标账户是否属于同一家庭 $account = FinanceAccount::find($data['account_id']); if ($account && $account->family_id != session('family_id')) { $this->error('您无权操作该账户'); } $validate = validate('FinanceRecord'); if (!$validate->check($data)) { $this->error($validate->getError()); } $result = FinanceRecord::create($data); if ($result) { $this->success('添加成功', 'index'); } else { $this->error('添加失败'); } } }session('family_id')是用户登录时存入的家族标识,$account->family_id是账户所属家族 ID。此逻辑确保 A 用户无法向 B 用户的账户记账,即使 URL 被篡改。这种「数据归属权」控制比 RBAC 更细粒度,是家庭场景的刚需。
3.4 报表生成中 SQL 聚合与 PHP 处理的分工边界
application/index/controller/Report.php的monthly()方法展示如何平衡数据库计算与 PHP 渲染:
public function monthly() { $year = input('year', date('Y')); $month = input('month', date('m')); // 数据库层聚合:按分类统计当月收支 $income = FinanceRecord::where('user_id', session('user_id')) ->where('is_income', 1) ->whereYear('create_time', $year) ->whereMonth('create_time', $month) ->field('category_id, SUM(amount) as total') ->group('category_id') ->select(); $expense = FinanceRecord::where('user_id', session('user_id')) ->where('is_income', 0) ->whereYear('create_time', $year) ->whereMonth('create_time', $month) ->field('category_id, SUM(amount) as total') ->group('category_id') ->select(); // PHP 层合并:将收入/支出按分类 ID 对齐,生成图表数据 $categories = FinanceCategory::all(['id', 'name', 'type']); // type=1收入,2支出 $chartData = []; foreach ($categories as $cat) { $inc = 0; $exp = 0; foreach ($income as $row) { if ($row['category_id'] == $cat['id']) $inc = $row['total']; } foreach ($expense as $row) { if ($row['category_id'] == $cat['id']) $exp = $row['total']; } $chartData[] = [ 'name' => $cat['name'], 'income' => $inc, 'expense' => $exp, ]; } $this->assign('chartData', json_encode($chartData)); return $this->fetch(); }此处SUM(amount)由 MySQL 完成,利用索引加速;而「按分类名对齐」逻辑交由 PHP 处理,因分类表较小(通常 < 50 条),PHP 循环成本远低于复杂 JOIN。json_encode()输出供 ECharts 前端渲染,体现前后端职责分离。
4. 从源码到可用系统的三类必调参数与两个高频排错点
4.1 数据库连接、时区、调试模式的三项基础配置修改
application/database.php、application/config.php、thinkphp/base.php是三处必须检查的配置文件:
| 配置文件 | 参数名 | 默认值 | 必调原因 | 建议值 |
|---|---|---|---|---|
application/database.php | 'debug' => true | true | 生产环境必须关闭,否则暴露 SQL 语句 | false |
application/config.php | 'default_timezone' => 'PRC' | 'Asia/Shanghai' | PRC 已废弃,PHP 7.0+ 要求标准时区名 | 'Asia/Shanghai' |
thinkphp/base.php | ini_set('display_errors', 'Off') | 'On' | 开发时可开,上线必须关,防止错误信息泄露 | 'Off' |
修改后需清空runtime/目录下所有缓存文件(rm -rf runtime/*),否则配置不生效。特别是runtime/cache/和runtime/log/,TP5 会缓存配置文件哈希值。
4.2 附件上传路径与权限的本地化适配
源码中图片附件功能常位于application/index/controller/Upload.php,其uploadImage()方法默认保存到public/uploads/。但 Linux 服务器需确保该目录可写:
# 进入 public 目录 cd public mkdir -p uploads chmod 755 uploads chown www-data:www-data uploads # Ubuntu/Debian # 或 chown nginx:nginx uploads # CentOS同时检查application/index/validate/UploadValidate.php中的fileSize规则:
'file_size' => 'require|fileSize:2097152', // 2MB 限制 'file_ext' => 'require|fileExt:jpg,png,gif,jpeg',若需上传 PDF 说明书,需在file_ext中追加pdf,并在php.ini中调整upload_max_filesize = 8M和post_max_size = 8M。
4.3 两个高频排错点:URL 重写失效与 Session 丢失
问题1:访问/index/account/index提示 404,但/index.php?s=/index/account/index正常
这是 Apache/Nginx 未启用 URL 重写导致。Apache 需确认.htaccess文件存在且AllowOverride All已开启;Nginx 需在 server 块中添加:
location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; break; } }问题2:登录后刷新页面提示「请先登录」,Session 无法保持
检查application/config.php中的session配置:
'session' => [ 'prefix' => 'family_', 'type' => '', 'auto_start' => true, ],若type为空,TP5 使用 PHP 默认 session 处理器,此时需确认session.save_path指向可写的目录(如/var/lib/php/sessions),并执行:
sudo chmod 777 /var/lib/php/sessions sudo chown www-data:www-data /var/lib/php/sessions提示:若使用 Redis 存储 Session,需安装
php-redis扩展,并将type设为'redis',同时配置redis_host、redis_port等参数。
5. 将家庭财务系统升级为多人协作模式的三个关键改造点
5.1 在用户表中增加 family_id 字段并建立家庭关系链
原始源码的finance_user表通常只有id、username、password字段。要支持「夫妻共管」,需扩展:
ALTER TABLE `finance_user` ADD COLUMN `family_id` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `id`, ADD COLUMN `role` TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '1-成员,2-管理员' AFTER `family_id`, ADD INDEX `idx_family_id` (`family_id`);role=2的用户可管理家庭成员(增删改其他用户),role=1仅能查看和记账。登录后,session('family_id')从用户表读取,替代硬编码。
5.2 账户表增加 shared_flag 字段控制跨成员可见性
finance_account表新增字段:
ALTER TABLE `finance_account` ADD COLUMN `shared_flag` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0-私有,1-家庭共享' AFTER `user_id`;在FinanceAccount模型的scopeShared方法中定义:
public function scopeShared($query) { $query->where('shared_flag', 1) ->where('family_id', session('family_id')); }则FinanceAccount::shared()->select()可获取所有家庭共享账户,供成员选择。
5.3 报表页增加「按成员筛选」的前端交互与后端聚合
前端report/monthly.html增加下拉框:
<select name="user_id" id="userFilter"> <option value="0">全部成员</option> {volist name="users" id="user"} <option value="{$user.id}">{$user.username}</option> {/volist} </select>后端Report/monthly方法接收user_id参数,并动态构建查询条件:
$user_id = input('user_id', 0); $where = ['user_id' => session('user_id')]; if ($user_id > 0) { // 管理员可查指定成员 $where = ['user_id' => $user_id, 'family_id' => session('family_id')]; } $income = FinanceRecord::where($where)->where('is_income', 1)->...->select();此改造使系统从「单人记账」跃升为「家庭财务中枢」,无需重写核心逻辑,仅通过字段扩展与查询条件组合即可实现。
5.4 使用 TP5 的事件系统解耦「记账成功」后的通知动作
在FinanceRecord模型的afterInsert钩子中触发事件,避免控制器逻辑臃肿:
// application/index/model/FinanceRecord.php protected function afterInsert($data) { // 记账成功后触发事件 \think\Event::trigger('FinanceRecordAdd', $this); } // application/event.php 中绑定监听器 return [ 'FinanceRecordAdd' => [ 'app\\index\\event\\SendSms', 'app\\index\\event\\UpdateBalance', ], ];UpdateBalance监听器可实时更新账户余额:
<?php namespace app\index\event; use app\index\model\FinanceAccount; class UpdateBalance { public function handle($record) { $account = FinanceAccount::find($record->account_id); if ($record->is_income) { $account->balance += $record->amount; } else { $account->balance -= $record->amount; } $account->save(); } }这种事件驱动设计,让「记账」与「余额更新」、「短信通知」解耦,后续增加邮件提醒只需新增监听器,不改动原有代码。
本文还有配套的精品资源,点击获取