feat(user): Implement user module foundation

This commit is contained in:
2025-04-02 01:54:12 +08:00
parent 8c4d8e4c6a
commit 730a23dee3
55 changed files with 3929 additions and 20 deletions
@@ -1,11 +1,18 @@
package com.nuaa.video.user;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 用户服务启动类
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.nuaa.video.user.mapper")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
@@ -0,0 +1,15 @@
package com.nuaa.video.user.config;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Retryer feignRetryer() {
// 禁用重试
return Retryer.NEVER_RETRY;
}
}
@@ -0,0 +1,50 @@
package com.nuaa.video.user.config;
import com.nuaa.video.common.utils.MinioUtils;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MinIO配置类
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
/**
* 服务地址
*/
private String endpoint;
/**
* 访问密钥
*/
private String accessKey;
/**
* 密钥
*/
private String secretKey;
/**
* 存储桶名称
*/
private String bucketName;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
@Bean
public MinioUtils minioUtils(MinioClient minioClient) {
return new MinioUtils(minioClient);
}
}
@@ -0,0 +1,42 @@
package com.nuaa.video.user.config;
import com.nuaa.video.user.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* 安全配置类
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/user/profile/public/**",
"/user/profile/init",
"/actuator/**",
"/error"
).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
@@ -0,0 +1,93 @@
package com.nuaa.video.user.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.entity.UserInfo;
import com.nuaa.video.user.service.UserFollowService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 用户关注控制器
*/
@Slf4j
@RestController
@RequestMapping("/user/follow")
@RequiredArgsConstructor
public class UserFollowController {
private final UserFollowService userFollowService;
/**
* 关注用户
*/
@PostMapping("/{followId}")
public Result<Void> followUser(HttpServletRequest request, @PathVariable Long followId) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userFollowService.followUser(userId, followId);
}
/**
* 取消关注
*/
@DeleteMapping("/{followId}")
public Result<Void> unfollowUser(HttpServletRequest request, @PathVariable Long followId) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userFollowService.unfollowUser(userId, followId);
}
/**
* 获取用户关注列表
*/
@GetMapping("/following/{userId}")
public Result<Page<UserInfo>> getFollowingList(
@PathVariable Long userId,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size) {
Page<UserInfo> pageParam = new Page<>(page, size);
return userFollowService.getFollowingList(userId, pageParam);
}
/**
* 获取用户粉丝列表
*/
@GetMapping("/followers/{userId}")
public Result<Page<UserInfo>> getFollowerList(
@PathVariable Long userId,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size) {
Page<UserInfo> pageParam = new Page<>(page, size);
return userFollowService.getFollowerList(userId, pageParam);
}
/**
* 检查是否已关注
*/
@GetMapping("/check/{followId}")
public Result<Boolean> checkIsFollowed(HttpServletRequest request, @PathVariable Long followId) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userFollowService.checkIsFollowed(userId, followId);
}
/**
* 从请求中获取用户ID
*/
private Long getUserIdFromRequest(HttpServletRequest request) {
Object userIdObj = request.getAttribute("userId");
if (userIdObj instanceof Long) {
return (Long) userIdObj;
}
return null;
}
}
@@ -0,0 +1,105 @@
package com.nuaa.video.user.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.dto.UserMessageDTO;
import com.nuaa.video.user.entity.UserMessage;
import com.nuaa.video.user.service.UserMessageService;
import com.nuaa.video.user.vo.UserMessageVO;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 用户私信控制器
*/
@Slf4j
@RestController
@RequestMapping("/user/message")
@RequiredArgsConstructor
public class UserMessageController {
private final UserMessageService userMessageService;
/**
* 发送私信
*/
@PostMapping
public Result<Void> sendMessage(HttpServletRequest request, @RequestBody @Valid UserMessageDTO messageDTO) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userMessageService.sendMessage(userId, messageDTO);
}
/**
* 获取私信列表(按会话分组)
*/
@GetMapping("/list")
public Result<Page<UserMessageVO>> getMessageList(
HttpServletRequest request,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
Page<UserMessage> pageParam = new Page<>(page, size);
return userMessageService.getMessageList(userId, pageParam);
}
/**
* 获取与指定用户的私信记录
*/
@GetMapping("/detail/{targetId}")
public Result<Page<UserMessageVO>> getMessageDetail(
HttpServletRequest request,
@PathVariable Long targetId,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "20") Integer size) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
Page<UserMessage> pageParam = new Page<>(page, size);
return userMessageService.getMessageDetail(userId, targetId, pageParam);
}
/**
* 标记私信为已读
*/
@PutMapping("/read/{senderId}")
public Result<Void> markMessageRead(HttpServletRequest request, @PathVariable Long senderId) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userMessageService.markMessageRead(userId, senderId);
}
/**
* 获取未读私信数量
*/
@GetMapping("/unread/count")
public Result<Integer> getUnreadMessageCount(HttpServletRequest request) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userMessageService.getUnreadMessageCount(userId);
}
/**
* 从请求中获取用户ID
*/
private Long getUserIdFromRequest(HttpServletRequest request) {
Object userIdObj = request.getAttribute("userId");
if (userIdObj instanceof Long) {
return (Long) userIdObj;
}
return null;
}
}
@@ -0,0 +1,87 @@
package com.nuaa.video.user.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.entity.UserNotification;
import com.nuaa.video.user.service.UserNotificationService;
import com.nuaa.video.user.vo.UserNotificationVO;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 用户通知控制器
*/
@Slf4j
@RestController
@RequestMapping("/user/notification")
@RequiredArgsConstructor
public class UserNotificationController {
private final UserNotificationService userNotificationService;
/**
* 获取通知列表
*/
@GetMapping("/list")
public Result<Page<UserNotificationVO>> getNotificationList(
HttpServletRequest request,
@RequestParam(value = "type", required = false) Integer type,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
Page<UserNotification> pageParam = new Page<>(page, size);
return userNotificationService.getNotificationList(userId, type, pageParam);
}
/**
* 标记通知为已读
*/
@PutMapping("/read")
public Result<Void> markAllNotificationRead(HttpServletRequest request) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userNotificationService.markNotificationRead(userId, null);
}
/**
* 标记指定通知为已读
*/
@PutMapping("/read/{notificationId}")
public Result<Void> markNotificationRead(HttpServletRequest request, @PathVariable Long notificationId) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userNotificationService.markNotificationRead(userId, notificationId);
}
/**
* 获取未读通知数量
*/
@GetMapping("/unread/count")
public Result<Integer> getUnreadNotificationCount(HttpServletRequest request) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userNotificationService.getUnreadNotificationCount(userId);
}
/**
* 从请求中获取用户ID
*/
private Long getUserIdFromRequest(HttpServletRequest request) {
Object userIdObj = request.getAttribute("userId");
if (userIdObj instanceof Long) {
return (Long) userIdObj;
}
return null;
}
}
@@ -0,0 +1,147 @@
package com.nuaa.video.user.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.dto.UserProfileDTO;
import com.nuaa.video.user.entity.UserInfo;
import com.nuaa.video.user.feign.AuthFeignClient;
import com.nuaa.video.user.service.UserInfoService;
import com.nuaa.video.user.vo.UserProfileVO;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
/**
* 用户资料控制器
*/
@Slf4j
@RestController
@RequestMapping("/user/profile")
@RequiredArgsConstructor
public class UserProfileController {
private final UserInfoService userInfoService;
private final AuthFeignClient authFeignClient;
/**
* 初始化用户资料
*/
@PostMapping("/init")
public Result<Void> initUserInfo(@RequestParam("userId") Long userId,
@RequestParam("username") String username) {
return userInfoService.createUserInfo(userId, username);
}
/**
* 获取当前用户资料
*/
@GetMapping("/current")
public Result<UserProfileVO> getCurrentUserProfile(HttpServletRequest request) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
// 检查用户资料是否存在
UserInfo userInfo = userInfoService.getById(userId);
if (userInfo == null) {
// 资料不存在,自动创建
String username = getUsernameFromRequest(request);
if (username == null) {
// 尝试从Auth服务获取用户名
try {
Result<Object> userResult = authFeignClient.getCurrentUser(
request.getHeader("Authorization"));
if (userResult.getCode() == 200 && userResult.getData() != null) {
Map<String, Object> userData = (Map<String, Object>) userResult.getData();
username = (String) userData.get("username");
}
} catch (Exception e) {
log.error("获取用户信息失败", e);
}
}
if (username != null) {
userInfoService.createUserInfo(userId, username);
userInfo = userInfoService.getById(userId);
} else {
return Result.error("无法创建用户资料,用户信息不完整");
}
}
return userInfoService.getUserProfile(userId, userId);
}
/**
* 获取指定用户资料
*/
@GetMapping("/public/{userId}")
public Result<UserProfileVO> getUserProfile(@PathVariable Long userId, HttpServletRequest request) {
Long currentUserId = getUserIdFromRequest(request);
return userInfoService.getUserProfile(userId, currentUserId);
}
/**
* 更新用户资料
*/
@PutMapping
public Result<Void> updateUserProfile(HttpServletRequest request, @RequestBody @Valid UserProfileDTO userProfileDTO) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userInfoService.updateUserProfile(userId, userProfileDTO);
}
/**
* 上传头像
*/
@PostMapping("/avatar")
public Result<String> uploadAvatar(HttpServletRequest request, @RequestParam("file") MultipartFile file) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userInfoService.uploadAvatar(userId, file);
}
/**
* 搜索用户
*/
@GetMapping("/search")
public Result<Page<UserInfo>> searchUsers(
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size) {
Page<UserInfo> pageParam = new Page<>(page, size);
return userInfoService.searchUsers(pageParam, keyword);
}
/**
* 从请求中获取用户名
*/
private String getUsernameFromRequest(HttpServletRequest request) {
Object usernameObj = request.getAttribute("username");
if (usernameObj instanceof String) {
return (String) usernameObj;
}
return null;
}
/**
* 从请求中获取用户ID
*/
private Long getUserIdFromRequest(HttpServletRequest request) {
Object userIdObj = request.getAttribute("userId");
if (userIdObj instanceof Long) {
return (Long) userIdObj;
}
return null;
}
}
@@ -0,0 +1,58 @@
package com.nuaa.video.user.controller;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.dto.UserSettingDTO;
import com.nuaa.video.user.entity.UserSetting;
import com.nuaa.video.user.service.UserSettingService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 用户设置控制器
*/
@Slf4j
@RestController
@RequestMapping("/user/setting")
@RequiredArgsConstructor
public class UserSettingController {
private final UserSettingService userSettingService;
/**
* 获取用户设置
*/
@GetMapping
public Result<UserSetting> getUserSetting(HttpServletRequest request) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userSettingService.getUserSetting(userId);
}
/**
* 更新用户设置
*/
@PutMapping
public Result<Void> updateUserSetting(HttpServletRequest request, @RequestBody @Valid UserSettingDTO settingDTO) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return userSettingService.updateUserSetting(userId, settingDTO);
}
/**
* 从请求中获取用户ID
*/
private Long getUserIdFromRequest(HttpServletRequest request) {
Object userIdObj = request.getAttribute("userId");
if (userIdObj instanceof Long) {
return (Long) userIdObj;
}
return null;
}
}
@@ -0,0 +1,29 @@
package com.nuaa.video.user.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
/**
* 用户私信DTO
*/
@Data
public class UserMessageDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 接收者ID
*/
@NotNull(message = "接收者ID不能为空")
private Long receiverId;
/**
* 消息内容
*/
@NotBlank(message = "消息内容不能为空")
@Size(max = 500, message = "消息内容不能超过500字")
private String content;
}
@@ -0,0 +1,44 @@
package com.nuaa.video.user.dto;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate;
/**
* 用户资料DTO
*/
@Data
public class UserProfileDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 昵称
*/
@Size(min = 2, max = 20, message = "昵称长度必须在2-20之间")
private String nickname;
/**
* 性别(0:未知,1:男,2:女)
*/
private Integer gender;
/**
* 生日
*/
private LocalDate birthday;
/**
* 个性签名
*/
@Size(max = 200, message = "个性签名不能超过200字")
private String signature;
/**
* 地区
*/
@Size(max = 100, message = "地区不能超过100字")
private String region;
}
@@ -0,0 +1,38 @@
package com.nuaa.video.user.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 用户设置DTO
*/
@Data
public class UserSettingDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 隐私等级(1:公开,2:仅关注,3:私密)
*/
private Integer privacyLevel;
/**
* 评论通知(0:关闭,1:开启)
*/
private Integer commentNotify;
/**
* 点赞通知(0:关闭,1:开启)
*/
private Integer likeNotify;
/**
* 关注通知(0:关闭,1:开启)
*/
private Integer followNotify;
/**
* 系统通知(0:关闭,1:开启)
*/
private Integer systemNotify;
}
@@ -0,0 +1,39 @@
package com.nuaa.video.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户关注实体
*/
@Data
@TableName("user_follow")
public class UserFollow implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 关注用户ID
*/
private Long followId;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@@ -0,0 +1,132 @@
package com.nuaa.video.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 用户信息实体
*/
@Data
@TableName("user_info")
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@TableId(value = "id", type = IdType.INPUT)
private Long id;
/**
* 昵称
*/
private String nickname;
/**
* 头像URL
*/
private String avatar;
/**
* 性别(0:未知,1:男,2:女)
*/
private Integer gender;
/**
* 生日
*/
private LocalDate birthday;
/**
* 个性签名
*/
private String signature;
/**
* 地区
*/
private String region;
/**
* 粉丝数
*/
private Integer followerCount;
/**
* 关注数
*/
private Integer followingCount;
/**
* 视频数
*/
private Integer videoCount;
/**
* 获赞数
*/
private Integer likeCount;
/**
* 硬币数
*/
private Integer coinCount;
/**
* 收藏数
*/
private Integer favoriteCount;
/**
* 播放数
*/
private Long viewCount;
/**
* 等级
*/
private Integer level;
/**
* VIP等级
*/
private Integer vipLevel;
/**
* VIP到期时间
*/
private LocalDateTime vipExpireTime;
/**
* 创建人
*/
private String createdBy;
/**
* 更新人
*/
private String updatedBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 是否关注(非数据库字段)
*/
@TableField(exist = false)
private Boolean isFollowed;
}
@@ -0,0 +1,74 @@
package com.nuaa.video.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户私信实体
*/
@Data
@TableName("user_message")
public class UserMessage implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 发送者ID
*/
private Long senderId;
/**
* 接收者ID
*/
private Long receiverId;
/**
* 消息内容
*/
private String content;
/**
* 是否已读(0:未读,1:已读)
*/
private Integer isRead;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 发送者昵称(非数据库字段)
*/
@TableField(exist = false)
private String senderNickname;
/**
* 发送者头像(非数据库字段)
*/
@TableField(exist = false)
private String senderAvatar;
/**
* 接收者昵称(非数据库字段)
*/
@TableField(exist = false)
private String receiverNickname;
/**
* 接收者头像(非数据库字段)
*/
@TableField(exist = false)
private String receiverAvatar;
}
@@ -0,0 +1,54 @@
package com.nuaa.video.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户通知实体
*/
@Data
@TableName("user_notification")
public class UserNotification implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 通知类型(1:点赞,2:评论,3:关注,4:系统通知)
*/
private Integer type;
/**
* 通知内容
*/
private String content;
/**
* 相关ID
*/
private Long relatedId;
/**
* 是否已读(0:未读,1:已读)
*/
private Integer isRead;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@@ -0,0 +1,74 @@
package com.nuaa.video.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户设置实体
*/
@Data
@TableName("user_setting")
public class UserSetting implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 隐私等级(1:公开,2:仅关注,3:私密)
*/
private Integer privacyLevel;
/**
* 评论通知(0:关闭,1:开启)
*/
private Integer commentNotify;
/**
* 点赞通知(0:关闭,1:开启)
*/
private Integer likeNotify;
/**
* 关注通知(0:关闭,1:开启)
*/
private Integer followNotify;
/**
* 系统通知(0:关闭,1:开启)
*/
private Integer systemNotify;
/**
* 创建人
*/
private String createdBy;
/**
* 更新人
*/
private String updatedBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
@@ -0,0 +1,75 @@
package com.nuaa.video.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 用户每日统计实体
*/
@Data
@TableName("user_stat_daily")
public class UserStatDaily implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 统计日期
*/
private LocalDate statDate;
/**
* 新增粉丝数
*/
private Integer followerIncrement;
/**
* 新增关注数
*/
private Integer followingIncrement;
/**
* 新增视频数
*/
private Integer videoIncrement;
/**
* 新增获赞数
*/
private Integer likeIncrement;
/**
* 新增硬币数
*/
private Integer coinIncrement;
/**
* 新增收藏数
*/
private Integer favoriteIncrement;
/**
* 新增播放数
*/
private Long viewIncrement;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@@ -0,0 +1,33 @@
// com.nuaa.video.user.feign.AuthFeignClient.java
package com.nuaa.video.user.feign;
import com.nuaa.video.common.model.vo.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
/**
* 认证服务Feign客户端
*/
@FeignClient(name = "video-auth", path = "/auth")
public interface AuthFeignClient {
/**
* 获取当前用户信息
*
* @param token 令牌
* @return 用户信息
*/
@GetMapping("/current")
Result<Object> getCurrentUser(@RequestHeader("Authorization") String token);
/**
* 根据ID获取用户名
*
* @param userId 用户ID
* @return 用户名
*/
@GetMapping("/username/{userId}")
String getUsernameById(@PathVariable("userId") Long userId);
}
@@ -0,0 +1,102 @@
package com.nuaa.video.user.filter;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* JWT认证过滤器(不依赖Feign)
*/
@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Value("${jwt.secret:video-platform-secret-key-2024-nuaa}")
private String secret;
private static final String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 从请求头中获取JWT令牌
String token = getJwtFromRequest(request);
// 如果令牌存在,则本地验证
if (StringUtils.hasText(token)) {
try {
// 验证令牌
Claims claims = Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
// 从令牌中提取用户ID和用户名
Long userId = Long.parseLong(claims.getSubject());
String username = claims.get("username", String.class);
// 设置用户权限
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// 创建认证对象
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, authorities);
// 设置额外信息
authentication.setDetails(userId);
// 设置认证信息到安全上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 在请求属性中存储用户ID
request.setAttribute("userId", userId);
request.setAttribute("username", username);
log.debug("认证成功: 用户ID={}, 用户名={}", userId, username);
} catch (Exception e) {
log.error("JWT验证失败: {}", e.getMessage());
SecurityContextHolder.clearContext();
}
}
filterChain.doFilter(request, response);
}
/**
* 从请求头中提取JWT令牌
*/
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(TOKEN_PREFIX.length());
}
return null;
}
/**
* 获取签名密钥
*/
private SecretKey getSigningKey() {
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
return Keys.hmacShaKeyFor(keyBytes);
}
}
@@ -0,0 +1,42 @@
package com.nuaa.video.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.user.entity.UserFollow;
import com.nuaa.video.user.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 用户关注Mapper
*/
@Mapper
public interface UserFollowMapper extends BaseMapper<UserFollow> {
/**
* 查询用户关注列表
*
* @param page 分页参数
* @param userId 用户ID
* @return 关注用户列表
*/
Page<UserInfo> selectFollowingList(Page<UserInfo> page, @Param("userId") Long userId);
/**
* 查询用户粉丝列表
*
* @param page 分页参数
* @param userId 用户ID
* @return 粉丝用户列表
*/
Page<UserInfo> selectFollowerList(Page<UserInfo> page, @Param("userId") Long userId);
/**
* 检查是否已关注
*
* @param userId 用户ID
* @param followId 关注用户ID
* @return 是否已关注
*/
int checkIsFollowed(@Param("userId") Long userId, @Param("followId") Long followId);
}
@@ -0,0 +1,31 @@
package com.nuaa.video.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nuaa.video.user.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 用户信息Mapper
*/
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
/**
* 增加关注数
*
* @param userId 用户ID
* @param count 增加数量
* @return 影响行数
*/
int increaseFollowingCount(@Param("userId") Long userId, @Param("count") int count);
/**
* 增加粉丝数
*
* @param userId 用户ID
* @param count 增加数量
* @return 影响行数
*/
int increaseFollowerCount(@Param("userId") Long userId, @Param("count") int count);
}
@@ -0,0 +1,50 @@
package com.nuaa.video.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.user.entity.UserMessage;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 用户私信Mapper
*/
@Mapper
public interface UserMessageMapper extends BaseMapper<UserMessage> {
/**
* 查询私信列表(按会话分组)
*
* @param page 分页参数
* @param userId 用户ID
* @return 私信列表
*/
Page<UserMessage> selectMessageGroupList(Page<UserMessage> page, @Param("userId") Long userId);
/**
* 查询与指定用户的私信记录
*
* @param page 分页参数
* @param userId 用户ID
* @param targetId 目标用户ID
* @return 私信记录
*/
Page<UserMessage> selectMessageDetail(Page<UserMessage> page, @Param("userId") Long userId, @Param("targetId") Long targetId);
/**
* 标记私信为已读
*
* @param userId 用户ID
* @param senderId 发送者ID
* @return 影响行数
*/
int markMessageRead(@Param("userId") Long userId, @Param("senderId") Long senderId);
/**
* 获取未读私信数量
*
* @param userId 用户ID
* @return 未读私信数量
*/
int countUnreadMessage(@Param("userId") Long userId);
}
@@ -0,0 +1,30 @@
package com.nuaa.video.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nuaa.video.user.entity.UserNotification;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 用户通知Mapper
*/
@Mapper
public interface UserNotificationMapper extends BaseMapper<UserNotification> {
/**
* 标记通知为已读
*
* @param userId 用户ID
* @param notificationId 通知ID,为空则标记所有
* @return 影响行数
*/
int markNotificationRead(@Param("userId") Long userId, @Param("notificationId") Long notificationId);
/**
* 获取未读通知数量
*
* @param userId 用户ID
* @return 未读通知数量
*/
int countUnreadNotification(@Param("userId") Long userId);
}
@@ -0,0 +1,12 @@
package com.nuaa.video.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nuaa.video.user.entity.UserSetting;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户设置Mapper
*/
@Mapper
public interface UserSettingMapper extends BaseMapper<UserSetting> {
}
@@ -0,0 +1,12 @@
package com.nuaa.video.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nuaa.video.user.entity.UserStatDaily;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户每日统计Mapper
*/
@Mapper
public interface UserStatDailyMapper extends BaseMapper<UserStatDaily> {
}
@@ -0,0 +1,58 @@
package com.nuaa.video.user.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.entity.UserFollow;
import com.nuaa.video.user.entity.UserInfo;
/**
* 用户关注服务接口
*/
public interface UserFollowService extends IService<UserFollow> {
/**
* 关注用户
*
* @param userId 用户ID
* @param followId 被关注用户ID
* @return 关注结果
*/
Result<Void> followUser(Long userId, Long followId);
/**
* 取消关注
*
* @param userId 用户ID
* @param followId 被关注用户ID
* @return 取消结果
*/
Result<Void> unfollowUser(Long userId, Long followId);
/**
* 获取用户关注列表
*
* @param userId 用户ID
* @param page 分页参数
* @return 关注列表
*/
Result<Page<UserInfo>> getFollowingList(Long userId, Page<UserInfo> page);
/**
* 获取用户粉丝列表
*
* @param userId 用户ID
* @param page 分页参数
* @return 粉丝列表
*/
Result<Page<UserInfo>> getFollowerList(Long userId, Page<UserInfo> page);
/**
* 检查是否已关注
*
* @param userId 用户ID
* @param followId 关注用户ID
* @return 是否已关注
*/
Result<Boolean> checkIsFollowed(Long userId, Long followId);
}
@@ -0,0 +1,68 @@
package com.nuaa.video.user.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.dto.UserProfileDTO;
import com.nuaa.video.user.entity.UserInfo;
import com.nuaa.video.user.vo.UserProfileVO;
import org.springframework.web.multipart.MultipartFile;
/**
* 用户信息服务接口
*/
public interface UserInfoService extends IService<UserInfo> {
/**
* 获取用户资料
*
* @param userId 用户ID
* @param currentUserId 当前登录用户ID,可为空
* @return 用户资料
*/
Result<UserProfileVO> getUserProfile(Long userId, Long currentUserId);
/**
* 更新用户资料
*
* @param userId 用户ID
* @param userProfileDTO 用户资料DTO
* @return 更新结果
*/
Result<Void> updateUserProfile(Long userId, UserProfileDTO userProfileDTO);
/**
* 上传头像
*
* @param userId 用户ID
* @param file 头像文件
* @return 头像URL
*/
Result<String> uploadAvatar(Long userId, MultipartFile file);
/**
* 创建用户信息(用户注册初始化)
*
* @param userId 用户ID
* @param username 用户名,用作初始昵称
* @return 创建结果
*/
Result<Void> createUserInfo(Long userId, String username);
/**
* 获取用户简要信息
*
* @param userId 用户ID
* @return 简要信息
*/
Result<UserInfo> getBriefUserInfo(Long userId);
/**
* 查询用户列表
*
* @param page 分页参数
* @param keyword 搜索关键词
* @return 用户列表
*/
Result<Page<UserInfo>> searchUsers(Page<UserInfo> page, String keyword);
}
@@ -0,0 +1,59 @@
package com.nuaa.video.user.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.dto.UserMessageDTO;
import com.nuaa.video.user.entity.UserMessage;
import com.nuaa.video.user.vo.UserMessageVO;
/**
* 用户私信服务接口
*/
public interface UserMessageService extends IService<UserMessage> {
/**
* 发送私信
*
* @param userId 用户ID
* @param messageDTO 私信DTO
* @return 发送结果
*/
Result<Void> sendMessage(Long userId, UserMessageDTO messageDTO);
/**
* 获取私信列表(按会话分组)
*
* @param userId 用户ID
* @param page 分页参数
* @return 私信列表
*/
Result<Page<UserMessageVO>> getMessageList(Long userId, Page<UserMessage> page);
/**
* 获取与指定用户的私信记录
*
* @param userId 用户ID
* @param targetId 目标用户ID
* @param page 分页参数
* @return 私信记录
*/
Result<Page<UserMessageVO>> getMessageDetail(Long userId, Long targetId, Page<UserMessage> page);
/**
* 标记私信为已读
*
* @param userId 用户ID
* @param senderId 发送者ID
* @return 标记结果
*/
Result<Void> markMessageRead(Long userId, Long senderId);
/**
* 获取未读私信数量
*
* @param userId 用户ID
* @return 未读私信数量
*/
Result<Integer> getUnreadMessageCount(Long userId);
}
@@ -0,0 +1,51 @@
package com.nuaa.video.user.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.entity.UserNotification;
import com.nuaa.video.user.vo.UserNotificationVO;
/**
* 用户通知服务接口
*/
public interface UserNotificationService extends IService<UserNotification> {
/**
* 创建通知
*
* @param userId 用户ID
* @param type 通知类型
* @param content 通知内容
* @param relatedId 相关ID
* @return 创建结果
*/
Result<Void> createNotification(Long userId, Integer type, String content, Long relatedId);
/**
* 获取用户通知列表
*
* @param userId 用户ID
* @param type 通知类型,null为全部
* @param page 分页参数
* @return 通知列表
*/
Result<Page<UserNotificationVO>> getNotificationList(Long userId, Integer type, Page<UserNotification> page);
/**
* 标记通知为已读
*
* @param userId 用户ID
* @param notificationId 通知ID,为空则标记所有
* @return 标记结果
*/
Result<Void> markNotificationRead(Long userId, Long notificationId);
/**
* 获取未读通知数量
*
* @param userId 用户ID
* @return 未读通知数量
*/
Result<Integer> getUnreadNotificationCount(Long userId);
}
@@ -0,0 +1,37 @@
package com.nuaa.video.user.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.dto.UserSettingDTO;
import com.nuaa.video.user.entity.UserSetting;
/**
* 用户设置服务接口
*/
public interface UserSettingService extends IService<UserSetting> {
/**
* 获取用户设置
*
* @param userId 用户ID
* @return 用户设置
*/
Result<UserSetting> getUserSetting(Long userId);
/**
* 更新用户设置
*
* @param userId 用户ID
* @param settingDTO 设置DTO
* @return 更新结果
*/
Result<Void> updateUserSetting(Long userId, UserSettingDTO settingDTO);
/**
* 创建默认设置(用户注册初始化)
*
* @param userId 用户ID
* @return 创建结果
*/
Result<Void> createDefaultSetting(Long userId);
}
@@ -0,0 +1,147 @@
package com.nuaa.video.user.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nuaa.video.common.model.enums.ResultCode;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.entity.UserFollow;
import com.nuaa.video.user.entity.UserInfo;
import com.nuaa.video.user.mapper.UserFollowMapper;
import com.nuaa.video.user.mapper.UserInfoMapper;
import com.nuaa.video.user.service.UserFollowService;
import com.nuaa.video.user.service.UserNotificationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
/**
* 用户关注服务实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserFollowServiceImpl extends ServiceImpl<UserFollowMapper, UserFollow> implements UserFollowService {
private final UserFollowMapper userFollowMapper;
private final UserInfoMapper userInfoMapper;
private final UserNotificationService userNotificationService;
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> followUser(Long userId, Long followId) {
// 验证用户和关注对象是否存在
UserInfo user = userInfoMapper.selectById(userId);
UserInfo followUser = userInfoMapper.selectById(followId);
if (user == null || followUser == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 不能关注自己
if (userId.equals(followId)) {
return Result.error("不能关注自己");
}
// 检查是否已关注
int count = userFollowMapper.checkIsFollowed(userId, followId);
if (count > 0) {
return Result.error("已经关注该用户");
}
// 创建关注关系
UserFollow userFollow = new UserFollow();
userFollow.setUserId(userId);
userFollow.setFollowId(followId);
userFollow.setCreateTime(LocalDateTime.now());
// 保存关注记录
save(userFollow);
// 更新用户和被关注用户的关注数和粉丝数
userInfoMapper.increaseFollowingCount(userId, 1);
userInfoMapper.increaseFollowerCount(followId, 1);
// 创建关注通知
String content = user.getNickname() + " 关注了你";
userNotificationService.createNotification(followId, 3, content, userId);
return Result.success();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> unfollowUser(Long userId, Long followId) {
// 验证用户和关注对象是否存在
UserInfo user = userInfoMapper.selectById(userId);
UserInfo followUser = userInfoMapper.selectById(followId);
if (user == null || followUser == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 检查是否已关注
int count = userFollowMapper.checkIsFollowed(userId, followId);
if (count == 0) {
return Result.error("未关注该用户");
}
// 删除关注关系
lambdaUpdate()
.eq(UserFollow::getUserId, userId)
.eq(UserFollow::getFollowId, followId)
.remove();
// 更新用户和被关注用户的关注数和粉丝数
userInfoMapper.increaseFollowingCount(userId, -1);
userInfoMapper.increaseFollowerCount(followId, -1);
return Result.success();
}
@Override
public Result<Page<UserInfo>> getFollowingList(Long userId, Page<UserInfo> page) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 查询关注列表
Page<UserInfo> followingList = userFollowMapper.selectFollowingList(page, userId);
return Result.success(followingList);
}
@Override
public Result<Page<UserInfo>> getFollowerList(Long userId, Page<UserInfo> page) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 查询粉丝列表
Page<UserInfo> followerList = userFollowMapper.selectFollowerList(page, userId);
return Result.success(followerList);
}
@Override
public Result<Boolean> checkIsFollowed(Long userId, Long followId) {
// 验证用户和关注对象是否存在
UserInfo user = userInfoMapper.selectById(userId);
UserInfo followUser = userInfoMapper.selectById(followId);
if (user == null || followUser == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 检查是否已关注
int count = userFollowMapper.checkIsFollowed(userId, followId);
return Result.success(count > 0);
}
}
@@ -0,0 +1,189 @@
package com.nuaa.video.user.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nuaa.video.common.model.enums.ResultCode;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.common.utils.MinioUtils;
import com.nuaa.video.user.dto.UserProfileDTO;
import com.nuaa.video.user.entity.UserInfo;
import com.nuaa.video.user.mapper.UserFollowMapper;
import com.nuaa.video.user.mapper.UserInfoMapper;
import com.nuaa.video.user.service.UserInfoService;
import com.nuaa.video.user.service.UserSettingService;
import com.nuaa.video.user.vo.UserProfileVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
/**
* 用户信息服务实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
private final UserInfoMapper userInfoMapper;
private final UserFollowMapper userFollowMapper;
private final UserSettingService userSettingService;
private final MinioUtils minioUtils;
@Override
public Result<UserProfileVO> getUserProfile(Long userId, Long currentUserId) {
// 查询用户信息
UserInfo userInfo = getById(userId);
if (userInfo == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
}
// 转换为VO
UserProfileVO profileVO = new UserProfileVO();
BeanUtils.copyProperties(userInfo, profileVO);
// 检查是否关注(如果当前用户已登录)
if (currentUserId != null && !currentUserId.equals(userId)) {
int count = userFollowMapper.checkIsFollowed(currentUserId, userId);
profileVO.setIsFollowed(count > 0);
} else {
profileVO.setIsFollowed(false);
}
return Result.success(profileVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> updateUserProfile(Long userId, UserProfileDTO userProfileDTO) {
// 查询用户信息
UserInfo userInfo = getById(userId);
if (userInfo == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
}
// 更新用户信息
if (userProfileDTO.getNickname() != null) {
userInfo.setNickname(userProfileDTO.getNickname());
}
if (userProfileDTO.getGender() != null) {
userInfo.setGender(userProfileDTO.getGender());
}
if (userProfileDTO.getBirthday() != null) {
userInfo.setBirthday(userProfileDTO.getBirthday());
}
if (userProfileDTO.getSignature() != null) {
userInfo.setSignature(userProfileDTO.getSignature());
}
if (userProfileDTO.getRegion() != null) {
userInfo.setRegion(userProfileDTO.getRegion());
}
userInfo.setUpdatedBy(userId.toString());
userInfo.setUpdateTime(LocalDateTime.now());
// 保存更新
updateById(userInfo);
return Result.success();
}
@Override
public Result<String> uploadAvatar(Long userId, MultipartFile file) {
try {
// 查询用户信息
UserInfo userInfo = getById(userId);
if (userInfo == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
}
// 上传头像
String avatarUrl = minioUtils.uploadImage(file);
// 更新用户头像
userInfo.setAvatar(avatarUrl);
userInfo.setUpdatedBy(userId.toString());
userInfo.setUpdateTime(LocalDateTime.now());
updateById(userInfo);
// 返回头像URL
return Result.success(minioUtils.getPreSignedUrl(avatarUrl));
} catch (Exception e) {
log.error("上传头像失败", e);
return Result.error("上传头像失败: " + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> createUserInfo(Long userId, String username) {
// 检查是否已存在
UserInfo existUser = getById(userId);
if (existUser != null) {
return Result.error("用户信息已存在");
}
// 创建用户信息
UserInfo userInfo = new UserInfo();
userInfo.setId(userId);
userInfo.setNickname(username);
userInfo.setAvatar("default/avatar.png"); // 默认头像
userInfo.setGender(0);
userInfo.setFollowerCount(0);
userInfo.setFollowingCount(0);
userInfo.setVideoCount(0);
userInfo.setLikeCount(0);
userInfo.setCoinCount(0);
userInfo.setFavoriteCount(0);
userInfo.setViewCount(0L);
userInfo.setLevel(1);
userInfo.setVipLevel(0);
userInfo.setCreatedBy("system");
userInfo.setUpdatedBy("system");
userInfo.setCreateTime(LocalDateTime.now());
userInfo.setUpdateTime(LocalDateTime.now());
// 保存用户信息
save(userInfo);
// 创建默认设置
userSettingService.createDefaultSetting(userId);
return Result.success();
}
@Override
public Result<UserInfo> getBriefUserInfo(Long userId) {
UserInfo userInfo = getById(userId);
if (userInfo == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
}
return Result.success(userInfo);
}
@Override
public Result<Page<UserInfo>> searchUsers(Page<UserInfo> page, String keyword) {
// 构建查询条件
LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
if (keyword != null && !keyword.trim().isEmpty()) {
queryWrapper.like(UserInfo::getNickname, keyword)
.or()
.like(UserInfo::getSignature, keyword);
}
// 按等级降序,注册时间降序
queryWrapper.orderByDesc(UserInfo::getLevel)
.orderByDesc(UserInfo::getCreateTime);
// 执行分页查询
Page<UserInfo> resultPage = page(page, queryWrapper);
return Result.success(resultPage);
}
}
@@ -0,0 +1,148 @@
package com.nuaa.video.user.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nuaa.video.common.model.enums.ResultCode;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.dto.UserMessageDTO;
import com.nuaa.video.user.entity.UserInfo;
import com.nuaa.video.user.entity.UserMessage;
import com.nuaa.video.user.mapper.UserInfoMapper;
import com.nuaa.video.user.mapper.UserMessageMapper;
import com.nuaa.video.user.service.UserMessageService;
import com.nuaa.video.user.vo.UserMessageVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.stream.Collectors;
/**
* 用户私信服务实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserMessageServiceImpl extends ServiceImpl<UserMessageMapper, UserMessage> implements UserMessageService {
private final UserMessageMapper userMessageMapper;
private final UserInfoMapper userInfoMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> sendMessage(Long userId, UserMessageDTO messageDTO) {
// 验证发送者和接收者是否存在
UserInfo sender = userInfoMapper.selectById(userId);
UserInfo receiver = userInfoMapper.selectById(messageDTO.getReceiverId());
if (sender == null || receiver == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 不能给自己发私信
if (userId.equals(messageDTO.getReceiverId())) {
return Result.error("不能给自己发私信");
}
// 创建私信记录
UserMessage message = new UserMessage();
message.setSenderId(userId);
message.setReceiverId(messageDTO.getReceiverId());
message.setContent(messageDTO.getContent());
message.setIsRead(0);
message.setCreateTime(LocalDateTime.now());
// 保存私信
save(message);
return Result.success();
}
@Override
public Result<Page<UserMessageVO>> getMessageList(Long userId, Page<UserMessage> page) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 查询私信列表
Page<UserMessage> messagePage = userMessageMapper.selectMessageGroupList(page, userId);
// 转换为VO对象
Page<UserMessageVO> resultPage = new Page<>();
BeanUtils.copyProperties(messagePage, resultPage, "records");
// 转换记录
resultPage.setRecords(messagePage.getRecords().stream().map(this::convertToVO).collect(Collectors.toList()));
return Result.success(resultPage);
}
@Override
public Result<Page<UserMessageVO>> getMessageDetail(Long userId, Long targetId, Page<UserMessage> page) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
UserInfo target = userInfoMapper.selectById(targetId);
if (user == null || target == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 查询私信记录
Page<UserMessage> messagePage = userMessageMapper.selectMessageDetail(page, userId, targetId);
// 标记私信为已读
markMessageRead(userId, targetId);
// 转换为VO对象
Page<UserMessageVO> resultPage = new Page<>();
BeanUtils.copyProperties(messagePage, resultPage, "records");
// 转换记录
resultPage.setRecords(messagePage.getRecords().stream().map(this::convertToVO).collect(Collectors.toList()));
return Result.success(resultPage);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> markMessageRead(Long userId, Long senderId) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 标记私信为已读
userMessageMapper.markMessageRead(userId, senderId);
return Result.success();
}
@Override
public Result<Integer> getUnreadMessageCount(Long userId) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 获取未读私信数量
int count = userMessageMapper.countUnreadMessage(userId);
return Result.success(count);
}
/**
* 将实体对象转换为VO对象
*/
private UserMessageVO convertToVO(UserMessage message) {
UserMessageVO vo = new UserMessageVO();
BeanUtils.copyProperties(message, vo);
return vo;
}
}
@@ -0,0 +1,156 @@
package com.nuaa.video.user.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nuaa.video.common.model.enums.ResultCode;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.entity.UserInfo;
import com.nuaa.video.user.entity.UserNotification;
import com.nuaa.video.user.entity.UserSetting;
import com.nuaa.video.user.mapper.UserInfoMapper;
import com.nuaa.video.user.mapper.UserNotificationMapper;
import com.nuaa.video.user.mapper.UserSettingMapper;
import com.nuaa.video.user.service.UserNotificationService;
import com.nuaa.video.user.vo.UserNotificationVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.stream.Collectors;
/**
* 用户通知服务实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserNotificationServiceImpl extends ServiceImpl<UserNotificationMapper, UserNotification> implements UserNotificationService {
private final UserNotificationMapper userNotificationMapper;
private final UserInfoMapper userInfoMapper;
private final UserSettingMapper userSettingMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> createNotification(Long userId, Integer type, String content, Long relatedId) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 检查用户通知设置
UserSetting setting = userSettingMapper.selectOne(
new LambdaQueryWrapper<UserSetting>().eq(UserSetting::getUserId, userId)
);
// 根据设置决定是否发送通知
if (setting != null) {
boolean shouldNotify = false;
switch (type) {
case 1: // 点赞通知
shouldNotify = setting.getLikeNotify() == 1;
break;
case 2: // 评论通知
shouldNotify = setting.getCommentNotify() == 1;
break;
case 3: // 关注通知
shouldNotify = setting.getFollowNotify() == 1;
break;
case 4: // 系统通知
shouldNotify = setting.getSystemNotify() == 1;
break;
default:
shouldNotify = true;
}
if (!shouldNotify) {
return Result.success(); // 用户已关闭该类型通知
}
}
// 创建通知
UserNotification notification = new UserNotification();
notification.setUserId(userId);
notification.setType(type);
notification.setContent(content);
notification.setRelatedId(relatedId);
notification.setIsRead(0);
notification.setCreateTime(LocalDateTime.now());
// 保存通知
save(notification);
return Result.success();
}
@Override
public Result<Page<UserNotificationVO>> getNotificationList(Long userId, Integer type, Page<UserNotification> page) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 构建查询条件
LambdaQueryWrapper<UserNotification> queryWrapper = new LambdaQueryWrapper<UserNotification>()
.eq(UserNotification::getUserId, userId)
.eq(type != null, UserNotification::getType, type)
.orderByDesc(UserNotification::getCreateTime);
// 分页查询
Page<UserNotification> notificationPage = page(page, queryWrapper);
// 转换为VO对象
Page<UserNotificationVO> resultPage = new Page<>();
BeanUtils.copyProperties(notificationPage, resultPage, "records");
// 转换记录
resultPage.setRecords(notificationPage.getRecords().stream().map(this::convertToVO).collect(Collectors.toList()));
return Result.success(resultPage);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> markNotificationRead(Long userId, Long notificationId) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 标记通知为已读
userNotificationMapper.markNotificationRead(userId, notificationId);
return Result.success();
}
@Override
public Result<Integer> getUnreadNotificationCount(Long userId) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 获取未读通知数量
int count = userNotificationMapper.countUnreadNotification(userId);
return Result.success(count);
}
/**
* 将实体对象转换为VO对象
*/
private UserNotificationVO convertToVO(UserNotification notification) {
UserNotificationVO vo = new UserNotificationVO();
BeanUtils.copyProperties(notification, vo);
return vo;
}
}
@@ -0,0 +1,130 @@
package com.nuaa.video.user.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nuaa.video.common.model.enums.ResultCode;
import com.nuaa.video.common.model.vo.Result;
import com.nuaa.video.user.dto.UserSettingDTO;
import com.nuaa.video.user.entity.UserInfo;
import com.nuaa.video.user.entity.UserSetting;
import com.nuaa.video.user.mapper.UserInfoMapper;
import com.nuaa.video.user.mapper.UserSettingMapper;
import com.nuaa.video.user.service.UserSettingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
/**
* 用户设置服务实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserSettingServiceImpl extends ServiceImpl<UserSettingMapper, UserSetting> implements UserSettingService {
private final UserSettingMapper userSettingMapper;
private final UserInfoMapper userInfoMapper;
@Override
public Result<UserSetting> getUserSetting(Long userId) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 查询用户设置
UserSetting setting = getOne(new LambdaQueryWrapper<UserSetting>().eq(UserSetting::getUserId, userId));
// 如果设置不存在,创建默认设置
if (setting == null) {
createDefaultSetting(userId);
setting = getOne(new LambdaQueryWrapper<UserSetting>().eq(UserSetting::getUserId, userId));
}
return Result.success(setting);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> updateUserSetting(Long userId, UserSettingDTO settingDTO) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 查询用户设置
UserSetting setting = getOne(new LambdaQueryWrapper<UserSetting>().eq(UserSetting::getUserId, userId));
// 如果设置不存在,创建默认设置
if (setting == null) {
setting = new UserSetting();
setting.setUserId(userId);
setting.setCreatedBy(userId.toString());
setting.setCreateTime(LocalDateTime.now());
}
// 更新设置
if (settingDTO.getPrivacyLevel() != null) {
setting.setPrivacyLevel(settingDTO.getPrivacyLevel());
}
if (settingDTO.getCommentNotify() != null) {
setting.setCommentNotify(settingDTO.getCommentNotify());
}
if (settingDTO.getLikeNotify() != null) {
setting.setLikeNotify(settingDTO.getLikeNotify());
}
if (settingDTO.getFollowNotify() != null) {
setting.setFollowNotify(settingDTO.getFollowNotify());
}
if (settingDTO.getSystemNotify() != null) {
setting.setSystemNotify(settingDTO.getSystemNotify());
}
setting.setUpdatedBy(userId.toString());
setting.setUpdateTime(LocalDateTime.now());
// 保存设置
saveOrUpdate(setting);
return Result.success();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> createDefaultSetting(Long userId) {
// 验证用户是否存在
UserInfo user = userInfoMapper.selectById(userId);
if (user == null) {
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
}
// 检查是否已存在设置
Long count = count(new LambdaQueryWrapper<UserSetting>().eq(UserSetting::getUserId, userId));
if (count > 0) {
return Result.success(); // 已存在设置,不需要创建
}
// 创建默认设置
UserSetting setting = new UserSetting();
setting.setUserId(userId);
setting.setPrivacyLevel(1); // 公开
setting.setCommentNotify(1); // 开启
setting.setLikeNotify(1); // 开启
setting.setFollowNotify(1); // 开启
setting.setSystemNotify(1); // 开启
setting.setCreatedBy("system");
setting.setUpdatedBy("system");
setting.setCreateTime(LocalDateTime.now());
setting.setUpdateTime(LocalDateTime.now());
// 保存设置
save(setting);
return Result.success();
}
}
@@ -0,0 +1,64 @@
package com.nuaa.video.user.vo;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户私信VO
*/
@Data
public class UserMessageVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 消息ID
*/
private Long id;
/**
* 发送者ID
*/
private Long senderId;
/**
* 发送者昵称
*/
private String senderNickname;
/**
* 发送者头像
*/
private String senderAvatar;
/**
* 接收者ID
*/
private Long receiverId;
/**
* 接收者昵称
*/
private String receiverNickname;
/**
* 接收者头像
*/
private String receiverAvatar;
/**
* 消息内容
*/
private String content;
/**
* 是否已读(0:未读,1:已读)
*/
private Integer isRead;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@@ -0,0 +1,44 @@
package com.nuaa.video.user.vo;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户通知VO
*/
@Data
public class UserNotificationVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 通知ID
*/
private Long id;
/**
* 通知类型(1:点赞,2:评论,3:关注,4:系统通知)
*/
private Integer type;
/**
* 通知内容
*/
private String content;
/**
* 相关ID
*/
private Long relatedId;
/**
* 是否已读(0:未读,1:已读)
*/
private Integer isRead;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@@ -0,0 +1,95 @@
package com.nuaa.video.user.vo;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 用户资料VO
*/
@Data
public class UserProfileVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long id;
/**
* 昵称
*/
private String nickname;
/**
* 头像URL
*/
private String avatar;
/**
* 性别(0:未知,1:男,2:女)
*/
private Integer gender;
/**
* 生日
*/
private LocalDate birthday;
/**
* 个性签名
*/
private String signature;
/**
* 地区
*/
private String region;
/**
* 粉丝数
*/
private Integer followerCount;
/**
* 关注数
*/
private Integer followingCount;
/**
* 视频数
*/
private Integer videoCount;
/**
* 获赞数
*/
private Integer likeCount;
/**
* 等级
*/
private Integer level;
/**
* VIP等级
*/
private Integer vipLevel;
/**
* VIP到期时间
*/
private LocalDateTime vipExpireTime;
/**
* 是否已关注
*/
private Boolean isFollowed;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@@ -0,0 +1,24 @@
spring:
datasource:
dynamic:
primary: master
strict: false
datasource:
master:
url: jdbc:mysql://5.180.253.115:3306/video_user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: sty@G458221
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
database: 1
host: 5.180.253.115
port: 6379
password: redis_EsJ8FX
timeout: 10000
logging:
level:
com.nuaa.video: debug
org.springframework.security: debug
+73 -11
View File
@@ -4,30 +4,92 @@ server:
spring:
application:
name: video-user
profiles:
active: dev
config:
import:
- optional:nacos:${spring.application.name}.yaml
cloud:
nacos:
discovery:
server-addr: 5.180.253.115:8848
namespace: public
group: DEFAULT_GROUP
server-addr: ${NACOS_HOST:5.180.253.115}:${NACOS_PORT:8848}
namespace: ${NACOS_NAMESPACE:public}
group: ${NACOS_GROUP:DEFAULT_GROUP}
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: ${spring.cloud.nacos.discovery.namespace}
group: ${spring.cloud.nacos.discovery.group}
file-extension: yaml
sentinel:
transport:
dashboard: 5.180.253.115:8080
dashboard: ${SENTINEL_DASHBOARD_HOST:5.180.253.115}:${SENTINEL_DASHBOARD_PORT:8080}
port: 8721
eager: true
datasource:
url: jdbc:mysql://5.180.253.115:3306/video_user?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: sty@G458221
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${MYSQL_HOST:5.180.253.115}:${MYSQL_PORT:3306}/video_user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: ${MYSQL_USERNAME:root}
password: ${MYSQL_PASSWORD:sty@G458221}
hikari:
minimum-idle: 5
maximum-pool-size: 20
idle-timeout: 30000
max-lifetime: 1200000
connection-timeout: 30000
connection-test-query: SELECT 1
data:
redis:
host: 5.180.253.115
port: 6379
password: redis_EsJ8FX
host: ${REDIS_HOST:5.180.253.115}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:redis_EsJ8FX}
database: 1
timeout: 10000
lettuce:
pool:
max-active: 200
max-wait: -1
max-idle: 10
min-idle: 0
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.nuaa.video.user.entity
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
cache-enabled: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: is_deleted
logic-delete-value: 1
logic-not-delete-value: 0
auth:
ignore-urls:
- /user/profile/public/**
- /user/profile/init
- /actuator/**
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
com.nuaa.video: info
org.springframework.security: info
# MinIO配置
minio:
endpoint: http://5.180.253.115:9002
accessKey: minioadmin
secretKey: minioadmin
bucketName: video-platform
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.user.mapper.UserFollowMapper">
<!-- 查询用户关注列表 -->
<select id="selectFollowingList" resultType="com.nuaa.video.user.entity.UserInfo">
SELECT u.*
FROM user_follow f
JOIN user_info u ON f.follow_id = u.id
WHERE f.user_id = #{userId}
ORDER BY f.create_time DESC
</select>
<!-- 查询用户粉丝列表 -->
<select id="selectFollowerList" resultType="com.nuaa.video.user.entity.UserInfo">
SELECT u.*
FROM user_follow f
JOIN user_info u ON f.user_id = u.id
WHERE f.follow_id = #{userId}
ORDER BY f.create_time DESC
</select>
<!-- 检查是否已关注 -->
<select id="checkIsFollowed" resultType="int">
SELECT COUNT(1)
FROM user_follow
WHERE user_id = #{userId} AND follow_id = #{followId}
</select>
</mapper>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.user.mapper.UserInfoMapper">
<!-- 增加关注数 -->
<update id="increaseFollowingCount">
UPDATE user_info
SET following_count = following_count + #{count}
WHERE id = #{userId}
</update>
<!-- 增加粉丝数 -->
<update id="increaseFollowerCount">
UPDATE user_info
SET follower_count = follower_count + #{count}
WHERE id = #{userId}
</update>
</mapper>
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.user.mapper.UserMessageMapper">
<!-- 查询私信列表(按会话分组) -->
<select id="selectMessageGroupList" resultType="com.nuaa.video.user.entity.UserMessage">
SELECT
m.*,
u1.nickname as sender_nickname,
u1.avatar as sender_avatar,
u2.nickname as receiver_nickname,
u2.avatar as receiver_avatar
FROM (
SELECT
MAX(id) as id,
IF(sender_id = #{userId}, receiver_id, sender_id) as chat_user_id,
MAX(create_time) as latest_time
FROM
user_message
WHERE
sender_id = #{userId} OR receiver_id = #{userId}
GROUP BY
chat_user_id
ORDER BY
latest_time DESC
) as t
JOIN user_message m ON t.id = m.id
JOIN user_info u1 ON m.sender_id = u1.id
JOIN user_info u2 ON m.receiver_id = u2.id
ORDER BY m.create_time DESC
</select>
<!-- 查询与指定用户的私信记录 -->
<select id="selectMessageDetail" resultType="com.nuaa.video.user.entity.UserMessage">
SELECT
m.*,
u1.nickname as sender_nickname,
u1.avatar as sender_avatar,
u2.nickname as receiver_nickname,
u2.avatar as receiver_avatar
FROM
user_message m
JOIN user_info u1 ON m.sender_id = u1.id
JOIN user_info u2 ON m.receiver_id = u2.id
WHERE
(m.sender_id = #{userId} AND m.receiver_id = #{targetId})
OR
(m.sender_id = #{targetId} AND m.receiver_id = #{userId})
ORDER BY
m.create_time ASC
</select>
<!-- 标记私信为已读 -->
<update id="markMessageRead">
UPDATE user_message
SET is_read = 1
WHERE receiver_id = #{userId} AND sender_id = #{senderId} AND is_read = 0
</update>
<!-- 获取未读私信数量 -->
<select id="countUnreadMessage" resultType="int">
SELECT COUNT(1)
FROM user_message
WHERE receiver_id = #{userId} AND is_read = 0
</select>
</mapper>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.user.mapper.UserNotificationMapper">
<!-- 标记通知为已读 -->
<update id="markNotificationRead">
UPDATE user_notification
SET is_read = 1
WHERE user_id = #{userId}
<if test="notificationId != null">
AND id = #{notificationId}
</if>
AND is_read = 0
</update>
<!-- 获取未读通知数量 -->
<select id="countUnreadNotification" resultType="int">
SELECT COUNT(1)
FROM user_notification
WHERE user_id = #{userId} AND is_read = 0
</select>
</mapper>