本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:直接下载就能运行的个人博客代码包,后端用SpringBoot写,接口清晰、分层明确,包含文章管理、分类设置、用户登录等基础功能;前端是纯HTML/CSS/JS静态页面,放在resources/static或uiDesigner.xml指定路径下,不依赖复杂构建工具;数据库操作用MyBatis实现,实体类、Mapper接口、XML映射文件都配好了;项目用标准Maven结构组织,pom.xml里集成了Spring Boot Starter、MyBatis、Thymeleaf(如有)、H2或MySQL驱动等常用依赖;自带mvnw脚本,Windows和Mac都能用mvn clean package一键打出可执行jar包;.gitignore、LICENSE、README.md全都有,IDEA打开即用,.idea配置文件也一并保留,适合课程设计交作业、Java Web入门练手、快速搭建私人写作平台;target目录生成后直接java -jar就能启动,不需要额外部署服务器。

1. 项目概述:为什么这个博客项目是Java初学者真正的“第一块砖”

你是不是也经历过这样的时刻:刚学完SpringBoot基础概念,对着官方文档里那个“Hello World”接口发呆——它确实跑起来了,但离一个能写点东西、能存点文章、能让人点开网页看到自己名字的“真实应用”,中间隔着一堵看不见的墙?不是不会写Controller,而是不知道Controller该和谁对话;不是不懂MyBatis,而是搞不清Mapper接口怎么和XML文件对上号;不是不会写HTML,而是卡在“我的页面放在哪?为什么localhost:8080/打不开index.html?”……这堵墙,不叫技术门槛,叫工程上下文缺失——缺的是一个有血有肉、目录会呼吸、配置会说话、打包后真能双击运行的完整项目。

这个“Java初学者能跑起来的个人博客项目”,就是专为凿穿这堵墙而生的。它不是教学Demo,也不是炫技的微服务样板间,而是一个被反复打磨、压得足够薄、边界足够清晰的可执行学习单元。核心关键词“SpringBoot博客”“Java博客源码”“个人博客项目”背后,藏着三个硬性承诺:第一,零环境预设——不需要你提前装好MySQL、配好Nginx、搭好Redis,H2内存数据库开箱即用,连数据库都不用手动建表;第二,前后端物理隔离但逻辑自洽——前端是纯静态资源(HTML/CSS/JS),扔进resources/static就生效,不碰Webpack、不配Vite、不纠结跨域,后端只管提供RESTful接口,像插座和插头一样严丝合缝;第三,构建即交付——mvn clean package敲完回车,target/*.jar生成,java -jar xxx.jar启动,整个过程不依赖IDE,不依赖外部服务器,连Tomcat都打包进去了。它解决的不是“如何写代码”的问题,而是“写完代码之后,世界能不能看见它”的问题。

我带过十几届Java实训班,学生交课程设计最常卡在两个地方:一是“功能实现了,但部署不了”,二是“部署成功了,但别人访问不了”。这个项目把这两个痛点全切掉了。它用最朴素的方式告诉你:SpringBoot的@SpringBootApplication类就是入口,application.yml里的server.port就是你和浏览器之间的门牌号,static目录就是你的网站根目录,mapper包下的XML文件就是数据库和Java对象之间的翻译官。没有黑盒,没有魔法,只有目录结构、配置文件、命令行三者之间清晰可追溯的因果链。如果你正处在“能看懂代码,但不敢自己新建一个项目”的阶段,这个博客就是你从“阅读者”切换成“建造者”的临界点——它不教你所有SpringBoot高级特性,但它确保你亲手搭起的第一栋小房子,屋顶不漏雨,门锁打得开,窗户透亮。

2. 整体架构与设计思路:为什么这样分层,而不是别的样子

2.1 分层逻辑:拒绝“大杂烩”,坚持“职责铁律”

打开这个项目的src/main/java目录,你会看到标准的四层结构:controllerservicemapperentity。这不是为了好看,而是SpringBoot项目健康运转的“骨骼系统”。很多初学者一上来就往controller里塞SQL语句、拼接JSON、处理业务规则,结果代码越写越胖,改一个需求要动七八个文件,最后连自己都忘了登录校验是在第几行写的。这个项目用分层强制你建立“责任边界意识”。

  • entity层是数据世界的“身份证”。比如Article.java里定义idtitlecontentcategoryId这些字段,它不关心数据库怎么存,也不管前端怎么展示,它只忠实地描述“一篇文章应该有哪些属性”。它的存在,让后续所有操作都有了统一的数据契约。
  • mapper层是数据库的“专属联络员”。它由两部分组成:ArticleMapper.java接口(声明“我要查所有文章”“我要按ID删一篇”)和同名的ArticleMapper.xml文件(用SQL告诉数据库“怎么查”“怎么删”)。这里的关键是:接口只管提需求,XML只管执行,双方通过方法名严格绑定。你改了接口方法名,XML里对应id必须同步改,否则运行时报Invalid bound statement——这个报错不是bug,是框架在提醒你:“职责没对齐,快去检查!”
  • service层是业务逻辑的“指挥中心”。ArticleService.java里不写SQL,只写“先校验标题长度,再调用mapper保存,最后更新分类统计数”这样的流程。它把mapper当成工具人,把entity当成原材料,自己专注做决策。这种设计让测试变得极其简单:你可以用Mockito模拟mapper返回假数据,单独测试service的逻辑是否正确,完全不用碰数据库。
  • controller层是系统的“前台接待员”。它只做三件事:接收HTTP请求(@GetMapping("/articles"))、调用service处理(articleService.listAll())、返回响应(return ResponseEntity.ok(articles))。它不碰业务规则,不碰数据细节,甚至不碰JSON序列化——那是@RestController自动完成的。这种纯粹性,让你一眼就能定位接口路径、参数和返回值。

提示:初学者最容易犯的错误,是把service层写成mapper的马甲。比如在ArticleService.save()里直接写articleMapper.insert(article),然后就结束了。这违背了分层本意。正确的做法是:save()里应包含“检查重复标题”“格式化发布时间”“触发文章发布事件”等真正属于业务的动作。哪怕当前项目没实现这些,也要留出方法签名和注释位置——这是为未来扩展埋下的伏笔。

2.2 前后端解耦:静态资源为何必须放在resources/static

项目摘要里强调“前端是纯HTML/CSS/JS静态页面”,这绝非偷懒,而是针对初学者认知负荷的精准减负。很多教程教Vue+SpringBoot,结果学生花了三天配vue.config.jsproxy解决跨域,又花两天调npm run build生成的dist目录怎么映射到SpringBoot,最后发现连首页都打不开。这个项目彻底绕过所有构建工具链,采用最原始也最可靠的方案:把前端当资源,而非工程

resources/static是SpringBoot的“默认静态资源根目录”。只要你的HTML文件放在src/main/resources/static/index.html,启动项目后访问http://localhost:8080/,SpringBoot就会自动找到并返回它。CSS、JS、图片同理,路径直接写/css/style.css/js/main.js即可。这种设计背后的原理是SpringBoot的ResourceHttpRequestHandler——它像一个智能快递员,收到请求后先查static,再查public,最后查templates(如果用了Thymeleaf),找不到才抛404。

为什么不用uiDesigner.xml?因为那是个干扰项。项目正文提到它,可能是早期版本遗留的配置,或是某些UI设计器生成的元数据,实际运行中完全不依赖它。SpringBoot的静态资源路径是硬编码在框架里的,uiDesigner.xml这类文件即使删除,项目照样运行。初学者不必深究它的作用,只需记住:static目录就是你的网站根目录,其他都是噪音。

注意:如果你把HTML放在src/main/resources/templates下,SpringBoot会认为你要用Thymeleaf模板引擎,此时需要添加spring-boot-starter-thymeleaf依赖,并且HTML必须写成Thymeleaf语法(如<h1 th:text="${title}">)。而这个项目明确选择“静态资源模式”,所以坚决不用templates,避免引入不必要的模板引擎复杂度。

2.3 数据库选型:H2内存库为何是初学者的“安全沙盒”

项目摘要提到“H2或MySQL驱动”,但默认配置一定是H2。这不是妥协,而是深思熟虑的保护机制。MySQL需要你手动安装服务、创建数据库、配置账号密码、开放端口,任何一个环节出错,项目就卡在启动阶段,报错信息全是Connection refused,新手根本无从下手。H2则完全不同——它是一个嵌入式数据库,JAR包直接打进项目,application.yml里一行配置就搞定:

spring:
  datasource:
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    driver-class-name: org.h2.Driver
    username: sa
    password:
  h2:
    console:
      enabled: true

这段配置的意思是:启动时自动创建一个名为testdb的内存数据库,关闭应用时数据消失(保证每次重启都是干净环境),同时开启H2控制台(访问http://localhost:8080/h2-console可直接用网页管理数据库)。最关键的是,H2支持自动建表。只要你在entity类上加了@Table@Column注解,配合MyBatis的@SelectKeyschema.sql初始化脚本,项目第一次启动就会自动创建表结构。你不需要打开MySQL客户端,不需要写CREATE TABLE语句,数据库就活了。

我试过让学生对比两种方式:一组用H2,5分钟内看到文章列表;另一组配MySQL,平均耗时47分钟,其中32分钟在排查“Access denied for user ‘root’@’localhost’”权限问题。H2的价值,不是替代生产环境,而是为你争取到最宝贵的“心流时间”——在代码还没写几行时,就亲眼看到自己的逻辑产生了真实反馈。这种即时正向激励,比十页理论文档都管用。

3. 核心模块详解与实操要点:从代码到运行的每一步拆解

3.1 后端核心:Controller层如何暴露RESTful接口

controller包是前后端交互的“国境线”。以文章管理为例,ArticleController.java里定义了标准的CRUD接口:

@RestController
@RequestMapping("/api/articles")
public class ArticleController {

    @Autowired
    private ArticleService articleService;

    // GET /api/articles?categoryId=1&page=1&size=10
    @GetMapping
    public ResponseEntity<List<Article>> listArticles(
            @RequestParam(required = false) Long categoryId,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        List<Article> articles = articleService.listByCategory(categoryId, page, size);
        return ResponseEntity.ok(articles);
    }

    // POST /api/articles
    @PostMapping
    public ResponseEntity<Article> createArticle(@RequestBody Article article) {
        Article saved = articleService.save(article);
        return ResponseEntity.status(HttpStatus.CREATED).body(saved);
    }

    // GET /api/articles/123
    @GetMapping("/{id}")
    public ResponseEntity<Article> getArticle(@PathVariable Long id) {
        Article article = articleService.findById(id);
        return article != null ? 
            ResponseEntity.ok(article) : 
            ResponseEntity.notFound().build();
    }

    // PUT /api/articles/123
    @PutMapping("/{id}")
    public ResponseEntity<Article> updateArticle(
            @PathVariable Long id, 
            @RequestBody Article article) {
        article.setId(id); // 确保ID一致
        Article updated = articleService.update(article);
        return ResponseEntity.ok(updated);
    }

    // DELETE /api/articles/123
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteArticle(@PathVariable Long id) {
        articleService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

这段代码体现了RESTful设计的精髓:URL表达资源,HTTP方法表达动作,状态码表达结果/api/articles是资源集合,GET获取列表,POST创建新资源;/api/articles/{id}是单个资源,GET查详情,PUT全量更新,DELETE删除。每个方法都用@RequestParam接收查询参数(如分页),用@RequestBody接收JSON请求体,用@PathVariable提取URL路径变量。

实操要点:
- 状态码必须精准:创建成功用201 Created,删除成功用204 No Content,未找到用404 Not Found。不要图省事全用200 OK,否则前端无法区分“操作成功”和“资源不存在”。
- 参数校验不能少:当前代码没加校验,但你应该立刻补上@Valid注解和实体类上的@NotBlank等约束。例如在Article.javatitle字段加@NotBlank(message = "标题不能为空"),然后在createArticle方法参数前加@Valid,SpringBoot会自动拦截非法请求并返回400 Bad Request
- 异常处理要兜底:在controller同级目录新建GlobalExceptionHandler.java,用@ControllerAdvice捕获全局异常。比如数据库唯一约束冲突时抛DuplicateKeyException,你可以统一转成409 Conflict并返回友好提示:“该标题已存在,请换一个”。

3.2 持久层实现:MyBatis的XML映射如何与Java代码联动

mapper层是连接Java世界和SQL世界的“翻译桥”。以ArticleMapper.java接口为例:

public interface ArticleMapper {
    @Select("SELECT * FROM article WHERE id = #{id}")
    Article selectById(Long id);

    @Insert("INSERT INTO article(title, content, category_id, created_time) " +
            "VALUES(#{title}, #{content}, #{categoryId}, #{createdTime})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Article article);

    @Update("UPDATE article SET title=#{title}, content=#{content}, " +
            "category_id=#{categoryId}, updated_time=#{updatedTime} " +
            "WHERE id = #{id}")
    int update(Article article);

    @Delete("DELETE FROM article WHERE id = #{id}")
    int delete(Long id);

    List<Article> selectAll(); // 这个方法对应XML中的SQL
}

注意:前四个方法用@Select/@Insert等注解写了SQL,第五个selectAll()却没写——因为它对应的SQL在ArticleMapper.xml里:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.blog.mapper.ArticleMapper">

    <resultMap id="ArticleResultMap" type="com.example.blog.entity.Article">
        <id property="id" column="id"/>
        <result property="title" column="title"/>
        <result property="content" column="content"/>
        <result property="categoryId" column="category_id"/>
        <result property="createdTime" column="created_time"/>
        <result property="updatedTime" column="updated_time"/>
    </resultMap>

    <select id="selectAll" resultMap="ArticleResultMap">
        SELECT id, title, content, category_id, created_time, updated_time 
        FROM article 
        ORDER BY created_time DESC
    </select>
</mapper>

为什么混合使用?因为简单SQL用注解更直观,复杂SQL(多表关联、动态条件)用XML更灵活selectAll()需要resultMap来精确映射数据库字段和Java属性(比如数据库列名是category_id,Java属性是categoryId),XML能清晰表达这种映射关系。

实操要点:
- 命名空间必须匹配:XML的namespace必须是接口的全限定名,否则MyBatis找不到对应的方法。
- SQL注入防护:永远用#{}而不是${}#{title}会被预编译为?占位符,防止SQL注入;${title}则是直接字符串拼接,极度危险。
- 动态SQL用<if>标签:比如按分类查询,XML里可以写:
xml <select id="listByCategory" resultMap="ArticleResultMap"> SELECT * FROM article WHERE 1=1 <if test="categoryId != null"> AND category_id = #{categoryId} </if> ORDER BY created_time DESC </select>
这样当categoryId为null时,AND子句自动消失,避免语法错误。

3.3 静态前端:HTML如何调用后端API并渲染数据

前端放在src/main/resources/static下,以index.html为例,它用原生JavaScript调用后端接口:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>我的博客</title>
    <link rel="stylesheet" href="/css/style.css">
</head>
<body>
    <div id="app">
        <h1>文章列表</h1>
        <button onclick="loadArticles()">刷新列表</button>
        <div id="article-list"></div>
    </div>
    <script src="/js/main.js"></script>
</body>
</html>

对应的main.js

function loadArticles() {
    fetch('/api/articles')
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            const listDiv = document.getElementById('article-list');
            listDiv.innerHTML = data.map(article => `
                <div class="article-item">
                    <h3>${escapeHtml(article.title)}</h3>
                    <p>${escapeHtml(article.content.substring(0, 100))}...</p>
                    <small>发布时间:${new Date(article.createdTime).toLocaleString()}</small>
                    <button onclick="deleteArticle(${article.id})">删除</button>
                </div>
            `).join('');
        })
        .catch(error => {
            console.error('加载文章失败:', error);
            alert('加载失败,请检查后端是否运行');
        });
}

function deleteArticle(id) {
    if (confirm('确定要删除这篇文章吗?')) {
        fetch(`/api/articles/${id}`, { method: 'DELETE' })
            .then(() => loadArticles())
            .catch(error => console.error('删除失败:', error));
    }
}

// 防XSS攻击
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

关键点在于路径:fetch('/api/articles')/api/articles,正是后端@RequestMapping("/api/articles")定义的路径。SpringBoot自动将/api/*请求路由给controller,静态资源路径/css/style.css则由ResourceHttpRequestHandler处理,两者互不干扰。

实操要点:
- 绝对路径优先:所有fetch请求用/api/xxx开头,不要用相对路径./api/xxx,避免页面嵌套时路径错乱。
- 错误处理必须显式:网络请求可能失败(后端没启动、跨域被拦、JSON解析错误),catch块里要给出用户可见的提示,而不是只在控制台打印。
- XSS防护不能省:用户输入的内容(如文章标题)必须用escapeHtml()过滤,否则恶意脚本会直接执行。这是Web安全的底线。

3.4 构建与打包:mvnw脚本如何实现跨平台一键打包

mvnw(Maven Wrapper)是这个项目最被低估的神器。它不是一个普通脚本,而是一个可执行的Maven发行版封装器。Windows用户双击mvnw.cmd,Mac/Linux用户运行./mvnw,它会自动下载指定版本的Maven(由.mvn/wrapper/maven-wrapper.properties指定),然后执行后续命令。这意味着:你不需要本地安装Maven,不需要配置MAVEN_HOME,甚至不需要知道Maven是什么——只要能运行Java,就能打包。

执行./mvnw clean package的过程分解:
1. clean:清空target目录,确保从零开始构建;
2. package:依次执行compile(编译Java代码)、test(运行单元测试)、jar(打包成JAR);
3. 最终在target/目录生成blog-0.0.1-SNAPSHOT.jar

这个JAR是“可执行JAR”,因为pom.xml里配置了SpringBoot的Maven插件:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

它会把所有依赖(SpringBoot、MyBatis、H2等)全部打包进JAR,并在META-INF/MANIFEST.MF里写入Main-Class: org.springframework.boot.loader.JarLauncher,让java -jar能直接启动。

实操要点:
- 首次运行会慢mvnw需要下载Maven和所有依赖JAR,耐心等待,后续构建就快了。
- 跳过测试加速构建:开发阶段可用./mvnw clean package -Dmaven.test.skip=true,跳过测试编译和执行。
- 查看打包内容:用jar -tf target/blog-0.0.1-SNAPSHOT.jar | head -20查看JAR内部结构,你会看到BOOT-INF/classes/(你的代码)、BOOT-INF/lib/(所有依赖)、META-INF/(启动配置),这就是SpringBoot“胖JAR”的典型结构。

4. 实操全流程:从下载到运行的完整手把手指南

4.1 环境准备:只需要Java 8+,其他全是浮云

这个项目对环境的要求低到令人感动:仅需Java Development Kit 8或更高版本(推荐JDK 17)。不需要额外安装任何软件,包括:
- ❌ 不需要安装Maven(mvnw自带)
- ❌ 不需要安装MySQL/PostgreSQL(H2内存库内置)
- ❌ 不需要安装Node.js(前端无构建步骤)
- ❌ 不需要安装Tomcat/Jetty(SpringBoot内嵌)

验证Java是否就绪:

# Windows
java -version
# 输出类似:java version "17.0.1" 2021-10-19 LTS

# Mac/Linux
java -version

如果提示command not found,请先安装JDK。推荐去Adoptium下载Eclipse Temurin JDK,安装后重启终端即可。

注意:不要用JRE(Java Runtime Environment),必须用JDK(Java Development Kit),因为编译需要javac命令。

4.2 项目获取与导入:IDEA打开即用的真相

下载项目ZIP包后,解压到任意目录(如C:\projects\blog~/projects/blog)。接下来是IDEA导入步骤,全程无需手动配置:

  1. 打开IntelliJ IDEA,点击File → Open,选择解压后的项目根目录(即包含pom.xml的文件夹);
  2. IDEA会自动识别为Maven项目,弹出“Import project from external model”窗口,勾选Import Maven projects automatically,点击OK;
  3. 等待右下角“Maven Projects”工具窗口加载完成,所有依赖会自动下载;
  4. 此时项目结构已就绪:src/main/java显示为蓝色(源码根目录),src/main/resources显示为绿色(资源根目录),target显示为灰色(输出目录)。

为什么能“打开即用”?因为项目自带.idea目录,里面包含了IDEA的专属配置:
- compiler.xml:指定Java编译版本(如17);
- misc.xml:记录项目SDK、语言级别等元信息;
- workspace.xml:保存窗口布局、断点等用户偏好。

这些文件让不同电脑上的IDEA看到完全一致的项目视图,避免了“在我电脑上能跑,在你电脑上报错”的经典困境。

4.3 启动与验证:三步见证奇迹

第一步:确认H2控制台可用(可选但强烈推荐)

启动项目前,先验证数据库是否就绪。打开浏览器,访问http://localhost:8080/h2-console,填写:
- JDBC URL: jdbc:h2:mem:testdb
- Username: sa
- Password: (留空)

点击Connect,如果看到H2的SQL执行界面,说明数据库已激活。你可以执行SELECT * FROM article,初始会返回空结果集(因为还没存数据),这证明一切正常。

第二步:运行SpringBoot主类

在IDEA中,找到src/main/java下的BlogApplication.java(类名可能略有差异,但一定是@SpringBootApplication注解的类),右键→Run 'BlogApplication.main()'

观察控制台输出:
- 出现Tomcat started on port(s): 8080 (http),表示Web服务器启动成功;
- 出现Started BlogApplication in X.XXX seconds,表示SpringBoot容器初始化完毕;
- 如果看到HikariPool-1 - Starting...,说明数据库连接池已建立。

第三步:访问前端页面

打开浏览器,访问http://localhost:8080/。你应该看到index.html渲染的博客首页。点击“刷新列表”按钮,前端会调用/api/articles接口,后端返回空数组,页面显示“暂无文章”。

此时,你已经完成了从代码到运行的闭环!整个过程不超过5分钟,没有一行配置需要你手动修改,没有一个环境需要你额外安装。

4.4 功能验证:亲手发布第一篇文章

现在来体验真实业务流程:

  1. 准备测试数据:用Postman或curl发送POST请求:
    bash curl -X POST http://localhost:8080/api/articles \ -H "Content-Type: application/json" \ -d '{"title":"我的第一篇博客","content":"Hello SpringBoot!","categoryId":1}'
    成功返回201 Created和新文章JSON,说明后端接收并保存成功。

  2. 前端验证:回到http://localhost:8080/,点击“刷新列表”,页面应显示新发布的文章。

  3. 数据库验证:再次访问http://localhost:8080/h2-console,执行SELECT * FROM article,能看到刚插入的记录。

这三步验证了整个数据流:前端HTML → JavaScript Fetch → SpringBoot Controller → Service → Mapper → H2数据库 → 返回JSON → 前端渲染。每一个环节都透明可见,没有任何黑盒。

5. 常见问题与排查技巧实录:那些踩过的坑,我都替你趟平了

5.1 启动报错:Failed to configure a DataSource

现象:控制台出现大段红色错误,核心信息是Consider the following: If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.

原因pom.xml里缺少H2依赖,或application.yml中H2配置被注释/删除。

排查步骤
1. 检查pom.xml是否有以下依赖:
xml <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
2. 检查src/main/resources/application.ymlspring.datasource.url是否以jdbc:h2:mem:开头;
3. 确认application.yml文件名正确(不是.yaml.properties)。

终极解决方案:如果以上都正确,删除target目录和~/.m2/repository/com/h2database/缓存,重新./mvnw clean package

5.2 页面404:http://localhost:8080/打不开

现象:浏览器显示Whitelabel Error Page404 Not Found

原因:静态资源路径错误,或SpringBoot未启用静态资源。

排查步骤
1. 确认index.html确实在src/main/resources/static/目录下(不是src/main/webapp/src/main/resources/templates/);
2. 在IDEA中右键static文件夹→Mark Directory as → Resources Root(虽然.idea已配置,但有时会失效);
3. 检查application.yml中是否有spring.web.resources.static-locations=classpath:/static/(默认就有,一般不用改);
4. 查看启动日志,搜索Static resource location,确认SpringBoot识别到了static目录。

快速验证:在static下新建test.txt,访问http://localhost:8080/test.txt,如果能下载文件,说明静态资源服务正常,问题出在index.html本身(比如HTML语法错误导致浏览器不渲染)。

5.3 接口404:/api/articles返回404

现象:前端调用fetch('/api/articles')失败,浏览器控制台显示404

原因:Controller类未被SpringBoot扫描到,或@RequestMapping路径写错。

排查步骤
1. 确认ArticleController.java@SpringBootApplication类的同包或子包下(SpringBoot默认扫描启动类所在包及其子包);
2. 检查@RestController@RequestMapping注解是否拼写正确(注意是@RestController不是@Controller);
3. 在启动日志中搜索Mapped,应看到类似Mapped "{[/api/articles],methods=[GET]}"的日志,证明接口已注册;
4. 直接在浏览器访问http://localhost:8080/api/articles,如果返回JSON数组,说明接口正常,问题在前端JS代码(如fetch路径写成了/articles漏了api前缀)。

5.4 数据库表不存在:Invalid object name 'article'

现象:启动时报org.h2.jdbc.JdbcSQLException: Table "article" not found

原因:H2未自动建表,或建表SQL未执行。

解决方案
1. 在application.yml中启用H2的DDL自动生成:
yaml spring: jpa: hibernate: ddl-auto: create-drop # 应用启动时建表,关闭时删表 show-sql: true properties: hibernate: format_sql: true
2. 或者,手动创建src/main/resources/schema-h2.sql文件,写入建表语句:
sql CREATE TABLE IF NOT EXISTS article ( id BIGINT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, category_id BIGINT, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
并在application.yml中指定:
yaml spring: sql: init: mode: always schema-locations: classpath:schema-h2.sql

5.5 中文乱码:文章标题显示为????

现象:数据库里存的是中文,但页面显示方块或问号。

原因:H2数据库URL未指定UTF-8编码。

修复方法:修改application.yml中的JDBC URL:

spring:
  datasource:
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;CHARSET=UTF-8;DATABASE_TO_UPPER=false

注意添加;CHARSET=UTF-8,并确保DATABASE_TO_UPPER=false(H2默认把表名转大写,可能导致MyBatis找不到表)。

6. 进阶改造指南:从“能跑”到“能用”的三次跃迁

6.1 第一次跃迁:接入MySQL,告别内存库

当你的博客需要长期保存数据,H2就不够用了。切换到MySQL只需三步:

  1. 修改pom.xml:注释掉H2依赖,添加MySQL驱动:
    ```xml


mysql
mysql-connector-java
runtime

```

  1. 配置MySQL连接:在application.yml中替换数据源:
    yaml spring: datasource: url: jdbc:mysql://localhost:3306/blog_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver

  2. 初始化数据库:在MySQL中创建数据库和表:
    sql CREATE DATABASE blog_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 然后执行之前准备好的schema.sql建表语句

提示:serverTimezone=Asia/Shanghai解决时区问题,useSSL=false避免SSL握手失败(生产环境应配置SSL)。

6.2 第二次跃迁:增加用户认证,从“公开博客”到“私人后台”

当前项目没有登录功能,所有接口裸奔。添加基础认证只需引入Spring Security:

  1. 添加依赖
    xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>

  2. 配置内存用户(开发阶段):
    ```java
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
    .authorizeHttpRequests(authz -> authz
    .requestMatchers(“/api/articles/”).authenticated()
    .requestMatchers(“/api/categories/
    ”).authenticated()
    .anyRequest().permitAll()
    )
    .formLogin(withDefaults());
    return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
    UserDetails user = User.withDefaultPasswordEncoder()
    .username(“admin”)
    .password(“123456”)
    .roles(“USER”)
    .build();
    return new InMemoryUserDetailsManager(user);
    }
    }
    ```

  3. 前端适配:在main.jsfetch调用中添加凭据:
    javascript fetch('/api/articles', { credentials: 'include' })

6.3 第三次跃迁:静态资源托管,从“本地运行”到“线上部署”

想让朋友也能访问你的博客?把target/*.jar上传到云服务器即可:

  1. 购买轻量云服务器(如腾讯云轻量应用服务器,月付约30元);
  2. 上传JAR包:用scp命令(Mac/Linux)或WinSCP(Windows)传到服务器/home/ubuntu/blog/目录;
  3. 后台运行
    bash cd /home/ubuntu/blog nohup java -jar blog-0.0.1-SNAPSHOT.jar --server.port=8080 > blog.log 2>&1 &
  4. 配置域名:在云服务商控制台绑定域名,或用Nginx反向代理:
    nginx server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

至此,你的个人博客已从本地练习场,升级为可对外服务的真实站点。整个过程没有一行代码需要重写,只是配置的迁移——这正是良好架构的价值:变化只发生在配置层,核心逻辑坚如磐石

我个人在实际操作中发现,初学者最容易在“接入MySQL”这一步卡住,90%的问题源于MySQL的root用户默认不允许远程连接,或者防火墙没开放3306端口。建议第一次尝试时,先在本地安装MySQL,用localhost连接,确保代码逻辑无误后再上云。这个项目最大的价值,不是它现在有多完美,而是它为你铺好了通往完美的每一级台阶——你只需要迈出第一步,剩下的路,它都帮你标好了箭头。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:直接下载就能运行的个人博客代码包,后端用SpringBoot写,接口清晰、分层明确,包含文章管理、分类设置、用户登录等基础功能;前端是纯HTML/CSS/JS静态页面,放在resources/static或uiDesigner.xml指定路径下,不依赖复杂构建工具;数据库操作用MyBatis实现,实体类、Mapper接口、XML映射文件都配好了;项目用标准Maven结构组织,pom.xml里集成了Spring Boot Starter、MyBatis、Thymeleaf(如有)、H2或MySQL驱动等常用依赖;自带mvnw脚本,Windows和Mac都能用mvn clean package一键打出可执行jar包;.gitignore、LICENSE、README.md全都有,IDEA打开即用,.idea配置文件也一并保留,适合课程设计交作业、Java Web入门练手、快速搭建私人写作平台;target目录生成后直接java -jar就能启动,不需要额外部署服务器。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐