拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

新闻管理系统课程设计:从需求到表结构与核心功能实现

新闻管理系统课程设计:从需求到表结构与核心功能实现

简介:这份文档资料面向软件综合实习、课程设计及Web开发入门者,提供一套完整的新闻管理系统设计方案,帮助读者理解前后台分离的典型网站架构与需求分析思路。资源包内仅含1个doc文档,约77KB,以图文结合方式呈现系统概述、功能结构图与需求分析,便于直接引用或作为实习报告参考。文档详细拆解了前台的主页导航、新闻分类导航、站内模糊搜索、分类显示与友情链接,以及后台的管理员设置、新闻信息管理和友情链接管理,并给出数据库表设计与安全验证思路。目前已有534人学习下载,适合需要快速搭建新闻发布平台原型、撰写实习文档或梳理Web项目模块划分的读者,可据此明确功能边界、规划开发顺序并补充实现细节。

1. 新闻管理系统拆包:一份能直接跑通课程设计的完整文档

课程设计选题拿到手,最怕的不是功能多,而是文档写得云里雾里,照着做都跑不起来。这份《新闻管理系统.doc》属于典型的软件综合实习可选题目,核心就两件事:前台让浏览者按分类看新闻、按内容搜新闻,后台让管理员管新闻、管链接、管账号。它把功能结构图、需求分析、页面交互逻辑都写清楚了,适合正在赶课程设计或想快速搭一个新闻类 Web 应用原型的同学。文档里明确给出了 8 个新闻分类——时政要闻、财经观察、世界军事、科学教育、法制道德、社会百态、体育世界、时尚娱乐,以及前台搜索按新闻内容而非标题匹配这个关键差异点。拿到这份文档,你手里其实已经有了一份可落地的需求规格说明书,接下来要做的就是把它翻译成表结构、接口和页面。

2. 从需求到表结构:四张核心表怎么定字段

2.1 先理清数据实体和关系

文档里提到的功能模块看着多,落到数据库层面其实就四个核心实体:新闻、新闻类别、管理员、友情链接。新闻和类别是多对一关系,一条新闻属于一个类别,一个类别下有多条新闻。管理员和友情链接是独立实体,不跟新闻直接关联。常见做法是把类别单独建表而不是在新闻表里写死字符串,这样后台加分类、前台导航渲染都方便。友情链接表要存网站名称、网站地址和 LOGO 图片路径,文档里明确说了 LOGO 是上传的,所以字段类型得能存文件路径而不是直接存二进制。

2.2 建表 SQL 与字段说明

-- 新闻类别表 CREATE TABLE news_category ( category_id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(50) NOT NULL, -- 如"时政要闻""财经观察" sort_order INT DEFAULT 0 -- 控制前台导航显示顺序 ); -- 新闻表 CREATE TABLE news ( news_id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, -- 外键,关联 news_category title VARCHAR(200) NOT NULL, content TEXT NOT NULL, -- 搜索按此字段模糊匹配 publish_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (category_id) REFERENCES news_category(category_id) ); -- 管理员表 CREATE TABLE admin ( admin_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL -- 存哈希值,不存明文 ); -- 友情链接表 CREATE TABLE friendly_link ( link_id INT PRIMARY KEY AUTO_INCREMENT, site_name VARCHAR(100) NOT NULL, site_url VARCHAR(255) NOT NULL, logo_path VARCHAR(255) -- 上传后的相对路径 );

新闻表的 content 字段用 TEXT 类型,因为新闻正文可能很长,VARCHAR 在多数数据库里有长度上限。搜索功能按文档要求是对 content 做 LIKE 模糊匹配,所以 content 字段不需要额外加全文索引也能跑,但数据量大了会慢,后面章节会讲优化。admin 表的 password 字段长度给到 255 是为了存 bcrypt 哈希值,直接存明文是血泪教训,课程设计答辩时被问到安全实现会很被动。friendly_link 表的 logo_path 存的是上传后的相对路径,比如/uploads/logo_xxx.png,不是把图片二进制塞进数据库。

2.3 类别初始数据怎么灌

文档里列了 8 个固定分类,建完表直接插进去,前台导航就能渲染出来。

INSERT INTO news_category (category_name, sort_order) VALUES ('时政要闻', 1), ('财经观察', 2), ('世界军事', 3), ('科学教育', 4), ('法制道德', 5), ('社会百态', 6), ('体育世界', 7), ('时尚娱乐', 8);

sort_order 字段控制前台导航按钮的排列顺序,不写这个字段的话默认按 category_id 排,后期想调整顺序就得改主键,不现实。这 8 个分类名直接来自文档,不要自己编,答辩时老师对照需求文档一看就对得上。

3. 前台三个核心功能:分类导航、模糊搜索、详情页

3.1 分类导航与 MORE 按钮的实现逻辑

前台首页要展示分类导航,每个分类下显示若干条新闻标题,还有一个 MORE 按钮查看该分类更多新闻。常见做法是首页每个分类只查最新 5 条,MORE 按钮跳转到分类列表页查全部。这里有个容易翻车的地方:如果每个分类都单独发一条 SQL 查,8 个分类就是 8 次查询,页面加载会明显变慢。我一般会一次性把每个分类的前 5 条查出来,在应用层按 category_id 分组。

# 假设用 Python + Flask + SQLAlchemy 的写法 from sqlalchemy import func # 子查询:每个分类取前5条 subq = db.session.query( News.news_id, News.category_id, News.title, News.publish_time, func.row_number().over( partition_by=News.category_id, order_by=News.publish_time.desc() ).label('rn') ).subquery() top_news = db.session.query(subq).filter(subq.c.rn <= 5).all() # 按 category_id 分组,传给模板 from collections import defaultdict grouped = defaultdict(list) for row in top_news: grouped[row.category_id].append(row)

func.row_number().over(partition_by=..., order_by=...)是窗口函数,按分类分区、按发布时间倒序编号,外层筛 rn <= 5 就拿到每个分类最新的 5 条。MySQL 8.0 以上和 PostgreSQL 都支持,如果课程设计用的是 MySQL 5.7,得换成变量法或者干脆每个分类查一次——8 次查询在课程设计的数据量下也能忍,但要知道这是个取舍。MORE 按钮的链接指向/category/<category_id>,分类列表页按 category_id 过滤后分页展示。

3.2 站内搜索:按内容模糊匹配而不是标题

文档里特意加了个注意:此查询中按新闻内容进行查询,而不是新闻标题。这个细节很多同学会忽略,直接对 title 做 LIKE,答辩时被问“搜索是按标题还是内容”就答不上来。正确做法是对 content 字段做 LIKE 匹配,同时支持按类别筛选。

SELECT news_id, title, publish_time FROM news WHERE content LIKE CONCAT('%', :keyword, '%') AND (:category_id IS NULL OR category_id = :category_id) ORDER BY publish_time DESC LIMIT 20;

CONCAT('%', :keyword, '%')是拼接通配符,前后都加 % 表示内容中任意位置包含关键字都算命中。:category_id IS NULL OR category_id = :category_id这个写法让类别筛选变成可选条件——用户不选类别时 category_id 传 NULL,条件恒真;选了类别就精确过滤。注意 LIKE 模糊匹配在数据量超过几万条后性能会明显下降,因为前置 % 会导致索引失效。课程设计的数据量通常几百条以内,不用太担心,但如果想提前留一手,可以在 content 字段上建全文索引,MySQL 用FULLTEXT(content),查询时换成MATCH(content) AGAINST(:keyword IN NATURAL LANGUAGE MODE)。不过全文索引对中文分词支持有限,课程设计阶段用 LIKE 就够了。

3.3 新闻详情页与友情链接渲染

点击新闻标题跳转到详情页,详情页要展示标题、发布时间、正文内容和所属分类。友情链接在首页底部或侧边栏展示,每个链接显示 LOGO 图片和网站名称,点击跳转到目标地址。友情链接的 LOGO 图片路径从数据库读出来后,模板里拼成<img src="{{ link.logo_path }}">即可。这里有个小坑:如果上传的 LOGO 文件名包含中文或空格,URL 里需要做编码处理,否则图片加载不出来。常见做法是上传时把文件名重命名为时间戳加随机数,只保留扩展名,比如1690000000_abc123.png,从源头避免编码问题。

4. 后台管理:登录验证、新闻 CRUD 与链接管理

4.1 管理员登录与密码存储

后台入口在前台主页最下方,点击进入登录页。登录逻辑本身不复杂,但密码存储方式是个分水岭。文档里只说了输入用户名密码验证,没提加密,但实际做的时候如果明文存密码,答辩时大概率被追问。常见做法是用 bcrypt 或 werkzeug 的 generate_password_hash 做哈希。

from werkzeug.security import generate_password_hash, check_password_hash # 添加管理员时 hashed = generate_password_hash('plain_password', method='pbkdf2:sha256') db.session.add(Admin(username='admin', password=hashed)) # 登录验证时 admin = Admin.query.filter_by(username=form.username).first() if admin and check_password_hash(admin.password, form.password): session['admin_id'] = admin.admin_id # 跳转后台首页

generate_password_hash每次生成的哈希值都不一样,因为内部带了随机盐,所以不能用等号比对,必须用check_password_hash验证。session 里存 admin_id 而不是用户名,后续权限判断直接查 session 里有没有 admin_id 就行。后台所有页面入口都要加一个装饰器检查 session,没登录就跳回登录页,这是文档里“后台安全退出”功能的基础——退出时清空 session 即可。

4.2 新闻添加、修改、删除的完整流程

新闻管理是后台最核心的模块。添加一条新闻要选类别、填标题、填内容,保存后前台对应分类下就能看到。修改时可以改类别、标题、内容。删除一般做软删除还是硬删除?课程设计阶段硬删除就够了,直接DELETE FROM news WHERE news_id = ?。但要注意删除前确认这条新闻存在,否则影响行数为 0 时前端还提示成功就尴尬了。

@app.route('/admin/news/add', methods=['POST']) def add_news(): category_id = request.form.get('category_id', type=int) title = request.form.get('title', '').strip() content = request.form.get('content', '').strip() if not all([category_id, title, content]): return '参数不完整', 400 news = News(category_id=category_id, title=title, content=content) db.session.add(news) db.session.commit() return redirect('/admin/news/list')

request.form.get('category_id', type=int)直接转成整数,避免字符串类型的 category_id 插库时类型不匹配。strip()去掉首尾空格,防止用户不小心多敲了空格导致标题看起来正常但实际带空白字符。参数校验放在最前面,缺任何一个都直接返回 400,不要等到插库报错才处理。修改和删除的逻辑类似,修改是先查再改字段,删除是查到了再删,查不到返回 404。

4.3 友情链接的 LOGO 上传处理

友情链接管理要输入网站名称、网站地址,还可以上传 LOGO 图片。文件上传有几个必须处理的点:限制扩展名、限制文件大小、重命名文件、存到指定目录。

import os, time, random from werkzeug.utils import secure_filename ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} UPLOAD_FOLDER = 'static/uploads' def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/admin/link/add', methods=['POST']) def add_link(): site_name = request.form.get('site_name', '').strip() site_url = request.form.get('site_url', '').strip() logo = request.files.get('logo') logo_path = None if logo and allowed_file(logo.filename): ext = logo.filename.rsplit('.', 1)[1].lower() new_name = f"{int(time.time())}_{random.randint(1000,9999)}.{ext}" save_path = os.path.join(UPLOAD_FOLDER, new_name) logo.save(save_path) logo_path = f"/{save_path}" link = FriendlyLink(site_name=site_name, site_url=site_url, logo_path=logo_path) db.session.add(link) db.session.commit() return redirect('/admin/link/list')

allowed_file只放行常见图片格式,防止上传可执行文件。重命名用时间戳加随机数,避免同名文件覆盖,也避免中文文件名在 URL 里出问题。logo_path存的是以/static/uploads/开头的相对路径,模板里直接拼到 img 标签的 src 属性就能访问。注意上传目录要提前创建好,os.makedirs(UPLOAD_FOLDER, exist_ok=True)放在应用启动时执行一次。

5. 避坑与排查:课程设计里最容易翻车的五个点

5.1 搜索按标题匹配了,答辩被追问

现象:搜索功能做完了,输入关键字能搜到结果,但答辩时老师问“你搜的是标题还是内容”,一看代码发现写的是WHERE title LIKE ...。原因:文档里明确写了按新闻内容查询,但开发时习惯性对 title 做匹配。解决:把 SQL 里的 title 改成 content,同时在前端搜索框的 placeholder 里写清楚“输入新闻内容关键字”,别写“输入标题”。

5.2 后台未登录也能直接访问管理页面

现象:直接在浏览器地址栏输入后台新闻列表的 URL,不登录也能看到页面。原因:只做了登录页的验证,没有对后台每个页面做 session 检查。解决:写一个装饰器,在每个后台路由函数上加一层判断,session 里没有 admin_id 就重定向到登录页。

from functools import wraps from flask import session, redirect, url_for def login_required(f): @wraps(f) def decorated(*args, **kwargs): if 'admin_id' not in session: return redirect(url_for('admin_login')) return f(*args, **kwargs) return decorated

5.3 删除新闻后前台分类下还显示

现象:后台删了一条新闻,前台刷新后那条新闻还在。原因:删除操作没提交事务,或者前台页面有缓存。解决:确认db.session.delete(news)后面跟了db.session.commit(),另外前台页面如果用了浏览器缓存,加一个Cache-Control: no-cache响应头,或者开发阶段直接禁用缓存。

5.4 友情链接 LOGO 上传后不显示

现象:后台上传了 LOGO 图片,前台友情链接区域图片裂了。原因:上传路径存的是绝对路径,或者路径里少了 static 前缀,或者上传目录没有读权限。解决:检查数据库里 logo_path 字段的值,确保是/static/uploads/xxx.png这种格式,然后在浏览器里直接访问这个路径看能不能打开图片。打不开就是路径或权限问题,能打开但页面不显示就是模板里 src 拼错了。

5.5 新闻内容里的换行在前台变成一坨

现象:后台添加新闻时内容分了段落,前台详情页显示时所有文字挤在一起。原因:textarea 里的换行符\n在 HTML 里不生效,需要转成<br>或者用<pre>标签包裹。解决:模板渲染时用{{ news.content | replace('\n', '<br>') | safe }},或者用 CSS 的white-space: pre-wrap让容器保留换行。注意用| safe时要确保内容是自己后台录入的,如果允许前台用户投稿就不能这么干,会有 XSS 风险。

6. 进阶技巧:用分页和查询优化把系统跑顺

课程设计的数据量通常不大,但如果你想让系统在答辩时看起来更专业,分页和查询优化是两个性价比最高的加分项。前台分类列表页和搜索结果页都应该加分页,每页 10 到 20 条,不要一次性把所有新闻都查出来渲染。分页的核心是 LIMIT 和 OFFSET,配合一个 COUNT 查询算总页数。

PAGE_SIZE = 10 def get_news_page(category_id=None, keyword=None, page=1): query = News.query if category_id: query = query.filter(News.category_id == category_id) if keyword: query = query.filter(News.content.like(f'%{keyword}%')) total = query.count() news_list = query.order_by(News.publish_time.desc()) \ .offset((page - 1) * PAGE_SIZE) \ .limit(PAGE_SIZE).all() total_pages = (total + PAGE_SIZE - 1) // PAGE_SIZE return news_list, total_pages

(total + PAGE_SIZE - 1) // PAGE_SIZE是向上取整的经典写法,避免用 math.ceil 引入额外 import。offset((page - 1) * PAGE_SIZE)在数据量大时性能会下降,因为数据库要扫描前 N 条再丢弃,但课程设计的数据量下完全够用。如果想让查询更快,可以在 news 表的 category_id 和 publish_time 上建联合索引,CREATE INDEX idx_category_time ON news(category_id, publish_time DESC),这样按分类查最新新闻时能直接走索引。

另一个容易被忽略的点是前台首页的查询次数。如果每个分类单独查一次,8 个分类加上友情链接和导航,首页可能要发 10 次以上数据库查询。我一般会在开发阶段打开 SQL 日志,数一下每个页面发了几条 SQL,超过 5 条就考虑合并。合并的方式前面讲过用窗口函数,或者干脆把首页数据缓存起来,用 Flask-Caching 或者简单的内存字典缓存 30 秒,课程设计答辩时演示完全够用。

从那以后我每次拿到课程设计文档,第一件事不是打开编辑器写代码,而是先把文档里的功能点逐条抄到纸上,每条后面标注对应的表、接口和页面,标不出来的就是还没想清楚的地方。这份新闻管理系统文档把功能结构图都画好了,照着这个习惯走一遍,基本不会漏功能。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表