☰
Java毕设如何选题?个人网站开发全流程实战指南
2026/10/13 7:33:35 网站建设 项目流程

1. 为什么我强烈建议拿"个人网站"当Java毕设题目

每年的毕业季,我都能在私信里收到大量"Java毕设怎么选题"的提问。很多人一上来就想做电商系统、外卖平台、图书管理系统,结果数据库建了十几张表,业务逻辑绕成一团,最后连演示都讲不顺。如果你也在为选题头疼,我真心建议认真看一眼"个人网站"这个方向。个人网站,也叫个人博客、个人内容管理网站,核心功能无非是文章发布、分类浏览、评论互动、后台管理。听起来不惊艳,但它恰好卡在"一个人能在毕业前做完、做完还能讲清楚"的甜点上。Java方向做个人网站,从Spring Boot到数据库再到前后端交互,该踩的技术点一个都不少,又不会膨胀到一个小组都未必做得完的程度。

这一类的题目为什么通过率高,我后面详细拆。这里先给学弟学妹一个中肯的判断:如果你的主要目标是顺利毕业、能展示完整的开发能力,而不是挑战一个研究级课题,那么个人网站是非常务实的选择。它不挑基础、不依赖团队协作、出成果周期短,而且"网站"这个东西人人都能理解,老师评审起来没有认知门槛。

1.1 这类题目的天然优势:复杂度适中、演示直观、延展空间大

先说说"复杂度适中"是什么意思。一个合格的毕设题目,应该是"完整但不过载",个人网站正好如此。它需要你掌握MVC分层、对象关系映射、分页查询、登录会话、文件上传、权限控制,几乎覆盖了大学四年Java课程里最重要的知识点,但每一块都不会复杂到失控。你不需要面对分布式事务、消息队列、微服务注册与发现这些让新手崩溃的内容。换句话说,它是把核心知识点装进了一个大小刚好的容器里。

再说演示直观。答辩现场老师最怕的是:你讲了一堆接口,但页面丑得没法看,或者根本打不开。个人网站天然是面向浏览器的,打开首页就能看到文章列表、分类导航、搜索框,视觉冲击力比纯接口演示强太多。你甚至不需要额外做PPT放截图,现场直接点开几个页面,功能就说明白了。很多做后端接口类项目的同学,答辩时只能展示Postman的调用截图,效果完全不在一个量级。

最后是延展空间。个人网站看着简单,但往上加东西特别顺手:加Redis缓存热门文章、加全文搜索、加评论审核流程、加数据可视化统计后台、加第三方登录,每一个都是常见加分项。我见过不少同学把一个"个人网站"做成包含文章、留言墙、相册、音乐播放器、友链申请的完整个人门户,照样在一个学期内完成。反观电商系统,想加一个"秒杀"都得掂量一下库存扣减怎么写,加一个"优惠券"就要考虑与订单状态的一致性。

1.2 选题前必须想清楚的三个问题

不过我不建议脑子一热就定题。选"个人网站"之前,有几个问题最好先想明白。

第一,你想让老师看到什么。如果你的重点在Java后端,那就把接口设计、权限控制、数据库设计做扎实,前端中规中矩即可;如果你的重点在前端交互,那就要花时间打磨页面,甚至做成前后端分离项目。方向想清楚,后续所有技术选型才有据可依。我辅导过一个同学,花了大量时间做了一套视觉惊艳的首页动画,结果答辩时被问到"你的事务怎么控制的",他完全答不上来,这就属于方向没想清楚。

第二,你的初始数据从哪来。答辩演示最怕空网站,一个只有"Hello World"标题的首页毫无说服力。提前准备20篇以上内容成体系的文章、分类、标签、几条评论,演示效果会截然不同。很多同学临近答辩才忙着造数据,结果标题都写不齐,一看就是应付。数据不要随便复制粘贴,最好围绕一个主题写,比如"Java学习路线""技术踩坑记录",这样演示时还能顺便讲一段内容背景,显得真实。

第三,时间分配。个人网站看着不难,但"做完"和"做扎实"差得很远。前端美化、异常处理、部署上线、演示脚本,每一项都需要单独的时间。如果只剩两周,就老老实实把核心功能跑通;如果还有两三个月,再考虑锦上添花。很多人的误区是前期磨蹭、最后熬夜,其实毕设最怕的不是功能少,而是功能没闭环。

2. 技术选型的务实逻辑:稳定压倒一切,别给自己挖坑

技术选型我只有一句话:毕设不是炫技场,稳定压倒一切。任何你只听过名字、没亲手写过Demo的技术,都不要在毕设里当主力,最多当加分项介绍。下面是我在反复验证过的一套稳妥组合,也是给同学们辅导时用得最多的一套。

2.1 后端框架层:Spring Boot为主体的稳健组合

Java方向我强烈推荐Spring Boot。对比老一代的SSM手工整合——Spring、SpringMVC、MyBatis三者都要各自写配置文件和扫描规则,光一个web.xml就能把人绕晕——Spring Boot用自动配置把大量重复工作收走了,你只需要专注业务代码。个人网站这样的系统,Spring Boot的默认约定已经覆盖了90%的日常需求,剩下的10%才需要你自己动手。

具体组合建议是:

  • Spring Boot 2.7.x:兼容性好,文档多,遇到问题基本都能搜到现成答案
  • MyBatis-Plus:比原生MyBatis省太多事,单表CRUD几乎不用写SQL,还有现成的分页插件和代码生成器
  • MySQL 8.x:主流默认,注意时区和字符集配置即可
  • Thymeleaf:服务端渲染模板,适合页面规模不大的项目
  • Lombok:省略Getter/Setter,代码看起来干净很多

这里我要特别说一下MyBatis-Plus。有些老师可能对" MP"这个框架持有保留态度,觉得学生应该手写SQL才能体现基本功。我的建议是:代码里用MyBatis-Plus提升效率没问题,但你要能讲清楚它帮你做了什么。比如条件构造器最终生成了什么SQL、分页插件是怎么改写SQL的,这些底层原理要懂。答辩时你说"MyBatis-Plus是一个基于MyBatis的增强工具,它通过动态代理和自定义拦截器实现单表CRUD的自动生成",老师一听就知道你不是只会套框架。

2.2 数据库与缓存:何时加Redis,何时别硬加

MySQL是必须的,这没什么好讨论。关键在于Redis要不要加。我的建议是:如果老师没有硬性要求,优先用Redis做"锦上添花"的加分项,而不是核心依赖。什么意思?比如你可以用Redis缓存首页热门文章列表、存入验证码、实现登录会话的集群共享,然后在论文里写一段"基于Redis的高频数据缓存优化",答辩时能讲清楚缓存更新策略就够了。但不要把评论、文章详情这些核心数据放在Redis里当唯一存储,万一服务重启数据丢失,整个项目就崩了。

Redis的价值在于"快",MySQL的价值在于"稳",毕设项目要把稳放在第一位。如果你对Redis不熟,完全可以用"后台接口加本地缓存Map+定时刷新"这种土办法模拟缓存,然后在论文里点一句"生产环境可替换为Redis",同样能体现出你的思考。最怕的是为了用Redis而用Redis,把简单问题复杂化,结果缓存穿透、缓存雪崩、数据一致性这些问题全都解释不清,反而弄巧成拙。

2.3 前端部分:服务端渲染与前后端分离的取舍

个人网站的页面量通常不大,我默认推荐Thymeleaf服务端渲染。好处很明显:不用处理跨域、不用单独部署前端服务、一个jar包就能跑完整套系统。你只需要理解Model、View、模板语法三件事,两天就能上手。Thymeleaf的语法很接近HTML,对后端出身的同学非常友好,不会像JSP那样在页面里写一堆Java代码。

前后端分离当然也可以,Vue或React+Spring Boot写API,效果更现代,简历上更好看。但代价是工作量翻倍:接口文档要写、Token机制要处理、跨域要配置、前端构建要折腾。如果时间和精力不够,很容易两头都做不精细。我见过一个同学,前端用了Vue3+Element Plus,后端用Spring Boot,看起来很高端,但接口文档写得很潦草,连分页参数都对不上,答辩时老师随便点一个功能就报错,非常可惜。

我见过一种折中方案:主体页面用Thymeleaf渲染,局部交互用Ajax调后端接口。比如文章列表分页、评论提交、点赞、搜索自动补全,这些用少量JavaScript就能实现。既保留了服务端渲染的低门槛,又展示了异步交互能力,答辩时老师问"你怎么实现异步刷新",你也能从容回答。

3. 先把数据地基画好:核心表结构与字段设计细节

代码可以后写,数据库必须先想清楚。表结构设计的质量,直接决定后面业务代码好不好写,也决定答辩时老师问"你的表为什么这么设计"时你能不能讲出道理。很多人上来就建表,想到什么加什么,最后表里塞满了用不上的字段,逻辑还自相矛盾。个人网站的数据库,我建议先按下面这张思路来。

3.1 五张核心表的建模思路

一个标准的个人网站,核心表我认为是这五张:用户表、文章表、分类表、标签表、评论表。如果还有后台配置需要,可以再加一个配置表。下面逐个说关键字段。

用户表建议包含:id、username、password、nickname、avatar、email、role、status、create_time。username和email要做唯一索引,password存的是加密后的哈希值,绝对不是明文。role用来区分管理员和普通用户,status用来控制账号是否可用。字段别贪多,像"最后登录IP""性别""生日"这些在个人网站里基本用不上。

文章表是重点,包含:id、author_id、category_id、title、summary、content、cover、status、view_count、like_count、create_time、update_time。其中author_id关联用户表,category_id关联分类表,status区分草稿、已发布、已下架三种状态。summary是文章摘要,列表页直接展示,避免把整篇content查出来。cover是封面图路径,列表页显示一张好看的封面,整体视觉效果立刻提升一个档次。这里要特别注意,文章的content字段类型用LONGTEXT,别用VARCHAR,不然内容稍长就存不进去。

分类表和标签表都属于"附加维度"。分类是树状的,表里可以加parent_id支持多级分类;标签是扁平的,直接维护标签名和文章数的统计字段即可。文章与标签是多对多关系,所以要加一张中间表article_tag,字段就两个:article_id和tag_id。很多同学会忘记加这张中间表,直接用逗号把标签拼在文章表里,短时间能跑,但查询和统计会非常痛苦。

评论表要特别设计parent_id,这样才能支持楼中楼回复。字段包括:id、article_id、user_id、parent_id、content、status、create_time。status设为待审核、已通过、已拒绝,配合后台审核功能使用。parent_id为0表示顶级评论,不为0表示回复某条评论,这样一个字段就把层级关系表达清楚了。

3.2 容易被忽视的字段设计细节

很多同学建表只关注主键和业务字段,几个细节经常被忽略,答辩时一问就露馅。

第一个是状态字段。所有需要"上架/下架""通过/拒绝"的业务表,都建议用一个tinyint类型的status字段,0表示未生效,1表示生效,2表示其他特定状态,并且加注释说明每个数字的含义。千万不要用字符串存"已通过""已拒绝"这种文本。数字类型占空间小、比较快,而且不会出现"已通过"和"已审核通过"这种写错文字的问题。建表SQL里一定要写COMMENT注释,这也是文档的一部分。

第二个是逻辑删除。很多商业系统都习惯用deleted字段做逻辑删除,而不是物理DELETE。毕设项目可以学这个做法,但要注意:用了逻辑删除,所有查询语句都要记得带上deleted=0条件。MyBatis-Plus有@TableLogic注解可以自动帮你加,但前提是你要知道它的存在,否则数据"删了"却还在列表里,非常诡异。答辩时老师可能会问"为什么用逻辑删除",你要答"保留历史数据、避免外键约束报错、后续可以做回收站",而不是说"别人都这么用"。

第三个是时间字段。建议统一用DATETIME类型,并且由应用层写入时间,不要依赖数据库的CURRENT_TIMESTAMP。为什么?因为应用服务器和数据库服务器的时区可能不一致,统一在Java代码里用LocalDateTime.now()写入,能保证全系统时间口径一致。每个表都带上create_time和update_time,update_time可以在代码里更新,也可以在数据库里用ON UPDATE CURRENT_TIMESTAMP,选一种方式全项目保持一致即可。

第四个是冗余字段与联表查询的权衡。文章表里我建议冗余一个author_name字段,存作者昵称,查询文章列表时就不用每次都去联用户表。数据量小的时候联表无所谓,但答辩时老师问"你这里为什么冗余了nickname",你可以回答"减少一次关联查询,在列表页高频场景下提升响应速度",这属于权衡能力,很加分。

3.3 权限与状态的设计:一条评论如何从"待审核"到"已发布"

权限设计毕设不用做得很重,但流程要完整。以评论为例,我讲一下状态流转,这样你写代码和写论文都有依据。

访客进入前台文章页,填写昵称和评论内容提交,后端插入评论记录,status默认0,也就是"待审核"。此时前台任何人都看不到这条评论。管理员登录后台,进入评论管理列表,这里查询的是所有评论,不管status是什么。管理员阅读后,点击"通过",status变为1;点击"拒绝",status变为2。与此同时,管理员可以选择"删除",这里走的是逻辑删除,deleted变为1。

前台文章页的评论列表,查询条件是status=1且deleted=0。这样一个简单的状态机,就同时实现了防垃圾评论、内容审核、软删除三个需求。答辩时老师问"如何防止有人发恶意评论",你就说"评论审核机制+XSS过滤",比单纯回答"我会拦截"有说服力得多。状态流转的本质是把业务规则显性化,每一条数据都有明确的生命周期,这个思路在后台管理模块同样适用。

4. 项目骨架搭建与第一个核心链路的落地

数据库设计完,接下来就是搭项目骨架。这一节我按实际动手的顺序来写,每一步都标出容易翻车的地方。照着做一遍,你的项目就有第一个能看的功能了。

4.1 初始化项目与配置文件里容易被忽视的坑

初始化Spring Boot项目,直接用IDE的Spring Initializr向导,勾选Web、Thymeleaf、MyBatis、MySQL Driver、Lombok这些依赖,一分钟生成项目。比手动建目录、写pom.xml稳得多。如果网络不方便,也可以从Maven中央仓库手动加依赖,但新手不建议折腾。

项目建好后,第一件事是配置application.yml。下面是一个我实际用过的完整配置:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/personal_site?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password thymeleaf: cache: false servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis-plus: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.personalsite.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这里有几个坑,我逐个提醒。第一个是URL里的serverTimezone=Asia/Shanghai。MySQL 8默认时区校验很严格,不加这个参数,启动时经常报数据库连接失败,错误信息是时区识别不了。这个问题每年都有人踩。第二个是thymeleaf.cache=false。开发阶段必须关掉模板缓存,不然你改了HTML刷新页面死活不变,还以为是代码没保存。关掉后改完立即生效。第三个是multipart大小限制。如果后期做图片上传,默认1MB根本不够用,文章封面图、详情插图很容易超限。这里我给了10MB和20MB,按需调整。第四个是map-underscore-to-camel-case,数据库字段是create_time,Java属性是createTime,这个配置能自动帮你完成驼峰映射,少写一堆别名。

再提醒一个目录结构问题。不要把所有类都堆在同一个包下面,建议按controller、service、mapper、entity、dto、config、interceptor、common分包。代码清晰了,答辩讲解时按包结构讲一遍,有条理得多。entity放数据库实体类,dto放页面传输对象,controller只做参数接收和结果返回,service写业务逻辑,mapper只做数据访问。

4.2 从数据库到页面的完整请求链路

骨架搭好后,第一个核心链路建议做"首页文章列表+分页"。它能让你的项目从"空壳"变成"能跑",而且验证了配置、Mapper、Service、Controller、模板渲染这一整条链。

先说分页。原生MyBatis要手写LIMIT和COUNT语句,很啰嗦。MyBatis-Plus提供了分页插件,配置一个拦截器即可:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

然后在Service里调用:

public Page<ArticleVO> pageArticles(int pageNum, int pageSize) { Page<Article> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Article::getStatus, 1) .orderByDesc(Article::getCreateTime); Page<Article> articlePage = articleMapper.selectPage(page, wrapper); // 补充分类名称、作者昵称等信息,转成ArticleVO }

注意我查的是status=1的文章,也就是已发布状态。草稿和下架的文章,管理员在后台能看到,前台永远看不到。这个过滤条件要全局统一,防止漏写导致"草稿被公开展示"的乌龙。转换VO这一步很重要,不要把数据库实体直接塞给前端,因为实体里可能有敏感字段或者多余字段,VO只包含页面需要展示的属性,这也是答辩时能讲的设计点。

Controller层把Page对象塞进Model:

@GetMapping("/") public String index(@RequestParam(defaultValue = "1") int pageNum, Model model) { Page<ArticleVO> page = articleService.pageArticles(pageNum, 8); model.addAttribute("page", page); return "index"; }

Thymeleaf模板里用th:each循环输出文章标题、摘要、时间,再渲染分页组件。这里有一个细节:分页组件要判断当前页、总页数、上一页、下一页,别用绝对路径写死页码。建议写一个通用的分页片段,后续分类页、标签页、搜索页都能复用。跑通这个链路之后,项目就有了"灵魂"。接下来的工作都是在这个基础上加功能,而不是从零开始。

5. 文章模块的实战细节:编辑器、图片上传与内容展示

如果说分页链路是项目的骨架,那文章模块就是个人网站的心脏。这块涉及编辑器、上传、渲染、性能,坑非常多,值得单开一章细说。

5.1 编辑器选型:功能与开发量的平衡

后台发布文章,不可能让管理员手写HTML,必须接一个富文本编辑器。选型我对比过好几个,给出我的结论。老牌的UEditor功能全但已经停止维护,而且存在不少已知漏洞,不建议新项目用。wangEditor是国产开源项目,接入简单,提供基础的加粗、标题、图片、链接、代码块能力,个人网站足够用,配套Vue版本也有。Markdown风格的话可以用Editor.md,适合喜欢写Markdown的开发者,预览体验很好。

我自己的选择是wangEditor。理由是API简洁、文档中文友好、图片上传接口可以直接对接自己的后端。接入时注意:编辑器实例的创建要放在页面加载完成后,否则会出现"编辑器区域空白"的问题;图片上传回调里要把返回的访问地址拼完整,否则图片上传成功但正文里显示的是/xxx.jpg这种本地路径,前台打不开。如果不想接第三方编辑器,最省事的方案是textarea+在线Markdown语法提示,但体验太原始,答辩时容易被挑刺"为什么不用富文本"。

5.2 图片上传链路与存储方案选择

编辑器接好后,核心难点是图片上传。上传链路大致是:前端编辑器把图片二进制POST到后端接口,后端校验大小和类型,写入存储位置,返回图片URL,前端把URL插入到正文中。链路不长,但每一步都有坑。

存储方案有两种主流选择。第一种是本地存储。在服务器上建一个upload目录,图片按日期分目录存放,比如upload/2025/05/01/xxx.jpg。后端写一个上传接口,用MultipartFile接收文件,保存到磁盘,同时把数据库记录的URL存成/upload/2025/05/01/xxx.jpg。这里最关键的是要配置虚拟路径映射,否则浏览器访问这个URL会404。配置方式如下:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath + "/"); } }

第二种是对象存储。把图片传到云上的对象存储桶,返回公网URL。好处是不占用自己的服务器带宽,也不怕重启丢文件;坏处是配置略微复杂,需要密钥管理,而且有些环境可能没条件。毕设项目我默认推荐本地存储,简单可控,但要在论文里写清楚"生产环境建议迁移到对象存储",展示你的架构思考。这里有个很现实的坑:开发时用的本地路径是/Users/xxx/upload,传到服务器上就变成/root/upload,路径不一样,图片全挂。解决办法是统一用配置文件维护uploadPath,不要在代码里写绝对路径。

5.3 文章详情页的友好展示与数据保护

文章详情页,重点是三件事:富文本内容如何安全展示、浏览量如何统计、数据一致性如何保证。

富文本内容存的是HTML字符串,Thymeleaf默认会转义输出,结果就是一整段源码字符。正确的做法是用th:utext渲染。但这里立刻引出一个安全问题:如果文章内容是后台管理员写的,相对可控;但如果你开放了用户投稿,必须做严格的白名单过滤,把script标签、事件属性、iframe全都过滤掉。最简单的手段是接入现成的HTML清洗库,比如jsoup的Safelist白名单模式,不能只用String.replace想当然处理,很容易被绕过。

浏览量统计,最简单的实现是每次点击详情接口,view_count+1。缺点很明显:自己刷新一次就加一次,数据虚高。答辩时老师可能会问"如何防止恶意刷浏览量",这是给你递话。你可以在Service里加一段逻辑:基于IP+文章ID判断,同一个IP一天最多计一次数。用一张访问记录表或者Redis的Set都能实现。如果用了Redis,还能顺便说说"同一篇文章的高频访问,先更新Redis里的计数器,再定时落库",整个回答就会显得非常有层次。

点赞功能同理,不建议做成每点击一次就+1的裸奔实现。至少要加一个"当前用户对某文章是否已点赞"的判断,用一张user_like_article表记录中间关系,或者用Redis Set去重。功能可以简单,但防重复的思考过程一定要有。

6. 交互功能的难点与排查思路:登录态、评论审核与搜索

个人网站除了文章展示,还需要登录、评论、搜索这些交互功能。它们难度不大,但每个都有几个容易想当然的细节。这一章重点讲"这些功能怎么实现才经得起追问",以及常见的坑。

6.1 登录状态管理:Session还是Token的实用主义选择

登录状态,毕设我推荐用Session,原因是简单可靠、学习成本低。流程是:用户提交用户名密码,校验通过后,把用户信息放入session,同时给前端种一个Cookie;后续请求携带Cookie,服务端从session取出用户,就能判断是否登录。具体实现上,我会用拦截器统一处理:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object user = session.getAttribute("loginUser"); if (user == null) { response.sendRedirect("/login"); return false; } return true; } }

然后注册拦截器的时候要区分路径:后台管理页全部拦截,前台页面只对"发布评论、点赞、个人中心"等接口拦截,静态资源比如CSS、JS、图片不能拦。这个拦截器的放行规则,是我见过翻车最多的地方。有些人图省事把所有路径都拦了,结果CSS拿不到,页面光秃秃的;有些人把后台接口漏掉了,没登录也能直接访问后台。配置示例:

registry.addInterceptor(loginInterceptor) .addPathPatterns("/admin/**", "/comment/submit", "/like/**") .excludePathPatterns("/login", "/register", "/css/**", "/js/**", "/images/**", "/upload/**");

如果你想让简历更有亮点,可以上JWT Token。但要清楚JWT的细节暴增:Token存哪、过期怎么续、登出如何失效、密钥怎么管理。这些在毕设答辩中都是灵魂拷问点。我的建议是:除非你非常熟悉,否则Session方案已经足够让老师认可。

密码安全这块,一定要用BCrypt加密,不要去搜"MD5加密密码"然后直接拿去用。MD5存在彩虹表攻击风险,加盐稍有不慎就形同虚设。Spring Security的crypto模块里有现成的BCryptPasswordEncoder,单独引这个类就行,不需要引入全套Spring Security。

6.2 评论审核与可见性控制

前面讲评论状态流转时,已经把数据库层的设计说完了,这里补几个开发时容易踩的坑。第一个坑是"回复的回复"。评论表用了parent_id之后,查询时不能只查一层。前台文章页展示评论,要把所有status=1的评论都查出来,然后自己写递归或循环按parent_id组装成树形结构。简单做法是查出来后用Map分组,再拼接父子层级;复杂做法是嵌套查询,但性能差。建议一次性把所有评论查出来,内存里组装成树,个人网站数据量小完全没问题。

第二个坑是"评论数"和"评论列表"不一致。文章表里加一个comment_count字段,发布评论成功时+1,审核通过时也可能+1,具体时机要想清楚。我自己的做法是:评论提交时status=0不计数,管理员审核通过时才comment_count+1,这样前台看到的评论数和评论列表永远一致,不会出现"显示有3条评论但打开只有1条"的尴尬。

第三个坑是评论时的身份认证。如果允许匿名评论,要防止"用户名伪造"——表单里传一个用户名,后端直接信了就存进去,这种漏洞非常低级。正确的做法是:登录用户评论时,后端从session拿用户信息,不信任前端传入的用户ID;匿名用户评论时,限制内容长度和敏感词,不要允许传头像链接之类容易被注入的属性。

6.3 搜索功能的轻量实现与优化方向

个人网站的搜索,最简单的方式是SQL LIKE:

SELECT * FROM article WHERE title LIKE CONCAT('%', #{keyword}, '%') OR summary LIKE CONCAT('%', #{keyword}, '%') ORDER BY create_time DESC;

这种写法数据量小的时候完全够用。但你要知道它的两个缺陷:第一个是使用LIKE '%xxx%'时,即使title建了索引也用不上,全表扫描,数据量上来之后肯定卡;第二个是没有分词,搜"个人网站"和搜"网站个人"结果完全不一样。答辩时可以主动说优化方向。

方案一是MySQL全文索引,对中文要指定ngram分词器:

ALTER TABLE article ADD FULLTEXT INDEX ft_title_summary (title, summary) WITH PARSER ngram;

然后查询用MATCH...AGAINST。这个方案不需要引入额外服务,效果比LIKE提升明显。方案二是引入Elasticsearch,索引文档后提供更专业的搜索能力。但毕设阶段硬上ES属于过度设计,我一般建议在论文里作为展望写,实际只做到MySQL全文索引就够。

搜索页还有一个小细节:搜索关键词要原样回显到搜索框里,标题里匹配到的关键字可以加高亮。这些细节不大,但答辩演示时很加印象分——说明你考虑到了用户体验,而不只是写完功能。

7. 从本地到公网:部署上线的完整过程与翻车记录

本地跑通和能给别人演示,是两回事。部署上线这一关,每年都有同学栽跟头。我按完整流程讲一遍,再把最容易翻车的三个点单独拎出来。这一章的含金量在于,你踩过的每一个坑,后面的人都可以绕过去。

7.1 环境准备与打包部署

建议准备一台Linux服务器。需要装的软件就三样:JDK、MySQL,以及可选的管理工具。装好之后创建数据库和账号,导入之前设计好的建表SQL。本地项目打包,在项目根目录执行:

mvn clean package -DskipTests

打包完成后target目录下会生成一个.jar文件。上传到服务器,然后启动:

nohup java -jar personal-site.jar --spring.profiles.active=prod > app.log 2>&1 &

这里有几个细节要讲清楚。第一,nohup和&配合才能让应用在关闭终端后继续运行;第二,日志一定要重定向到文件,不然出问题无从查起;第三,生产环境建议用独立的配置文件application-prod.yml,把数据库密码、上传路径都放到里面,不要改公共配置。如果你会用Docker,也可以写一个简单的Dockerfile把项目镜像化,部署命令变成docker run,但这属于进阶操作,不会也没关系,毕竟答辩主要看业务功能。

如果服务器内存不大,启动时最好加-Xms256m -Xmx512m限制堆内存。默认参数下Spring Boot可能吃1G内存,小机器容易直接OOM卡死。启动完用curl -I http://localhost:8080验证一下,返回200说明启动成功。

7.2 最容易翻车的三个点:白屏、404、数据连不上

部署之后访问首页,大概率会踩到下面几个坑。我把排查链路写出来,照着走就行。

第一个翻车点是完全白屏,或者页面只有文字没有样式。排查顺序是:先看浏览器开发者工具的Network面板,CSS、JS文件返回什么状态码。如果是404,多半是静态资源路径问题,比如Thymeleaf模板里硬编码了/static/css/xxx.css,但Spring Boot静态资源默认路径不同。如果是500,多半是路径映射配置错误或者资源没打进去。解决方法是把静态资源路径统一写成Thymeleaf语法依赖上下文路径,比如th:href="@{/css/common.css}",不要写绝对路径。

第二个翻车点是请求路径404。分两种情况:一种是后端确实没有这个接口,那要去Controller里核对RequestMapping;另一种是拦截器把请求拦截了,返回404或者直接跳到登录页。排查方法是看启动日志里有没有Mapped到对应URL,再看拦截器注册的excludePathPatterns是否把前台路径放行了。再有就是context-path配置了/xxx,但你访问的时候没带,结果全路径对不上。

第三个翻车点是数据库连不上,报Communications link failure或者Access denied。排查顺序是:先确认MySQL服务有没有启动;再确认账号密码对不对;然后确认MySQL授权是否允许远程连接(很多默认配置只允许localhost);接着确认服务器防火墙和安全组有没有放行3306端口;最后确认配置文件里的URL中serverTimezone和useSSL参数是否正确。这五个原因我至少各见过一次。想省事的话,可以先用命令行工具在服务器本机连一下MySQL,验证账号和权限,再回头看应用日志。

排查任何问题,第一原则是看日志。启动日志、应用日志、数据库日志,一行行读,不要猜。很多同学一报错就问别人"为什么启动失败",其实日志里已经写了,只是没认真看。日志里出现的关键词,比如Connection refused、Access denied、ClassNotFound、Port already in use,每一个都指向一个明确的方向。

8. 答辩与演示的准备:让成果被看见也是能力的一部分

最后这一节,聊聊很多人忽视的答辩环节。项目做得再好,演示讲得稀碎,照样拿不到好成绩。反过来,功能中规中矩但演示流畅、讲解清晰,分数往往不低。这不是鼓励你只做表面功夫,而是说"把成果展示出来"本身也是工程能力的一部分。

8.1 演示脚本设计

演示不是打开网站随便点两下。我建议按一条"有逻辑的故事线"走:第一步,登录管理员账号,展示后台首页和功能菜单。第二步,发布一篇新文章,插入一张图片,设置分类和标签,点击发布。第三步,切换到前台首页,展示刚才的文章出现在列表第一位,点击进入详情,浏览量加一。第四步,模拟访客在文章下发表评论,切回后台,审核通过,再回前台刷新,评论出现。第五步,搜索一个关键词,展示搜索结果。第六步,如果还有时间,展示你做的亮点功能,比如Redis缓存、数据统计图表。

整个演示控制在10分钟左右,每个环节预留一个"如果出错"的备选操作。比如某个页面打不开,提前准备好另一个浏览器标签页已打开同样的内容。演示前把所有数据库初始数据准备好,不要现场造。演示时还要注意别把后台的调试信息暴露出来。浏览器控制台的报错、页面上显示的异常堆栈、未处理的乱码,都会给老师留下"项目不严谨"的印象。提前把明显的问题处理掉,不知道会不会报错的接口,先多访问几遍测试。

8.2 老师最爱问的问题与回答思路

我把答辩现场高频出现的提问整理了一下,每个给一个回答思路,不是让你背稿子,而是让你知道老师想知道什么。

  • 你的项目分层结构是什么?为什么这样分?回答核心:Controller只负责任务分发,Service做业务逻辑,Mapper只做数据访问,职责分离便于维护和测试。
  • 数据库为什么这样设计?回答核心:三范式结合实际情况,为了查询效率适当冗余,比如文章表冗余了author_name,避免每次联表查询。
  • 分页是怎么实现的?回答核心:MyBatis-Plus分页插件在底层生成LIMIT语句,利用ThreadLocal保存分页参数,同时执行COUNT查询。把底层机制说透,老师会满意。
  • 浏览量怎么防刷?回答核心:IP加文章ID去重,或在Redis里维护访问记录,定期落库。
  • 密码安全怎么做的?回答核心:BCrypt加盐哈希,不存明文。为什么不用MD5?因为MD5自带彩虹表风险,加盐后也不够强。
  • 如果访问量突然变大怎么办?回答核心:静态资源加速、文章内容缓存到Redis、数据库读写分离。再追问的话,把简历里写的技术点拿出来说明。
  • 前端怎么防XSS?回答核心:后端对HTML内容做白名单过滤,编辑器本身也有过滤机制,输出时做转义。
  • 项目的不足和后续计划?回答核心:可以提搜索目前用的是MySQL全文索引,生产环境可升级为Elasticsearch;上传目前是本地存储,可迁移到对象存储;还可以加消息推送、内容采集等。关键是说明"知道问题在哪、知道怎么改"。

说话节奏上,先给结论、再给理由、最后举例子。比如"这个表我用了冗余字段,因为……"比长篇大论更容易抓人。被问到不会的问题,千万不要瞎编,坦诚说"这个点我还没有深入研究,我的理解是……",同时把话题引回你熟悉的领域,基本能圆回来。

我在实际辅导中见过最可惜的一种情况:功能全做完了,论文也写完了,但演示时由于紧张把流程讲乱,老师只看了两个页面就结束提问,最终分数平平。所以真的建议提前演练三遍以上,对着室友讲、对着手机录下来自己看,把每一个点击动作都练熟。这个成本很低,收益却非常高。到了答辩现场,你只需要记住一件事:你比在场的任何人都熟悉这个项目的每一行代码,放松讲就行。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询