ITPub博客

首页 > IT职业 > IT职场 > Java博客系统实战:从零搭建

Java博客系统实战:从零搭建

原创 IT职场 作者:易挂 时间:2025-11-25 20:40:16 0 删除 编辑

Java博客系统实战:从零搭建

引言

在当今数字化时代,拥有一个个人博客是展示专业能力、分享知识的重要途径。本文将带您从零开始,使用Java技术栈构建一个功能完善的博客系统。我们将采用Spring Boot框架、MySQL数据库和Thymeleaf模板引擎,打造一个具备用户管理、文章发布、评论互动等核心功能的博客平台。

一、系统架构设计

1.1 技术选型

  • ‌ 后端框架‌:Spring Boot 2.7.12(稳定版本)
  • ‌ 数据库‌:MySQL 8.0
  • ‌ 模板引擎‌:Thymeleaf 3.1.1
  • ‌ 安全框架‌:Spring Security 5.7.10
  • ‌ 前端‌:HTML5/CSS3/JavaScript
  • ‌ 构建工具‌:Maven 3.8.6

1.2 核心模块设计

textCopy Code├── com.example.blog
│   ├── config            # 配置类
│   ├── controller        # 控制器层
│   ├── model             # 数据模型
│   ├── service           # 业务逻辑层
│   ├── repository        # 数据访问层
│   ├── security          # 安全相关
│   └── web               # 前端资源

二、数据库设计

2.1 用户表设计

javaCopy Code@Entity@Table(name = "users")@Getter @Setter@NoArgsConstructor@AllArgsConstructor@EqualsAndHashCode@ToString@Builder@NoArgsConstructor@AllArgsConstructor@Builder(toBuilder = true)public class User implements Serializable {    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;    
    @Column(name = "username", unique = true, nullable = false, length = 50)
    private String username;    
    @Column(name = "password", nullable = false, length = 255)
    private String password;    
    @Column(name = "email", unique = true, nullable = false, length = 100)
    private String email;    
    @Column(name = "role", nullable = false)
    private String role;    
    @Column(name = "created_at", insertable = false, updatable = false)
    private LocalDateTime createdAt;    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;    
    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Post> posts = new ArrayList<>();
}

2.2 文章表设计

javaCopy Code@Entity@Table(name = "posts")@Getter @Setter@NoArgsConstructor@AllArgsConstructor@EqualsAndHashCode@ToString@Builder@NoArgsConstructor@AllArgsConstructor@Builder(toBuilder = true)public class Post {    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;    
    @Column(name = "title", nullable = false, length = 200)
    private String title;    
    @Column(name = "content", nullable = false, columnDefinition = "TEXT")
    private String content;    
    @Column(name = "slug", unique = true, nullable = false, length = 200)
    private String slug;    
    @Column(name = "status", nullable = false)
    private String status;    
    @Column(name = "created_at", insertable = false, updatable = false)
    private LocalDateTime createdAt;    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;    
    @ManyToOne
    @JoinColumn(name = "author_id", referencedColumnName = "id")
    private User author;    
    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Comment> comments = new ArrayList<>();
}

三、核心功能实现

3.1 用户认证与授权

javaCopy Code@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter {    
    @Autowired
    private UserDetailsService userDetailsService;    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();
    }    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/about", "/contact", "/post/&zwnj;**", "/category/**&zwnj;", "/tag/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .invalidateHttpSession(true)
                .clearAuthentication(true)
                .logoutSuccessUrl("/")
                .permitAll()
            )
            .sessionManagement(session -> session
                .sessionFixation().migrateSession()
                .invalidSessionUrl("/login?error")
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
            );        
        return http.build();
    }    
    @Bean
    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();
    }    
    @Bean
    public UserDetailsService userDetailsService() {        return new UserDetailsServiceImpl(userRepository);
    }
}

3.2 文章管理功能

‌ PostController.java‌

javaCopy Code@RestController@RequestMapping("/api/posts")@RequiredArgsConstructorpublic class PostController {    
    private final PostService postService;    
    @GetMapping
    public List<Post> getAllPosts(@RequestParam(required = false) String status) {        return postService.getPostsByStatus(status);
    }    
    @GetMapping("/{id}")
    public Post getPost(@PathVariable Long id) {        return postService.getPostById(id);
    }    
    @PostMapping
    public ResponseEntity<Post> createPost(@RequestBody Post post) {        Post savedPost = postService.createPost(post);        return ResponseEntity.ok(savedPost);
    }    
    @PutMapping("/{id}")
    public ResponseEntity<Post> updatePost(@PathVariable Long id, @RequestBody Post post) {        Post updatedPost = postService.updatePost(id, post);        return ResponseEntity.ok(updatedPost);
    }    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deletePost(@PathVariable Long id) {
        postService.deletePost(id);        return ResponseEntity.noContent().build();
    }
}

‌ PostService.java‌

javaCopy Code@Service@RequiredArgsConstructorpublic class PostService {    
    private final PostRepository postRepository;    
    public List<Post> getPostsByStatus(String status) {        if (status == null) {            return postRepository.findAll();
        }        return postRepository.findByStatus(status);
    }    
    public Post getPostById(Long id) {        return postRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Post not found"));
    }    
    public Post createPost(Post post) {
        post.setCreatedAt(LocalDateTime.now());
        post.setUpdatedAt(LocalDateTime.now());        return postRepository.save(post);
    }    
    public Post updatePost(Long id, Post post) {        Post existingPost = postRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Post not found"));
        
        existingPost.setTitle(post.getTitle());
        existingPost.setContent(post.getContent());
        existingPost.setSlug(post.getSlug());
        existingPost.setStatus(post.getStatus());
        existingPost.setUpdatedAt(LocalDateTime.now());        
        return postRepository.save(existingPost);
    }    
    public void deletePost(Long id) {
        postRepository.deleteById(id);
    }
}

四、前端界面实现

4.1 文章列表页面

htmlCopy Code<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head>
    <meta charset="UTF-8">
    <title>Blog - 文章列表</title>
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
    <link rel="stylesheet" th:href="@{/css/style.css}"></head><body><div class="container">
    <div class="row">
        <div class="col-md-8">
            <h1 class="mb-4">所有文章</h1>
            
            <div th:if="${#lists.size(posts) == 0}">
                <p>暂无文章</p>
            </div>
            
            <div th:each="post : ${posts}" th:class="post">
                <div class="card mb-4">
                    <div class="card-body">
                        <h2 class="card-title"><a th:href="@{/post/} + ${post.slug}" th:text="${post.title}"></a></h2>
                        <p class="card-text" th:text="${#strings.truncate(post.content, 200)}"></p>
                        <div class="d-flex justify-content-between align-items-center">
                            <div class="btn-group">
                                <a th:href="@{/post/} + ${post.slug}" class="btn btn-primary">阅读更多</a>
                            </div>
                            <small th:text="${#dates.format(post.createdAt, 'yyyy-MM-dd')}"></small>
                        </div>
                    </div>
                </div>
            </div>
            
            <!-- 分页 -->
            <nav>
                <ul class="pagination">
                    <li th:class="${pageNumber == 1} ? 'disabled' : ''" th:classappend="${pageNumber == 1} ? 'disabled' : ''">
                        <a th:href="@{/posts?page=${pageNumber - 1}}" aria-label="Previous">
                            <span aria-hidden="true">&laquo;</span>
                        </a>
                    </li>
                    <li th:each="page : ${#numbers.array(pageCount, 1)}" th:class="${pageNumber == page} ? 'active' : ''">
                        <a th:href="@{/posts?page=${page - 1}}">[[${page}]]</a>
                    </li>
                    <li th:class="${pageNumber == pageCount} ? 'disabled' : ''">
                        <a th:href="@{/posts?page=${pageNumber + 1}}" aria-label="Next">
                            <span aria-hidden="true">&raquo;</span>
                        </a>
                    </li>
                </ul>
            </nav>
        </div>
        
        <div class="col-md-4">
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">分类</h5>
                    <ul class="list-group">
                        <li class="list-group-item" th:each="category : ${categories}" th:text="${category.name}"></li>
                    </ul>
                </div>
            </div>
            
            <div class="card mt-4">
                <div class="card-body">
                    <h5 class="card-title">标签云</h5>
                    <div class="tag-cloud">
                        <a th:href="@{/tag/} + ${tag}" th:each="tag : ${tags}" th:text="${tag}" class="btn btn-sm btn-light me-2 mb-2"></a>
                    </div>
                </div>
            </div>
        </div>
    </div></div><script th:src="@{/js/jquery-3.6.0.min.js}"></script><script th:src="@{/js/bootstrap.bundle.min.js}"></script></body></html>

4.2 文章详情页面

htmlCopy Code<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head>
    <meta charset="UTF-8">
    <title th:text="${post.title} + ' - 我的博客'"></title>
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
    <link rel="stylesheet" th:href="@{/css/style.css}"></head><body><div class="container">
    <div class="row">
        <div class="col-md-8">
            <h1 th:text="${post.title}"></h1>
            <p class="text-muted">
                <span th:text="${#dates.format(post.createdAt, 'yyyy-MM-dd')}"></span>
                <span th:text="${post.author.username}"></span>
            </p>
            
            <div th:utext="${post.content}"></div>
            
            <hr>
            
            <h3>评论</h3>
            <div th:each="comment : ${post.comments}" th:class="comment">
                <div class="card mb-3">
                    <div class="card-body">
                        <h5 class="card-title" th:text="${comment.author.username}"></h5>
                        <p class="card-text" th:text="${comment.content}"></p>
                        <p class="text-muted" th:text="${#dates.format(comment.createdAt, 'yyyy-MM-dd HH:mm')}"></p>
                    </div>
                </div>
            </div>
            
            <h3>发表评论</h3>
            <form th:action="@{/post/${post.id}/comment}" method="post">
                <div class="mb-3">
                    <textarea class="form-control" name="content" rows="5" required></textarea>
                </div>
                <button type="submit" class="btn btn-primary">提交评论</button>
            </form>
        </div>
        
        <div class="col-md-4">
            <!-- 侧边栏内容 -->
        </div>
    </div></div><script th:src="@{/js/jquery-3.6.0.min.js}"></script><script th:src="@{/js/bootstrap.bundle.min.js}"></script></body></html>

五、部署与优化

5.1 Docker部署

‌ Dockerfile‌

dockerfileCopy CodeFROM openjdk:11-jre-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

‌ docker-compose.yml‌

yamlCopy Codeversion: '3.8'services:
  blog:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
    depends_on:
      - db
    restart: unless-stopped
  db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=blog
      - MYSQL_USER=user
      - MYSQL_PASSWORD=password
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - "3306:3306"
    restart: unless-stoppedvolumes:
  db_data:

5.2 性能优化

  1. ‌ 数据库优化‌:

    • 添加索引
    • 使用连接池(HikariCP)
    • 批量操作
  2. ‌ 缓存策略‌:

    • 使用Redis缓存热门文章
    • 实现页面静态化
  3. ‌ 异步处理‌:

    • 使用@Async处理耗时操作
    • 实现消息队列(RabbitMQ/Kafka)

六、扩展功能建议

  1. ‌ SEO优化‌:添加SEO元数据,生成sitemap
  2. ‌ Markdown支持‌:集成Parsedown或commonmark-java
  3. ‌ 全文搜索‌:使用Elasticsearch或Solr
  4. ‌ 多语言支持‌:国际化(i18n)配置
  5. ‌ 统计分析‌:集成Google Analytics或自定义统计
  6. ‌ 备份系统‌:定期备份数据库和文件

结语

通过本文,我们完整地构建了一个基于Java的博客系统。这个项目涵盖了Spring Boot的核心功能,包括:

  • 用户认证与授权
  • RESTful API设计
  • 数据库操作
  • 前端模板渲染
  • 部署配置

新建文本文档.txt


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69921587/viewspace-3103436/ ,如需转载,请注明出处,否则将追究法律责任。

上一篇: 没有了~
下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2025-11-25

  • 博文量
    1
  • 访问量
    1055