4 Commits

Author SHA1 Message Date
34392f137f 完成用户对用户登录和发布动态的数据分析 2026-01-12 19:35:52 +08:00
KilLze
4f94c43f94 修bug 2026-01-11 00:57:29 +08:00
KilLze
7a8a03510c 修bug 2026-01-11 00:28:23 +08:00
KilLze
2189575659 修bug 2026-01-11 00:25:19 +08:00
9 changed files with 214 additions and 131 deletions

View File

@@ -0,0 +1,66 @@
package com.bao.dating.controller;
import com.bao.dating.common.Result;
import com.bao.dating.common.ResultCode;
import com.bao.dating.pojo.vo.DataAnalysisVO;
import com.bao.dating.util.DataAnalysisRedisService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
@RestController
@RequestMapping("/dataAnalysis")
public class DataAnalysisController {
@Resource
private DataAnalysisRedisService dataAnalysisRedisService;
/**
* 统计动态
* @param days 统计时常
* @return result
*/
@GetMapping("/postTotal")
public Result<DataAnalysisVO> postDataAnalysis(@RequestParam Integer days){
if (checkParam(days)){
return Result.error(ResultCode.PARAM_ERROR);
}
Long count = dataAnalysisRedisService.countPostTotalByDays(days);
String timePeriod = dataAnalysisRedisService.calculateTimeRanges(LocalDateTime.now(), days);
DataAnalysisVO dataAnalysisVO = new DataAnalysisVO();
dataAnalysisVO.setTotal(count);
dataAnalysisVO.setTimePeriod(timePeriod);
return Result.success(ResultCode.SUCCESS,dataAnalysisVO);
}
@GetMapping("/loginUserTotal")
public Result<DataAnalysisVO> loginUserDataAnalysis(@RequestParam Integer days){
if (checkParam(days)){
return Result.error(ResultCode.PARAM_ERROR);
}
Long count = dataAnalysisRedisService.countLoginUserTotalByDays(days);
String timePeriod = dataAnalysisRedisService.calculateTimeRanges(LocalDateTime.now(), days);
DataAnalysisVO dataAnalysisVO = new DataAnalysisVO();
dataAnalysisVO.setTotal(count);
dataAnalysisVO.setTimePeriod(timePeriod);
return Result.success(ResultCode.SUCCESS,dataAnalysisVO);
}
/**
* 检验参数
* @param days 查询天数
* @return 参数是否合法
*/
public Boolean checkParam(Integer days){
ArrayList<Integer> day = new ArrayList<>();
day.add(7);
day.add(15);
day.add(30);
day.add(365);
return days != null && day.contains(days);
}
}

View File

@@ -1,17 +1,17 @@
package com.bao.dating.controller;
import com.bao.dating.anno.Log;
import com.bao.dating.common.Result;
import com.bao.dating.common.ResultCode;
import com.bao.dating.pojo.dto.PostRequestDTO;
import com.bao.dating.pojo.entity.Post;
import com.bao.dating.pojo.vo.PostEditVO;
import com.bao.dating.service.PostService;
import com.bao.dating.util.DataAnalysisRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
/**
@@ -26,6 +26,9 @@ public class PostController {
@Autowired
private PostService postService;
@Resource
private DataAnalysisRedisService dataAnalysisRedisService;
/**
* 上传媒体文件接口 like
* @param files 媒体文件数组
@@ -46,6 +49,9 @@ public class PostController {
public Result<Post> createPostJson(@RequestBody PostRequestDTO postDTO) {
// 调用 Service 层处理发布动态业务逻辑
Post result = postService.createPost(postDTO);
//这里将发布动态操作记录到redis中
Long postId = result.getPostId();
dataAnalysisRedisService.recordPostData(postId);
return Result.success(ResultCode.SUCCESS, "动态发布成功,等待审核。", result);
}

View File

@@ -4,17 +4,18 @@ import com.bao.dating.anno.Log;
import com.bao.dating.common.Result;
import com.bao.dating.common.ResultCode;
import com.bao.dating.context.UserContext;
import com.bao.dating.pojo.dto.UserInfoUpdateDTO;
import com.bao.dating.pojo.dto.UserLoginDTO;
import com.bao.dating.pojo.dto.UserInfoDTO;
import com.bao.dating.pojo.dto.UserLoginDTO;
import com.bao.dating.pojo.vo.UserInfoVO;
import com.bao.dating.pojo.vo.UserLoginVO;
import com.bao.dating.service.UserService;
import com.bao.dating.util.DataAnalysisRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@@ -30,6 +31,9 @@ public class UserController {
@Autowired
private UserService userService;
@Resource
private DataAnalysisRedisService dataAnalysisRedisService;
/**
* 登录
* @param userLoginDTO 登录参数
@@ -37,6 +41,12 @@ public class UserController {
@PostMapping("/login")
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {
UserLoginVO userloginVO = userService.userLogin(userLoginDTO);
//将用户登录记录到redis中
if (userloginVO != null){
Long userId = userloginVO.getUserId();
dataAnalysisRedisService.recordLoginUserData(userId);
}
return Result.success(ResultCode.SUCCESS, "登录成功", userloginVO);
}
@@ -93,7 +103,7 @@ public class UserController {
*/
@Log
@PostMapping("/info/update")
public Result<UserInfoVO> userInfoUpdate(@RequestBody UserInfoUpdateDTO userInfoUpdateDTO) {
public Result<UserInfoVO> userInfoUpdate(@RequestBody UserInfoDTO userInfoUpdateDTO) {
Long userId = UserContext.getUserId();
userInfoUpdateDTO.setUserId(userId);
UserInfoVO userInfoVO =userService.updateUserInfo(userInfoUpdateDTO);
@@ -129,6 +139,11 @@ public class UserController {
}
//登录
UserLoginVO vo = userService.loginByPhone(phone);
//将用户登录操作记录到redis中
if (vo != null){
Long userId = vo.getUserId();
dataAnalysisRedisService.recordLoginUserData(userId);
}
return Result.success(ResultCode.SUCCESS, "登录成功", vo);
}
@@ -201,6 +216,9 @@ public class UserController {
if (userLoginVO == null){
return Result.error(ResultCode.FAIL,"请先注册用户或添加邮箱");
}
//将用户登录操作记录到redis中
Long userId = userLoginVO.getUserId();
dataAnalysisRedisService.recordLoginUserData(userId);
return Result.success(ResultCode.SUCCESS,"用户登录成功",userLoginVO);
}

View File

@@ -1,4 +0,0 @@
package com.bao.dating.controller;
public class text {
}

View File

@@ -1,25 +0,0 @@
package com.bao.dating.pojo.dto;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* 用户信息更新数据传输对象
* @author KilLze
*/
@Data
public class UserInfoUpdateDTO {
private Long userId;
private String userName;
private String nickname;
private String avatarUrl;
private String backgroundUrl;
private Integer gender;
private LocalDate birthday;
private List<String> hobbies;
private String signature;
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,11 @@
package com.bao.dating.pojo.vo;
import lombok.Data;
@Data
public class DataAnalysisVO {
//所有总数
private Long total;
//时间段
private String timePeriod;
}

View File

@@ -1,9 +1,7 @@
package com.bao.dating.service;
import com.bao.dating.pojo.dto.UserInfoDTO;
import com.bao.dating.pojo.dto.UserInfoUpdateDTO;
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 org.springframework.web.multipart.MultipartFile;
@@ -79,7 +77,6 @@ public interface UserService {
* @return 用户
*/
UserInfoDTO getUserInfoById(Long userId);
UserInfoVO updateUserInfo(UserInfoUpdateDTO userInfoUpdateDTO);
void sendSmsCode(String phone);

View File

@@ -1,18 +1,14 @@
package com.bao.dating.service.impl;
import com.bao.dating.common.Result;
import com.bao.dating.common.ResultCode;
import com.bao.dating.common.aliyun.AliOssUtil;
import com.bao.dating.common.aliyun.GreenImageScan;
import com.bao.dating.common.aliyun.GreenTextScan;
import com.bao.dating.common.aliyun.SmsUtil;
import com.bao.dating.common.result.AliOssResult;
import com.bao.dating.common.result.GreenAuditResult;
import com.bao.dating.config.RedisConfig;
import com.bao.dating.context.UserContext;
import com.bao.dating.mapper.UserMapper;
import com.bao.dating.pojo.dto.UserInfoDTO;
import com.bao.dating.pojo.dto.UserInfoUpdateDTO;
import com.bao.dating.pojo.dto.UserLoginDTO;
import com.bao.dating.pojo.entity.User;
import com.bao.dating.pojo.vo.UserInfoVO;
@@ -28,6 +24,7 @@ import io.jsonwebtoken.Claims;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@@ -243,100 +240,13 @@ public class UserServiceImpl implements UserService {
}
/**
* 更新用户信息
* @param userInfoUpdateDTO
* @return
*/
@Override
public UserInfoVO updateUserInfo(UserInfoUpdateDTO userInfoUpdateDTO) {
Long userId = userInfoUpdateDTO.getUserId();
User user = userMapper.selectByUserId(userId);
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 将需要审核的内容合并成一个文本,用于减少调用次数
StringBuilder textBuilder = new StringBuilder();
if (userInfoUpdateDTO.getNickname() != null && !userInfoUpdateDTO.getNickname().isEmpty()) {
textBuilder.append(userInfoUpdateDTO.getNickname()).append(" ");
}
if (userInfoUpdateDTO.getHobbies() != null && !userInfoUpdateDTO.getHobbies().isEmpty()) {
// 将爱好列表转换为字符串,用空格分隔
String hobbiesStr = String.join(" ", userInfoUpdateDTO.getHobbies());
textBuilder.append(hobbiesStr).append(" ");
}
if (userInfoUpdateDTO.getSignature() != null && !userInfoUpdateDTO.getSignature().isEmpty()) {
textBuilder.append(userInfoUpdateDTO.getSignature()).append(" ");
}
// 文本审核
if (textBuilder.length() > 0) {
Map textResult;
try {
textResult = greenTextScan.greeTextScan(textBuilder.toString());
} catch (Exception e) {
throw new RuntimeException("用户信息文本审核失败");
}
String suggestion = (String) textResult.get("suggestion");
if (GreenAuditResult.BLOCK.equals(suggestion)) {
throw new RuntimeException("用户信息包含违规内容,修改失败");
}
if (GreenAuditResult.REVIEW.equals(suggestion)) {
throw new RuntimeException("用户信息需要人工审核,暂无法修改");
}
}
// 图片审核
List<String> imageKeys = new ArrayList<>();
if (userInfoUpdateDTO.getAvatarUrl() != null && !userInfoUpdateDTO.getAvatarUrl().isEmpty()) {
imageKeys.add(userInfoUpdateDTO.getAvatarUrl());
}
if (userInfoUpdateDTO.getBackgroundUrl() != null && !userInfoUpdateDTO.getBackgroundUrl().isEmpty()) {
imageKeys.add(userInfoUpdateDTO.getBackgroundUrl());
}
if (!imageKeys.isEmpty()) {
Map imageResult;
try {
imageResult = greenImageScan.imageScan(imageKeys);
} catch (Exception e) {
throw new RuntimeException("用户图片审核失败");
}
String suggestion = (String) imageResult.get("suggestion");
if (GreenAuditResult.BLOCK.equals(suggestion)) {
throw new RuntimeException("头像或背景图不合规,修改失败");
}
if (GreenAuditResult.REVIEW.equals(suggestion)) {
throw new RuntimeException("头像或背景图需要人工审核,暂无法修改");
}
}
// 默认昵称兜底
if (userInfoUpdateDTO.getNickname() == null || userInfoUpdateDTO.getNickname().trim().isEmpty()) {
userInfoUpdateDTO.setNickname(user.getUserName());
}
userInfoUpdateDTO.setUpdatedAt(LocalDateTime.now());
// 更新数据库
userMapper.updateUserInfoByUserId(userInfoUpdateDTO);
// 封装返回结果
User updatedUser = userMapper.selectByUserId(userInfoUpdateDTO.getUserId());
UserInfoVO userInfoVO = new UserInfoVO();
BeanUtils.copyProperties(updatedUser, userInfoVO);
return userInfoVO;
}
/**
* 更新用户信息
*
* @param userInfoUpdateDTO 用户信息更新参数
*/
@Override
public UserInfoVO updateUserInfo(UserInfoUpdateDTO userInfoUpdateDTO) {
public UserInfoVO updateUserInfo(UserInfoDTO userInfoUpdateDTO) {
Long userId = userInfoUpdateDTO.getUserId();
User user = userMapper.selectByUserId(userId);
if (user == null) {
@@ -417,6 +327,7 @@ public class UserServiceImpl implements UserService {
return userInfoVO;
}
/**
* 用户注册
* @param userName 用户名称
@@ -459,11 +370,13 @@ public class UserServiceImpl implements UserService {
@Override
public UserLoginVO emailLogin(String email, String code) {
User user = userMapper.selectByUserEmailUser(email);
if (user == null)
if (user == null) {
return null;
}
boolean flag = verificationCodeService.verifyEmailCode(email, code);
if (!flag)
if (!flag) {
return null;
}
// 生成token
String token = JwtUtil.generateToken(String.valueOf(user.getUserId()));
UserLoginVO userLoginVO = new UserLoginVO();

View File

@@ -0,0 +1,101 @@
package com.bao.dating.util;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
/**
* 基于redis的数据分析工具 -统计动态总数和登录人数
*/
@Component
public class DataAnalysisRedisService {
// Redis Key 定义
//动态总数
private static final String REDIS_KEY_POST_TOTAL = "data:post:total";
//活跃人数
private static final String REDIS_KEY_LOGIN_USER_TOTAL = "data:loginUser:total";
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 记录动态向redis中添加数据
* @param postId 动态id
*/
public void recordPostData(Long postId) {
// 获取当前毫秒级时间戳作为Sorted Set的score
long currentTimestamp = System.currentTimeMillis();
// 1. 记录动态数据
redisTemplate.opsForZSet().add(REDIS_KEY_POST_TOTAL, postId, currentTimestamp);
// 可选设置Key的过期时间1年+1天避免数据无限累积
setKeyExpire(REDIS_KEY_POST_TOTAL);
}
/**
* 记录动态向redis中添加数据
* @param userId 用户id
*/
public void recordLoginUserData(Long userId) {
// 获取当前毫秒级时间戳作为Sorted Set的score
long currentTimestamp = System.currentTimeMillis();
// 1. 记录动态数据
redisTemplate.opsForZSet().add(REDIS_KEY_LOGIN_USER_TOTAL, userId, currentTimestamp);
// 可选设置Key的过期时间1年+1天避免数据无限累积
setKeyExpire(REDIS_KEY_LOGIN_USER_TOTAL);
}
/**
* 设置Redis Key的过期时间1年+1天确保1年内的数据都能被查询
*/
private void setKeyExpire(String redisKey) {
if (Boolean.FALSE.equals(redisTemplate.hasKey(redisKey))) {
redisTemplate.expire(redisKey, 366, TimeUnit.DAYS);
}
}
/**
* 统计指定时间段内发布的动态
* @param days 指定时间段
* @return 总数
*/
public Long countPostTotalByDays(int days) {
// 1. 计算时间范围:结束时间=当前时间,起始时间=当前时间 - N天的毫秒数
long endTimestamp = System.currentTimeMillis();
long startTimestamp = endTimestamp - (long) days * 24 * 60 * 60 * 1000;
// 2. key min max - 统计指定score范围内的成员数量高效直接返回数量
return redisTemplate.opsForZSet().count(REDIS_KEY_POST_TOTAL, startTimestamp, endTimestamp);
}
public Long countLoginUserTotalByDays(int days) {
// 1. 计算时间范围:结束时间=当前时间,起始时间=当前时间 - N天的毫秒数
long endTimestamp = System.currentTimeMillis();
long startTimestamp = endTimestamp - (long) days * 24 * 60 * 60 * 1000;
// 2. key min max - 统计指定score范围内的成员数量高效直接返回数量
return redisTemplate.opsForZSet().count(REDIS_KEY_LOGIN_USER_TOTAL, startTimestamp, endTimestamp);
}
/**
* 核心方法:传入当前时间,返回各时间段的结果
* @param currentDateTime 当前时间LocalDateTime类型可直接通过LocalDateTime.now()获取)
*/
public String calculateTimeRanges(LocalDateTime currentDateTime,int days) {
// 1. 计算各时间段的起始时间(当前时间往前推对应时长)
LocalDateTime sevenDaysAgo = currentDateTime.minusDays(7);
LocalDateTime fifteenDaysAgo = currentDateTime.minusDays(15);
LocalDateTime thirtyDaysAgo = currentDateTime.minusDays(30);
LocalDateTime oneYearAgo = currentDateTime.minusYears(1);
if (days == 7)
return sevenDaysAgo + " - " + currentDateTime;
if (days == 15)
return fifteenDaysAgo + " - " + currentDateTime;
if (days == 30)
return thirtyDaysAgo + " - " + currentDateTime;
if (days == 365)
return oneYearAgo + " - " + currentDateTime;
return null;
}
}