简介:基于ThinkPHP框架的新闻发布系统完整源码,面向PHP初学者与中小型Web项目开发者,提供前台展示与后台管理一体化实现方案。系统采用MVC模式组织模型、视图与控制器,集成百度UEditor富文本编辑器,覆盖文章发布、分类管理、图片上传等典型功能,适合课程设计、毕业设计、实战练习与快速二次开发。RAR压缩包共1117个文件,压缩后仅6.43MB,内容以414个PHP文件与503个GIF演示图为主,辅以HTML/CSS/JS前端资源、SQL数据脚本以及UEditor服务端接口配置,可分层查阅源码、界面演示与数据表结构。截至当前已有4735人学习下载,通过该资源可完整理解ThinkPHP的路由、数据库操作、模板渲染与安全过滤机制,并参照UEditor从部署到接口接入的完整用例,配合大量动图演示降低上手门槛,是一份易学易用的实战参考。
1. 新闻发布系统不难做,难的是你还没找到一个能照抄的架子
刚接触 ThinkPHP 那阵子,我最头疼的事不是写不出增删改查,而是写完前台又要写后台,写完列表又要搞编辑器,好不容易跑通了,一回过头来发现代码乱成一锅粥。这个新闻发布系统就是冲着这个痛点来的——它把前台展示、后台管理、编辑器接入、MVC 分层一次给齐,学习的价值在于它的完整度刚刚好:没有多余的企业级封装,也没有省掉关键流程。你照着敲一遍,理解了一个请求是怎么从浏览器跑到控制器、再落到模板渲染出来的,比看十篇框架教程都有用。
这套东西适合谁?刚把 PHP 语法过了一遍想接触框架的初学者,以及手头有个简单内容站点需求、不想从零开始重写后台的开发者。它跑起来以后的真实体验是:后台登录、栏目管理、文章发布、图片上传、前台列表和详情页,全流程是通的。你拿到手第一件事不是改需求,而是先把它跑通,看数据是怎么从数据库走到页面上的。有了这个底子,二次开发最多头疼业务设计,不会再在框架本身上翻车。
2. 技术选型与目录结构:ThinkPHP 三件套 + MVC 分层怎么落到盘面上
这个项目用的是 ThinkPHP 框架,配合 PHP 原生 Web 运行环境,前端集成 UEditor 富文本编辑器,整体是一个标准的 MVC 结构。项目的易学性首先体现在目录组织上——ThinkPHP 的惯例是 Application 目录下按模块划分,Home 模块管前台,Admin 模块管后台,公共代码放 Common,这样前后台互不干扰,学习的时候也方便对照。
2.1 核心目录逻辑:Home 模块与 Admin 模块的分工
用 ThinkPHP 开发的第一个认知,就是模块化目录不是摆设。这个项目的 Application 目录是这样的布局:
Application/ ├── Common/ // 公共配置和公共函数 │ └── Common/function.php ├── Home/ // 前台模块 │ ├── Controller/ │ │ ├── IndexController.class.php │ │ └── ArticleController.class.php │ ├── Model/ │ │ └── ArticleModel.class.php │ └── View/ │ ├── Index/ │ └── Article/ ├── Admin/ // 后台模块 │ ├── Controller/ │ │ ├── LoginController.class.php │ │ ├── IndexController.class.php │ │ └── ArticleController.class.php │ └── View/ │ ├── Login/ │ ├── Index/ │ └── Article/ └── Runtime/ // 运行时缓存目录前台模块和后台模块在 Controller 层各管各的,Model 层可以共用同一张数据表。这个设计对学习最友好的地方在于:你想看前台怎么查文章,直接开 Home 模块的 ArticleController;你想看后台怎么处理表单提交,直接开 Admin 模块的 ArticleController。两个文件放在不同目录,但功能边界非常清晰。
2.2 数据库设计要点:文章表和栏目的关联
新闻系统的核心表通常是 news 和 category,这个项目跑起来需要两张主表。设计上保持了最低成本的三字段核心关联:文章表里存一个分类ID,外键约束没做,为的是降低初学者的理解成本,靠模型层去保证数据一致性。
CREATE TABLE `news` ( `id` int(11) NOT NULL AUTO_INCREMENT, `cat_id` int(11) NOT NULL DEFAULT '0' COMMENT '分类ID', `title` varchar(200) NOT NULL COMMENT '标题', `content` text COMMENT '正文内容', `thumb` varchar(255) DEFAULT NULL COMMENT '缩略图', `create_time` int(11) DEFAULT NULL COMMENT '创建时间', `update_time` int(11) DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `category` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '分类名', `sort` int(11) DEFAULT '0' COMMENT '排序', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;create_time 用 int 类型存时间戳是 ThinkPHP 自动时间戳的约定,写入的时候框架会自动填充,不需要手动处理。news 表和 category 表通过 cat_id 关联,这个字段在列表页做 JOIN 查询时要用到。缩略图字段 thumb 默认允许为空,有些文章没有配图也不影响展示。
2.3 UEditor 接入的位置:它是一个独立上传入口
UEditor 在这个项目里的角色值得单独拿出来说。很多初学者以为编辑器就是一个 textarea 换成了富文本,其实它的核心在于图片上传。UEditor 前端是一个独立组件,后端需要一个接收上传请求的入口。这个项目把 upload 处理放在 Admin 模块的 PublicController 里,日志和权限校验都在那一个动作里完成。
UEditor 的上传机制是:编辑器把图片通过 HTTP POST 传到后端接口,后端保存文件并返回一个 JSON 格式的地址,编辑器再把地址回填到内容区。这个链路如果断了,最典型的症状就是文字能录入、图片显示红叉。所以拆项目时,优先搞懂这块的数据流向,比纠结编辑器本身的配置项更有价值。
3. 核心模块拆解:前台列表、后台管理和编辑器上传的完整链路
中间这些代码是这个项目最有营养的部分。三个关键场景串起整个系统:前台文章列表和详情展示、后台登录与文章发布、UEditor 的图片上传。每个场景都走一遍请求链路的思路,从路由到控制器到模板渲染。
3.1 前台文章列表:控制器怎么取数、模板怎么循环
前台首页的逻辑是最简单也最经典的:查询文章列表,按时间倒序,传到模板去循环。对应的 IndexController 里的代码大致是这个样子:
<?php namespace Home\Controller; use Think\Controller; class IndexController extends Controller { public function index() { // 实例化文章模型 $articleModel = M('news'); // 查询已发布文章,按创建时间倒序,一次取10条 $list = $articleModel->order('create_time DESC')->limit(10)->select(); // 把数据assign给模板变量 $this->assign('list', $list); $this->display(); } }这里的 M('news') 方法返回值是一个 Model 对象,直接链式调用 order 和 limit 就能完成查询。select() 返回的是一个二维数组,每一行是一条文章记录。display() 方法会自动匹配当前模块下对应控制器的同名模板——也就是 Application/Home/View/Index/index.html,不需要在代码里写模板路径。
模板这边的循环代码用的是 ThinkPHP 内置标签,写起来很接近原生 PHP,但多了些便利语法:
<volist name="list" id="article"> <div class="news-item"> <h3> <a href="{:U('/article/' . $article['id'])}">{$article.title}</a> </h3> <p>{$article.create_time|date='Y-m-d H:i',###}</p> </div> </volist>volist 标签是 ThinkPHP 中最常用的循环标签,name 对应控制器 assign 的变量名,id 是循环体内的变量名。模板函数调用用竖线语法。日期时间戳在模板里直接格式化,减少了控制器里的额外处理。注意 U 函数生成路由地址,配合后端的路由配置能输出伪静态链接。
3.2 后台登录与鉴权:Session 校验是怎么拦截的
后台和前台最大的区别在于访问控制。登录成功后写入 Session,后台所有控制器继承一个需要校验登录状态的基类控制器。这个设计在 ThinkPHP 里非常常规,也最值得抄。
<?php namespace Admin\Controller; use Think\Controller; class BaseController extends Controller { public function _initialize() { // 检查用户是否已登录 if (!session('user_id')) { // 未登录则跳转到登录页 $this->redirect('Login/index'); } } }_initialize 方法是 ThinkPHP 控制器的初始化方法,它会先于控制器里的任何动作执行。放在 BaseController 里之后,后台其他控制器只要继承这个类,就自动拥有了登录拦截能力:
<?php namespace Admin\Controller; class ArticleController extends BaseController { public function add() { // 继承BaseController,自动执行登录检查 $this->display(); } }这个继承思路是后台模块的核心骨架。以后想加权限细分,比如不同管理员只能管理不同栏目,可以直接在 BaseController 里扩展校验逻辑,控制器的动作方法不用大改,这一点对二次开发很友好。
3.3 文章发布的后端处理:接收表单并写入数据库
后台发布文章的表单提交到 Admin 模块的 ArticleController 的 insert 动作,处理逻辑包含了字段过滤和时间戳处理。这里有一个很重要的安全习惯:POST 过来的数据不要直接整包入库。
public function insert() { $data = I('post.'); $data['create_time'] = time(); $data['update_time'] = time(); $model = M('news'); $result = $model->add($data); if ($result) { $this->success('发布成功', U('Article/index')); } else { $this->error('发布失败'); } }I('post.') 是 ThinkPHP 的输入过滤函数,会自动处理 SQL 注入和 XSS 的常见攻击向量,但项目里缩略图和正文本身是 HTML 内容,content 字段会经过 UEditor 的过滤处理。add 方法返回的是新插入记录的自增 ID,判断这个值是否大于 0 就能确认写入是否成功。
这里有个常见翻车点:如果表单里没有 create_time 字段,直接 I('post.') 取到的数组是缺这个字段的,所以必须在控制器里手动补上时间戳,否则数据库会报字段不能为空。UEditor 的内容字段 content 也是通过同一个 POST 数组提交的,因为 HTML 源码里已经做了转义,入库前不需要再 strip_tags。
3.4 UEditor 图片上传:文件接收与 JSON 返回
UEditor 的后端接口是这个项目里最容易出问题的部分,核心代码如下:
public function upload() { $upload = new \Think\Upload(); $upload->maxSize = 3145728; // 限制文件大小3M $upload->exts = array('jpg', 'gif', 'png', 'jpeg'); $upload->rootPath = './Uploads/'; $upload->savePath = 'editor/'; $info = $upload->uploadOne($_FILES['upfile']); if (!$info) { $result = array('state' => 'ERROR', 'message' => $upload->getError()); } else { $result = array( 'state' => 'SUCCESS', 'url' => '/Uploads/' . $info['savepath'] . $info['savename'], 'title' => $info['savename'], ); } echo json_encode($result); }UEditor 规定的返回格式必须包含 state 和 url 两个字段,前端拿到 SUCCESS 之后才会把 url 回填到编辑区。uploadOne 方法接收的是表单文件域的名字,这个参数值必须和 UEditor 前端配置的 fileFieldName 一致,默认是 upfile。rootPath 是上传根目录,savePath 会在根目录下再建子目录,保存路径拼接起来就是最终的访问 URL。
如果上传返回了 SUCCESS 但图片访问 404,优先检查 rootPath 目录是否有写入权限,以及 Public 目录下是否已经配置了 Uploads 的访问别名。
4. 跑起来的完整过程:环境准备、配置修改、数据初始化
想把这个项目从压缩包变成一个能访问的站点,一共就三步:准备运行环境、改数据库配置、导入 SQL 初始化数据。这里用 PHPStudy 这类集成环境说明,加载 ThinkPHP 源码包之后打开网站应该直接看到首页,最怕的是看到一屏报错,先不要慌,按下面的顺序排查。
4.1 修改数据库连接配置
ThinkPHP 3.2 的数据库配置在 Application/Common/Conf/config.php 里,你需要改的是主机、库名、账号和密码四项:
<?php return array( 'DB_TYPE' => 'mysql', 'DB_HOST' => '127.0.0.1', 'DB_NAME' => 'news_system', 'DB_USER' => 'root', 'DB_PWD' => 'your_password', 'DB_PORT' => '3306', 'DB_PREFIX' => '', );DB_PREFIX 默认留空,如果建表时统一加上了前缀,比如 tp_news,这里需要改成 tp_。改完配置后,框架里所有 M('news') 查询都会自动拼接前缀,不需要模型层手动处理。如果数据库密码包含特殊字符,比如 @ 或 #,建议用引号包好,避免解析错误。
4.2 导入数据并配置伪静态
新建一个空数据库,导入项目自带的 SQL 文件。导入完成后,检查一下 news 表里有没有测试数据——正常情况下应该有两条左右的示例文章,方便确认前台列表能显示出内容。前台的路由配置在 Application/Home/Conf/config.php 里:
'URL_ROUTER_ON' => true, 'URL_ROUTE_RULES' => array( 'article/:id' => 'Article/detail', ),开启路由后,访问 /article/1 会映射到 ArticleController 的 detail 动作,参数 id 会被自动绑定到方法参数里。这里有一个环境相关的细节:如果你用的是 Nginx,需要在 server 配置里加一行 try_files 指令,否则所有带路径的请求都会报 404;Apache 环境则需要在项目根目录放置 .htaccess 文件。这是跑通过程中最先踩的坑。
4.3 后台入口与默认账户
后台入口默认是 /index.php/Admin/Login/index(或伪静态路径 Admin/Login/index)。这个项目的默认管理账号密码,通常写在后端的登录代码里或初始化文档中,一般是 admin / admin123 这类便于测试的组合。登录成功后,后台首页能看到文章列表、添加文章的入口。
后台新增文章页面的最后一步,检验整个项目是否真正跑通的关键路径是:在 UEditor 里插入一张图片并确认能显示、然后点击发布,再回到前台首页刷新列表,看新发布的文章是否排到第一条。这一步成功,说明数据库写入、上传目录、模板渲染三个环节全部是通的。
5. 避坑记录:跑这个项目一定会遇到的 4 个问题
这个项目虽然定位易学易用,但它毕竟是 ThinkPHP 3.2 时代的产物,放到今天的 PHP 版本和 Web 服务器环境下,有几个问题几乎是必现的。我把最常见的几个坑连原因带解法整理出来,照着查能省不少时间。
5.1 访问后台地址出现 404 页面
现象是输入后台 URL 后页面显示 404,但前台访问正常。原因是后台模块的入口没有被正确解析,或者服务器伪静态规则没有覆盖到 Admin 模块。解决方式是检查入口文件 index.php 是否存在于项目根目录,并确认 Nginx 下伪静态规则里的 rewrite 是否正确匹配了路径。用 PHPStudy 的话,直接换成 Apache 模式通常能立刻解决。
5.2 UEditor 图片上传返回失败
上传图片时前端提示上传失败,浏览器控制台看到 HTTP 500 错误。最常见的原因是 ThinkPHP 的 Upload 类在保存文件时没有目录写入权限。解决方式是给 Uploads 目录设置可写权限,Linux 下执行 chmod -R 777 Uploads,并检查 PHP 的 upload_max_filesize 配置是否小于代码里设置的 3M 上限。
5.3 使用 PHP 7+ 运行时报出 deprecated 错误
ThinkPHP 3.2 在设计时主要支持 PHP 5.x,跑在 PHP 7.2 以上环境中,页面顶部或多或少的会飘出一行行 deprecation 提示。原因是框架内部使用了不少 PHP 7 中已废弃的语法特性。解决方式有两种:第一种是修改 php.ini 中的 error_reporting 配置,屏蔽 E_DEPRECATED 级别错误;第二种是把入口文件 index.php 里加上一行错误级别控制代码。实际部署时用第一种,开发调试时应该先把代码错误修掉。
5.4 文章内容在详情页不换行
后台录入文章时,编辑器里分段明确,前台展示时所有段落挤在一起。原因是 UEditor 生成的 HTML 标签是基于 p 标签的,但模板输出时被样式表覆盖了段间距。解决方式是在详情页模板中为内容区域添加一个容器 class,并附上标签默认样式。不是框架问题,是 CSS 重置造成的常见现象。
6. 二次开发实战:给文章表增加点击量统计与热门排序
当你把项目跑得滚瓜烂熟之后,最想做的第一件事肯定是加点自己的东西。这里以一个常见的需求为例——给文章增加点击量统计,并在前台按点击量排序展示热门文章。改动很小,但能帮你彻底理解 ThinkPHP 的模型操作与模板数据流之间的关系。
先在 news 表里加一个 hits 字段,默认值为 0:
ALTER TABLE `news` ADD `hits` int(11) NOT NULL DEFAULT '0';然后在前台的 ArticleController 的 detail 方法里,取出文章详情的同时执行一次自增操作,把点击量加上去:
public function detail($id) { $articleModel = M('news'); // 点击文章时,浏览量加1 $articleModel->where('id=' . $id)->setInc('hits'); // 查询文章详情 $article = $articleModel->where('id=' . $id)->find(); $this->assign('article', $article); $this->display(); }setInc 是 ThinkPHP 自带的字段自增方法,它内部会生成一条 UPDATE 语句,因为先执行了自增再查询,所以详情页拿到的 numbers 已经是当前最新的点击量。要加上热门文章列表,就在前台 Index 控制器里再写一个按 hits 排序的列表:
$hotList = $articleModel->order('hits DESC')->limit(5)->select(); $this->assign('hotList', $hotList);模板里把 hotList 循环输出到侧边栏。这就是一次完整的增量开发——数据库加字段、控制器加逻辑、模板加循环,三个文件动完,功能就通了。做这个改动时,我习惯每改一步就在浏览器里刷新验证一次:先看 hits 字段有没有值,再点一次看数字加不加一,最后看排序变不变。从那以后,我给项目加任何字段都强制走一遍这种最小验证流程,不再攒到最后一起测。希望你拿到这个项目以后也能保持这个习惯,进步就藏在这些一个字段一个字段养成的直觉里。
本文还有配套的精品资源,点击获取