本文目录导读:

我来为你提供一个完整的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或本地存储
- 消息通知: 评论通知、@功能
- 积分系统: 发帖/回复获取积分
- 敏感词过滤: 内容安全
- 数据统计: 活跃度分析
这个论坛系统案例包含了完整的核心功能,可以根据实际需求进行调整和扩展,需要我详细说明哪个部分?
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播