简介这份文档资料面向软件综合实习、课程设计及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_byNews.category_id, order_byNews.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, methodpbkdf2:sha256) db.session.add(Admin(usernameadmin, passwordhashed)) # 登录验证时 admin Admin.query.filter_by(usernameform.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, typeint) title request.form.get(title, ).strip() content request.form.get(content, ).strip() if not all([category_id, title, content]): return 参数不完整, 400 news News(category_idcategory_id, titletitle, contentcontent) db.session.add(news) db.session.commit() return redirect(/admin/news/list)request.form.get(category_id, typeint)直接转成整数避免字符串类型的 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_namesite_name, site_urlsite_url, logo_pathlogo_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_okTrue)放在应用启动时执行一次。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 decorated5.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_idNone, keywordNone, page1): 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 秒课程设计答辩时演示完全够用。从那以后我每次拿到课程设计文档第一件事不是打开编辑器写代码而是先把文档里的功能点逐条抄到纸上每条后面标注对应的表、接口和页面标不出来的就是还没想清楚的地方。这份新闻管理系统文档把功能结构图都画好了照着这个习惯走一遍基本不会漏功能。希望帮到你。本文还有配套的精品资源点击获取