ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ThinkPHP与Laravel双框架实战:旅游线路社区商城系统开发解析

ThinkPHP与Laravel双框架实战:旅游线路社区商城系统开发解析

1. 项目拆解:这个“三位一体”网站到底做了什么

1.1 核心需求解析

这套名为“Thinkphp_Laravel框架嗨玩-旅游线路社区交流商城网站”的项目,光看名字就知道它把三件事揉到了一起:旅游线路展示与预订、社区内容交流、在线商城交易。说实在的,这种“内容 + 交易”的混合场景比单纯做一个商城或单纯做一个博客要有意思得多,因为业务之间会互相咬合,数据关系也更复杂。我在本地把整套系统完整跑通之后,最大的感受是:它的业务逻辑并不算深,但覆盖的知识点非常全,几乎把PHP开发里常见的坑都替你踩了一遍。

如果你正准备找一个能练手、能二次开发、又能拿得出手的PHP全栈项目,这套东西很适合拿来当蓝本。它适合的人群大致有三类:刚入门PHP框架、想找个完整案例打通“前端展示—后端管理—数据库设计”整条链路的人;需要做课程设计或者毕业设计、想要一个业务闭环完整、演示效果直观的系统的人;以及有旅游行业资源、想快速搭一个社区电商原型做市场验证的人。

从功能粒度上看,这个系统可以分为用户端和管理端两条线。用户端围绕“看、聊、买”三个动作展开:游客可以浏览旅游线路的图文详情、查看行程亮点和价格说明,注册登录后可以发布游记、给线路写评论、收藏喜欢的路线,也可以直接选择出发日期和人数,把线路产品加入购物车并完成在线下单支付,最后在个人中心查看订单状态。管理端则负责维护线路数据、审核用户发布的社区内容、处理订单和退款、管理会员状态、配置基础参数。整个系统的业务闭环非常完整,拿来当课程设计或项目复盘都够分量。

1.2 我跑通它时的技术栈构成

先说一下本地环境。项目要求PHP版本在8.0以上,MySQL使用5.7或8.0均可,Web服务器我测试过Nginx和Apache两种,都有对应的伪静态规则。核心框架方面,主流程走的是Laravel,ThinkPHP这边更多是作为另一个可选的实现版本出现。这种“一个项目两套框架对照”的结构并不罕见,不少源码包为了满足不同使用者的习惯,会同时提供两种框架的代码,或者在不同子系统里分别采用不同框架。我在复现时选择以Laravel版为主线,重点看业务逻辑和组织方式;ThinkPHP版作为对照,用来对比两个框架在同一个场景下写法的差异,这样更容易理解框架设计的取舍逻辑。

2. ThinkPHP和Laravel同时出现:框架选型背后要搞清楚的事

2.1 两个框架的核心差异对比

很多新手看到项目标题里两个框架并列,第一反应是“它们是不是混在一起用的”。这个疑问完全可以理解。实际上,ThinkPHP和Laravel是两个独立的PHP全栈框架,设计理念和工程习惯差异不小。我花了大半天时间把两边代码的相同功能点逐一对照了一遍,整理了一份很直观的对比表:

对比维度Laravel版ThinkPHP版
路由定义全部集中在routes目录,支持按文件分类,语法灵活路由配置在route目录,支持注解和数组两种方式
ORMEloquent,模型即类,关联关系通过方法来声明内置ORM,模型、查询、验证分层相对轻量
模板渲染Blade,在HTML里用{{ }}输出内容,逻辑控制用指令模板标签使用{$变量}和volist/if等标签语法
数据库迁移用migrations管理表结构,版本化建表更多依赖SQL文件或命令行工具,没有完全对等的迁移机制
认证体系自带完整的auth脚手架,支持邮箱、手机号等扩展有内置认证类库,但需要手动组织登录会话和权限
中间件中间件机制贯穿HTTP请求生命周期,玩法非常灵活5.1以上版本已支持中间件,理念类似但生态要轻一些
中文生态中文资料足够多,但官方文档以英文为主官方文档全中文,社区新手友好度极高
上手成本目录规范、代码结构相对繁复,学习曲线略陡适合快速开发,约定简单,入门节奏快

实际操作下来,Laravel对代码组织和长期维护更友好,Blade模板渲染也干净利落;ThinkPHP则明显更贴合“快速出活”的场景,文档读起来不用来回切换语境。如果团队规模小、追求开发速度,ThinkPHP是更省事的选择;如果项目会持续迭代、需要丰富的组件生态,选Laravel会更稳妥一些。

2.2 项目中的实际落地方案

在我看到的这套项目里,官方源码直接把两种实现拆成了两个子目录,互不干扰,部署时只能选其中一个作为主服务。这种做法的好处很明显:使用者可以按自己的学习方向选择阅读主线,不会因为框架混在一起导致目录混乱。坏处同样明显,后期维护时同一套业务逻辑要同步改两遍,成本会翻倍。所以如果你打算在此基础上二次开发,我的建议是只保留一个框架作为主线,另一份代码当作参考手册来看,不要双线并行维护。

选型背后的逻辑要理解透:旅游线路商城这类业务,核心是“商品—订单—支付—售后”这条交易链路,框架本身不会直接决定业务成败,影响最大的是开发效率和排查问题的速度。Laravel的ORM在关联查询上非常顺手,比如要查“某条线路下关联的目的地、行程亮点、评论数、最低价格”,一句with链式调用就能解决,不用手拼一大堆SQL。而ThinkPHP的优势在于是国产框架,很多中文教程、网上的企业实战案例都基于它,遇到问题搜到答案的概率非常高。对我个人来说,这种“双框架对照”的源码包正好是学习对比的绝佳素材。

2.3 路由、模型与模板层的写法对照

看代码时我养成了一个习惯:同一个页面,先打开Laravel版看一遍实现,再切到ThinkPHP版去对比差异,几轮下来框架的个性就非常清晰了。

路由层是差异最先暴露的地方。Laravel定义一个带控制器方法的GET路由,直接用:

Route::get('/line/{id}', [LineController::class, 'show'])->name('line.show');

而ThinkPHP更习惯在控制器类上直接绑定URL访问,或者定义一个路由规则:

Route::get('line/:id', 'LineController/show');

控制器和模型层的差别也很有代表性。Laravel的Model类里,通过方法声明关联关系:

class Line extends Model { public function comments() { return $this->hasMany(Comment::class); } }

ThinkPHP 6之后也支持类似写法,但很多老项目还在用数组条件查询,写起来相对直观但嵌套查询时容易显得冗长:

$lines = Db::name('line') ->where('status', 1) ->order('id', 'desc') ->paginate(10);

模板层的对比更有意思。同样输出一个文章标题,Laravel的Blade这样写:

{{ $post->title }}

ThinkPHP的模板标签则是:

{$post.title}

两者最终都会在服务端编译成PHP输出。我个人的体会是,Blade的{{ }}语法和前端界面的融合度更高,不容易和原生PHP代码混淆,ThinkPHP的标签风格则更接近Smarty,老程序员上手会很快。没有绝对的高低之分,关键看你手里的团队之前用什么。

3. 数据库设计与核心表结构

3.1 业务主体表设计

整套系统的数据库是典型的“用户—内容—商品—订单”四类业务叠加,表数量我数了一下,核心业务表大概有十几张,外加管理员权限相关的表。这里我挑最关键的表结构展开说明,方便你直接参考设计思路。

users表不用多说,除了常规的手机号、密码、昵称、头像、性别、注册时间外,还多了几个和社区功能相关的字段,比如积分、个人简介、账号状态。注意Laravel在密码处理上默认用的是bcrypt加密,字段长度要预留到255位。

线路表是商城和社区内容结合的载体,我管它叫lines表。包含线路标题、封面图、轮播图组、出发地、目的地、出行天数、行程亮点简介、详细行程内容、费用说明、注意事项、最低成人价、原价、销售状态、排序权重、是否置顶等字段。这里有两个字段特别值得注意:一个是销售状态status,用于控制前台是否展示,另一个是审核状态audit_status,用于区分草稿、待审核、已通过、已驳回。旅游线路和普通商品不一样,它不是一条简单规格的SKU,可能涉及多个团期、多档价格,所以我把团期和价格拆到了一张独立表line_plans里,关联线路ID并且记录出发日期、余位数量、成人价、儿童价、优惠价格等。

订单表orders记录订单号、用户ID、线路ID、团期ID、出行人数、总金额、支付状态、支付时间、订单状态、取消原因、退款状态等字段。订单创建时会同时往order_items表里写入订单明细,保证一条订单可以拆出多个人头或多个附加项。商品和订单的关系必须用订单快照的方式保存,不能只存商品ID,否则商品后续改价或下架,历史订单数据就会失去参考意义。

社区内容这块,我命名为posts表,记录用户发布的游记或提问,包含标题、正文内容、配图、所属目的地、标签、浏览量、点赞量、评论量、审核状态等字段。评论单独拆了一张comments表,通过parent_id字段支持楼中楼回复,同时用commentable_type和commentable_id的多态关联来兼容“评论线路”和“评论帖子”两种场景。

管理员表则采用RBAC权限模型,包含admins、roles、permissions三张主表,另加role_admin和permission_role两张关联表,用来实现管理员分组授权。

3.2 关键表的字段设计参考

为了让你能直接抄作业,我简化出一版可以跑的建表SQL思路,以线路表和订单表为例:

CREATE TABLE `lines` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `title` varchar(150) NOT NULL COMMENT '线路标题', `cover_image` varchar(255) DEFAULT '' COMMENT '封面图', `images` text COMMENT '轮播图,JSON数组', `departure` varchar(50) DEFAULT '' COMMENT '出发地', `destination` varchar(100) DEFAULT '' COMMENT '目的地', `days` tinyint DEFAULT 1 COMMENT '行程天数', `price_min` decimal(10,2) DEFAULT 0 COMMENT '最低价', `original_price` decimal(10,2) DEFAULT 0 COMMENT '原价', `content` longtext COMMENT '行程详情', `tips` text COMMENT '出行注意事项', `status` tinyint DEFAULT 0 COMMENT '0下架 1上架 2售罄', `audit_status` tinyint DEFAULT 0 COMMENT '0草稿 1待审 2通过 3驳回', `sort` int DEFAULT 0 COMMENT '排序权重', `user_id` bigint DEFAULT 0 COMMENT '发布管理员ID', `created_at` timestamp NULL, `updated_at` timestamp NULL, PRIMARY KEY (`id`), KEY `idx_status_sort` (`status`, `sort`), KEY `idx_destination` (`destination`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='旅游线路表';
CREATE TABLE `orders` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '唯一订单号', `user_id` bigint NOT NULL, `line_id` bigint NOT NULL, `plan_id` bigint NOT NULL COMMENT '团期ID', `travel_date` date DEFAULT NULL COMMENT '出行日期', `adult_count` tinyint DEFAULT 1, `child_count` tinyint DEFAULT 0, `total_amount` decimal(10,2) DEFAULT 0, `pay_amount` decimal(10,2) DEFAULT 0 COMMENT '实付金额', `status` tinyint DEFAULT 0 COMMENT '0待支付 1已支付 2已完成 3已取消 4退款中 5已退款', `pay_time` datetime DEFAULT NULL, `cancel_reason` varchar(255) DEFAULT '', `remark` varchar(255) DEFAULT '' COMMENT '用户备注', `created_at` timestamp NULL, `updated_at` timestamp NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user_status` (`user_id`, `status`), KEY `idx_line_plan` (`line_id`, `plan_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

3.3 关联查询与索引设计心得

数据库设计阶段最容易出的问题是没有为常用查询设计好索引。比如前端线路列表页总要按“上架状态 + 排序权重”来拉数据,我就在lines表里建了联合索引idx_status_sort,避免文件排序。订单列表页要按用户查自己的订单,同时又要过滤状态,所以orders表里建了idx_user_status联合索引。评论查询总是根据帖子ID或线路ID查,comments表的commentable_id和commentable_type也必须建联合索引。这些基础工作做好了,后端的查询逻辑写起来会轻松得多。

关联查询方面,Laravel的Eloquent带来的体验提升很大。过去手工写多条SQL再在PHP里拼接的做法容易出错,现在直接在模型里声明关联方法,然后用with预加载查出来,效率高且代码直观。但要注意一个问题:关联字段的类型不一致会导致隐式转换问题,比如posts表的user_id是整型,users表的id是整型,两边都建表时类型必须对齐,否则关联查询会全表扫描甚至查不出数据。

4. 核心业务模块的实现细节

4.1 用户认证与登录状态管理

用户模块是整套系统的入口。Laravel版直接使用了自带的auth脚手架,我在复现时把默认的email认证改成了手机号加密码登录,改动量不大。登录成功后会生成会话,前端页面通过auth中间件控制需要登录才能访问的页面,比如购物车、下单、个人中心、发布游记等。这里有一个容易踩的坑:默认的登录限制只校验密码,但社区功能要求封禁用户不能登录,所以我在认证逻辑里加了一层状态判断,登录时先检查账号状态字段,如果被封禁就抛出异常并提示。

ThinkPHP版的认证实现则相对原始,登录后把用户信息写入session,退出时清空session,做基本的前后台分开登录。权限控制方面,Laravel推荐用Gate授权加策略类,ThinkPHP则更多靠中间件或公共控制器基类里写check方法。小项目中不用把这个设计得太复杂,但要记住一点:管理员和普通用户必须分开登录、分开会话,不能在同一个认证体系里混着来,否则很容易出现越权操作。

4.2 线路产品、购物车与库存控制

商城的核心产品是旅游线路,但它跟普通电商的SKU逻辑不太一样。普通商品是颜色、尺寸这类静态规格,旅游线路则要有“团期”这个时间维度。以某条线路为例,一个四川游线路可能包含4月20日、5月1日、5月15日等多个团期,每个团期有独立库存和价格。所以购物车里的商品项必须同时携带线路ID和团期ID,下单时必须校验团期库存是否足够。

库存操作我测试过好几种方案。最简单的是在PHP里先SELECT余额再UPDATE扣减,但并发高时一定会出现超卖。稳健些的做法是扣减库存时利用数据库的原子更新语句,再加上事务控制:

UPDATE line_plans SET remain_count = remain_count - 2 WHERE id = ? AND remain_count >= 2;

受影响行数为1才代表扣减成功,然后继续走创建订单、支付流程。如果受影响行数为0,说明库存不足,事务回滚。这套方案不需要引入Redis也能保证基本正确性,当然后续项目大了再接Redis做秒杀级别的控制也不迟。还有一个细节:支付超时后订单要自动取消并释放库存,这里我建议用Laravel的定时任务或者队列延迟消息来处理,不要让用户取消订单时才发现库存没有退回。

4.3 订单创建与支付回调

订单创建的代码是整套系统的核心。为了保证数据一致,我把“创建订单记录 + 写入订单明细 + 扣减对应团期库存”这三步放进同一个数据库事务里。状态机是前文提到的那一套:待支付、已支付、已完成、已取消、退款中、已退款。

支付环节没有接真实的第三方支付接口,源码里用了模拟支付来打通流程。我把它改造成可以对接标准微信支付或支付宝异步回调的格式,核心逻辑是:客户端生成订单后跳转到支付页,支付成功后第三方服务异步通知站点的回调地址,回调里必须先校验签名,确认是平台发送的通知,再根据out_trade_no找到订单,处理订单状态并发送提醒。

支付回调处理有个特别重要的原则:回调处理必须是幂等的。因为第三方支付通知可能会重复推送,如果代码没有做防重处理,会导致订单状态被反复更新,甚至用户余额多扣一条记录。我的处理方式是每次回调先检查订单当前状态,只有“待支付”状态才更新为“已支付”,其他状态一律忽略。这是整套系统里最值得认真看的一段代码。

4.4 社区交流:帖子、评论、点赞的关系设计

社区模块在业务上独立于商城,但浏览者往往从游记看到线路,又从线路跳去下单,两个模块之间有天然的导流作用。我设计帖子时没有让它直接关联线路表,而是通过“目的地”这个抽象概念间接关联。用户在写游记时选择目的地,系统可以在目的地下方展示关联线路,这样逻辑更干净,也方便以后把内容扩展到酒店、美食等其他品类。

评论功能是经典的多态关联模型,既能评论帖子又能评论线路,而且支持回复。我在comments表里用commentable_type、commentable_id两个字段定位目标对象,再通过parent_id字段定位回复关系。列表查询时,按commentable_id和parent_id先查出顶级评论,再统一取出所有回复,用PHP按parent_id分组拼装成树形结构,比在SQL里做递归查询要高效得多。

点赞功能更简单,一张likes表记录用户ID和被点赞对象的多态关联,设置唯一索引防止重复点赞。点赞量的实时统计也可以在表里加一个冗余字段,每次点赞成功后update对应计数,这样列表页不用每次count查询。

5. 前端渲染、后台管理与部署要点

5.1 前端页面方案与资源处理

前端没有使用前后端分离架构,还是传统的服务端渲染模式。Laravel版用Blade模板,ThinkPHP版用模板标签。页面结构上,首页推荐位、线路列表、线路详情、社区列表、帖子详情、购物车、订单结算、个人中心这几个页面是必需的基础页面。

模板层面有个经验值得分享:公共头部和底部一定要抽成include文件,不然几十个页面里改一个导航链接会改到崩溃。静态资源我统一放在public目录,引入方式是域名加资源路径,这样部署到线上时可以方便地换成CDN地址。图片上传是项目的刚需,封面图和相册图走后台统一上传接口,保存到storage目录或者OSS,数据库只存相对路径。注意storage下的图片要记得执行软链接命令,否则前端访问不到。

5.2 后台管理端的权限矩阵

后台管理相比前台功能简单一些,但权限控制不容忽视。管理员表走RBAC模型,有超级管理员、线路编辑、社区审核、订单客服等角色。每个角色关联若干权限点,权限点精确到“菜单 + 按钮操作”级别。我在代码里封装了一个权限中间件,每个后台路由都指定需要的权限标识,越权访问直接返回403页面。

这套权限体系的优点是新增角色不用改代码,在后台配置权限即可;缺点是前期配置表结构工作量稍大,但考虑到这套系统后续会被当成毕业设计或商城运营后台来用,多投入这一步非常值得。社区内容的审核流程也必须放进权限矩阵:用户发布的游记不会直接出现在公共列表页,而是进入审核状态,只有审核通过的内容才展示,这个机制从一开始就要考虑进去,不能等上线之后再补。

5.3 配置、日志与部署要点

部署这块我踩过几个小坑,提前给你提个醒。首先,Laravel项目的入口需要软链到Web根目录,或者配置Nginx的root指向public目录,同时写好伪静态规则。下面是一个通用Nginx配置片段:

server { listen 80; server_name example.test; root /var/www/html/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/tmp/php-cgi.sock; } }

其次,Laravel的环境配置放在.env文件里,千万别把数据库密码和支付密钥硬编码在代码中。每个环境单独维护一套.env,迁移到服务器时只需配置好环境变量就能无缝切换。日志方面,Laravel默认把日志写入storage/logs目录,建议按天切分并定期清理,避免磁盘被打满。

6. 安全加固与性能优化

6.1 安全防护清单

这类带有交易功能的系统,安全一定要放在开发优先级的高位。我按重要程度整理了一份防护清单,每一条都对应具体的代码动作:

  • 防止SQL注入:所有数据库操作都使用ORM或查询构造器的参数绑定,禁止拼接字符串进SQL。
  • 防止XSS攻击:前端输出用户提交的正文时,必须经过HTML实体转义,富文本内容用白名单过滤,只允许特定标签和属性。
  • 防止CSRF攻击:所有表单都带上CSRF令牌,Laravel中间件默认开启CSRF保护,ThinkPHP也提供了token验证函数,前后台都要开启。
  • 文件上传安全:上传接口必须校验文件后缀、MIME类型和文件内容魔数,只允许图片类型;图片保存后单独设置访问目录,不允许通过PHP执行。
  • 支付验签:所有第三方回调都要验签,验签方式以官方文档为准,不能用客户端传参替代服务端签名校验。
  • 会话安全:登录接口加上频率限制,防止暴力破解;账号密码加密存储,密码重置仅允许通过绑定的信息验证。

6.2 性能瓶颈排查与缓存策略

线路详情页是访客高频访问的页面,数据库查询也最复杂:要查出线路基本信息、团期价格、帖子评价、运营推荐等内容。如果每次都实时查全,数据库压力很大。我的优化策略是给线路详情页套上页面级缓存,缓存时间设为10分钟,缓存键包含线路ID,后台编辑线路时主动清除对应缓存。社区列表和热门游记也可以使用Redis做简单缓存,排序结果和数量统计全部缓存起来,避免高频QPS直接把数据库打穿。

另一个常见的性能问题出在列表查询的N+1问题上。比如查询线路列表时,每条线路都要查关联评论数、收藏数和最低价格,如果每条线路都单独执行一条SQL,10条线路就会执行几十次SQL。解决方法是使用ORM的with预加载,用一次IN查询把所有关联对象取出来,再在内存中完成组装。这个优化做完,接口响应时间往往能下降一个量级。

7. 常见问题与排查技巧实录

7.1 环境安装类问题

PHP版本不匹配是最常见的问题。项目如果你用PHP 7.4去跑Laravel 9,composer install会直接报错,原因是依赖包要求的PHP版本无法满足。解决方法是按项目要求安装对应PHP版本,Windows用户可以用集成环境切换PHP版本,Linux用户建议用软件源或编译安装多个版本并存。另一个高频问题是缺少PHP扩展,比如PDO、OpenSSL、Mbstring、Redis扩展缺失导致启动报错,安装扩展后重新加载PHP-FPM即可。

数据库方面,吐槽最多的是中文乱码。建表时所有表和字段统一使用utf8mb4字符集,数据库连接串也要指定charset。如果老表的字符集已是utf8,建议连带现有数据一起转换,方法是在数据库命令行中执行ALTER TABLE语句转换字符集。

7.2 业务逻辑类问题

库存超卖虽然我前面已经提过,但值得再次强调。如果你发现本地并发测试时订单数量大于团期库存数量,十有八九是没做原子扣减。排查时先打开数据库慢查询日志,检查下单接口的UPDATE语句是否包含条件判断,或者在PHP日志里查看是否走了“先查后改”的逻辑。还有一个被忽视的坑:用户支付超时后系统自动取消订单,如果定时任务没跑,那些超过支付期限的订单会一直占用库存。解决办法是配置Laravel的调度任务,每分钟扫描超时未支付订单并自动关闭。

支付回调失败的原因通常集中在两点:一是回调地址被第三方平台无法访问,本地测试时没有使用内网穿透工具,导致回调无法到达;二是回调代码里直接抛异常,导致第三方重推几次后放弃。正确做法是回调里捕获所有异常、写日志、返回成功标识,保证接口永远可用。

7.3 框架版本兼容与常见坑

框架版本兼容问题非常考验细心。Laravel不同大版本之间API有变化,比如Laravel 8和Laravel 11在中间件写法上完全不同,直接复制网上旧代码运行会报类不存在。建议动手前先查看composer.json里指定的框架版本,再按对应版本文档查API。ThinkPHP方面经常遇到的坑是模板缓存不更新,修改模板文件后页面还是旧内容,需要删除runtime目录下的编译缓存并重新生成,本地开发建议关闭模板缓存。还有一个小细节,ThinkPHP的数据库配置中如果使用了PDO的持久连接,在高并发下可能导致连接堆积,测试时调整回普通连接即可。

8. 最后说点实际的:搭建完这套项目之后的个人体会

整套项目完整搭下来,我更强烈地感受到,这种旅游线路社区商城系统的价值不在某一个功能多精巧,而在于它把电商、社区、内容管理、权限体系、订单状态机这些常见业务串到了同一个舞台上,逼着你去思考数据怎么设计、状态怎么流转、安全怎么保障、性能怎么兜底。如果你目前处于“会写语法但不知道一个完整系统长什么样”的阶段,照着这个项目拆一遍,收获会非常大。

从实际操作中,我总结了几条原则性的经验。第一,动手写代码之前先把订单状态机画清楚,状态不明确的系统早晚会在支付回调或退款环节爆炸。第二,社区功能的审核机制一定要提前设计,哪怕内部测试用不到,上线后也一定会遇到违规内容问题。第三,双框架存在的源码包只能作为学习对照,真正做二次开发时只保留一个框架,不然每次改需求都要双倍工作量。最后分享一个调试小技巧:写ORM查询遇到结果不对时,开启框架的SQL查询日志,把最后的原生SQL拉出来到数据库客户端里执行一遍,往往马上就能找到问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进