本文目录导读:

我来为你提供一个完整的Java论坛系统案例,包含核心功能实现和代码示例。
系统架构设计
技术栈
- 后端: Spring Boot + MyBatis + MySQL
- 前端: Thymeleaf + Bootstrap
- 安全: Spring Security + JWT
- 其他: Redis缓存, Elasticsearch搜索
项目结构
forum-system/
├── src/main/java/com/example/forum/
│ ├── controller/
│ ├── service/
│ ├── mapper/
│ ├── entity/
│ ├── config/
│ └── utils/
├── src/main/resources/
│ ├── templates/
│ ├── static/
│ └── application.yml
└── pom.xml
核心代码实现
实体类
// User.java
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String email;
private String avatar;
private Integer role; // 1:管理员 2:普通用户
private Integer status; // 1:正常 2:禁言
private Integer reputation; // 积分
private LocalDateTime createTime;
// getter/setter省略
}
// Topic.java
@Entity
@Table(name = "topic")
public class Topic {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
private Long userId;
private Long categoryId;
private Integer viewCount;
private Integer replyCount;
private Integer likeCount;
private Long lastReplyId;
private LocalDateTime createTime;
private LocalDateTime updateTime;
@Transient
private User user; // 帖子作者信息
@Transient
private List<Reply> replies; // 回复列表
}
// Reply.java
@Entity
@Table(name = "reply")
public class Reply {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long topicId;
private Long userId;
private String content;
private Integer likeCount;
private LocalDateTime createTime;
@Transient
private User user;
}
Mapper层
// TopicMapper.java
@Mapper
public interface TopicMapper {
@Select("SELECT * FROM topic WHERE status = 1 ORDER BY create_time DESC LIMIT #{offset}, #{limit}")
List<Topic> findLatestTopics(@Param("offset") int offset, @Param("limit") int limit);
@Select("SELECT * FROM topic WHERE category_id = #{categoryId} AND status = 1 ORDER BY create_time DESC")
List<Topic> findByCategory(@Param("categoryId") Long categoryId);
@Select("SELECT * FROM topic WHERE title LIKE CONCAT('%', #{keyword}, '%') OR content LIKE CONCAT('%', #{keyword}, '%')")
List<Topic> search(@Param("keyword") String keyword);
@Select("SELECT * FROM topic WHERE id = #{id} AND status = 1")
Topic findById(@Param("id") Long id);
@Insert("INSERT INTO topic(title, content, user_id, category_id) VALUES(#{title}, #{content}, #{userId}, #{categoryId})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(Topic topic);
@Update("UPDATE topic SET title = #{title}, content = #{content}, category_id = #{categoryId} WHERE id = #{id}")
int update(Topic topic);
@Delete("DELETE FROM topic WHERE id = #{id}")
int delete(@Param("id") Long id);
@Update("UPDATE topic SET view_count = view_count + 1 WHERE id = #{id}")
int incrementViewCount(@Param("id") Long id);
}
Service层
// TopicService.java
@Service
@Transactional
public class TopicService {
@Autowired
private TopicMapper topicMapper;
@Autowired
private ReplyMapper replyMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 获取热门帖子(缓存)
public List<Topic> getHotTopics() {
String key = "hot_topics";
List<Topic> topics = (List<Topic>) redisTemplate.opsForValue().get(key);
if (topics == null) {
topics = topicMapper.findHotTopics();
redisTemplate.opsForValue().set(key, topics, 10, TimeUnit.MINUTES);
}
return topics;
}
// 发布帖子
public Result publish(Topic topic, Long userId) {
topic.setUserId(userId);
if (topicMapper.insert(topic) > 0) {
// 清理缓存
redisTemplate.delete("hot_topics");
return Result.success("发布成功", topic);
}
return Result.error("发布失败");
}
// 查看帖子详情
public Topic getTopicDetail(Long topicId) {
Topic topic = topicMapper.findById(topicId);
if (topic != null) {
// 增加浏览数
topicMapper.incrementViewCount(topicId);
// 获取回复列表
List<Reply> replies = replyMapper.findByTopicId(topicId);
topic.setReplies(replies);
// 加载用户信息
topic.setUser(userMapper.findById(topic.getUserId()));
}
return topic;
}
// 分页获取帖子
public PageResult<Topic> getTopicsByPage(int page, int size, Long categoryId) {
int offset = (page - 1) * size;
List<Topic> topics;
long total;
if (categoryId == null) {
topics = topicMapper.findLatestTopics(offset, size);
total = topicMapper.countAll();
} else {
topics = topicMapper.findByCategory(offset, size, categoryId);
total = topicMapper.countByCategory(categoryId);
}
return new PageResult<>(topics, total, page, size);
}
}
控制器层
// ForumController.java
@Controller
@RequestMapping("/forum")
public class ForumController {
@Autowired
private TopicService topicService;
@Autowired
private CategoryService categoryService;
@Autowired
private UserService userService;
// 论坛首页
@GetMapping("/index")
public String index(Model model) {
// 获取热门帖子
List<Topic> hotTopics = topicService.getHotTopics();
model.addAttribute("hotTopics", hotTopics);
// 获取分类列表
List<Category> categories = categoryService.getAllCategories();
model.addAttribute("categories", categories);
// 获取最新帖子
PageResult<Topic> latestTopics = topicService.getTopicsByPage(1, 20, null);
model.addAttribute("latestTopics", latestTopics);
return "forum/index";
}
// 查看帖子详情
@GetMapping("/topic/{id}")
public String topicDetail(@PathVariable("id") Long id, Model model) {
Topic topic = topicService.getTopicDetail(id);
if (topic == null) {
return "error/404";
}
model.addAttribute("topic", topic);
model.addAttribute("replies", topic.getReplies());
return "forum/topic-detail";
}
// 发布帖子页面
@GetMapping("/publish")
public String showPublishPage(Model model) {
List<Category> categories = categoryService.getAllCategories();
model.addAttribute("categories", categories);
return "forum/publish";
}
// 发布帖子处理
@PostMapping("/publish")
@ResponseBody
public Result publishTopic(@RequestBody TopicForm form) {
// 获取当前登录用户
User currentUser = userService.getCurrentUser();
Topic topic = new Topic();
topic.setTitle(form.getTitle());
topic.setContent(form.getContent());
topic.setCategoryId(form.getCategoryId());
Result result = topicService.publish(topic, currentUser.getId());
return result;
}
// 回复帖子
@PostMapping("/topic/{id}/reply")
@ResponseBody
public Result replyTopic(@PathVariable("id") Long topicId,
@RequestBody ReplyForm form) {
User currentUser = userService.getCurrentUser();
Reply reply = new Reply();
reply.setTopicId(topicId);
reply.setContent(form.getContent());
reply.setUserId(currentUser.getId());
return topicService.reply(reply);
}
// 搜索帖子
@GetMapping("/search")
public String search(@RequestParam("keyword") String keyword, Model model) {
List<Topic> results = topicService.search(keyword);
model.addAttribute("topics", results);
model.addAttribute("keyword", keyword);
return "forum/search-result";
}
// 分类浏览
@GetMapping("/category/{categoryId}")
public String categoryTopics(@PathVariable("categoryId") Long categoryId,
@RequestParam(defaultValue = "1") int page,
Model model) {
PageResult<Topic> topics = topicService.getTopicsByPage(page, 10, categoryId);
model.addAttribute("topics", topics);
model.addAttribute("currentCategoryId", categoryId);
return "forum/category-topics";
}
}
用户认证
// UserService.java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtUtils jwtUtils;
// 注册
public Result register(RegisterForm form) {
// 检查用户名是否已存在
if (userMapper.findByUsername(form.getUsername()) != null) {
return Result.error("用户名已存在");
}
// 检查邮箱是否已存在
if (userMapper.findByEmail(form.getEmail()) != null) {
return Result.error("邮箱已被注册");
}
User user = new User();
user.setUsername(form.getUsername());
user.setPassword(passwordEncoder.encode(form.getPassword()));
user.setEmail(form.getEmail());
user.setRole(2); // 普通用户
user.setStatus(1); // 正常状态
user.setCreateTime(LocalDateTime.now());
userMapper.insert(user);
return Result.success("注册成功");
}
// 登录
public Result login(LoginForm form) {
User user = userMapper.findByUsername(form.getUsername());
if (user == null || !passwordEncoder.matches(form.getPassword(), user.getPassword())) {
return Result.error("用户名或密码错误");
}
if (user.getStatus() == 2) {
return Result.error("账号已被禁用");
}
// 生成JWT token
String token = jwtUtils.generateToken(user);
Map<String, Object> data = new HashMap<>();
data.put("token", token);
data.put("user", user);
return Result.success("登录成功", data);
}
}
// JwtUtils.java
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(User user) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration * 1000);
return Jwts.builder()
.setSubject(String.valueOf(user.getId()))
.claim("username", user.getUsername())
.claim("role", user.getRole())
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
}
前端页面示例
首页模板 (index.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">技术论坛</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/forum/index">技术论坛</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="/forum/index">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/forum/publish">发帖</a>
</li>
</ul>
<form class="d-flex" action="/forum/search" method="get">
<input class="form-control me-2" type="search" name="keyword" placeholder="搜索帖子...">
<button class="btn btn-outline-light" type="submit">搜索</button>
</form>
</div>
</div>
</nav>
<div class="container mt-4">
<div class="row">
<!-- 左侧分类列表 -->
<div class="col-md-3">
<div class="card">
<div class="card-header">分类</div>
<ul class="list-group list-group-flush" th:each="category : ${categories}">
<li class="list-group-item">
<a th:href="@{/forum/category/{id}(id=${category.id})}"
th:text="${category.name}">分类名称</a>
</li>
</ul>
</div>
</div>
<!-- 帖子列表 -->
<div class="col-md-9">
<div class="card">
<div class="card-header">
<h5>最新帖子</h5>
</div>
<div class="list-group list-group-flush" th:each="topic : ${latestTopics.data}">
<div class="list-group-item">
<div class="d-flex justify-content-between">
<h6><a th:href="@{/forum/topic/{id}(id=${topic.id})}"
th:text="${topic.title}">帖子标题</a></h6>
<small class="text-muted" th:text="${#dates.format(topic.createTime, 'yyyy-MM-dd HH:mm')}">发布时间</small>
</div>
<p class="mb-1 text-truncate" th:text="${topic.content}">帖子内容</p>
<div>
<small>
<span class="me-3">
<i class="bi bi-eye"></i>
<span th:text="${topic.viewCount}">浏览数</span>
</span>
<span class="me-3">
<i class="bi bi-chat"></i>
<span th:text="${topic.replyCount}">回复数</span>
</span>
<span>发布于 <span th:text="${topic.user.username}">作者</span></span>
</small>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<nav class="mt-3">
<ul class="pagination">
<li class="page-item" th:if="${latestTopics.page > 1}">
<a class="page-link" th:href="@{/forum/index(page=${latestTopics.page - 1})}">上一页</a>
</li>
<li class="page-item" th:each="i : ${#numbers.sequence(1, latestTopics.totalPages)}"
th:classappend="${i == latestTopics.page} ? 'active'">
<a class="page-link" th:href="@{/forum/index(page=${i})}" th:text="${i}">1</a>
</li>
<li class="page-item" th:if="${latestTopics.page < latestTopics.totalPages}">
<a class="page-link" th:href="@{/forum/index(page=${latestTopics.page + 1})}">下一页</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
配置与数据库
数据库表结构
-- 用户表 CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(100) NOT NULL, `email` varchar(100) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `role` int DEFAULT '2' COMMENT '1:管理员 2:普通用户', `status` int DEFAULT '1' COMMENT '1:正常 2:禁言', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ); -- 主题表 CREATE TABLE `topic` ( `id` bigint NOT NULL AUTO_INCREMENT, varchar(200) NOT NULL, `content` text, `user_id` bigint NOT NULL, `category_id` bigint DEFAULT NULL, `view_count` int DEFAULT '0', `reply_count` int DEFAULT '0', `like_count` int DEFAULT '0', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_category_id` (`category_id`) ); -- 分类表 CREATE TABLE `category` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `description` varchar(200) DEFAULT NULL, `sort_order` int DEFAULT '0', PRIMARY KEY (`id`) );
application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/forum
username: root
password: root
redis:
host: localhost
port: 6379
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.forum.entity
jwt:
secret: your-secret-key-here
expiration: 86400 # 24小时
功能扩展建议
- 富文本编辑器: 使用UEditor或TinyMCE
- 图片上传: 集成OSS或本地存储
- 消息通知: 评论通知、@功能
- 积分系统: 发帖/回复获取积分
- 敏感词过滤: 内容安全
- 数据统计: 活跃度分析
这个论坛系统案例包含了完整的核心功能,可以根据实际需求进行调整和扩展,需要我详细说明哪个部分?