Compare commits
7 Commits
d53bc3966c
...
feature-ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39af4e6596 | ||
|
|
ea5c95584d | ||
|
|
e170016019 | ||
|
|
6d95b8d391 | ||
|
|
1c2a4edae9 | ||
|
|
088c94e723 | ||
|
|
a3d0d7423c |
@@ -1,16 +0,0 @@
|
||||
package com.bao.dating.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "aliyun.oss")
|
||||
public class AliyunOSSConfig {
|
||||
private String endpoint;
|
||||
private String accessKeyId;
|
||||
private String accessKeySecret;
|
||||
private String bucketName;
|
||||
|
||||
}
|
||||
@@ -34,8 +34,8 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
"/user/emailLogin",
|
||||
"/api/verification/send-email-code",
|
||||
"/ip/location",
|
||||
"/user/sendCode",
|
||||
"/download/{postId}"
|
||||
"/user/login",
|
||||
"/user/sendCode"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
51
src/main/java/com/bao/dating/controller/AdminController.java
Normal file
51
src/main/java/com/bao/dating/controller/AdminController.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.bao.dating.controller;
|
||||
|
||||
import com.bao.dating.common.Result;
|
||||
import com.bao.dating.common.ResultCode;
|
||||
import com.bao.dating.pojo.dto.UserBanDTO;
|
||||
import com.bao.dating.pojo.entity.UserBan;
|
||||
import com.bao.dating.service.UserBanService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 管理员控制器
|
||||
* @author lenovo
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
public class AdminController {
|
||||
@Autowired
|
||||
private UserBanService userBanService;
|
||||
|
||||
/**
|
||||
* 封禁用户
|
||||
*/
|
||||
@PostMapping("/{userId}/ban")
|
||||
public Result<?> banUser(@PathVariable Long userId,
|
||||
@RequestBody UserBanDTO userBanDTO) {
|
||||
userBanDTO.setUserId(userId);
|
||||
userBanService.banUser(userBanDTO);
|
||||
return Result.success(ResultCode.SUCCESS, "封禁成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解封用户
|
||||
*/
|
||||
@PostMapping("/{userId}/unban")
|
||||
public Result<?> unbanUser(@PathVariable Long userId) {
|
||||
|
||||
userBanService.unbanUser(userId);
|
||||
return Result.success(ResultCode.SUCCESS, "解封成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询封禁状态
|
||||
*/
|
||||
@GetMapping("/{userId}/banInfo")
|
||||
public Result<UserBan> banInfo(@PathVariable Long userId) {
|
||||
|
||||
UserBan ban = userBanService.getActiveBan(userId);
|
||||
return Result.success(ResultCode.SUCCESS, "查询成功", ban);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ package com.bao.dating.controller;
|
||||
import com.bao.dating.context.UserContext;
|
||||
import com.bao.dating.service.ContactsService;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -14,7 +12,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/contacts")
|
||||
public class ContactsController {
|
||||
@Resource
|
||||
private ContactsService contactsService;
|
||||
@@ -45,56 +42,4 @@ public class ContactsController {
|
||||
result.put("data", friends);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 拉黑联系人接口
|
||||
* @param userId 当前用户ID
|
||||
* @param contactUserId 被拉黑联系人ID
|
||||
* @return 接口响应
|
||||
*/
|
||||
@PostMapping("/blacklist/{userId}/{contactUserId}")
|
||||
public ResponseEntity<Map<String, Object>> blacklistContact(
|
||||
@PathVariable Long userId,
|
||||
@PathVariable Long contactUserId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
boolean success = contactsService.blacklistContact(userId, contactUserId);
|
||||
if (success) {
|
||||
result.put("code", 200);
|
||||
result.put("msg", "拉黑联系人成功");
|
||||
result.put("data", null);
|
||||
return ResponseEntity.ok(result);
|
||||
} else {
|
||||
result.put("code", 500);
|
||||
result.put("msg", "拉黑联系人失败,联系人不存在或参数错误");
|
||||
result.put("data", null);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除联系人接口
|
||||
* @param userId 当前用户ID
|
||||
* @param contactUserId 被删除联系人ID
|
||||
* @return 接口响应
|
||||
*/
|
||||
@PostMapping("/delete/{userId}/{contactUserId}")
|
||||
public ResponseEntity<Map<String, Object>> deleteContact(
|
||||
@PathVariable Long userId,
|
||||
@PathVariable Long contactUserId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
boolean success = contactsService.deleteContact(userId, contactUserId);
|
||||
if (success) {
|
||||
result.put("code", 200);
|
||||
result.put("msg", "删除联系人成功");
|
||||
result.put("data", null);
|
||||
return ResponseEntity.ok(result);
|
||||
} else {
|
||||
result.put("code", 500);
|
||||
result.put("msg", "删除联系人失败,联系人不存在或参数错误");
|
||||
result.put("data", null);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -86,20 +83,4 @@ public class PostController {
|
||||
PostEditVO result = postService.updatePost(postId, postRequestDTO);
|
||||
return Result.success(ResultCode.SUCCESS, "动态更新成功", result);
|
||||
}
|
||||
|
||||
@GetMapping("/download/{postId}")
|
||||
public void downloadPostImage(@PathVariable Long postId, HttpServletResponse response) throws Exception {
|
||||
try {
|
||||
//Service 返回已经加好水印的图片
|
||||
BufferedImage image = postService.downloadWithWatermark(postId);
|
||||
//设置响应头,触发浏览器下载
|
||||
response.setContentType("image/jpeg");
|
||||
response.setHeader("Content-Disposition", "attachment;filename=post_" + postId + ".jpg");
|
||||
//输出到浏览器
|
||||
ImageIO.write(image, "jpg", response.getOutputStream());
|
||||
response.getOutputStream().flush();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import com.bao.dating.common.ResultCode;
|
||||
import com.bao.dating.context.UserContext;
|
||||
import com.bao.dating.pojo.dto.UserInfoDTO;
|
||||
import com.bao.dating.pojo.dto.UserLoginDTO;
|
||||
import com.bao.dating.pojo.entity.User;
|
||||
import com.bao.dating.pojo.vo.UserInfoVO;
|
||||
import com.bao.dating.pojo.vo.UserLoginVO;
|
||||
import com.bao.dating.service.UserService;
|
||||
@@ -204,4 +203,15 @@ public class UserController {
|
||||
return Result.success(ResultCode.SUCCESS,"用户登录成功",userLoginVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否在线
|
||||
* @param userId 用户ID
|
||||
* @return 用户是否在线
|
||||
*/
|
||||
@GetMapping("/{userId}/online")
|
||||
public Result<Boolean> isUserOnline(@PathVariable Long userId) {
|
||||
|
||||
boolean online = userService.isUserOnline(userId);
|
||||
return Result.success(ResultCode.SUCCESS, "查询成功", online);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,10 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
}
|
||||
// 从 header 获取 token
|
||||
String token = request.getHeader("token");
|
||||
|
||||
|
||||
try {
|
||||
log.info("jwt校验: {}", token);
|
||||
|
||||
|
||||
// 验证 token 是否有效(包括是否过期)
|
||||
if (!JwtUtil.validateToken(token)) {
|
||||
log.error("Token无效或已过期");
|
||||
@@ -66,10 +66,22 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
response.getWriter().write("登录已失效, 请重新登录");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 解析 token
|
||||
Long userId = Long.valueOf(JwtUtil.getSubjectFromToken(token));
|
||||
|
||||
// 检查用户是否被封禁
|
||||
String banKey = "user:ban:" + userId;
|
||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||
String reason = String.valueOf(redisTemplate.opsForValue().get(banKey));
|
||||
log.error("用户 {} 已被封禁,原因:{}", userId, reason);
|
||||
|
||||
response.setStatus(403);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("账号已被封禁:" + reason);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从Redis获取存储的token进行比对
|
||||
Object redisTokenObj = redisTemplate.opsForValue().get("login:token:" + userId);
|
||||
String redisToken = redisTokenObj != null ? redisTokenObj.toString() : null;
|
||||
|
||||
@@ -73,6 +73,14 @@ public class WsAuthInterceptor implements HandshakeInterceptor {
|
||||
|
||||
Long userId = Long.valueOf(userIdStr);
|
||||
|
||||
// 检查用户是否被封禁
|
||||
String banKey = "user:ban:" + userId;
|
||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||
String reason = String.valueOf(redisTemplate.opsForValue().get(banKey));
|
||||
log.error("WebSocket拒绝:用户 {} 被封禁,原因:{}", userId, reason);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从Redis获取存储的token进行比对
|
||||
String redisTokenKey = "login:token:" + userId;
|
||||
Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey);
|
||||
|
||||
@@ -17,20 +17,4 @@ public interface ContactMapper {
|
||||
*/
|
||||
List<Map<String, Object>> selectFriendsByUserId(@Param("userId") Long userId);
|
||||
|
||||
|
||||
/**
|
||||
* 拉黑联系人
|
||||
* @param userId 当前用户ID
|
||||
* @param contactUserId 被拉黑的联系人ID
|
||||
* @return 影响行数
|
||||
*/
|
||||
int blacklistContact(@Param("userId") Long userId, @Param("contactUserId") Long contactUserId);
|
||||
|
||||
/**
|
||||
* 删除联系人(逻辑删除)
|
||||
* @param userId 当前用户ID
|
||||
* @param contactUserId 被删除的联系人ID
|
||||
* @return 影响行数
|
||||
*/
|
||||
int deleteContact(@Param("userId") Long userId, @Param("contactUserId") Long contactUserId);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态Mapper
|
||||
@@ -118,12 +117,4 @@ public interface PostMapper {
|
||||
* @return 影响行数
|
||||
*/
|
||||
int decreaseFavoriteCount(Long postId);
|
||||
|
||||
/**
|
||||
* 根据动态id查询用户名和媒体信息
|
||||
*
|
||||
* @param postId 动态id
|
||||
* @return 用户名和媒体信息
|
||||
*/
|
||||
Map<String, Object> getUsernameByUserId(Long postId);
|
||||
}
|
||||
|
||||
43
src/main/java/com/bao/dating/mapper/UserBanMapper.java
Normal file
43
src/main/java/com/bao/dating/mapper/UserBanMapper.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.bao.dating.mapper;
|
||||
|
||||
import com.bao.dating.pojo.entity.UserBan;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface UserBanMapper {
|
||||
|
||||
/**
|
||||
* 新增封禁记录
|
||||
* @param userBan 封禁记录
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insertBan(UserBan userBan);
|
||||
|
||||
/**
|
||||
* 查询是否存在生效中的封禁
|
||||
* @param userId 用户ID
|
||||
* @return 存在返回1,不存在返回0
|
||||
*/
|
||||
int existsActiveBan(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 查询生效中的封禁记录
|
||||
* @param userId 用户ID
|
||||
* @return 封禁记录
|
||||
*/
|
||||
UserBan selectActiveBan(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 解封用户
|
||||
* @param userId 用户ID
|
||||
* @return 影响行数
|
||||
*/
|
||||
int unbanUser(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 定时任务:过期自动解封
|
||||
* @return 影响行数
|
||||
*/
|
||||
int updateExpiredBans();
|
||||
}
|
||||
13
src/main/java/com/bao/dating/pojo/dto/UserBanDTO.java
Normal file
13
src/main/java/com/bao/dating/pojo/dto/UserBanDTO.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.bao.dating.pojo.dto;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户封禁数据传输对象
|
||||
* @author KilLze
|
||||
*/
|
||||
@Data
|
||||
public class UserBanDTO {
|
||||
private Long userId;
|
||||
private String reason;
|
||||
private Integer banDays;
|
||||
}
|
||||
30
src/main/java/com/bao/dating/pojo/entity/UserBan.java
Normal file
30
src/main/java/com/bao/dating/pojo/entity/UserBan.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.bao.dating.pojo.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户封禁记录
|
||||
* @author KilLze
|
||||
*/
|
||||
@Data
|
||||
public class UserBan {
|
||||
|
||||
private Long id;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String reason;
|
||||
|
||||
private LocalDateTime banStartTime;
|
||||
|
||||
private LocalDateTime banEndTime;
|
||||
|
||||
/**
|
||||
* 1:封禁中 0:已解封
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -29,4 +29,6 @@ public class ChatSessionsVO {
|
||||
private Integer topStatus;
|
||||
/** 免打扰状态 */
|
||||
private Integer muteStatus;
|
||||
/** 会话状态 */
|
||||
private Boolean online;
|
||||
}
|
||||
|
||||
@@ -26,4 +26,5 @@ public class UserInfoVO implements Serializable {
|
||||
private LocalDateTime createdAt;
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
private Boolean online;
|
||||
}
|
||||
|
||||
@@ -15,20 +15,6 @@ public interface ContactsService {
|
||||
List<Map<String, Object>> getFriendsByUserId(Long userId);
|
||||
|
||||
|
||||
/**
|
||||
* 拉黑联系人
|
||||
* @param userId 当前用户ID
|
||||
* @param contactUserId 被拉黑联系人ID
|
||||
* @return 操作是否成功
|
||||
*/
|
||||
boolean blacklistContact(Long userId, Long contactUserId);
|
||||
|
||||
/**
|
||||
* 删除联系人(逻辑删除)
|
||||
* @param userId 当前用户ID
|
||||
* @param contactUserId 被删除联系人ID
|
||||
* @return 操作是否成功
|
||||
*/
|
||||
boolean deleteContact(Long userId, Long contactUserId);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.bao.dating.pojo.entity.Post;
|
||||
import com.bao.dating.pojo.vo.PostEditVO;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -56,11 +55,4 @@ public interface PostService {
|
||||
* @return 用户id
|
||||
*/
|
||||
Long selectUserIdByPostId(Long postId);
|
||||
|
||||
/**
|
||||
* 下载动态图片并添加水印
|
||||
* @param postId 动态ID
|
||||
* @return 带水印的图片
|
||||
*/
|
||||
BufferedImage downloadWithWatermark(Long postId) throws Exception;
|
||||
}
|
||||
31
src/main/java/com/bao/dating/service/UserBanService.java
Normal file
31
src/main/java/com/bao/dating/service/UserBanService.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.bao.dating.service;
|
||||
|
||||
import com.bao.dating.pojo.dto.UserBanDTO;
|
||||
import com.bao.dating.pojo.entity.UserBan;
|
||||
|
||||
/**
|
||||
* 用户封禁服务接口
|
||||
* @author KilLze
|
||||
*/
|
||||
public interface UserBanService {
|
||||
|
||||
/**
|
||||
* 封禁用户
|
||||
* @param userBanDTO 用户封禁信息
|
||||
*
|
||||
*/
|
||||
void banUser(UserBanDTO userBanDTO);
|
||||
|
||||
/**
|
||||
* 解封用户
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
void unbanUser(Long userId);
|
||||
|
||||
/**
|
||||
* 查询封禁信息
|
||||
* @param userId 用户ID
|
||||
* @return 封禁信息
|
||||
*/
|
||||
UserBan getActiveBan(Long userId);
|
||||
}
|
||||
@@ -92,4 +92,11 @@ public interface UserService {
|
||||
* @return 用户列表
|
||||
*/
|
||||
List<UserInfoVO> findNearbyUsers(double lat,double lng,double radiusKm);
|
||||
|
||||
/**
|
||||
* 判断用户是否在线
|
||||
* @param userId 用户ID
|
||||
* @return 是否在线
|
||||
*/
|
||||
boolean isUserOnline(Long userId);
|
||||
}
|
||||
|
||||
@@ -236,6 +236,7 @@ public class ChatServiceImpl implements ChatService {
|
||||
vo.setSessionName("用户" + session.getTargetUserId());
|
||||
vo.setAvatarUrl(null);
|
||||
}
|
||||
vo.setOnline(userService.isUserOnline(vo.getTargetUserId()));
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.bao.dating.service.ContactsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -20,34 +19,4 @@ public class ContactServiceImpl implements ContactsService {
|
||||
// 直接调用Mapper查询,无额外封装
|
||||
return contactMapper.selectFriendsByUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉黑联系人
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean blacklistContact(Long userId, Long contactUserId) {
|
||||
// 参数校验
|
||||
if (userId == null || contactUserId == null || userId.equals(contactUserId)) {
|
||||
return false;
|
||||
}
|
||||
// 执行拉黑操作
|
||||
int affectRows = contactMapper.blacklistContact(userId, contactUserId);
|
||||
return affectRows > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除联系人
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteContact(Long userId, Long contactUserId) {
|
||||
// 参数校验
|
||||
if (userId == null || contactUserId == null || userId.equals(contactUserId)) {
|
||||
return false;
|
||||
}
|
||||
// 执行删除操作
|
||||
int affectRows = contactMapper.deleteContact(userId, contactUserId);
|
||||
return affectRows > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,11 @@ import com.bao.dating.mapper.PostLikeMapper;
|
||||
import com.bao.dating.mapper.PostMapper;
|
||||
import com.bao.dating.pojo.dto.PostRequestDTO;
|
||||
import com.bao.dating.pojo.entity.Post;
|
||||
import com.bao.dating.pojo.entity.User;
|
||||
import com.bao.dating.pojo.vo.PostEditVO;
|
||||
import com.bao.dating.service.PostService;
|
||||
import com.bao.dating.common.aliyun.AliOssUtil;
|
||||
import com.bao.dating.service.UserService;
|
||||
import com.bao.dating.util.FileUtil;
|
||||
import com.bao.dating.util.WatermarkUtil;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -24,7 +22,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -60,9 +57,6 @@ public class PostServiceImpl implements PostService {
|
||||
@Autowired
|
||||
private CommentsMapper commentsMapper;
|
||||
|
||||
@Autowired
|
||||
private WatermarkUtil watermarkUtil;
|
||||
|
||||
/**
|
||||
* 上传媒体文件
|
||||
* @param files 媒体文件数组
|
||||
@@ -350,61 +344,4 @@ public class PostServiceImpl implements PostService {
|
||||
return postMapper.selectUserIdByPostId(postId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载动态图片并添加水印
|
||||
*
|
||||
* @param postId 动态ID
|
||||
* @return 带水印的图片
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public BufferedImage downloadWithWatermark(Long postId) throws Exception {
|
||||
// 一次性查出 动态图片 + 作者信息
|
||||
Map<String, Object> map = postMapper.getUsernameByUserId(postId);
|
||||
|
||||
if (map == null || map.isEmpty()) {
|
||||
Post post = postMapper.selectById(postId);
|
||||
if (post == null) {
|
||||
throw new RuntimeException("未找到指定postId的帖子: " + postId);
|
||||
}
|
||||
throw new RuntimeException("未找到与postId相关的用户和媒体信息: " + postId);
|
||||
}
|
||||
|
||||
String mediaUrl = (String) map.get("media_oss_keys");
|
||||
String username = (String) map.get("user_name");
|
||||
Object userIdObj = map.get("user_id");
|
||||
|
||||
if (mediaUrl == null || username == null || userIdObj == null) {
|
||||
throw new RuntimeException("用户或媒体信息不完整: " + map);
|
||||
}
|
||||
|
||||
mediaUrl = mediaUrl.trim();
|
||||
if (mediaUrl.isEmpty()) {
|
||||
throw new RuntimeException("媒体URL为空,postId: " + postId);
|
||||
}
|
||||
|
||||
Long userId = userIdObj instanceof Number
|
||||
? ((Number) userIdObj).longValue()
|
||||
: Long.valueOf(userIdObj.toString());
|
||||
|
||||
// 解析 OSS ObjectKey(支持完整URL和直接存key两种)
|
||||
String cleanUrl = mediaUrl.split("\\?")[0]; // 去掉 ? 后面的参数
|
||||
String objectKey;
|
||||
|
||||
if (cleanUrl.startsWith("http")) {
|
||||
// https://xxx.oss-cn-xxx.aliyuncs.com/post/xxx.jpg → post/xxx.jpg
|
||||
objectKey = cleanUrl.substring(cleanUrl.indexOf(".com/") + 5);
|
||||
} else {
|
||||
objectKey = cleanUrl;
|
||||
}
|
||||
|
||||
if (objectKey.trim().isEmpty()) {
|
||||
throw new RuntimeException("解析后的ObjectKey为空,url: " + mediaUrl);
|
||||
}
|
||||
|
||||
// 下载并动态加水印(只给下载的人看,OSS原图不改,数据库不动)
|
||||
return watermarkUtil.downloadAndWatermark(objectKey, username, userId);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.bao.dating.service.impl;
|
||||
|
||||
import com.bao.dating.mapper.UserBanMapper;
|
||||
import com.bao.dating.pojo.dto.UserBanDTO;
|
||||
import com.bao.dating.pojo.entity.UserBan;
|
||||
import com.bao.dating.service.UserBanService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class UserBanServiceImpl implements UserBanService {
|
||||
|
||||
@Autowired
|
||||
private UserBanMapper userBanMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Override
|
||||
public void banUser(UserBanDTO userBanDTO) {
|
||||
|
||||
// 已被封禁,直接拒绝
|
||||
if (userBanMapper.existsActiveBan(userBanDTO.getUserId()) > 0) {
|
||||
throw new RuntimeException("用户已处于封禁状态");
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime endTime = userBanDTO.getBanDays() == null ? null : now.plusDays(userBanDTO.getBanDays());
|
||||
|
||||
// 1. 写数据库
|
||||
UserBan ban = new UserBan();
|
||||
ban.setUserId(userBanDTO.getUserId());
|
||||
ban.setReason(userBanDTO.getReason());
|
||||
ban.setBanStartTime(now);
|
||||
ban.setBanEndTime(endTime);
|
||||
ban.setStatus(1);
|
||||
userBanMapper.insertBan(ban);
|
||||
|
||||
// 2. 写 Redis
|
||||
String key = "user:ban:" + userBanDTO.getUserId();
|
||||
if (userBanDTO.getBanDays() == null) {
|
||||
redisTemplate.opsForValue().set(key, userBanDTO.getReason());
|
||||
} else {
|
||||
redisTemplate.opsForValue().set(key, userBanDTO.getReason(), userBanDTO.getBanDays(), TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
// 3. 踢下线
|
||||
redisTemplate.delete("login:token:" + userBanDTO.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解封用户
|
||||
*/
|
||||
@Override
|
||||
public void unbanUser(Long userId) {
|
||||
// 更新数据库
|
||||
userBanMapper.unbanUser(userId);
|
||||
|
||||
// 删除 Redis
|
||||
redisTemplate.delete("user:ban:" + userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户封禁信息
|
||||
*/
|
||||
@Override
|
||||
public UserBan getActiveBan(Long userId) {
|
||||
return userBanMapper.selectActiveBan(userId);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import com.bao.dating.util.CodeUtil;
|
||||
import com.bao.dating.util.FileUtil;
|
||||
import com.bao.dating.util.JwtUtil;
|
||||
import com.bao.dating.util.MD5Util;
|
||||
import com.bao.dating.util.UserBanUtil;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -66,6 +67,9 @@ public class UserServiceImpl implements UserService {
|
||||
@Autowired
|
||||
private VerificationCodeService verificationCodeService;
|
||||
|
||||
@Autowired
|
||||
private UserBanUtil userBanValidator;
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
@@ -92,6 +96,8 @@ public class UserServiceImpl implements UserService {
|
||||
if (!match) {
|
||||
throw new RuntimeException("密码错误");
|
||||
}
|
||||
// 用户封禁验证
|
||||
userBanValidator.validateUserNotBanned(user.getUserId());
|
||||
// 生成token
|
||||
String token = JwtUtil.generateToken(String.valueOf(user.getUserId()));
|
||||
|
||||
@@ -118,6 +124,9 @@ public class UserServiceImpl implements UserService {
|
||||
@Override
|
||||
public void logout(String token) {
|
||||
Claims claims = JwtUtil.getClaimsFromToken(token);
|
||||
// 获取token信息
|
||||
String subject = claims.getSubject();
|
||||
// 获取token的过期时间
|
||||
Date expiration = claims.getExpiration();
|
||||
// 判断 token 是否已过期
|
||||
long ttl = expiration.getTime() - System.currentTimeMillis();
|
||||
@@ -126,6 +135,10 @@ public class UserServiceImpl implements UserService {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从Redis中删除登录token记录
|
||||
String loginTokenKey = "login:token:" + subject;
|
||||
redisTemplate.delete(loginTokenKey);
|
||||
|
||||
String logoutKey = "jwt:blacklist:" + token;
|
||||
redisTemplate.opsForValue().set(
|
||||
logoutKey,
|
||||
@@ -485,4 +498,23 @@ public class UserServiceImpl implements UserService {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserOnline(Long userId) {
|
||||
if (userId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. 是否被封禁
|
||||
String banKey = "user:ban:" + userId;
|
||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 是否存在登录 token
|
||||
String tokenKey = "login:token:" + userId;
|
||||
Boolean online = redisTemplate.hasKey(tokenKey);
|
||||
|
||||
return Boolean.TRUE.equals(online);
|
||||
}
|
||||
}
|
||||
26
src/main/java/com/bao/dating/task/UserBanScheduleTask.java
Normal file
26
src/main/java/com/bao/dating/task/UserBanScheduleTask.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.bao.dating.task;
|
||||
|
||||
import com.bao.dating.mapper.UserBanMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@EnableScheduling
|
||||
public class UserBanScheduleTask {
|
||||
|
||||
@Autowired
|
||||
private UserBanMapper userBanMapper;
|
||||
|
||||
/**
|
||||
* 每天凌晨 3 点同步过期封禁
|
||||
*/
|
||||
@Scheduled(cron = "0 0 3 * * ?")
|
||||
public void syncExpiredUserBan() {
|
||||
int rows = userBanMapper.updateExpiredBans();
|
||||
log.info("封禁同步任务执行完成,解封 {} 个用户", rows);
|
||||
}
|
||||
}
|
||||
65
src/main/java/com/bao/dating/util/UserBanUtil.java
Normal file
65
src/main/java/com/bao/dating/util/UserBanUtil.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package com.bao.dating.util;
|
||||
|
||||
import com.bao.dating.context.UserContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 用户封禁验证工具类
|
||||
* 提供统一的用户封禁状态检查功能
|
||||
*
|
||||
* @author KilLze
|
||||
*/
|
||||
@Component
|
||||
public class UserBanUtil {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 验证指定用户是否被封禁
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @throws RuntimeException 如果用户被封禁则抛出异常
|
||||
*/
|
||||
public void validateUserNotBanned(Long userId) {
|
||||
String banKey = "user:ban:" + userId;
|
||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||
String reason = (String) redisTemplate.opsForValue().get(banKey);
|
||||
|
||||
// 获取剩余过期时间(秒)
|
||||
Long ttlSeconds = redisTemplate.getExpire(banKey, TimeUnit.SECONDS);
|
||||
String remainingTime = "";
|
||||
if (ttlSeconds != null && ttlSeconds > 0) {
|
||||
long days = ttlSeconds / (24 * 3600);
|
||||
long hours = (ttlSeconds % (24 * 3600)) / 3600;
|
||||
long minutes = (ttlSeconds % 3600) / 60;
|
||||
|
||||
if (days > 0) {
|
||||
remainingTime = ",剩余时间:" + days + "天" + hours + "小时";
|
||||
} else if (hours > 0) {
|
||||
remainingTime = ",剩余时间:" + hours + "小时" + minutes + "分钟";
|
||||
} else {
|
||||
remainingTime = ",剩余时间:" + minutes + "分钟";
|
||||
}
|
||||
} else {
|
||||
remainingTime = ",永久封禁";
|
||||
}
|
||||
|
||||
throw new RuntimeException("账号已被封禁,原因:" + reason + remainingTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证当前登录用户是否被封禁
|
||||
*
|
||||
* @throws RuntimeException 如果用户被封禁则抛出异常
|
||||
*/
|
||||
public void validateCurrentUserNotBanned() {
|
||||
Long userId = UserContext.getUserId();
|
||||
validateUserNotBanned(userId);
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.bao.dating.util;
|
||||
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.OSSClientBuilder;
|
||||
import com.bao.dating.config.AliyunOSSConfig;
|
||||
import com.bao.dating.mapper.PostMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStream;
|
||||
|
||||
@Component
|
||||
public class WatermarkUtil {
|
||||
|
||||
@Autowired
|
||||
private AliyunOSSConfig aliyunOSSConfig;
|
||||
|
||||
|
||||
public BufferedImage downloadAndWatermark(String objectKey, String username, Long userId) throws Exception {
|
||||
OSS ossClient = new OSSClientBuilder().build(
|
||||
aliyunOSSConfig.getEndpoint(),
|
||||
aliyunOSSConfig.getAccessKeyId(),
|
||||
aliyunOSSConfig.getAccessKeySecret()
|
||||
);
|
||||
|
||||
InputStream inputStream = ossClient.getObject(aliyunOSSConfig.getBucketName(), objectKey).getObjectContent();
|
||||
BufferedImage image = ImageIO.read(inputStream);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
|
||||
// 字体小一点
|
||||
Font font = new Font("微软雅黑", Font.BOLD, 24);
|
||||
g2d.setFont(font);
|
||||
|
||||
String text = "作者:" + username + " (ID:" + userId + ")";
|
||||
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
int textWidth = fm.stringWidth(text);
|
||||
int textHeight = fm.getHeight();
|
||||
|
||||
// 右下角留边距
|
||||
int x = image.getWidth() - textWidth - 20;
|
||||
int y = image.getHeight() - 20;
|
||||
|
||||
// 黑色描边
|
||||
g2d.setColor(Color.BLACK);
|
||||
g2d.drawString(text, x - 1, y - 1);
|
||||
g2d.drawString(text, x + 1, y - 1);
|
||||
g2d.drawString(text, x - 1, y + 1);
|
||||
g2d.drawString(text, x + 1, y + 1);
|
||||
|
||||
// 白色正文
|
||||
g2d.setColor(Color.WHITE);
|
||||
g2d.drawString(text, x, y);
|
||||
|
||||
g2d.dispose();
|
||||
ossClient.shutdown();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -21,29 +21,5 @@
|
||||
AND c.relation_type != 3 -- 排除黑名单
|
||||
AND c.user_id != c.contact_user_id -- 排除自己
|
||||
</select>
|
||||
|
||||
|
||||
<!-- 拉黑联系人:更新relation_type为3(黑名单)、contact_status为3(已拉黑) -->
|
||||
<update id="blacklistContact">
|
||||
UPDATE contacts
|
||||
SET relation_type = 3,
|
||||
contact_status = 3,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE user_id = #{userId}
|
||||
AND contact_user_id = #{contactUserId}
|
||||
AND contact_status != 2
|
||||
</update>
|
||||
|
||||
<!-- 删除联系人:更新contact_status为2(已删除) -->
|
||||
<update id="deleteContact">
|
||||
UPDATE contacts
|
||||
SET contact_status = 2,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE user_id = #{userId}
|
||||
AND contact_user_id = #{contactUserId}
|
||||
AND contact_status != 2
|
||||
</update>
|
||||
</mapper>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -135,11 +135,5 @@
|
||||
<select id="selectFavoriteCount" resultType="java.lang.Integer">
|
||||
select dating.post.favorite_count from dating.post where post.post_id = #{postId}
|
||||
</select>
|
||||
<select id="getUsernameByUserId" resultType="map">
|
||||
SELECT u.user_name, u.user_id, p.media_oss_keys
|
||||
FROM post p
|
||||
LEFT JOIN user u ON p.user_id = u.user_id
|
||||
WHERE p.post_id = #{postId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
50
src/main/resources/com/bao/dating/mapper/UserBanMapper.xml
Normal file
50
src/main/resources/com/bao/dating/mapper/UserBanMapper.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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.bao.dating.mapper.UserBanMapper">
|
||||
|
||||
<!-- 向数据库中添加用户封禁信息 -->
|
||||
<insert id="insertBan" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO user_ban
|
||||
(user_id, reason, ban_start_time, ban_end_time, status)
|
||||
VALUES
|
||||
(#{userId}, #{reason}, #{banStartTime}, #{banEndTime}, #{status})
|
||||
</insert>
|
||||
|
||||
<!-- 查询指定用户是否存在未过期的封禁信息 -->
|
||||
<select id="existsActiveBan" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM user_ban
|
||||
WHERE user_id = #{userId}
|
||||
AND status = 1
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 查询指定用户是否存在未过期的封禁信息 -->
|
||||
<select id="selectActiveBan" resultType="com.bao.dating.pojo.entity.UserBan">
|
||||
SELECT *
|
||||
FROM user_ban
|
||||
WHERE user_id = #{userId}
|
||||
AND status = 1
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 解封指定用户 -->
|
||||
<update id="unbanUser">
|
||||
UPDATE user_ban
|
||||
SET status = 0
|
||||
WHERE user_id = #{userId}
|
||||
AND status = 1
|
||||
</update>
|
||||
|
||||
<!-- 批量更新已过期的封禁信息 -->
|
||||
<update id="updateExpiredBans">
|
||||
UPDATE user_ban
|
||||
SET status = 0
|
||||
WHERE status = 1
|
||||
AND ban_end_time IS NOT NULL
|
||||
AND ban_end_time < NOW()
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -111,9 +111,9 @@
|
||||
<result property="latitude" column="user_latitude"/>
|
||||
<result property="longitude" column="user_longitude"/>
|
||||
</resultMap>
|
||||
|
||||
|
||||
<select id="findByLatLngRange" resultMap="UserInfoVOResultMap">
|
||||
SELECT
|
||||
SELECT
|
||||
user_id,
|
||||
user_name,
|
||||
nickname,
|
||||
|
||||
Reference in New Issue
Block a user