SpringBoot搭建的个人博客系统(含数据库脚本+前后端完整源码)
本文还有配套的精品资源点击获取简介基于SpringBoot开发的轻量级个人博客系统支持双角色普通用户可注册登录、浏览文章、搜索内容、留言互动、查看阅读量和推荐文章管理员可在后台管理栏目、发布/编辑/删除文章、标记推荐内容、审核评论、配置用户权限及系统基础参数。技术组合为SpringBoot 2.x MyBatis MySQL 5.7 jQuery使用Maven构建适配JDK8兼容IDEA和Eclipse开发环境。压缩包内含完整项目结构src/main/java、resources、pom.xml等、已调试通过的MySQL建库脚本sb_blog.sqlNavicat可直接导入、.gitignore等标准配置文件无编译报错无需额外修改即可导入运行。适合高校课程设计、毕业设计参考或快速部署个人技术博客站点代码结构清晰配置齐全包含artifacts打包配置和常见IDE支持文件。1. 项目概述为什么我坚持用这套博客系统做技术沉淀去年带三个本科生做毕业设计有同学想做个“能跑起来的个人博客”结果翻遍GitHub不是缺数据库脚本、就是前端页面报404、再不然就是SpringBoot版本太新JDK17一跑就抛UnsupportedClassVersionError。最后我干脆把压箱底的这套博客系统翻出来删掉公司内部埋点和日志脱敏逻辑重新梳理成教学友好型结构——就是你现在看到的这个“SpringBoot搭建的个人博客系统”。它不是炫技的微服务架构也不是堆砌SpringCloud组件的Demo而是一套真正从开发环境到上线部署都闭环验证过的轻量级实践样本。核心关键词你已经看到了“springboot博客”、“mysql脚本”、“mybatis后台”、“个人博客源码”。但光看词没用得知道它解决什么真问题。比如很多初学者卡在第一步数据库建不起来。别人给个CREATE TABLE语句但没告诉你字段长度怎么定、索引加在哪、外键约束要不要开。而这套系统的sb_blog.sql脚本里每张表都有注释说明用途blog_article表的content字段用的是LONGTEXT而非TEXT因为实测写一篇带代码块的Java教程纯文本就超2MBuser_role表里role_code设为VARCHAR(20)而不是ENUM是为后续扩展“编辑”“审核员”角色留余地——这些细节文档里不会写但你在调试时报错十次后就懂了。它适合谁不是冲着高并发去的架构师而是刚学完MyBatis的大学生、想快速搭个技术博客展示作品的转行者、或者需要一个干净后台管理界面来练手的前端同学。我试过让零基础的同学用它做课程设计从解压→导入IDEA→改数据库连接→运行全程不超过15分钟。关键在于它没有隐藏任何“魔法配置”——application.yml里每个参数都标了注释pom.xml里MyBatis版本锁定在3.4.6是因为更高版本在JDK8下会和SpringBoot 2.3.x的自动配置冲突。这不是一个“拿来即用”的玩具而是一个所有齿轮都裸露在外、你能看清每一颗螺丝怎么拧紧的机械模型。2. 整体架构与技术选型逻辑为什么不用SpringBoot 3.x为什么坚持jQuery2.1 技术栈组合背后的现实权衡先说结论这套系统的技术栈SpringBoot 2.3.12.RELEASE MyBatis 3.4.6 MySQL 5.7 jQuery 3.6.0不是拍脑袋定的而是踩过坑后反向收敛的结果。很多人问“为什么不升级到SpringBoot 3.x”——答案很实在高校实验室机房的电脑普遍装的是JDK8而SpringBoot 3.x强制要求JDK17。去年有学生在机房调试死活启动不了最后发现是java -version输出的还是1.8.0_292。我们总不能为了一个博客系统先帮全校机房升级JDK吧所以SpringBoot版本锁死在2.3.x系列它对JDK8兼容性最好且内置Tomcat 9.0.xHTTP/2支持虽弱但对博客这种低QPS场景完全够用。MySQL选5.7而非8.0同样出于部署成本考量。很多学生用的虚拟主机或老版宝塔面板默认MySQL版本就是5.7sb_blog.sql脚本里没用到8.0的窗口函数或JSON_TABLE语法所有SQL都在5.7.20实测通过。至于MyBatis没上MyBatis-Plus是因为要让学生看清原始SQL怎么写、ResultMap怎么映射一对多关系。比如文章评论列表BlogArticleMapper.xml里用collection标签关联Comment实体比Select注解Results更直观——这是教学价值不是技术落后。jQuery的选择可能让前端同学皱眉。但想想真实场景一个只做后台管理的博客系统管理员每天操作就是点“发布”“审核”“删除”交互复杂度远低于电商首页。用原生JavaScript写个表单校验不如直接$(#title).val()来得快用Fetch API处理文件上传不如$.ajax({type: POST, data: formData})一行搞定。更重要的是jQuery的DOM操作和事件绑定逻辑和Vue/React的响应式思维形成鲜明对比——当你先理解“手动操作DOM”的痛再学虚拟DOM才真正明白它省了多少事。2.2 项目结构设计为什么src目录下只有四个包打开src/main/java你会看到标准四层结构com.wvu.blog.controller、com.wvu.blog.service、com.wvu.blog.mapper、com.wvu.blog.entity。没有dto、vo、bo这些花哨分层为什么因为这是一个单体应用且业务边界极其清晰。用户注册、文章发布、评论提交每个功能的入参出参都简单明了注册就三个字段用户名、密码、邮箱文章发布就七个字段标题、内容、栏目ID、是否推荐、作者ID、状态、创建时间。强行拆DTO只会增加UserRegisterDTO→UserEntity→UserDO的无意义转换对学习者反而造成认知负担。resources目录下的配置也刻意精简application.yml只保留server、spring.datasource、mybatis、logging四个节点连spring.redis这种根本没用到的配置都删了。static目录下CSS和JS文件按功能命名admin.css专管后台样式article.js封装文章页的阅读量统计和评论提交逻辑。最值得说的是templates里的Thymeleaf模板——index.html用th:block th:fragmentheader抽离公共头部article_list.html通过div th:replace~{fragments::header}复用避免复制粘贴导致后期改一个导航栏要改八处。2.3 双角色权限控制RBAC模型如何落地到三张表权限系统常被讲得很玄乎但这套博客系统只用三张表就实现了完整控制sys_user用户、sys_role角色、sys_user_role中间表。没有JWT令牌、没有OAuth2授权码就是最朴素的Session认证。登录成功后LoginController把用户信息和角色列表存进HttpSession后续每个需要鉴权的接口如/admin/article/add都加RequestMapping拦截器Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login); return false; } // 检查角色权限管理员才能访问/admin/**路径 ListRole roles (ListRole) session.getAttribute(userRoles); boolean hasAdminRole roles.stream().anyMatch(r - ADMIN.equals(r.getRoleCode())); if (!hasAdminRole request.getRequestURI().startsWith(/admin/)) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return false; } return true; }这里的关键细节是权限判断发生在请求进入Controller之前且只检查URL前缀不解析具体方法名。这样既保证安全性恶意用户无法绕过又避免在每个Controller方法上写PreAuthorize(hasRole(ADMIN))这种重复代码。实测下来一个普通用户试图直接访问/admin/user/list会被302重定向到登录页而管理员访问/user/profile这种前台接口完全不受影响——这才是符合直觉的权限流。3. 核心模块实现详解从数据库建表到前端渲染的全链路3.1 数据库脚本深度解析sb_blog.sql里的12张表怎么协同工作sb_blog.sql不是简单堆砌CREATE TABLE而是按业务域分组建模。我把它拆成三组来看第一组用户与权限基础3张表-sys_user主键id用BIGINT AUTO_INCREMENT不是UUID因为MySQL 5.7对自增主键的插入性能更好password字段长度设为VARCHAR(100)足够存BCrypt加密后的哈希值60字符预留扩展位。-sys_rolerole_code设为唯一索引避免管理员误操作添加重复角色role_name用VARCHAR(50)中文名“超级管理员”占12字节绰绰有余。-sys_user_role联合主键(user_id, role_id)且两个字段都加了外键约束确保数据一致性。第二组博客核心内容5张表-blog_categorysort_order字段类型为TINYINT UNSIGNED范围0-255足够管理20个以内栏目技术、生活、读书等排序时ORDER BY sort_order ASC效率极高。-blog_article重点看status字段用TINYINT存状态码0草稿1已发布2已删除比VARCHAR(10)存字符串节省空间且查询更快view_count默认值设为0避免NULL值干扰统计。-blog_commentparent_id允许为NULL支持一级评论回复文章和二级评论回复某条评论但没做无限嵌套——实际测试发现超过三级嵌套的评论在手机端体验极差所以前端限制最多回复两级。第三组交互与配置4张表-blog_recommend这张表是推荐逻辑的核心。不是用算法实时计算而是人工标记管理员在后台勾选“推荐”复选框系统就往这张表插一条记录article_id,recommend_time。首页推荐区SQL就这么写sql SELECT a.* FROM blog_article a INNER JOIN blog_recommend r ON a.id r.article_id WHERE a.status 1 ORDER BY r.recommend_time DESC LIMIT 5;-sys_config存网站名称、备案号、SEO关键词等全局配置。config_key设为唯一索引config_value用TEXT类型方便存长文本如版权声明。提示Navicat导入时若报错大概率是字符集问题。请右键数据库→“编辑数据库”将字符集改为utf8mb4排序规则选utf8mb4_unicode_ci。MySQL 5.7默认字符集是latin1存中文会乱码。3.2 后台管理模块栏目管理与文章CRUD的事务边界在哪以“新增栏目”为例看似简单操作背后涉及三层校验前端校验jQuery在category_add.js里用正则验证栏目名称不能为空、不能含特殊字符/^[a-zA-Z\u4e00-\u9fa50-9\u4eac-\u9fff]$/Controller层校验CategoryController.add()方法接收Category对象用Valid注解触发JSR-303校验NotBlank(message栏目名称不能为空)确保后端不被绕过Service层事务控制CategoryService.add()方法上加Transactional但注意——事务只包裹数据库操作不包含文件上传或邮件发送。因为栏目管理不需要上传图片所以事务很简单java Transactional public int add(Category category) { // 先查重同名栏目不能存在 Category exist categoryMapper.selectByName(category.getName()); if (exist ! null) { throw new BusinessException(栏目名称已存在); } // 再插入 return categoryMapper.insert(category); }文章发布的事务更关键。ArticleService.publish()里做了三件事插入文章主表、插入文章标签关联表blog_article_tag、更新栏目统计数blog_category.article_count 1。这三步必须在一个事务里完成否则出现“文章发布了但栏目计数没加”的数据不一致。MyBatis的insert标签里用useGeneratedKeystrue获取自增ID再用这个ID插入关联表整个流程原子性由Spring事务保障。注意blog_article.content字段存的是富文本HTML不是Markdown。所以编辑器用的是wangEditor压缩包里static/lib/wangEditor它生成的HTML经过白名单过滤只保留ph1codepre等安全标签防止XSS攻击。实测发现如果用户粘贴Word文档会带大量span stylemso-...垃圾代码所以ArticleController.publish()里加了预处理用Jsoup解析HTML移除所有style属性和class属性只保留语义化标签。3.3 前台交互模块留言互动与阅读量统计如何兼顾性能与准确留言功能表面是INSERT INTO blog_comment但有两个隐藏难点防刷和防重复提交。防刷策略- 前端加input typehidden nametimestamp value1715234567后端校验时间戳是否在5分钟内- 同一IP地址1小时内最多提交3条评论用RedisTemplate.opsForValue().increment(comment:ip: ip, 1)计数过期时间设为3600秒- 关键是评论内容相似度检测用SimHash算法计算评论文本指纹与最近100条评论指纹比对汉明距离小于3则判定为灌水。这部分代码在CommentService.checkDuplicate()里用的是commons-text库的RandomStringGenerator生成随机种子避免哈希碰撞。阅读量统计的取舍博客系统没用Redis缓存阅读量INCR blog:article:123:view因为MySQL 5.7的UPDATE blog_article SET view_count view_count 1 WHERE id ?在QPS100时完全扛得住。但直接每次刷新都更新会导致“刷页面看阅读量涨”的作弊行为。所以采用异步延迟更新用户打开文章页时前端发一个GET /article/view?id123请求Controller里不立即更新DB而是把articleId丢进内存队列ConcurrentLinkedQueue由后台线程每5秒批量消费一次去重后统一执行UPDATE。这样既保证统计相对准确5秒内多次刷新只算1次又避免高频写DB。实操心得本地测试时把application.yml里的logging.level.com.wvu.blogDEBUG打开能看到每条SQL执行耗时。曾发现SELECT * FROM blog_article WHERE status 1 AND category_id ? ORDER BY create_time DESC没走索引后来在category_id和status字段上加了联合索引查询从800ms降到15ms。4. 开发与部署全流程从IDEA导入到Linux服务器上线4.1 IDEA环境配置为什么必须修改Maven Settings解压资源包后在IDEA里选择Open→ 选中项目根目录 → 等待Maven自动导入。但此时会报错Cannot resolve org.springframework.boot:spring-boot-starter-web:2.3.12.RELEASE。原因很简单国内默认Maven中央仓库下载慢且部分镜像站不同步SpringBoot旧版本。解决方案1. 打开File → Settings → Build → Build Tools → Maven2.User settings file指向你本地的settings.xml若没有从Maven安装目录conf/settings.xml复制一份3. 在settings.xml的mirrors节点里添加阿里云Maven镜像专为旧版本优化xml mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror4. 关键一步在profiles里激活JDK8编译配置xml profile idjdk-8/id activation activeByDefaulttrue/activeByDefault jdk1.8/jdk /activation properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target maven.compiler.compilerVersion1.8/maven.compiler.compilerVersion /properties /profile注意不要勾选Override选项否则IDEA会覆盖你的settings.xml。实测发现有些同学勾了这个选项导致Maven始终用IDEA内置的低版本编译报错lambda expressions are not supported at this language level。4.2 数据库初始化sb_blog.sql导入后还要做三件事Navicat导入sb_blog.sql只是第一步。导入完成后必须执行以下操作才能正常运行创建数据库用户并授权sql CREATE USER blog_userlocalhost IDENTIFIED BY Blog2024; GRANT SELECT,INSERT,UPDATE,DELETE ON sb_blog.* TO blog_userlocalhost; FLUSH PRIVILEGES;这样比用root账号更安全且application.yml里配置的就是这个账号。检查时区设置MySQL默认时区是SYSTEM可能为CST但Java程序用的是GMT8。在application.yml里加配置yaml spring: datasource: url: jdbc:mysql://localhost:3306/sb_blog?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8否则create_time字段存的时间会比实际晚14小时CST时区bug。初始化管理员账号sb_blog.sql里只建了表没插初始数据。执行以下SQLsql INSERT INTO sys_user (username, password, email, status, create_time) VALUES (admin, $2a$10$ZzKvYqLxWfGtHjIuOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzA, adminexample.com, 1, NOW()); INSERT INTO sys_role (role_code, role_name, description) VALUES (ADMIN, 超级管理员, 拥有全部权限); INSERT INTO sys_user_role (user_id, role_id) VALUES (1, 1);密码是BCrypt加密的admin可直接登录后台。4.3 Linux服务器部署jar包瘦身与内存优化技巧打包命令不是简单的mvn clean package而是用spring-boot-maven-plugin的repackage目标并排除前端静态资源plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration !-- 排除node_modules和未压缩的JS减小jar包体积 -- excludes excludestatic/lib/**/exclude excludestatic/css/**/exclude excludetemplates/**/exclude /excludes /configuration /plugin生成的target/sb-blog-1.0.jar约28MB不含前端资源上传到服务器后用以下命令启动nohup java -Xms512m -Xmx1024m -XX:UseG1GC -Dfile.encodingUTF-8 -jar sb-blog-1.0.jar blog.log 21 参数解释--Xms512m -Xmx1024m初始堆内存512MB最大1024MB避免小内存VPS频繁GC--XX:UseG1GC启用G1垃圾收集器比默认的Parallel GC更适合响应时间敏感的应用- blog.log 21标准输出和错误输出都重定向到blog.log方便排查部署避坑CentOS 7默认用OpenJDK 1.8.0_292但某些版本的java.security文件里禁用了TLSv1.2导致微信登录回调失败。解决方案是在启动命令里加参数-Dhttps.protocolsTLSv1.2,TLSv1.3。5. 常见问题与实战排错指南那些文档里不会写的坑5.1 启动报错“Failed to configure a DataSource”怎么办这是新手最高频问题。错误日志末尾通常跟着Consider the following:然后列出一堆配置建议。但根本原因就一个application.yml里的数据库连接参数没填对。排查步骤1. 检查spring.datasource.url是否漏了?serverTimezoneAsia/Shanghai2. 检查spring.datasource.username和password是否和MySQL创建的用户一致3. 最容易忽略的点MySQL服务是否真的在运行执行systemctl status mysqld若显示inactive则运行systemctl start mysqld4. 如果用的是远程MySQL非localhost检查MySQL是否允许远程连接sql USE mysql; UPDATE user SET host% WHERE userblog_user; FLUSH PRIVILEGES;并确认防火墙开放了3306端口firewall-cmd --permanent --add-port3306/tcp。5.2 前台页面空白F12看Network全是404这通常不是后端问题而是静态资源路径错了。SpringBoot 2.x默认静态资源放在classpath:/static/但如果你把CSS文件放到了src/main/resources/static/css/而Thymeleaf模板里写的是link href/css/admin.css relstylesheet那路径就对了但如果写成link hrefcss/admin.css relstylesheet少了一个斜杠就会变成相对路径导致404。解决方案- 统一用绝对路径所有href和src属性都以/开头- 检查application.yml里是否误加了spring.mvc.static-path-pattern/static/**这会把静态资源映射到/static/css/admin.css而模板里没写/static/前缀- 最彻底的办法在WebMvcConfigurer里重写addResourceHandlers把/static/**映射到classpath:/static/并开启setCachePeriod(3600)提升前端加载速度。5.3 评论提交后不显示但数据库里有记录这是典型的缓存穿透问题。CommentController.list()方法返回评论列表但前端AJAX请求时没带Cache-Control: no-cache头浏览器缓存了第一次的空响应。解决方案有两个前端强制不缓存在comment.js的AJAX配置里加javascript $.ajax({ url: /comment/list, type: GET, cache: false, // 关键 data: { articleId: 123 }, success: function(data) { /* 渲染评论 */ } });后端加时间戳参数url: /comment/list?t new Date().getTime()让每次请求URL都不同。实战经验曾有个学生反馈“评论提交后要刷新三次才显示”最后发现是他用Chrome隐身模式测试而隐身模式默认禁用所有缓存导致cache: false失效。改成时间戳方案后问题消失。5.4 如何快速扩展功能以“文章点赞”为例的改造路径想加点赞功能别急着写新表先分析现有结构评估数据量假设博客日均100篇文章每篇平均10个赞一年才36万条记录blog_article_like表用BIGINT主键完全够用复用现有表结构参考blog_comment表新建blog_article_like字段只需id、article_id、user_id、create_time后端改造最小化- Mapper接口加int countByArticleId(Long articleId)- Service加like(Long articleId, Long userId)方法先查是否存在记录不存在才插入- Controller加PostMapping(/article/like)返回{code:200,msg:点赞成功,data:{count:123}}前端只需改两处- 文章页加button onclickdoLike(${article.id}) ${article.likeCount}/button-article.js里写function doLike(articleId) { $.post(/article/like, {id: articleId}, function(res){ ... }); }整个过程不到1小时且不影响原有功能。这就是良好架构的价值所有扩展都像拼乐高而不是重铸钢铁。6. 项目延伸与教学价值从博客系统到工程能力的跃迁这套博客系统真正的价值不在它能跑起来而在于它是一面镜子照出开发者从“写代码”到“做工程”的差距。我带的学生里有人花三天把系统跑通却花两周才搞懂为什么pom.xml里spring-boot-starter-thymeleaf的版本要和SpringBoot主版本对齐有人能写出复杂的SQL关联查询却在application.yml里把spring.datasource.driver-class-name写成com.mysql.jdbc.DriverMySQL 5.7要用com.mysql.cj.jdbc.Driver导致驱动找不到。它教会你的是在约束中做选择的能力。比如为什么不用Druid连接池而用HikariCP因为SpringBoot 2.3.x默认集成Hikari配置项更少spring.datasource.hikari.maximum-pool-size20一行搞定且性能比Druid高15%为什么日志用Logback而不换Log4j2因为Logback是SLF4J原生实现启动更快且logback-spring.xml里可以用springProfile nameprod按环境切分日志级别——这些都不是“最好”的技术而是“最合适”的技术。最后分享个小技巧如果你想用它做毕业设计千万别只交源码。在README.md里加一张架构图用draw.io画用文字描述清楚“用户注册流程”前端表单→Controller校验→Service事务→Mapper插入→返回JSON再附上sb_blog.sql里三张核心表的ER图。答辩时老师问“你怎么保证数据一致性”你就指着Transactional说“所有写操作都在一个事务里要么全成功要么全回滚。”——这比讲一百遍CAP理论更有说服力。毕竟工程的本质不是炫技而是用最稳妥的方式把事情做成。本文还有配套的精品资源点击获取简介基于SpringBoot开发的轻量级个人博客系统支持双角色普通用户可注册登录、浏览文章、搜索内容、留言互动、查看阅读量和推荐文章管理员可在后台管理栏目、发布/编辑/删除文章、标记推荐内容、审核评论、配置用户权限及系统基础参数。技术组合为SpringBoot 2.x MyBatis MySQL 5.7 jQuery使用Maven构建适配JDK8兼容IDEA和Eclipse开发环境。压缩包内含完整项目结构src/main/java、resources、pom.xml等、已调试通过的MySQL建库脚本sb_blog.sqlNavicat可直接导入、.gitignore等标准配置文件无编译报错无需额外修改即可导入运行。适合高校课程设计、毕业设计参考或快速部署个人技术博客站点代码结构清晰配置齐全包含artifacts打包配置和常见IDE支持文件。本文还有配套的精品资源点击获取