社交新闻媒体平台网站


一、开发环境介绍
使用软件:
- 前端开发:IntelliJ IDEA 2025.1.2
- 后端开发:IntelliJ IDEA 2025.1.2
- 接口测试:Postman
前端开发:
-
项目依赖:
生产依赖(dependencies):
vue@3.5.17(核心框架)vue-router@4.5.1(路由管理)vuex@4.1.0(状态管理)element-plus@2.10.2(UI 组件库)@element-plus/icons-vue@2.3.1(Element Plus 图标库)axios@1.10.0(HTTP 请求库)core-js@3.43.0(JavaScript polyfill 库)date-fns@4.1.0(日期处理工具)
开发依赖(devDependencies):
@vue/cli-service@5.0.8(Vue CLI 核心服务)@vue/cli-plugin-babel@5.0.8(Babel 插件)@vue/cli-plugin-eslint@5.0.8(ESLint 插件)@vue/cli-plugin-router@5.0.8(路由插件)@vue/cli-plugin-vuex@5.0.8(Vuex 插件)@babel/core@7.27.4(Babel 核心)@babel/eslint-parser@7.27.5(Babel ESLint 解析器)eslint@7.32.0(代码检查工具)eslint-plugin-vue@8.7.1(Vue 专用 ESLint 插件)sass@1.89.2(SASS 编译器)sass-loader@12.6.0(Webpack 处理 SASS 的加载器)
后端开发:
-
开发语言:Java
-
包管理:Maven
-
项目依赖
主要依赖:
-
jackson-datatype-jsr310 (Jackson JSR310 时间支持)
-
lombok (简化 Java 代码的注解工具)
-
mysql-connector-j (MySQL 驱动)
-
mybatis-spring-boot-starter:3.0.2 (MyBatis 框架)
-
spring-boot-starter-web (Spring Boot Web 支持)
测试相关依赖:spring-boot-starter-test
构建插件:spring-boot-maven-plugin
-
三、数据库设计
3.1 用户表 (users)
存储用户的基本信息,包括用户名、密码、邮箱、头像、角色等,并确保用户名唯一。
CREATE TABLE IF NOT EXISTS users (
id BIGINT NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
avatar VARCHAR(255),
bio VARCHAR(255),
role VARCHAR(10) DEFAULT 'USER' COMMENT '用户角色:ADMIN-管理员,USER-普通用户',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_username (username)
);

3.2 帖子表(posts)
功能:存储帖子内容,关联发帖用户(外键引用 users 表),并记录点赞数和评论数。
CREATE TABLE IF NOT EXISTS posts (
id BIGINT NOT NULL AUTO_INCREMENT,
user_id BIGINT NOT NULL,
content TEXT NOT NULL,
image MEDIUMTEXT,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
like_count INT DEFAULT 0,
comment_count INT DEFAULT 0,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES users(id)
);

3.3 评论表(comments)
功能:存储用户对帖子的评论内容,关联评论用户和对应的帖子。
CREATE TABLE IF NOT EXISTS comments (
id BIGINT NOT NULL AUTO_INCREMENT,
post_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
content TEXT NOT NULL,
create_time DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (post_id) REFERENCES posts(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);

3.4 点赞表(likes)
功能:记录用户对帖子的点赞行为,防止重复点赞(通过唯一索引 uk_post_user)。
CREATE TABLE IF NOT EXISTS likes (
id BIGINT NOT NULL AUTO_INCREMENT,
post_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
create_time DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_post_user (post_id, user_id),
FOREIGN KEY (post_id) REFERENCES posts(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);

3.5 私信表(messages)
功能:存储用户之间的私信内容,包含发送者、接收者、消息是否已读状态及发送时间。
CREATE TABLE IF NOT EXISTS messages (
id BIGINT NOT NULL AUTO_INCREMENT,
sender_id BIGINT NOT NULL,
receiver_id BIGINT NOT NULL,
content TEXT NOT NULL,
is_read BOOLEAN DEFAULT FALSE,
create_time DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (sender_id) REFERENCES users(id),
FOREIGN KEY (receiver_id) REFERENCES users(id)
);

3.6 通知表(notifications)
功能:记录系统生成的通知(如点赞、评论、私信),包含通知类型、来源对象、目标用户及是否已读状态。
CREATE TABLE IF NOT EXISTS notifications (
id BIGINT NOT NULL AUTO_INCREMENT,
user_id BIGINT NOT NULL,
type VARCHAR(20) NOT NULL, -- LIKE, COMMENT, MESSAGE
source_id BIGINT NOT NULL,
source_user_id BIGINT NOT NULL,
is_read BOOLEAN DEFAULT FALSE,
content VARCHAR(255) NOT NULL,
create_time DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (source_user_id) REFERENCES users(id)
);

四、后端结构简介
4.1 配置类(config包)
-
CorsConfig.java: 实现跨域请求配置。
- 实现跨域请求配置。
- 允许所有来源访问所有路径 (/**)
- 支持 GET/POST/PUT/DELETE/OPTIONS 方法
- 允许所有请求头,支持凭据,最大预检请求缓存时间为 3600 秒
// ... existing code ... @Configuration public class CorsConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }; } } -
JacksonConfig.java: 定义JSON序列化方式,特别是处理LocalDateTime类型。
- 定义 JSON 序列化方式。
- 注册 JavaTimeModule 模块以支持 LocalDateTime 类型
- 自定义 LocalDateTime 的序列化格式为 “yyyy-MM-dd HH:mm:ss”
package com.socialplatform.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import java.time.format.DateTimeFormatter; import java.time.LocalDateTime; import java.util.Locale; @Configuration public class JacksonConfig { @Bean public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { ObjectMapper mapper = new ObjectMapper(); // 注册 JavaTimeModule 模块以支持 Java 8 的日期/时间类型 mapper.registerModule(new JavaTimeModule()); // 自定义 LocalDateTime 序列化格式 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); mapper.setDateFormat(new LocalDateTimeSerializer(formatter).getDateFormat()); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(mapper); return converter; } } -
WebConfig.java: Web层通用配置,包括静态资源路径、拦截器注册等。
- Web 层通用配置。
- 静态资源路径映射:
- /uploads/** 映射到 file:uploads/
- 所有路径 /** 映射到 classpath:/static/
- 拦截器注册:实现会话拦截器,对未登录用户返回 401 状态码
// ... existing code ... @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(sessionInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/api/auth/login", "/api/auth/register"); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/uploads/**") .addResourceLocations("file:uploads/"); registry.addResourceHandler("/**") .addResourceLocations("classpath:/static/"); } @Bean public HandlerInterceptor sessionInterceptor() { return new HandlerInterceptor() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(false); if (session == null && !request.getRequestURI().startsWith("/api/auth")) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } return true; } }; } }
4.2 控制器层(controller包)
负责接收 HTTP 请求,调用服务层并返回响应
AuthController.java
- 认证接口。
- 登录接口 /login:接收用户名密码,返回用户基本信息并设置 Session
- 注册接口 /register:创建新用户并自动登录
// ... existing code ...
/**
* 用户登录
*/
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody Map<String, String> loginForm, HttpSession session) {
String username = loginForm.get("username");
String password = loginForm.get("password");
Optional<User> userOpt = userService.login(username, password);
if (userOpt.isPresent()) {
User user = userOpt.get();
session.setAttribute("currentUser", user);
// 返回用户信息(不包含密码)
Map<String, Object> response = new HashMap<>();
response.put("id", user.getId());
response.put("username", user.getUsername());
response.put("avatar", user.getAvatar());
response.put("role", user.getRole());
return ResponseEntity.ok(response);
} else {
Map<String, String> error = new HashMap<>();
error.put("message", "用户名或密码错误");
return ResponseEntity.badRequest().body(error);
}
}
/**
* 用户注册
*/
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody User user, HttpSession session) {
User currentSessionUser = (User) session.getAttribute("currentUser");
if (currentSessionUser != null) {
session.invalidate();
session = null;
}
try {
Optional<User> registeredUserOpt = userService.register(user);
if (registeredUserOpt.isPresent()) {
User registeredUser = registeredUserOpt.get();
Map<String, Object> response = new HashMap<>();
response.put("message", "注册成功");
response.put("id", registeredUser.getId());
response.put("username", registeredUser.getUsername());
return ResponseEntity.ok(response);
} else {
Map<String, String> error = new HashMap<>();
error.put("message", "用户名已存在");
return ResponseEntity.badRequest().body(error);
}
} catch (Exception e) {
Map<String, String> error = new HashMap<>();
error.put("message", "注册失败:" + e.getMessage());
return ResponseEntity.status(500).body(error);
}
}
// ... existing code ...
UserController.java
- 用户接口。
- 获取用户详情 /users/{id}:过滤敏感字段(如密码)
- 获取所有用户列表 /users:返回用户列表并过滤敏感字段
- 更新用户信息 /users/{id}:只更新非空字段
- 删除用户 /users/{id}:删除指定 ID 的用户
// ... existing code ...
/**
* 获取所有用户列表
*/
@GetMapping
public ResponseEntity<ApiResponse<Iterable<Map<String, Object>>>> getAllUsers() {
Iterable<User> users = userService.getAllUsers();
Iterable<Map<String, Object>> filteredUsers = () -> {
return new Iterator<>() {
private final Iterator<User> userIterator = users.iterator();
@Override
public boolean hasNext() {
return userIterator.hasNext();
}
@Override
public Map<String, Object> next() {
User user = userIterator.next();
Map<String, Object> filteredUser = new HashMap<>();
filteredUser.put("id", user.getId());
filteredUser.put("username", user.getUsername());
filteredUser.put("avatar", user.getAvatar());
filteredUser.put("role", user.getRole());
return filteredUser;
}
};
};
return ResponseEntity.ok(ApiResponse.success(filteredUsers));
}
/**
* 更新用户信息
*/
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<?>> updateUser(@PathVariable Long id, @RequestBody User userUpdate) {
Optional<User> updatedUserOpt = userService.updateUser(id, userUpdate);
if (updatedUserOpt.isPresent()) {
Map<String, String> response = new HashMap<>();
response.put("message", "用户更新成功");
return ResponseEntity.ok(ApiResponse.success(response));
} else {
return ResponseEntity.ok(ApiResponse.error("用户不存在"));
}
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<?>> deleteUser(@PathVariable Long id) {
boolean deleted = userService.deleteUser(id);
if (deleted) {
Map<String, String> response = new HashMap<>();
response.put("message", "用户删除成功");
return ResponseEntity.ok(ApiResponse.success(response));
} else {
return ResponseEntity.ok(ApiResponse.error("用户不存在"));
}
}
// ... existing code ...
PostController.java
- 帖子接口。
- 创建帖子 /posts:需登录后操作,自动绑定当前用户 ID
- 获取所有帖子 /posts:返回所有帖子数据
- 根据 ID 获取帖子 /posts/{id}:返回指定 ID 的帖子
- 更新帖子 /posts/{id}:仅限原作者修改
- 删除帖子 /posts/{id}:仅限原作者删除
// ... existing code ...
/**
* 获取所有帖子
*/
@GetMapping
public ResponseEntity<List<Post>> getAllPosts(HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
List<Post> posts;
if (currentUser != null) {
posts = postService.getAllPostsWithUserInfo(currentUser.getId());
} else {
posts = postService.getAllPosts();
}
return ResponseEntity.ok(posts);
}
/**
* 获取帖子详情
*/
@GetMapping("/{id}")
public ResponseEntity<?> getPostById(@PathVariable Long id, HttpSession session) {
Post post = postService.getPostById(id);
if (post != null) {
User currentUser = (User) session.getAttribute("currentUser");
if (currentUser != null) {
// 这里应该调用LikeService检查用户是否点赞过帖子
}
return ResponseEntity.ok(post);
} else {
Map<String, String> error = new HashMap<>();
error.put("message", "帖子不存在");
return ResponseEntity.badRequest().body(error);
}
}
/**
* 创建帖子
*/
@PostMapping
public ResponseEntity<?> createPost(@RequestBody Post post, HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
if (currentUser == null) {
Map<String, String> error = new HashMap<>();
error.put("message", "未登录");
return ResponseEntity.status(401).body(error);
}
try {
if (post.getImage() != null && post.getImage().length() > 0) {
if (post.getImage().length() > 1024 * 1024) {
Map<String, String> error = new HashMap<>();
error.put("message", "图片数据过大,请压缩后重试");
return ResponseEntity.badRequest().body(error);
}
}
post.setUserId(currentUser.getId());
Post createdPost = postService.createPost(post);
return ResponseEntity.ok(createdPost);
} catch (Exception e) {
Map<String, String> error = new HashMap<>();
error.put("message", "发布失败:" + e.getMessage());
return ResponseEntity.status(500).body(error);
}
}
/**
* 更新帖子
*/
@PutMapping("/{id}")
public ResponseEntity<?> updatePost(@PathVariable Long id, @RequestBody Post post, HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
if (currentUser == null) {
Map<String, String> error = new HashMap<>();
error.put("message", "未登录");
return ResponseEntity.status(401).body(error);
}
Post existingPost = postService.getPostById(id);
if (existingPost == null) {
Map<String, String> error = new HashMap<>();
error.put("message", "帖子不存在");
return ResponseEntity.badRequest().body(error);
}
if (!existingPost.getUserId().equals(currentUser.getId())) {
Map<String, String> error = new HashMap<>();
error.put("message", "无权限修改此帖子");
return ResponseEntity.status(403).body(error);
}
try {
if (post.getImage() != null && post.getImage().length() > 0) {
if (post.getImage().length() > 1024 * 1024) {
Map<String, String> error = new HashMap<>();
error.put("message", "图片数据过大,请压缩后重试");
return ResponseEntity.badRequest().body(error);
}
}
post.setId(id);
post.setUserId(currentUser.getId());
Post updatedPost = postService.updatePost(post);
return ResponseEntity.ok(updatedPost);
} catch (Exception e) {
Map<String, String> error = new HashMap<>();
error.put("message", "更新失败:" + e.getMessage());
return ResponseEntity.status(500).body(error);
}
}
/**
* 删除帖子
*/
@DeleteMapping("/{id}")
public ResponseEntity<?> deletePost(@PathVariable Long id, HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
if (currentUser == null) {
Map<String, String> error = new HashMap<>();
error.put("message", "未登录");
return ResponseEntity.status(401).body(error);
}
boolean success = false;
String successMessage = "";
if (currentUser.isAdmin()) {
success = postService.deletePostByAdmin(id);
successMessage = "管理员删除帖子成功";
} else {
success = postService.deletePost(id, currentUser.getId());
successMessage = "帖子删除成功";
}
if (success) {
Map<String, String> response = new HashMap<>();
response.put("message", successMessage);
return ResponseEntity.ok(response);
} else {
Map<String, String> error = new HashMap<>();
error.put("message", "帖子不存在或无权限删除");
return ResponseEntity.badRequest().body(error);
}
}
// ... existing code ...
CommentController.java
-
评论接口。
-
添加评论 /comments:需登录后操作,自动绑定当前用户 ID
-
获取帖子的所有评论 /comments/post/{postId}:返回指定帖子的评论
列表
- 更新评论 /comments/{id}:仅限原评论者修改
- 删除评论 /comments/{id}:仅限原评论者删除
// ... existing code ...
/**
* 获取帖子评论列表
*/
@GetMapping("/post/{postId}")
public ResponseEntity<List<Comment>> getCommentsByPostId(@PathVariable Long postId) {
List<Comment> comments = commentService.getCommentsByPostId(postId);
return ResponseEntity.ok(comments);
}
/**
* 添加评论
*/
@PostMapping
public ResponseEntity<?> addComment(@RequestBody Comment comment, HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
if (currentUser == null) {
Map<String, String> error = new HashMap<>();
error.put("message", "未登录");
return ResponseEntity.status(401).body(error);
}
comment.setUserId(currentUser.getId());
try {
Comment addedComment = commentService.addComment(comment);
return ResponseEntity.ok(addedComment);
} catch (Exception e) {
Map<String, String> error = new HashMap<>();
error.put("message", "添加评论失败: " + e.getMessage());
return ResponseEntity.status(500).body(error);
}
}
/**
* 删除评论(用户删除自己的评论,管理员可删除任意评论)
*/
@DeleteMapping("/{commentId}")
public ResponseEntity<?> deleteComment(@PathVariable Long commentId, HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
if (currentUser == null) {
Map<String, String> error = new HashMap<>();
error.put("message", "未登录");
return ResponseEntity.status(401).body(error);
}
Comment comment = commentService.getCommentById(commentId);
if (comment == null) {
Map<String, String> error = new HashMap<>();
error.put("message", "评论不存在");
return ResponseEntity.badRequest().body(error);
}
boolean success = false;
if (currentUser.isAdmin()) {
success = commentService.deleteCommentByAdmin(commentId);
} else if (currentUser.getId().equals(comment.getUserId())) {
success = commentService.deleteComment(commentId, currentUser.getId());
} else {
Map<String, String> error = new HashMap<>();
error.put("message", "无权限删除此评论");
return ResponseEntity.status(403).body(error);
}
if (success) {
Map<String, Object> response = new HashMap<>();
response.put("message", "评论删除成功");
return ResponseEntity.ok(response);
} else {
Map<String, String> error = new HashMap<>();
error.put("message", "评论删除失败");
return ResponseEntity.status(500).body(error);
}
}
// ... existing code ...
LikeController.java
- 点赞接口。
- 点赞帖子 /likes/{postId}:需登录后操作,防止重复点赞
- 取消点赞 /likes/{postId}:根据用户ID和帖子ID删除点赞记录
- 获取帖子的点赞数 /likes/{postId}/count:返回帖子的点赞总数
// ... existing code ...
/**
* 点赞帖子
*/
@PostMapping("/{postId}")
public ResponseEntity<?> likePost(@PathVariable Long postId, HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
if (currentUser == null) {
Map<String, String> error = new HashMap<>();
error.put("message", "未登录");
return ResponseEntity.status(401).body(error);
}
Like like = likeService.likePost(postId, currentUser.getId());
if (like != null) {
Map<String, Object> response = new HashMap<>();
response.put("message", "点赞成功");
response.put("likeCount", likeService.getLikeCount(postId));
return ResponseEntity.ok(response);
} else {
Map<String, String> error = new HashMap<>();
error.put("message", "已经点赞过了");
return ResponseEntity.badRequest().body(error);
}
}
/**
* 取消点赞
*/
@DeleteMapping("/{postId}")
public ResponseEntity<?> unlikePost(@PathVariable Long postId, HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
if (currentUser == null) {
Map<String, String> error = new HashMap<>();
error.put("message", "未登录");
return ResponseEntity.status(401).body(error);
}
boolean success = likeService.unlikePost(postId, currentUser.getId());
if (success) {
Map<String, Object> response = new HashMap<>();
response.put("message", "取消点赞成功");
response.put("likeCount", likeService.getLikeCount(postId));
return ResponseEntity.ok(response);
} else {
Map<String, String> error = new HashMap<>();
error.put("message", "未点赞或操作失败");
return ResponseEntity.badRequest().body(error);
}
}
// ... existing code ...
MessageController.java
- 发送私信:用户可以向其他用户发送私信。
- 获取对话记录:查看与某个用户的完整私信往来,并标记对方发送的消息为已读。
- 获取联系人列表:列出所有有私信往来的用户。
- 获取未读消息:获取当前用户所有未读的私信内容。
- 获取未读消息数量:统计当前用户的未读私信总数。
- 标记消息为已读:将某条消息标记为已读状态。
- 删除消息:删除指定的私信。
// ... existing code ...
/**
* 发送私信
*/
@PostMapping
public ResponseEntity<?> sendMessage(@RequestBody Message message, @SessionAttribute("currentUser") User user) {
message.setSenderId(user.getId());
messageService.sendMessage(message);
return ResponseEntity.ok(Map.of("success", true));
}
/**
* 获取与特定用户的对话
*/
@GetMapping("/conversation/{userId}")
public ResponseEntity<?> getConversation(@PathVariable Long userId, @SessionAttribute("currentUser") User user) {
List<Message> conversation = messageService.getConversation(user.getId(), userId);
List<MessageDTO> messageDTOs = conversation.stream()
.map(MessageDTO::new)
.peek(dto -> dto.setSelf(dto.getSenderId().equals(user.getId())))
.collect(Collectors.toList());
messageService.markConversationAsRead(user.getId(), userId);
return ResponseEntity.ok(messageDTOs);
}
/**
* 获取联系人列表
*/
@GetMapping("/contacts")
public ResponseEntity<?> getContacts(@SessionAttribute("currentUser") User user) {
List<Map<String, Object>> contacts = messageService.getContacts(user.getId());
return ResponseEntity.ok(contacts);
}
/**
* 获取未读消息
*/
@GetMapping("/unread")
public ResponseEntity<?> getUnreadMessages(@SessionAttribute("currentUser") User user) {
List<Message> unreadMessages = messageService.getUnreadMessages(user.getId());
List<MessageDTO> messageDTOs = unreadMessages.stream()
.map(MessageDTO::new)
.collect(Collectors.toList());
return ResponseEntity.ok(messageDTOs);
}
/**
* 获取未读消息数量
*/
@GetMapping("/unread/count")
public ResponseEntity<?> getUnreadMessagesCount(@SessionAttribute("currentUser")
NotificationController.java
- 通知接口。
- 获取用户的通知列表 /notifications/user/{userId}:返回指定用户的全部通知
- 标记通知为已读 /notifications/{id}/read:将指定 ID 的通知标记为已读
- 删除通知 /notifications/{id}:删除指定 ID 的通知
// ... existing code ...
/**
* 获取通知列表(分页)
*/
@GetMapping
public ResponseEntity<?> getNotifications(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int pageSize,
@SessionAttribute("currentUser") User user) {
List<Notification> notifications = notificationService.getNotifications(user.getId(), page, pageSize);
int total = notificationService.getNotificationCount(user.getId());
Map<String, Object> response = new HashMap<>();
response.put("notifications", notifications);
response.put("total", total);
response.put("page", page);
response.put("pageSize", pageSize);
return ResponseEntity.ok(response);
}
/**
* 标记通知为已读
*/
@PutMapping("/{id}/read")
public ResponseEntity<?> markAsRead(@PathVariable Long id) {
notificationService.markAsRead(id);
return ResponseEntity.ok(Map.of("success", true));
}
/**
* 删除通知
*/
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteNotification(@PathVariable Long id) {
notificationService.deleteNotification(id);
return ResponseEntity.ok(Map.of("success", true));
}
// ... existing code ...
4.3 数据传输对象(dto包)
功能: 定义了统一的 API 响应结构。
- ApiResponse.java: 封装统一的API响应格式。
/**
* 统一API响应类
*/
public class ApiResponse<T> {
private Integer code;
private String message;
private T data;
private Boolean success;
public ApiResponse() {}
public ApiResponse(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
this.success = code == 200;
}
4.4 模型层(model包)
功能: 定义了业务实体类,映射数据库表。
-
User.java - 用户实体
-
private Long id; private Long postId; private Long userId; private String content; private Date createTime; private User user;
-
-
Post.java - 帖子实体
-
Comment.java - 评论实体
-
Like.java - 点赞实体
-
private Long id; private Long postId; private Long userId; private Date createTime;
-
-
Message.java - 私信实体
-
private Long id; private Long senderId; private Long receiverId; private String content; private boolean isRead; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; // Additional fields for frontend display private String senderUsername; private String senderAvatar; private String receiverUsername; private String receiverAvatar;
-
-
MessageDTO.java - 私信数据传输对象
-
private Long id; private Long senderId; private Long receiverId; private String content; private boolean isRead; private String createTime; // Additional fields for frontend display private String senderUsername; private String senderAvatar; private String receiverUsername; private String receiverAvatar; private boolean isSelf; // 前端需要的字段
-
-
Notification.java - 通知实体
-
private Long id; private Long userId; // 接收通知的用户ID private String type; // 通知类型: LIKE, COMMENT, MESSAGE private Long sourceId; // 通知来源ID (例如: 帖子ID, 评论ID, 消息ID) private Long sourceUserId; // 触发通知的用户ID private boolean isRead; // 是否已读 private String content; // 通知内容 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; // Additional fields for display private String sourceUsername; private String sourceUserAvatar;
-
4.5 数据访问层(repository包)
-
功能: 数据访问层,定义了与数据库交互的接口。
-
关键方法:
-
MessageMapper: 管理私信数据,支持发送、读取、标记为已读等操作。
@Insert("INSERT INTO messages (sender_id, receiver_id, content, is_read, create_time) " + "VALUES (#{senderId}, #{receiverId}, #{content}, #{isRead}, #{createTime})") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(Message message); @Select("SELECT m.*, " + "sender.username as senderUsername, sender.avatar as senderAvatar, " + "receiver.username as receiverUsername, receiver.avatar as receiverAvatar " + "FROM messages m " + "JOIN users sender ON m.sender_id = sender.id " + "JOIN users receiver ON m.receiver_id = receiver.id " + "WHERE m.id = #{id}") Message findById(Long id); @Select("SELECT m.*, " + "sender.username as senderUsername, sender.avatar as senderAvatar, " + "receiver.username as receiverUsername, receiver.avatar as receiverAvatar " + "FROM messages m " + "JOIN users sender ON m.sender_id = sender.id " + "JOIN users receiver ON m.receiver_id = receiver.id " + "WHERE (m.sender_id = #{userId1} AND m.receiver_id = #{userId2}) " + "OR (m.sender_id = #{userId2} AND m.receiver_id = #{userId1}) " + "ORDER BY m.create_time ASC") List<Message> findConversation(@Param("userId1") Long userId1, @Param("userId2") Long userId2); @Select("SELECT m.*, " + "sender.username as senderUsername, sender.avatar as senderAvatar " + "FROM messages m " + "JOIN users sender ON m.sender_id = sender.id " + "WHERE m.receiver_id = #{userId} " + "AND m.is_read = false " + "ORDER BY m.create_time DESC") List<Message> findUnreadMessages(Long userId); @Update("UPDATE messages SET is_read = true WHERE id = #{id}") int markAsRead(Long id); @Update("UPDATE messages SET is_read = true WHERE receiver_id = #{receiverId} AND sender_id = #{senderId}") int markConversationAsRead(@Param("receiverId") Long receiverId, @Param("senderId") Long senderId); @Delete("DELETE FROM messages WHERE id = #{id}") int delete(Long id); /** * 删除用户发送的所有消息(用于删除用户时的级联删除) */ @Delete("DELETE FROM messages WHERE sender_id = #{senderId}") int deleteBySenderId(@Param("senderId") Long senderId); /** * 删除用户接收的所有消息(用于删除用户时的级联删除) */ @Delete("DELETE FROM messages WHERE receiver_id = #{receiverId}") int deleteByReceiverId(@Param("receiverId") Long receiverId); // Get a list of distinct users who have sent or received messages from/to the specified user @Select("SELECT DISTINCT " + "u.id, u.username, u.avatar, " + "(SELECT MAX(m2.create_time) FROM messages m2 " + " WHERE (m2.sender_id = #{userId} AND m2.receiver_id = u.id) " + " OR (m2.sender_id = u.id AND m2.receiver_id = #{userId})) as last_message_time " + "FROM users u " + "WHERE u.id IN (" + " SELECT DISTINCT CASE " + " WHEN m.sender_id = #{userId} THEN m.receiver_id " + " ELSE m.sender_id " + " END " + " FROM messages m " + " WHERE m.sender_id = #{userId} OR m.receiver_id = #{userId}" + ") " + "ORDER BY last_message_time DESC") List<Map<String, Object>> findContactsByUserId(Long userId); -
CommentMapper: 管理评论数据,支持增删查改。
/** * 添加评论 */ @Insert("INSERT INTO comments(post_id, user_id, content, create_time) VALUES(#{postId}, #{userId}, #{content}, NOW())") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(Comment comment); /** * 删除评论(用户只能删除自己的评论) */ @Delete("DELETE FROM comments WHERE id = #{id} AND user_id = #{userId}") int delete(@Param("id") Long id, @Param("userId") Long userId); /** * 管理员删除评论(可删除任意评论) */ @Delete("DELETE FROM comments WHERE id = #{id}") int deleteByAdmin(@Param("id") Long id); /** * 删除帖子的所有评论(用于级联删除) */ @Delete("DELETE FROM comments WHERE post_id = #{postId}") int deleteByPostId(@Param("postId") Long postId); /** * 根据ID查询评论 */ @Select("SELECT * FROM comments WHERE id = #{id}") Comment findById(Long id); /** * 获取帖子评论数 */ @Select("SELECT COUNT(*) FROM comments WHERE post_id = #{postId}") int countByPostId(Long postId); /** * 获取帖子的所有评论 */ @Select("SELECT * FROM comments WHERE post_id = #{postId} ORDER BY create_time DESC") List<Comment> findByPostId(Long postId); /** * 获取所有评论(管理员功能) */ @Select("SELECT * FROM comments ORDER BY create_time DESC") List<Comment> findAll(); /** * 获取用户的所有评论 */ @Select("SELECT * FROM comments WHERE user_id = #{userId} ORDER BY create_time DESC") List<Comment> findByUserId(Long userId); /** * 批量获取帖子的评论数 */ @Select({ "<script>", "SELECT post_id, COUNT(*) as count FROM comments", "WHERE post_id IN", "<foreach collection='postIds' item='id' open='(' separator=',' close=')'>", "#{id}", "</foreach>", "GROUP BY post_id", "</script>" }) List<Object[]> countByPostIds(@Param("postIds") List<Long> postIds); -
LikeMapper: 管理点赞数据,支持点赞、取消点赞。
/** * 添加点赞 */ @Insert("INSERT INTO likes(post_id, user_id, create_time) VALUES(#{postId}, #{userId}, NOW())") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(Like like); /** * 取消点赞 */ @Delete("DELETE FROM likes WHERE post_id = #{postId} AND user_id = #{userId}") int delete(@Param("postId") Long postId, @Param("userId") Long userId); /** * 删除帖子的所有点赞记录(用于级联删除) */ @Delete("DELETE FROM likes WHERE post_id = #{postId}") int deleteByPostId(@Param("postId") Long postId); /** * 删除用户的所有点赞记录(用于删除用户时的级联删除) */ @Delete("DELETE FROM likes WHERE user_id = #{userId}") int deleteByUserId(@Param("userId") Long userId); /** * 查询用户是否点赞过帖子 */ @Select("SELECT COUNT(*) FROM likes WHERE post_id = #{postId} AND user_id = #{userId}") boolean exists(@Param("postId") Long postId, @Param("userId") Long userId); /** * 获取帖子点赞数 */ @Select("SELECT COUNT(*) FROM likes WHERE post_id = #{postId}") int countByPostId(Long postId); /** * 获取帖子的所有点赞 */ @Select("SELECT * FROM likes WHERE post_id = #{postId}") List<Like> findByPostId(Long postId); /** * 获取用户的所有点赞 */ @Select("SELECT * FROM likes WHERE user_id = #{userId}") List<Like> findByUserId(Long userId); /** * 批量获取帖子的点赞数 */ @Select({ "<script>", "SELECT post_id, COUNT(*) as count FROM likes", "WHERE post_id IN", "<foreach collection='postIds' item='id' open='(' separator=',' close=')'>", "#{id}", "</foreach>", "GROUP BY post_id", "</script>" }) List<Object[]> countByPostIds(@Param("postIds") List<Long> postIds); -
NotificationMapper: 管理通知数据,支持创建、查询、标记已读。
@Insert("INSERT INTO notifications (user_id, type, source_id, source_user_id, is_read, content, create_time) " + "VALUES (#{userId}, #{type}, #{sourceId}, #{sourceUserId}, #{isRead}, #{content}, #{createTime})") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(Notification notification); @Select("SELECT n.*, u.username as sourceUsername, u.avatar as sourceUserAvatar " + "FROM notifications n " + "LEFT JOIN users u ON n.source_user_id = u.id " + "WHERE n.id = #{id}") Notification findById(Long id); @Select("SELECT n.*, u.username as sourceUsername, u.avatar as sourceUserAvatar " + "FROM notifications n " + "LEFT JOIN users u ON n.source_user_id = u.id " + "WHERE n.user_id = #{userId} " + "ORDER BY n.create_time DESC " + "LIMIT #{limit} OFFSET #{offset}") List<Notification> findByUserId(@Param("userId") Long userId, @Param("offset") int offset, @Param("limit") int limit); @Select("SELECT COUNT(*) FROM notifications WHERE user_id = #{userId}") int countByUserId(Long userId); @Select("SELECT COUNT(*) FROM notifications WHERE user_id = #{userId} AND is_read = false") int countUnreadByUserId(Long userId); @Update("UPDATE notifications SET is_read = true WHERE id = #{id}") int markAsRead(Long id); @Update("UPDATE notifications SET is_read = true WHERE user_id = #{userId}") int markAllAsRead(Long userId); @Delete("DELETE FROM notifications WHERE id = #{id}") int delete(Long id); @Delete("DELETE FROM notifications WHERE user_id = #{userId} AND is_read = true") int deleteAllRead(Long userId); /** * 删除用户的所有通知记录(用于删除用户时的级联删除) */ @Delete("DELETE FROM notifications WHERE user_id = #{userId}") int deleteByUserId(@Param("userId") Long userId); -
PostMapper: 管理帖子数据,支持增删查改。
/** * 插入帖子 */ @Insert("INSERT INTO posts (user_id, content, image, create_time, update_time) VALUES (#{userId}, #{content}, #{image}, NOW(), NOW())") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(Post post); /** * 根据ID查询帖子 */ @Select("SELECT * FROM posts WHERE id = #{id}") Post findById(Long id); /** * 查询全部帖子,按创建时间倒序排序 */ @Select("SELECT * FROM posts ORDER BY create_time DESC") List<Post> findAll(); /** * 查询用户的所有帖子 */ @Select("SELECT * FROM posts WHERE user_id = #{userId} ORDER BY create_time DESC") List<Post> findByUserId(Long userId); /** * 更新帖子 */ @Update("UPDATE posts SET content = #{content}, image = #{image}, update_time = NOW() WHERE id = #{id}") int update(Post post); /** * 删除帖子 */ @Delete("DELETE FROM posts WHERE id = #{id}") int deleteById(Long id); -
UserMapper: 管理用户数据,支持注册、登录、增删查改。
/** * 根据用户名查询用户 */ @Select("SELECT * FROM users WHERE username = #{username}") User findByUsername(String username); /** * 根据ID查询用户 */ @Select("SELECT * FROM users WHERE id = #{id}") User findById(Long id); /** * 插入新用户 */ @Insert("INSERT INTO users(username, password, avatar, email, bio, role, create_time, update_time) VALUES(#{username}, #{password}, #{avatar}, #{email}, #{bio}, #{role}, NOW(), NOW())") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(User user); /** * 更新用户信息 */ @Update("UPDATE users SET username = #{username}, password = #{password}, avatar = #{avatar}, email = #{email}, bio = #{bio}, role = #{role}, update_time = NOW() WHERE id = #{id}") int update(User user); /** * 获取所有用户 */ @Select("SELECT * FROM users") List<User> findAll(); /** * 删除用户 */ @Delete("DELETE FROM users WHERE id = #{id}") int deleteById(Long id);
-
4.6 业务逻辑层(service包)
-
功能: 业务逻辑层,处理核心功能。
-
关键接口及实现:
-
MessageService: 处理私信发送、获取对话记录、标记为已读等。
@Autowired private MessageMapper messageMapper; @Autowired private NotificationService notificationService; @Override public Message sendMessage(Message message) { message.setCreateTime(LocalDateTime.now()); message.setRead(false); messageMapper.insert(message); // Create a notification for the message recipient try { notificationService.createMessageNotification(message.getId(), message.getSenderId()); } catch (Exception e) { // 记录异常但不影响消息发送功能 System.err.println("创建消息通知失败: " + e.getMessage()); // 不要将异常向上传播 } return message; } @Override public Message getMessageById(Long id) { return messageMapper.findById(id); } @Override public List<Message> getConversation(Long userId1, Long userId2) { return messageMapper.findConversation(userId1, userId2); } @Override public List<Message> getUnreadMessages(Long userId) { return messageMapper.findUnreadMessages(userId); } @Override public void markAsRead(Long id) { messageMapper.markAsRead(id); } @Override public void markConversationAsRead(Long receiverId, Long senderId) { messageMapper.markConversationAsRead(receiverId, senderId); } @Override public void deleteMessage(Long id) { messageMapper.delete(id); } @Override public List<Map<String, Object>> getContacts(Long userId) { try { List<Map<String, Object>> contacts = messageMapper.findContactsByUserId(userId); // 为每个联系人添加最后一条消息内容和未读状态 for (Map<String, Object> contact : contacts) { Long contactId = ((Number) contact.get("id")).longValue(); // 获取与此联系人的最后一条消息 List<Message> conversation = messageMapper.findConversation(userId, contactId); if (!conversation.isEmpty()) { Message lastMessage = conversation.get(conversation.size() - 1); contact.put("lastMessage", lastMessage.getContent()); // 将LocalDateTime转换为字符串以避免序列化问题 contact.put("lastTime", lastMessage.getCreateTime().toString()); } else { contact.put("lastMessage", ""); // 处理可能的null值并转换为字符串 Object lastMessageTime = contact.get("last_message_time"); if (lastMessageTime != null) { contact.put("lastTime", lastMessageTime.toString()); } else { contact.put("lastTime", ""); } } // 检查是否有未读消息 List<Message> unreadMessages = messageMapper.findUnreadMessages(userId); boolean hasUnread = unreadMessages.stream() .anyMatch(msg -> msg.getSenderId().equals(contactId)); contact.put("hasUnread", hasUnread); // 移除内部字段 contact.remove("last_message_time"); } return contacts; } catch (Exception e) { // 记录错误并返回空列表,避免500错误 System.err.println("获取联系人列表失败: " + e.getMessage()); e.printStackTrace(); return new ArrayList<>(); } } -
CommentService: 处理评论添加、删除、查询。
@Autowired private CommentMapper commentMapper; @Autowired private UserMapper userMapper; @Autowired private NotificationService notificationService; @Override public Comment addComment(Comment comment) { // 确保评论数据完整性 if (comment.getUserId() == null) { throw new IllegalArgumentException("用户ID不能为空"); } // 验证用户是否存在 User user = userMapper.findById(comment.getUserId()); if (user == null) { throw new IllegalArgumentException("用户不存在: " + comment.getUserId()); } System.out.println("添加评论 - 用户ID: " + comment.getUserId() + ", 用户名: " + user.getUsername()); commentMapper.insert(comment); // 创建评论通知,添加错误处理避免影响主要功能 try { notificationService.createCommentNotification(comment.getPostId(), comment.getId(), comment.getUserId()); } catch (Exception e) { // 记录错误但继续执行 System.err.println("创建评论通知失败,但不影响评论功能: " + e.getMessage()); } // 重新查询用户信息确保数据一致性 User freshUser = userMapper.findById(comment.getUserId()); comment.setUser(freshUser); System.out.println("评论创建成功 - 评论ID: " + comment.getId() + ", 关联用户: " + (freshUser != null ? freshUser.getUsername() : "null")); return comment; } @Override public boolean deleteComment(Long commentId, Long userId) { return commentMapper.delete(commentId, userId) > 0; } @Override public boolean deleteCommentByAdmin(Long commentId) { return commentMapper.deleteByAdmin(commentId) > 0; } @Override public Comment getCommentById(Long commentId) { Comment comment = commentMapper.findById(commentId); if (comment != null) { // 查询评论用户信息 User user = userMapper.findById(comment.getUserId()); comment.setUser(user); System.out.println("查询评论 - ID: " + commentId + ", user_id: " + comment.getUserId() + ", 用户名: " + (user != null ? user.getUsername() : "null")); } return comment; } @Override public List<Comment> getCommentsByPostId(Long postId) { List<Comment> comments = commentMapper.findByPostId(postId); System.out.println("查询帖子评论 - 帖子ID: " + postId + ", 评论数量: " + comments.size()); return enrichCommentsWithUserInfo(comments); } @Override public List<Comment> getAllComments() { List<Comment> comments = commentMapper.findAll(); System.out.println("管理员查询所有评论 - 评论数量: " + comments.size()); return enrichCommentsWithUserInfo(comments); } @Override public int getCommentCount(Long postId) { return commentMapper.countByPostId(postId); } @Override public Map<Long, Integer> batchGetCommentCounts(List<Long> postIds) { Map<Long, Integer> result = new HashMap<>(); if (postIds == null || postIds.isEmpty()) { return result; } List<Object[]> counts = commentMapper.countByPostIds(postIds); for (Object[] count : counts) { Long postId = (Long) count[0]; Integer commentCount = ((Number) count[1]).intValue(); result.put(postId, commentCount); } // 确保所有帖子ID都有对应的评论数 for (Long postId : postIds) { if (!result.containsKey(postId)) { result.put(postId, 0); } } return result; } @Override public boolean deleteAllCommentsForPost(Long postId) { try { int deletedCount = commentMapper.deleteByPostId(postId); System.out.println("删除帖子 " + postId + " 的 " + deletedCount + " 条评论记录"); return true; // 即使没有评论也算成功 } catch (Exception e) { System.err.println("删除帖子评论失败: " + e.getMessage()); return false; } } /** * 为评论列表添加用户信息 */ private List<Comment> enrichCommentsWithUserInfo(List<Comment> comments) { return comments.stream().map(comment -> { try { User user = userMapper.findById(comment.getUserId()); comment.setUser(user); // 添加调试日志 System.out.println("关联用户信息 - 评论ID: " + comment.getId() + ", user_id: " + comment.getUserId() + ", 查询到的用户: " + (user != null ? user.getUsername() : "null")); return comment; } catch (Exception e) { System.err.println("查询用户信息失败 - 评论ID: " + comment.getId() + ", user_id: " + comment.getUserId() + ", 错误: " + e.getMessage()); return comment; } }).collect(Collectors.toList()); } -
LikeService: 处理点赞、取消点赞、批量统计。
@Autowired private LikeMapper likeMapper; @Autowired private NotificationService notificationService; @Override public Like likePost(Long postId, Long userId) { // 检查是否已经点赞 if (hasLiked(postId, userId)) { return null; } Like like = new Like(); like.setPostId(postId); like.setUserId(userId); likeMapper.insert(like); // 创建点赞通知,但不影响点赞功能 try { notificationService.createLikeNotification(postId, userId); } catch (Exception e) { // 记录错误但继续执行 System.err.println("创建点赞通知失败,但不影响点赞功能: " + e.getMessage()); } return like; } @Override public boolean unlikePost(Long postId, Long userId) { return likeMapper.delete(postId, userId) > 0; } @Override public boolean hasLiked(Long postId, Long userId) { return likeMapper.exists(postId, userId); } @Override public int getLikeCount(Long postId) { return likeMapper.countByPostId(postId); } @Override public Map<Long, Integer> batchGetLikeCounts(List<Long> postIds) { Map<Long, Integer> result = new HashMap<>(); if (postIds == null || postIds.isEmpty()) { return result; } List<Object[]> counts = likeMapper.countByPostIds(postIds); for (Object[] count : counts) { Long postId = (Long) count[0]; Integer likeCount = ((Number) count[1]).intValue(); result.put(postId, likeCount); } // 确保所有帖子ID都有对应的点赞数 for (Long postId : postIds) { if (!result.containsKey(postId)) { result.put(postId, 0); } } return result; } @Override public Map<Long, Boolean> batchCheckUserLikes(List<Long> postIds, Long userId) { Map<Long, Boolean> result = new HashMap<>(); if (postIds == null || postIds.isEmpty() || userId == null) { return result; } // 获取用户所有点赞 List<Like> userLikes = likeMapper.findByUserId(userId); Map<Long, Like> likeMap = new HashMap<>(); for (Like like : userLikes) { likeMap.put(like.getPostId(), like); } // 检查每个帖子是否被点赞 for (Long postId : postIds) { result.put(postId, likeMap.containsKey(postId)); } return result; } @Override public boolean deleteAllLikesForPost(Long postId) { try { int deletedCount = likeMapper.deleteByPostId(postId); System.out.println("删除帖子 " + postId + " 的 " + deletedCount + " 个点赞记录"); return true; // 即使没有点赞记录也算成功 } catch (Exception e) { System.err.println("删除帖子点赞记录失败: " + e.getMessage()); return false; } } -
NotificationService: 创建通知(如点赞、评论、私信)、标记为已读。
@Autowired private NotificationMapper notificationMapper; @Autowired private PostMapper postMapper; @Autowired private CommentMapper commentMapper; @Autowired private MessageMapper messageMapper; @Override public void createLikeNotification(Long postId, Long sourceUserId) { Post post = postMapper.findById(postId); if (post == null || post.getUserId().equals(sourceUserId)) { return; // Don't notify if post doesn't exist or user is liking their own post } Notification notification = new Notification(); notification.setUserId(post.getUserId()); notification.setType("LIKE"); notification.setSourceId(postId); notification.setSourceUserId(sourceUserId); notification.setRead(false); notification.setContent("有人赞了你的帖子"); notification.setCreateTime(LocalDateTime.now()); notificationMapper.insert(notification); } @Override public void createCommentNotification(Long postId, Long commentId, Long sourceUserId) { Post post = postMapper.findById(postId); if (post == null) { return; // Don't notify if post doesn't exist } Comment comment = commentMapper.findById(commentId); if (comment == null) { return; // Don't notify if comment doesn't exist } // Don't notify if user is commenting on their own post if (!post.getUserId().equals(sourceUserId)) { Notification notification = new Notification(); notification.setUserId(post.getUserId()); notification.setType("COMMENT"); notification.setSourceId(commentId); notification.setSourceUserId(sourceUserId); notification.setRead(false); notification.setContent("有人评论了你的帖子"); notification.setCreateTime(LocalDateTime.now()); notificationMapper.insert(notification); } } @Override public void createMessageNotification(Long messageId, Long sourceUserId) { if (messageId == null || sourceUserId == null) { return; // 如果messageId或sourceUserId为null,直接返回 } Message message = messageMapper.findById(messageId); if (message == null) { return; // Don't notify if message doesn't exist } // 消息接收者和发送者相同时不创建通知 if (message.getReceiverId().equals(sourceUserId)) { return; } try { Notification notification = new Notification(); notification.setUserId(message.getReceiverId()); notification.setType("MESSAGE"); notification.setSourceId(messageId); notification.setSourceUserId(sourceUserId); notification.setRead(false); notification.setContent("你收到了一条新私信"); notification.setCreateTime(LocalDateTime.now()); notificationMapper.insert(notification); } catch (Exception e) { // 记录错误但不抛出异常 System.err.println("保存消息通知出错: " + e.getMessage()); } } @Override public Notification getNotificationById(Long id) { return notificationMapper.findById(id); } @Override public List<Notification> getNotifications(Long userId, int page, int pageSize) { int offset = (page - 1) * pageSize; return notificationMapper.findByUserId(userId, offset, pageSize); } @Override public int getNotificationCount(Long userId) { return notificationMapper.countByUserId(userId); } @Override public int getUnreadNotificationCount(Long userId) { return notificationMapper.countUnreadByUserId(userId); } @Override public void markAsRead(Long id) { notificationMapper.markAsRead(id); } @Override public void markAllAsRead(Long userId) { notificationMapper.markAllAsRead(userId); } @Override public void deleteNotification(Long id) { notificationMapper.delete(id); } @Override public void deleteAllReadNotifications(Long userId) { notificationMapper.deleteAllRead(userId); } -
PostService: 处理帖子发布、删除、更新。
@Autowired private PostMapper postMapper; @Autowired private UserMapper userMapper; @Autowired private LikeService likeService; @Autowired private CommentService commentService; @Override public Post createPost(Post post) { postMapper.insert(post); return post; } @Override public Post getPostById(Long id) { Post post = postMapper.findById(id); if (post != null) { // 查询帖子关联的用户信息 User user = userMapper.findById(post.getUserId()); post.setUser(user); // 查询点赞数和评论数 post.setLikeCount(likeService.getLikeCount(id)); post.setCommentCount(commentService.getCommentCount(id)); } return post; } @Override public List<Post> getAllPosts() { List<Post> posts = postMapper.findAll(); return enrichPostsWithUserInfo(posts); } @Override public List<Post> getAllPostsWithUserInfo(Long currentUserId) { List<Post> posts = postMapper.findAll(); return enrichPostsWithUserInfoAndLikeStatus(posts, currentUserId); } @Override public List<Post> getPostsByUserId(Long userId) { List<Post> posts = postMapper.findByUserId(userId); return enrichPostsWithUserInfo(posts); } @Override public List<Post> getPostsByUserIdWithUserInfo(Long userId, Long currentUserId) { List<Post> posts = postMapper.findByUserId(userId); return enrichPostsWithUserInfoAndLikeStatus(posts, currentUserId); } @Override public Post updatePost(Post post) { int result = postMapper.update(post); if (result > 0) { return postMapper.findById(post.getId()); } return null; } @Override public boolean deletePost(Long id, Long userId) { Post post = postMapper.findById(id); if (post != null && post.getUserId().equals(userId)) { return deletePostWithCascade(id); } return false; } @Override public boolean deletePostByAdmin(Long id) { Post post = postMapper.findById(id); System.out.println("=== 管理员删除帖子服务 ==="); System.out.println("要删除的帖子ID: " + id); System.out.println("帖子存在: " + (post != null)); if (post != null) { System.out.println("帖子作者ID: " + post.getUserId()); System.out.println("帖子内容: " + (post.getContent() != null ? post.getContent().substring(0, Math.min(20, post.getContent().length())) + "..." : "null")); boolean result = deletePostWithCascade(id); System.out.println("删除结果: " + result); return result; } return false; } /** * 级联删除帖子(先删除相关评论和点赞,再删除帖子) */ private boolean deletePostWithCascade(Long postId) { try { System.out.println("开始级联删除帖子 " + postId); // 1. 删除帖子的所有评论(批量删除) try { boolean commentResult = commentService.deleteAllCommentsForPost(postId); System.out.println("删除帖子评论结果: " + commentResult); } catch (Exception e) { System.err.println("删除评论时出错: " + e.getMessage()); // 继续执行,不要因为评论删除失败而停止 } // 2. 删除帖子的所有点赞(批量删除) try { boolean likeResult = likeService.deleteAllLikesForPost(postId); System.out.println("删除帖子点赞结果: " + likeResult); } catch (Exception e) { System.err.println("删除点赞时出错: " + e.getMessage()); // 继续执行,不要因为点赞删除失败而停止 } // 3. 最后删除帖子本身 boolean result = postMapper.deleteById(postId) > 0; System.out.println("帖子删除结果: " + result); return result; } catch (Exception e) { System.err.println("级联删除帖子失败: " + e.getMessage()); e.printStackTrace(); return false; } } /** * 为帖子列表添加用户信息 */ private List<Post> enrichPostsWithUserInfo(List<Post> posts) { if (posts == null || posts.isEmpty()) { return posts; } try { // 获取所有帖子ID List<Long> postIds = posts.stream().map(Post::getId).collect(Collectors.toList()); // 批量获取点赞数和评论数 Map<Long, Integer> likeCounts = new HashMap<>(); Map<Long, Integer> commentCounts = new HashMap<>(); try { likeCounts = likeService.batchGetLikeCounts(postIds); } catch (Exception e) { System.err.println("获取点赞数失败: " + e.getMessage()); } try { commentCounts = commentService.batchGetCommentCounts(postIds); } catch (Exception e) { System.err.println("获取评论数失败: " + e.getMessage()); } Map<Long, Integer> finalLikeCounts = likeCounts; Map<Long, Integer> finalCommentCounts = commentCounts; return posts.stream().map(post -> { try { // 添加用户信息 User user = userMapper.findById(post.getUserId()); post.setUser(user); // 添加点赞数和评论数 post.setLikeCount(finalLikeCounts.getOrDefault(post.getId(), 0)); post.setCommentCount(finalCommentCounts.getOrDefault(post.getId(), 0)); } catch (Exception e) { System.err.println("处理帖子数据失败: " + e.getMessage()); } return post; }).collect(Collectors.toList()); } catch (Exception e) { System.err.println("处理帖子列表失败: " + e.getMessage()); return posts; } } /** * 为帖子列表添加用户信息和点赞状态 */ private List<Post> enrichPostsWithUserInfoAndLikeStatus(List<Post> posts, Long currentUserId) { if (posts == null || posts.isEmpty()) { return posts; } try { // 获取基本用户信息和统计数据 List<Post> enrichedPosts = enrichPostsWithUserInfo(posts); // 如果当前用户已登录,获取点赞状态 if (currentUserId != null) { try { List<Long> postIds = posts.stream().map(Post::getId).collect(Collectors.toList()); Map<Long, Boolean> userLikes = likeService.batchCheckUserLikes(postIds, currentUserId); // 添加用户点赞状态 for (Post post : enrichedPosts) { post.setHasLiked(userLikes.getOrDefault(post.getId(), false)); } } catch (Exception e) { System.err.println("获取用户点赞状态失败: " + e.getMessage()); } } return enrichedPosts; } catch (Exception e) { System.err.println("处理帖子列表和点赞状态失败: " + e.getMessage()); return posts; } } -
UserService: 处理用户注册、登录、查询。
@Autowired private UserMapper userMapper; @Override public Optional<User> login(String username, String password) { if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) { return Optional.empty(); } User user = userMapper.findByUsername(username); if (user != null && password.equals(user.getPassword())) { return Optional.of(user); } return Optional.empty(); } @Override public Optional<User> register(User user) { System.out.println("=== UserService.register 开始 ==="); // 参数验证 if (!StringUtils.hasText(user.getUsername())) { System.out.println("注册失败:用户名为空"); return Optional.empty(); } if (!StringUtils.hasText(user.getPassword())) { System.out.println("注册失败:密码为空"); return Optional.empty(); } System.out.println("用户名: " + user.getUsername()); System.out.println("密码长度: " + user.getPassword().length()); System.out.println("邮箱: " + (user.getEmail() != null ? user.getEmail() : "null")); try { // 检查用户名是否已存在 System.out.println("检查用户名是否已存在..."); User existingUser = userMapper.findByUsername(user.getUsername()); if (existingUser != null) { System.out.println("用户名已存在,注册失败"); return Optional.empty(); } System.out.println("用户名可用,开始创建用户..."); // 设置默认值 if (user.getEmail() == null) { user.setEmail(""); } if (user.getBio() == null) { user.setBio(""); } if (user.getAvatar() == null) { user.setAvatar("/avatar-placeholder.png"); } if (user.getRole() == null) { user.setRole("USER"); } // 设置时间 Date now = new Date(); user.setCreateTime(now); user.setUpdateTime(now); System.out.println("准备插入数据库..."); System.out.println("最终用户数据:"); System.out.println("- 用户名: " + user.getUsername()); System.out.println("- 邮箱: " + user.getEmail()); System.out.println("- 头像: " + user.getAvatar()); System.out.println("- 个人简介: " + user.getBio()); System.out.println("- 角色: " + user.getRole()); // 保存到数据库 int result = userMapper.insert(user); System.out.println("数据库插入结果: " + result); if (result > 0) { System.out.println("注册成功!用户ID: " + user.getId()); // 返回注册成功的用户(不包含密码) user.setPassword(null); return Optional.of(user); } else { System.out.println("数据库插入失败"); return Optional.empty(); } } catch (Exception e) { System.err.println("注册过程中发生异常: " + e.getMessage()); e.printStackTrace(); return Optional.empty(); } } @Override public Optional<User> getUserById(Long id) { if (id == null || id <= 0) { return Optional.empty(); } return Optional.ofNullable(userMapper.findById(id)); } @Override public Optional<User> getUserByUsername(String username) { if (!StringUtils.hasText(username)) { return Optional.empty(); } return Optional.ofNullable(userMapper.findByUsername(username)); } @Override public List<User> getAllUsers() { return userMapper.findAll(); } @Override public boolean deleteUser(Long id) { if (id == null || id <= 0) { return false; } try { // 检查用户是否存在 User user = userMapper.findById(id); if (user == null) { return false; } // 防止删除管理员账户 if ("ADMIN".equals(user.getRole())) { System.out.println("警告:不能删除管理员账户,用户ID: " + id); return false; } // 暂时禁用用户删除功能,因为需要先解决外键约束问题 System.out.println("警告:用户删除功能暂时禁用,因为存在外键约束"); return false; } catch (Exception e) { System.err.println("删除用户时发生异常: " + e.getMessage()); e.printStackTrace(); return false; } }
-
4.7 主启动类(SocialPlatformApplication.java)
- Spring Boot主启动类
- 扫描repository包
- 初始化时创建uploads目录
@SpringBootApplication
@MapperScan("com.socialplatform.repository")
public class SocialPlatformApplication {
public static void main(String[] args) {
SpringApplication.run(SocialPlatformApplication.class, args);
}
@PostConstruct
public void init() {
// 确保上传目录存在
File uploadsDir = new File("uploads");
if (!uploadsDir.exists()) {
uploadsDir.mkdirs();
}
}
}
4.8 工具类(util包)
UserValidationUtil.java: 提供用户验证和会话管理相关的工具方法。
@Autowired
private UserService userService;
/**
* 验证并刷新Session中的用户信息
* 确保Session中的用户数据与数据库一致
*/
public User validateAndRefreshSessionUser(HttpSession session) {
User sessionUser = (User) session.getAttribute("currentUser");
if (sessionUser == null) {
return null;
}
// 从数据库重新获取用户信息,确保数据一致性
Optional<User> dbUserOpt = userService.getUserById(sessionUser.getId());
if (dbUserOpt.isPresent()) {
User dbUser = dbUserOpt.get();
// 检查用户名是否一致
if (!sessionUser.getUsername().equals(dbUser.getUsername())) {
System.err.println("警告:Session用户信息与数据库不一致!");
System.err.println("Session用户: " + sessionUser.getUsername() + " (ID: " + sessionUser.getId() + ")");
System.err.println("数据库用户: " + dbUser.getUsername() + " (ID: " + dbUser.getId() + ")");
// 使用数据库中的最新信息更新Session
session.setAttribute("currentUser", dbUser);
return dbUser;
}
return sessionUser;
} else {
// 用户在数据库中不存在,清除Session
System.err.println("Session中的用户在数据库中不存在,清除Session");
session.invalidate();
return null;
}
}
/**
* 创建安全的用户信息副本(不包含密码)
*/
public User createSafeUserCopy(User user) {
if (user == null) {
return null;
}
User safeUser = new User();
safeUser.setId(user.getId());
safeUser.setUsername(user.getUsername());
safeUser.setEmail(user.getEmail());
safeUser.setAvatar(user.getAvatar());
safeUser.setBio(user.getBio());
safeUser.setCreateTime(user.getCreateTime());
safeUser.setUpdateTime(user.getUpdateTime());
// 不复制密码字段
return safeUser;
}
/**
* 记录用户操作日志
*/
public void logUserAction(String action, User user, String details) {
System.out.println("=== 用户操作日志 ===");
System.out.println("操作: " + action);
System.out.println("用户: " + (user != null ? user.getUsername() : "null") +
" (ID: " + (user != null ? user.getId() : "null") + ")");
System.out.println("详情: " + details);
System.out.println("时间: " + new java.util.Date());
System.out.println("==================");
}
五、前端结构简介
5.1 根目录
作用:管理项目基础配置和依赖关系
功能:
- package.json:定义项目元信息、管理生产/开发依赖、定义构建脚本
- vue.config.js:配置API代理、图标支持、ESLint规则
- babel.config.js:配置JavaScript语法转换规则
- public/:存放静态资源文件(如占位脚本)
5.2 components/(组件目录)
作用:提供可复用的UI组件,实现页面功能模块化
功能:
- CreatePost.vue:图文混排编辑、图片上传预览、话题标签识别
- NotificationList.vue:未读/已读状态区分、通知分类筛选、批量操作
- PostItem.vue:标准化内容展示、点赞评论交互、响应式媒体支持
5.3 router/(路由目录)
作用:管理应用路由和导航逻辑
功能:
- 定义12个核心路由(首页、登录、注册等)
- 实现路由懒加载策略优化性能
- 配置导航守卫处理认证状态验证
- 支持动态路由参数(帖子ID、用户ID)
5.4 store/(状态管理目录)
作用:集中管理应用全局状态数据
功能:
- 用户认证状态管理(登录、登出、自动续签)
- 数据持久化存储(用户信息、帖子列表、消息通知)
- 响应式更新机制支持多组件共享状态
- 异步操作封装(API请求统一处理)
5.5 views/(页面目录)
作用:实现具体业务场景的可视化界面
功能:
- Home.vue:动态流式布局、发帖组件集成、用户推荐展示
- Login.vue:表单验证、记住我功能、移动端验证码登录
- Register.vue:多步骤流程、邀请码输入、协议勾选确认
- Profile.vue:资料编辑、头像上传、发帖历史展示
- Messages.vue:联系人列表管理、消息发送接收、新消息通知
- Trending.vue:热门话题榜单、话题分类浏览、趋势箭头指示
- Search.vue:多标签搜索、高级筛选、搜索结果高亮
- Discover.vue:用户推荐系统、热门用户展示、社交邀请功能
- Notifications.vue:通知分类管理、免打扰时段设置、批量操作
- PostDetail.vue:内容完整展示、评论发布功能、分享与举报
- PostEdit.vue:图文编辑、图片删除、草稿自动保存
- Admin.vue:用户管理、数据统计、权限分配
- NotFound.vue:友好错误提示、搜索框快捷入口
5.6 入口文件 main.js
作用:初始化Vue应用并挂载全局依赖
功能:
- 创建Vue应用实例
- 引入Element Plus和全局样式
- 配置Axios请求拦截器
- 挂载Vue Router和Vuex
- 检查用户认证状态
5.7 根组件 App.vue
作用:提供全局布局和基础功能支持
功能:
- 使用渲染动态路由
- 实现响应式导航栏
- 集成Element Plus图标系统
- 处理移动端适配逻辑
- 管理用户认证状态显示
5.8 构建与依赖管理
作用:确保项目可构建和依赖可维护
功能:
- 构建脚本:serve启动开发服务器、build打包生产环境代码
- 代理配置:/api → 后端服务
- 图标支持:处理i-ep-自定义元素
- 代码规范:ESLint校验Vue 3语法
六、 项目展示
6.1 登录与注册
注册
-
管理员:在数据库脚本运行后初始化
默认账号:root
默认密码:root123

-
普通用户

-
输入两次密码不一致会报错:

-
登录
-
管理员:

-
普通用户:

6.2 评论和点赞
6.2.1 评论和点赞

6.2.2 帖内回复

6.2.3 删除评论(只能由评论者和管理员删除)


6.2.4 编辑评论内容


6.3 聊天室对话


6.4 个人资料编辑


七、总结
这次课设相对以往的课设,更具备挑战性。时间的有限和仓促的准备,让我感受到前所未有的紧迫感。多亏了网络上高度发达的资源,学习借鉴是解决问题的一个很好的办法。与此同时,也很感谢愿意抽出休息时间帮助我的同学和老师。
通过这次课设,逐步对项目有了初步的了解,也理解了在项目开发过程中会不断面临的窘境。在此之后的学习中,会逐渐养成良好的开发习惯。痛苦于启动时的命名不规范、本地仓库的胡乱构建…同时我也逐渐意识到老师们的良苦用心。
任何事情都不是一蹴而就的,日后将努力提高自身的开发能力。
最后,再次感谢所有老师的付出和同学的陪伴!


505

被折叠的 条评论
为什么被折叠?



