已读和统计逻辑初始提交

This commit is contained in:
easonzhu 2025-02-13 19:05:42 +08:00
parent 53a6dbea2d
commit fdc4a4aeb9
33 changed files with 794 additions and 663 deletions

22
pom.xml
View File

@ -180,31 +180,9 @@
<version>1.7.36</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<skipTests>true</skipTests>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>

View File

@ -20,12 +20,8 @@ public class CacheKey {
public static final String DISTRIBUTED_LOCK = "distributed_lock";
public static class LockKey {
// 从cache刷新观点互动数量到DB 分布式锁(字符串常量)
public static final String SAVE_VIEW_COUNT_TO_DB_LOCK = "save_view_count_to_db_lock";
// 清除多余定时器日志
public static final String CLEAR_HISTORY_SCHEDULE_LOG = "clearHistoryScheduleLog";
// 自动解除锁定用户
public static final String UN_LOCK_USER = "un_lock_user";
// 结束直播中/暂停中的直播 分布式锁(字符串常量)
public static final String STOP_LIVING_VIDEO_LOCK = "stop_living_video_lock";
// 刷新视频直播状态 分布式锁(字符串常量)
@ -47,6 +43,9 @@ public class CacheKey {
public static final String SYNC_APP_ORDER = "sync_app_order";
public static final String LOAD_USER_BLACK_LIST = "load_user_black_list";
public static final String SAVE_MESSAGE_READ = "save_message_read";
public static final String SAVE_GROUP_USER = "save_group_user";
public static final String COLLECT_GROUP_DATA = "collect_group_data";
}
// 消息主题
@ -135,71 +134,6 @@ public class CacheKey {
public static final String USER_ADVISOR_DEPT_MAP = "user_advisor_dept_map";
}
// 观点栏目
public static final String VIEW_COLUMN = "view_column";
public static final class ViewColumnKey {
// columnId -> columnName
public static final String VIEW_COLUMN_MAP = "column_map";
// List<ViewColumnAppVO>
public static final String APP_COLUMN_LIST = "app_column_list|";
}
// 观点
public static final String VIEW_INFO = "view_info";
public static class ViewInfoKey {
// SortedSet<ViewSortEntity, Integer> (publishTime, viewId)
public static final String APP_PUBLISH_TIME_LIST = "app_publish_time_list";
// SortedSet<ViewSortEntity, Integer> (readCount, viewId)
public static final String APP_READ_COUNT_LIST = "app_read_count_list";
// SortedSet<ViewSortEntity, Integer> (publishTime, viewId)
public static final String APP_KEYWORD_LIST = "app_keyword_all|";
// SortedSet<ViewSortEntity, Integer> (publishTime, viewId)
public static final String APP_COLUMN_LIST = "app_column_list|";
// SortedSet<ViewSortEntity, Integer> (publishTime, viewId)
public static final String APP_ADVISOR_LIST = "app_advisor_list|";
// SortedSet<ViewSortEntity, Integer> (publishTime, viewId)
public static final String APP_PACKAGE_LIST = "app_package_list|";
// ViewInfoAppVO
public static final String APP_OBJ = "app_obj|";
// ViewDetailAppVO
public static final String APP_DETAIL_OBJ = "app_detail_obj|";
// userId -> Set<ViewId>
public static final String USER_FAVOR_VIEW_IDS = "user_favor_view_ids|";
// viewId -> PN Counter<favorCount>
public static final String APP_FAVOR_COUNT = "app_favor_count|";
// viewId -> PN Counter<readCount>
public static final String APP_READ_COUNT = "app_read_count|";
// Set<ViewRead>
public static final String TEMP_READ_COUNT_SET = "temp_read_count_set";
}
// 观点包
public static final String VIEW_PACKAGE = "view_package";
public static class ViewPackageKey {
// id -> ViewPackageAppVO
public static final String APP_OBJ = "app_obj|";
// SortedSet<ViewSortEntity> (weight, subCount, packageId)
public static final String APP_LIST = "app_list";
// SortedSet<ViewSortEntity> (inPackageWeight, subCount, packageId)
public static final String APP_KEYWORD_LIST = "app_keyword_list|";
// SortedSet<ViewSortEntity, Integer> (publishTime, viewId)
public static final String APP_COLUMN_LIST = "app_column_list|";
// SortedSet<ViewSortEntity, Integer> (publishTime, viewId)
public static final String APP_ADVISOR_LIST = "app_advisor_list|";
public static final String APP_ADVISOR_ORDER_BY_SUBCOUNT_LIST = "app_advisor_order_by_subcount_list|";
}
//观点卡片
public static final String VIEW_CARD = "view_card";
public static class ViewCardKey {
//viewId --> List<ViewCard>
public static final String VIEW_CARD_LIST = "view_card_list|";
}
public static final String ADVERT = "advert";
public static class AdvertKey {
@ -420,6 +354,7 @@ public class CacheKey {
public static final String GROUP_MESSAGE_LIST = "group_message_list|";
public static final String GROUP_MESSAGE_DETAIL = "group_message_detail|";
public static final String USER_TOTAL_ONLINE = "user_total_online|";
public static final String TEMP_READ_LIST = "temp_read_list";
}
}

View File

@ -60,11 +60,7 @@ public class HazelcastConfiguration {
configMap.put(DEPT, new LocalMapConfig(10000, 3600));
configMap.put(TAG, new LocalMapConfig(10000, 3600));
configMap.put(VIEW_COLUMN, new LocalMapConfig(10000, 3600));
configMap.put(ADVISOR_INFO, new LocalMapConfig(10000, 300));
configMap.put(VIEW_INFO, new LocalMapConfig(10000, 300));
configMap.put(VIEW_PACKAGE, new LocalMapConfig(10000, 300));
configMap.put(VIEW_CARD, new LocalMapConfig(10000, 300));
configMap.put(RECOMMEND, new LocalMapConfig(10000, 300));
configMap.put(USER, new LocalMapConfig(10000, 3600));

View File

@ -1,4 +1,4 @@
package com.upchina.video.entity;
package com.upchina.common.entity;
import io.swagger.annotations.ApiModelProperty;

View File

@ -3,10 +3,10 @@ package com.upchina.common.interceptor;
import com.hazelcast.map.IMap;
import com.upchina.common.constant.IsOrNot;
import com.upchina.common.constant.ProductType;
import com.upchina.common.entity.OnlineUser;
import com.upchina.common.vo.FrontUserVO;
import com.upchina.group.service.common.GroupCacheService;
import com.upchina.group.service.common.GroupMessageService;
import com.upchina.video.entity.OnlineUser;
import com.upchina.video.service.common.VideoCacheService;
import com.upchina.video.service.common.VideoMessageService;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;

View File

@ -6,6 +6,7 @@ import com.upchina.common.result.CommonResult;
import com.upchina.common.result.ResponseStatus;
import com.upchina.common.vo.FrontUserVO;
import com.upchina.group.query.message.ListGroupMessageAppQuery;
import com.upchina.group.query.message.ReadGroupMessageAppQuery;
import com.upchina.group.query.message.SendGroupMessageAppQuery;
import com.upchina.group.service.app.AppGroupMessageService;
import com.upchina.group.vo.message.GroupMessageVO;
@ -48,4 +49,13 @@ public class AppGroupMessageController {
return CommonResult.success(vo);
}
@ApiOperation("APP保存消息已读")
@PostMapping("/app/group/message/readMessage")
public CommonResult<Void> readMessage(
@Validated @RequestBody @ApiParam(required = true) ReadGroupMessageAppQuery query,
@RequestAttribute(value = "frontUser", required = false) FrontUserVO frontUser) {
appGroupMessageService.readMessage(query, frontUser);
return CommonResult.success();
}
}

View File

@ -0,0 +1,223 @@
package com.upchina.group.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* <p>
* 交易圈统计信息
* </p>
*
* @author easonzhu
* @since 2025-02-13
*/
public class GroupCollect implements Serializable {
/**
* ID
*/
@TableId(value = "group_id", type = IdType.AUTO)
private Integer groupId;
/**
* 日期
*/
private LocalDate date;
/**
* 总成员数
*/
@TableField("total_members")
private Integer totalMembers;
/**
* 访问成员数
*/
@TableField("visited_members")
private Integer visitedMembers;
/**
* 新增成员数
*/
@TableField("new_members")
private Integer newMembers;
/**
* 发互动成员数
*/
@TableField("interaction_members")
private Integer interactionMembers;
/**
* 发私聊成员数
*/
@TableField("private_chat_members")
private Integer privateChatMembers;
/**
* 投顾发布互动数
*/
@TableField("advisor_group_content")
private Integer advisorGroupContent;
/**
* 助教发布互动数
*/
@TableField("assistant_group_content")
private Integer assistantGroupContent;
/**
* 用户发布互动数
*/
@TableField("customer_group_content")
private Integer customerGroupContent;
/**
* 投顾发布私聊数
*/
@TableField("advisor_private_content")
private Integer advisorPrivateContent;
/**
* 助教发布私聊数
*/
@TableField("assistant_private_content")
private Integer assistantPrivateContent;
/**
* 用户发布私聊数
*/
@TableField("customer_private_content")
private Integer customerPrivateContent;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
public Integer getGroupId() {
return groupId;
}
public void setGroupId(Integer groupId) {
this.groupId = groupId;
}
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public Integer getTotalMembers() {
return totalMembers;
}
public void setTotalMembers(Integer totalMembers) {
this.totalMembers = totalMembers;
}
public Integer getVisitedMembers() {
return visitedMembers;
}
public void setVisitedMembers(Integer visitedMembers) {
this.visitedMembers = visitedMembers;
}
public Integer getNewMembers() {
return newMembers;
}
public void setNewMembers(Integer newMembers) {
this.newMembers = newMembers;
}
public Integer getInteractionMembers() {
return interactionMembers;
}
public void setInteractionMembers(Integer interactionMembers) {
this.interactionMembers = interactionMembers;
}
public Integer getPrivateChatMembers() {
return privateChatMembers;
}
public void setPrivateChatMembers(Integer privateChatMembers) {
this.privateChatMembers = privateChatMembers;
}
public Integer getAdvisorGroupContent() {
return advisorGroupContent;
}
public void setAdvisorGroupContent(Integer advisorGroupContent) {
this.advisorGroupContent = advisorGroupContent;
}
public Integer getAssistantGroupContent() {
return assistantGroupContent;
}
public void setAssistantGroupContent(Integer assistantGroupContent) {
this.assistantGroupContent = assistantGroupContent;
}
public Integer getCustomerGroupContent() {
return customerGroupContent;
}
public void setCustomerGroupContent(Integer customerGroupContent) {
this.customerGroupContent = customerGroupContent;
}
public Integer getAdvisorPrivateContent() {
return advisorPrivateContent;
}
public void setAdvisorPrivateContent(Integer advisorPrivateContent) {
this.advisorPrivateContent = advisorPrivateContent;
}
public Integer getAssistantPrivateContent() {
return assistantPrivateContent;
}
public void setAssistantPrivateContent(Integer assistantPrivateContent) {
this.assistantPrivateContent = assistantPrivateContent;
}
public Integer getCustomerPrivateContent() {
return customerPrivateContent;
}
public void setCustomerPrivateContent(Integer customerPrivateContent) {
this.customerPrivateContent = customerPrivateContent;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "GroupCollect{" +
"groupId=" + groupId +
", date=" + date +
", totalMembers=" + totalMembers +
", visitedMembers=" + visitedMembers +
", newMembers=" + newMembers +
", interactionMembers=" + interactionMembers +
", privateChatMembers=" + privateChatMembers +
", advisorGroupContent=" + advisorGroupContent +
", assistantGroupContent=" + assistantGroupContent +
", customerGroupContent=" + customerGroupContent +
", advisorPrivateContent=" + advisorPrivateContent +
", assistantPrivateContent=" + assistantPrivateContent +
", customerPrivateContent=" + customerPrivateContent +
", createTime=" + createTime +
"}";
}
}

View File

@ -0,0 +1,81 @@
package com.upchina.group.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 交易圈消息已读
* </p>
*
* @author easonzhu
* @since 2025-02-13
*/
public class GroupMessageRead implements Serializable {
/**
* 消息ID
*/
@TableField("message_id")
private Integer messageId;
/**
* 用户ID
*/
@TableField("user_id")
private String userId;
/**
* 交易圈ID
*/
@TableField("group_id")
private Integer groupId;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
public Integer getMessageId() {
return messageId;
}
public void setMessageId(Integer messageId) {
this.messageId = messageId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Integer getGroupId() {
return groupId;
}
public void setGroupId(Integer groupId) {
this.groupId = groupId;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "GroupMessageRead{" +
"messageId=" + messageId +
", userId=" + userId +
", groupId=" + groupId +
", createTime=" + createTime +
"}";
}
}

View File

@ -0,0 +1,16 @@
package com.upchina.group.mapper;
import com.upchina.common.mapper.EasyBaseMapper;
import com.upchina.group.entity.GroupCollect;
/**
* <p>
* 交易圈统计信息 Mapper 接口
* </p>
*
* @author easonzhu
* @since 2025-02-13
*/
public interface GroupCollectMapper extends EasyBaseMapper<GroupCollect> {
}

View File

@ -2,9 +2,12 @@ package com.upchina.group.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.upchina.group.entity.GroupMessage;
import com.upchina.group.entity.GroupMessageRead;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.time.LocalDateTime;
import java.util.List;
/**
@ -25,4 +28,25 @@ public interface GroupMessageMapper extends BaseMapper<GroupMessage> {
") t\n" +
"WHERE rn = 1")
List<GroupMessage> selectPrivateChatList(@Param("groupId") Integer groupId);
@Update("<script>" +
"INSERT INTO group_message_read (message_id, user_id, group_id) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
"(#{item.messageId}, #{item.userId}, #{item.groupId})" +
"</foreach>" +
" ON DUPLICATE KEY UPDATE message_id = message_id" +
"</script>")
void replaceBatch(List<GroupMessageRead> list);
@Select("SELECT group_id, interactive_type, user_type, COUNT(0) AS id \n" +
"FROM group_message \n" +
"WHERE create_time >= #{startTime} AND create_time < #{endTime} \n" +
"GROUP BY group_id, interactive_type, user_type")
List<GroupMessage> collectMessage(@Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime);
@Select("SELECT group_id, interactive_type, COUNT(DISTINCT user_id) AS id \n" +
"FROM group_message \n" +
"WHERE user_type = 2 AND create_time >= #{startTime} AND create_time < #{endTime} \n" +
"GROUP BY group_id, interactive_type")
List<GroupMessage> collectMessageMember(@Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime);
}

View File

@ -0,0 +1,16 @@
package com.upchina.group.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.upchina.group.entity.GroupMessageRead;
/**
* <p>
* 交易圈消息已读 Mapper 接口
* </p>
*
* @author easonzhu
* @since 2025-02-13
*/
public interface GroupMessageReadMapper extends BaseMapper<GroupMessageRead> {
}

View File

@ -1,13 +1,15 @@
package com.upchina.group.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.upchina.common.entity.OnlineUser;
import com.upchina.common.mapper.EasyBaseMapper;
import com.upchina.common.vo.IdCountVO;
import com.upchina.group.entity.GroupUserFlow;
import com.upchina.video.entity.OnlineUser;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.List;
/**
@ -18,10 +20,22 @@ import java.util.List;
* @author easonzhu
* @since 2025-02-04
*/
public interface GroupUserFlowMapper extends BaseMapper<GroupUserFlow> {
public interface GroupUserFlowMapper extends EasyBaseMapper<GroupUserFlow> {
@Select("SELECT h.group_id, h.user_id, h.session_id, 2 as is_online " +
"FROM group_user_flow h " +
"${ew.customSqlSegment}")
List<OnlineUser> loadHis(@Param(Constants.WRAPPER) LambdaQueryWrapper<GroupUserFlow> wrapper);
@Select("SELECT \n" +
" group_id, \n" +
" COUNT(DISTINCT user_id) AS user_count \n" +
"FROM \n" +
" group_user_flow \n" +
"WHERE \n" +
" time >= #{startTime} \n" +
" AND time < #{endTime} \n" +
"GROUP BY \n" +
" group_id")
List<IdCountVO> selectGroupUserCount(@Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime);
}

View File

@ -0,0 +1,22 @@
package com.upchina.group.query.message;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotEmpty;
import java.util.List;
public class ReadGroupMessageAppQuery {
@ApiModelProperty("消息ID")
@NotEmpty
private List<Integer> messageIds;
public List<Integer> getMessageIds() {
return messageIds;
}
public void setMessageIds(List<Integer> messageIds) {
this.messageIds = messageIds;
}
}

View File

@ -0,0 +1,51 @@
package com.upchina.group.schedule;
import com.upchina.common.config.cache.CacheKey;
import com.upchina.common.service.CacheService;
import com.upchina.group.service.common.GroupCommonService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component
public class GroupTask {
@Resource
private CacheService cacheService;
@Resource
private GroupCommonService groupCommonService;
/**
* 拉取云端视频转码状态
*/
@Scheduled(cron = "${cron.saveGroupMessageRead}")
public void saveGroupMessageRead() {
cacheService.lock(CacheKey.LockKey.SAVE_MESSAGE_READ,
0, TimeUnit.SECONDS,
4, TimeUnit.MINUTES,
groupCommonService::saveGroupMessageRead
);
}
@Scheduled(cron = "${cron.saveGroupUser}")
public void saveGroupUser() {
cacheService.lock(CacheKey.LockKey.SAVE_GROUP_USER,
0, TimeUnit.SECONDS,
4, TimeUnit.MINUTES,
groupCommonService::saveGroupUser
);
}
@Scheduled(cron = "${cron.collectGroupData}")
public void collectGroupData() {
cacheService.lock(CacheKey.LockKey.COLLECT_GROUP_DATA,
0, TimeUnit.SECONDS,
4, TimeUnit.MINUTES,
groupCommonService::collectGroupData
);
}
}

View File

@ -18,12 +18,14 @@ import com.upchina.group.constant.QueryGroupMessageType;
import com.upchina.group.entity.GroupMessage;
import com.upchina.group.mapper.GroupMessageMapper;
import com.upchina.group.query.message.ListGroupMessageAppQuery;
import com.upchina.group.query.message.ReadGroupMessageAppQuery;
import com.upchina.group.query.message.SendGroupMessageAppQuery;
import com.upchina.group.service.GroupInfoService;
import com.upchina.group.service.common.GroupCacheService;
import com.upchina.group.service.common.GroupCommonService;
import com.upchina.group.service.common.GroupMessageService;
import com.upchina.group.vo.GroupVO;
import com.upchina.group.vo.message.GroupMessageReadVO;
import com.upchina.group.vo.message.GroupMessageVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -127,4 +129,8 @@ public class AppGroupMessageService {
return vo;
}
public void readMessage(ReadGroupMessageAppQuery query, FrontUserVO frontUser) {
GroupMessageReadVO vo = new GroupMessageReadVO(frontUser.getUserId(), query.getMessageIds());
groupCacheService.readMessage(vo);
}
}

View File

@ -2,22 +2,23 @@ package com.upchina.group.service.common;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
import com.upchina.advisor.vo.AdvisorBasicVO;
import com.upchina.common.config.cache.CacheKey;
import com.upchina.common.constant.IsOrNot;
import com.upchina.common.handler.BizException;
import com.upchina.common.result.ResponseStatus;
import com.upchina.common.entity.OnlineUser;
import com.upchina.common.service.CacheService;
import com.upchina.group.constant.GroupInteractiveType;
import com.upchina.group.constant.GroupMessageUserType;
import com.upchina.group.constant.QueryGroupMessageType;
import com.upchina.group.entity.GroupMessage;
import com.upchina.group.entity.GroupUserFlow;
import com.upchina.group.mapper.GroupInfoMapper;
import com.upchina.group.mapper.GroupMessageMapper;
import com.upchina.group.mapper.GroupUserFlowMapper;
import com.upchina.group.vo.message.GroupMessageReadVO;
import com.upchina.group.vo.message.GroupMessageVO;
import com.upchina.video.entity.OnlineUser;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@ -28,9 +29,15 @@ import java.util.stream.Collectors;
@Service
public class GroupCacheService {
@Resource
private HazelcastInstance hazelcastInstance;
@Resource
private CacheService cacheService;
@Resource
private GroupInfoMapper groupInfoMapper;
@Resource
private GroupMessageMapper groupMessageMapper;
@ -40,15 +47,6 @@ public class GroupCacheService {
@Resource
private IMap<String, Object> groupCache;
// public void clearMessageCache(Integer msgId, Integer groupId) {
// if (msgId != null) {
// groupCache.remove(CacheKey.GroupKey.GROUP_MESSAGE_DETAIL + msgId);
// }
// if (groupId != null) {
// groupCache.remove(CacheKey.GroupKey.GROUP_MESSAGE_LIST + groupId);
// }
// }
public NavigableSet<Integer> getMessageIdSet(Integer groupId, String userId, QueryGroupMessageType type) {
String cacheKey = buildMessageIdSetKey(userId, type);
return cacheService.get(groupCache, cacheKey, () -> {
@ -136,7 +134,7 @@ public class GroupCacheService {
}
public GroupMessageVO getMessage(GroupMessage message, Map<Integer, AdvisorBasicVO> advisorMap) {
GroupMessageVO vo = new GroupMessageVO(message, advisorMap.get(message.getAdvisorId()));
GroupMessageVO vo = new GroupMessageVO(message, advisorMap == null ? null : advisorMap.get(message.getAdvisorId()));
vo.setReplyMessage(getMessage(message.getReplyId(), advisorMap));
vo.setQuoteMessage(getMessage(message.getQuoteId(), advisorMap));
return vo;
@ -161,4 +159,9 @@ public class GroupCacheService {
}
return cacheKey;
}
public void readMessage(GroupMessageReadVO vo) {
hazelcastInstance.getList(CacheKey.GroupKey.TEMP_READ_LIST).add(vo);
}
}

View File

@ -1,13 +1,236 @@
package com.upchina.group.service.common;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
import com.upchina.common.config.cache.CacheKey;
import com.upchina.common.constant.IsOrNot;
import com.upchina.common.entity.OnlineUser;
import com.upchina.common.vo.IdCountVO;
import com.upchina.group.constant.GroupInteractiveType;
import com.upchina.group.constant.GroupMessageUserType;
import com.upchina.group.entity.*;
import com.upchina.group.mapper.GroupCollectMapper;
import com.upchina.group.mapper.GroupInfoMapper;
import com.upchina.group.mapper.GroupMessageMapper;
import com.upchina.group.mapper.GroupUserFlowMapper;
import com.upchina.group.vo.GroupVO;
import com.upchina.group.vo.message.GroupMessageReadVO;
import com.upchina.group.vo.message.GroupMessageVO;
import com.upchina.video.schedule.CollectTask;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class GroupCommonService {
@Resource
private HazelcastInstance hazelcastInstance;
@Resource
private GroupCacheService groupCacheService;
@Resource
private GroupMessageMapper groupMessageMapper;
@Resource
private GroupInfoMapper groupInfoMapper;
@Resource
private GroupUserFlowMapper groupUserFlowMapper;
@Resource
private GroupCollectMapper groupCollectMapper;
private CollectTask collectTask;
public boolean validateUserPermission(String userId, GroupVO groupVO) {
return true;
}
@Transactional(rollbackFor = Exception.class)
public void saveGroupMessageRead() {
List<GroupMessageReadVO> cacheList = hazelcastInstance.getList(CacheKey.GroupKey.TEMP_READ_LIST);
// 合并重复项
Map<Integer, Set<String>> map = new HashMap<>(cacheList.size());
for (GroupMessageReadVO read : cacheList) {
if (read != null) {
read.getMessageIds().stream().forEach(m ->
map.computeIfAbsent(m, k -> new HashSet<>()).add(read.getUserId()));
}
}
List<GroupMessageRead> list = map.entrySet().stream()
.map(entry -> {
Integer messageId = entry.getKey();
GroupMessageVO message = groupCacheService.getMessage(messageId, null);
if (message == null
|| !GroupInteractiveType.GROUP.value.equals(message.getInteractiveType())
|| (!GroupMessageUserType.ADVISOR.value.equals(message.getUserType()) &&
!GroupMessageUserType.ASSISTANT.value.equals(message.getUserType()))) {
return null;
}
return entry.getValue().stream().map(userId -> {
GroupMessageRead read = new GroupMessageRead();
read.setMessageId(messageId);
read.setUserId(userId);
read.setGroupId(message.getGroupId());
return read;
}).collect(Collectors.toList());
}).filter(Objects::nonNull).flatMap(List::stream).collect(Collectors.toList());
if (CollUtil.isNotEmpty(list)) {
groupMessageMapper.replaceBatch(list);
}
cacheList.clear();
}
/**
* 写入视频用户数据
*/
@Transactional(rollbackFor = Exception.class)
public void saveGroupUser() {
LocalDateTime now = LocalDateTime.now();
LambdaQueryWrapper<GroupInfo> wrapper = Wrappers.<GroupInfo>lambdaQuery()
.select(GroupInfo::getId, GroupInfo::getStatus);
List<GroupInfo> groups = groupInfoMapper.selectList(wrapper);
if (CollUtil.isEmpty(groups)) {
return;
}
LocalDateTime time = now.withSecond(0).withNano(0);
LocalDateTime startTime = now.plusMinutes(-1);
groups.forEach(g -> {
Integer id = g.getId();
IMap<String, OnlineUser> onlineMap = groupCacheService.getTotalOnlineMap(id);
if (onlineMap == null || onlineMap.isEmpty()) {
return;
}
// 将实时在线人落库并添加是否正在观看
List<GroupUserFlow> onLineList = onlineMap.values().stream()
.filter(u -> IsOrNot.IS.value.equals(u.getIsOnline()) || (u.getExitTime() != null && u.getExitTime().isAfter(startTime)))
.map(o -> {
GroupUserFlow groupUserFlow = new GroupUserFlow();
groupUserFlow.setGroupId(id);
groupUserFlow.setUserId(o.getUserId());
groupUserFlow.setSessionId(o.getSessionId());
groupUserFlow.setTime(time);
groupUserFlow.setEnterTime(o.getCreateTime() != null ? o.getCreateTime().withSecond(0).withNano(0) : null);
groupUserFlow.setExitTime(o.getExitTime() != null ? o.getExitTime().withSecond(0).withNano(0) : null);
return groupUserFlow;
})
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(onLineList)) {
ListUtil.split(onLineList, 1000).stream().filter(CollUtil::isNotEmpty).forEach(groupUserFlowMapper::insertBatchSomeColumn);
}
});
}
public void collectGroupData() {
LocalDate today = LocalDate.now();
LocalDate yesterday = today.minusDays(1);
LambdaQueryWrapper<GroupInfo> groupWrapper = Wrappers.<GroupInfo>lambdaQuery()
.select(GroupInfo::getId);
List<GroupInfo> groups = groupInfoMapper.selectList(groupWrapper);
if (CollUtil.isEmpty(groups)) {
return;
}
List<Integer> groupIds = groups.stream().map(GroupInfo::getId).collect(Collectors.toList());
// 获取昨日数据
// 如果昨天数据还是昨天统计的那需要重新计算否则不需计算
QueryWrapper<GroupCollect> collectWrapper = Wrappers.<GroupCollect>query()
.select("max(create_time) as create_time")
.eq("date", yesterday);
GroupCollect yesterdayLatest = groupCollectMapper.selectOne(collectWrapper);
if (yesterdayLatest != null && yesterdayLatest.getCreateTime().isBefore(today.atStartOfDay())) {
collectGroupData(groupIds, yesterday);
}
collectGroupData(groupIds, today);
}
public void collectGroupData(List<Integer> groupIds, LocalDate date) {
LocalDateTime startTime = date.atStartOfDay();
LocalDateTime endTime = date.plusDays(1).atStartOfDay();
List<IdCountVO> visitMemberList = groupUserFlowMapper.selectGroupUserCount(startTime, endTime);
Map<Integer, Integer> visitMemberMap = visitMemberList.stream().collect(Collectors.toMap(IdCountVO::getId, IdCountVO::getCount));
List<GroupMessage> messageMemberCollect = groupMessageMapper.collectMessageMember(startTime, endTime);
Map<Integer, List<GroupMessage>> groupMessageMemberMap = messageMemberCollect.stream().collect(Collectors.groupingBy(GroupMessage::getGroupId));
List<GroupMessage> messageCollect = groupMessageMapper.collectMessage(startTime, endTime);
Map<Integer, List<GroupMessage>> groupMessageMap = messageCollect.stream().collect(Collectors.groupingBy(GroupMessage::getGroupId));
List<GroupCollect> collectList = groupIds.stream().map(groupId -> {
GroupCollect collect = new GroupCollect();
collect.setGroupId(groupId);
collect.setDate(date);
collect.setTotalMembers(getTotalMembers(groupId));
collect.setVisitedMembers(visitMemberMap.getOrDefault(groupId, 0));
collect.setNewMembers(getNewMembers(groupId));
collect.setInteractionMembers(0);
collect.setPrivateChatMembers(0);
List<GroupMessage> messageMemberList = groupMessageMemberMap.get(groupId);
if (CollUtil.isNotEmpty(messageMemberList)) {
for (GroupMessage groupMessage : messageMemberList) {
if (GroupInteractiveType.GROUP.value.equals(groupMessage.getInteractiveType())) {
collect.setInteractionMembers(groupMessage.getId());
} else if (GroupInteractiveType.PRIVATE.value.equals(groupMessage.getInteractiveType())) {
collect.setPrivateChatMembers(groupMessage.getId());
}
}
}
collect.setAdvisorGroupContent(0);
collect.setAssistantGroupContent(0);
collect.setCustomerGroupContent(0);
collect.setAdvisorPrivateContent(0);
collect.setAssistantPrivateContent(0);
collect.setCustomerPrivateContent(0);
List<GroupMessage> messageList = groupMessageMap.get(groupId);
if (CollUtil.isNotEmpty(messageList)) {
for (GroupMessage groupMessage : messageList) {
if (GroupInteractiveType.GROUP.value.equals(groupMessage.getInteractiveType())) {
if (GroupMessageUserType.ADVISOR.value.equals(groupMessage.getUserType())) {
collect.setAdvisorGroupContent(groupMessage.getId());
} else if (GroupMessageUserType.ASSISTANT.value.equals(groupMessage.getUserType())) {
collect.setAssistantGroupContent(groupMessage.getId());
} else if (GroupMessageUserType.CUSTOMER.value.equals(groupMessage.getUserType())) {
collect.setCustomerGroupContent(groupMessage.getId());
}
} else if (GroupInteractiveType.PRIVATE.value.equals(groupMessage.getInteractiveType())) {
if (GroupMessageUserType.ADVISOR.value.equals(groupMessage.getUserType())) {
collect.setAdvisorPrivateContent(groupMessage.getId());
} else if (GroupMessageUserType.ASSISTANT.value.equals(groupMessage.getUserType())) {
collect.setAssistantPrivateContent(groupMessage.getId());
} else if (GroupMessageUserType.CUSTOMER.value.equals(groupMessage.getUserType())) {
collect.setCustomerPrivateContent(groupMessage.getId());
}
}
}
}
return collect;
}).collect(Collectors.toList());
if (CollUtil.isNotEmpty(collectList)) {
groupCollectMapper.delete(Wrappers.<GroupCollect>lambdaQuery().eq(GroupCollect::getDate, date));
groupCollectMapper.insertBatchSomeColumn(collectList);
}
}
private Integer getTotalMembers(Integer groupId) {
// TODO
return 0;
}
private Integer getNewMembers(Integer groupId) {
// TODO
return 0;
}
}

View File

@ -6,6 +6,7 @@ import com.hazelcast.topic.ITopic;
import com.upchina.advisor.service.AdvisorInfoService;
import com.upchina.advisor.vo.AdvisorBasicVO;
import com.upchina.common.config.cache.CacheKey;
import com.upchina.common.entity.OnlineUser;
import com.upchina.common.vo.FrontUserVO;
import com.upchina.group.constant.GroupInteractiveType;
import com.upchina.group.constant.GroupMessageChannel;
@ -13,7 +14,6 @@ import com.upchina.group.constant.GroupMessageType;
import com.upchina.group.entity.GroupMessage;
import com.upchina.group.vo.message.GroupMessageVO;
import com.upchina.group.vo.message.GroupWsMessageVO;
import com.upchina.video.entity.OnlineUser;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

View File

@ -0,0 +1,34 @@
package com.upchina.group.vo.message;
import java.io.Serializable;
import java.util.List;
public class GroupMessageReadVO implements Serializable {
private String userId;
private List<Integer> messageIds;
public GroupMessageReadVO() {}
public GroupMessageReadVO(String userId, List<Integer> messageIds) {
this.userId = userId;
this.messageIds = messageIds;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public List<Integer> getMessageIds() {
return messageIds;
}
public void setMessageIds(List<Integer> messageIds) {
this.messageIds = messageIds;
}
}

View File

@ -6,6 +6,7 @@ import com.upchina.common.result.CommonResult;
import com.upchina.common.vo.AuthVO;
import com.upchina.common.vo.BackendUserVO;
import com.upchina.rbac.query.ChangeMobileQuery;
import com.upchina.rbac.query.ChangePasswordQuery;
import com.upchina.rbac.query.LoginDeptQuery;
import com.upchina.rbac.query.LoginQuery;
import com.upchina.rbac.service.AuthService;
@ -82,6 +83,15 @@ public class AuthController {
return CommonResult.success(vo);
}
@ApiOperation("修改用户密码")
@PostMapping("/changePassword")
@Auth(role = AccessRole.LOGIN)
public CommonResult<Void> changePassword(@Validated @RequestBody @ApiParam(required = true) ChangePasswordQuery query,
@RequestAttribute(value = "backendUser", required = false) BackendUserVO backendUserVO) {
authService.changePassword(query, backendUserVO);
return CommonResult.success(null);
}
@ApiOperation("修改用户手机号")
@PostMapping("/changeMobile")
@Auth(role = AccessRole.LOGIN)

View File

@ -25,10 +25,7 @@ import com.upchina.rbac.entity.UserDept;
import com.upchina.rbac.entity.UserLogin;
import com.upchina.rbac.mapper.UserDeptMapper;
import com.upchina.rbac.mapper.UserLoginMapper;
import com.upchina.rbac.query.ChangeMobileQuery;
import com.upchina.rbac.query.ListRoleByUserIdQuery;
import com.upchina.rbac.query.LoginDeptQuery;
import com.upchina.rbac.query.LoginQuery;
import com.upchina.rbac.query.*;
import com.upchina.rbac.vo.*;
import com.upchina.video.constant.VideoUserType;
import com.wf.captcha.SpecCaptcha;
@ -183,6 +180,25 @@ public class AuthService {
}
}
@Transactional(rollbackFor = Exception.class)
public void changePassword(ChangePasswordQuery query, BackendUserVO backendUserVO) {
Integer loginId = backendUserVO.getLoginId();
UserLogin userInDB = userLoginMapper.selectById(loginId);
if (userInDB == null) {
throw new BizException(ResponseStatus.USER_STATUS_ERROR);
}
String originalPassword = query.getOriginalPassword();
if (!CodecUtil.md5(originalPassword).equals(userInDB.getPassword())) {
throw new BizException(ResponseStatus.ORIGINAL_PASSWORD_ERROR);
}
String md5 = CodecUtil.md5(query.getPassword());
UserLogin user = new UserLogin();
user.setLoginId(loginId);
user.setPassword(md5);
userLoginMapper.updateById(user);
userService.clearCache();
}
@Transactional(rollbackFor = Exception.class)
public void changeMobile(ChangeMobileQuery query, BackendUserVO backendUserVO) {
Integer loginId = backendUserVO.getLoginId();

View File

@ -2,8 +2,8 @@ package com.upchina.video.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.upchina.common.entity.OnlineUser;
import com.upchina.common.mapper.EasyBaseMapper;
import com.upchina.video.entity.OnlineUser;
import com.upchina.video.entity.VideoLiveTrend;
import com.upchina.video.entity.VideoUserFlow;
import org.apache.ibatis.annotations.Param;

View File

@ -14,6 +14,7 @@ import com.upchina.advisor.entity.AdvisorFollow;
import com.upchina.advisor.mapper.AdvisorFollowMapper;
import com.upchina.common.constant.IsFollow;
import com.upchina.common.constant.IsOrNot;
import com.upchina.common.entity.OnlineUser;
import com.upchina.video.constant.VideoLiveStatus;
import com.upchina.video.constant.VideoMessageContentType;
import com.upchina.video.constant.VideoMessageStatus;

View File

@ -15,6 +15,7 @@ import com.upchina.common.config.cache.CacheKey;
import com.upchina.common.constant.IsOrNot;
import com.upchina.common.constant.ProductType;
import com.upchina.common.constant.UserType;
import com.upchina.common.entity.OnlineUser;
import com.upchina.common.handler.BizException;
import com.upchina.common.result.Pager;
import com.upchina.common.result.ResponseStatus;

View File

@ -15,6 +15,7 @@ import com.upchina.common.config.cache.CacheKey;
import com.upchina.common.constant.IsActive;
import com.upchina.common.constant.IsOrNot;
import com.upchina.common.constant.ProductType;
import com.upchina.common.entity.OnlineUser;
import com.upchina.common.result.AppPager;
import com.upchina.common.service.AdvertService;
import com.upchina.common.service.CacheService;

View File

@ -9,6 +9,7 @@ import com.google.common.collect.Table;
import com.upchina.common.config.cache.CacheKey;
import com.upchina.common.constant.IsOrNot;
import com.upchina.common.constant.ProductType;
import com.upchina.common.entity.OnlineUser;
import com.upchina.common.handler.BizException;
import com.upchina.common.result.ResponseStatus;
import com.upchina.common.service.*;

View File

@ -9,13 +9,13 @@ import com.hazelcast.map.IMap;
import com.hazelcast.topic.ITopic;
import com.upchina.advisor.service.AdvisorInfoService;
import com.upchina.common.constant.IsOrNot;
import com.upchina.common.entity.OnlineUser;
import com.upchina.common.handler.BizException;
import com.upchina.common.result.ResponseStatus;
import com.upchina.common.util.logger.LoggerUtil;
import com.upchina.common.vo.FrontUserVO;
import com.upchina.rbac.service.UserService;
import com.upchina.video.constant.*;
import com.upchina.video.entity.OnlineUser;
import com.upchina.video.entity.VideoLive;
import com.upchina.video.entity.VideoLiveMessage;
import com.upchina.video.helper.VideoHelper;

View File

@ -1,6 +1,6 @@
package com.upchina.video.vo.message;
import com.upchina.video.entity.OnlineUser;
import com.upchina.common.entity.OnlineUser;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;

View File

@ -1,6 +1,6 @@
package com.upchina.video.vo.message;
import com.upchina.video.entity.OnlineUser;
import com.upchina.common.entity.OnlineUser;
import com.upchina.video.entity.VideoCart;
import com.upchina.video.vo.cart.CouponVO;
import com.upchina.video.vo.question.NotifyQuestionVO;

View File

@ -15,16 +15,19 @@ hazelcast:
serverPort: 5709 #自己作为缓存服务器监听的端口号
scheduledEnable: true
cron:
collectLivingVideo: "30 1/5 * * * ?" #每分钟统计已开始但未结束的视频直播数据
saveVideoCount: "30 2/2 * * * ?" #从cache刷新视频播放量到DB 每分钟的第10s执行
saveVideoUserDataToDB: "30 3/5 * * * ?"
saveCustomerDataToDB: "30 4/5 * * * ?" #收集用户信息
refreshTranscodeStatus: "30 0/5 * * * ?" #从腾讯云拉取录播上传视频信息更新到DB
collectLivingVideo: "30 0/5 * * * ?" #每分钟统计已开始但未结束的视频直播数据
saveVideoCount: "30 1/2 * * * ?" #从cache刷新视频播放量到DB 每分钟的第10s执行
saveVideoUserDataToDB: "30 2/5 * * * ?"
saveCustomerDataToDB: "30 3/5 * * * ?" #收集用户信息
refreshTranscodeStatus: "30 4/5 * * * ?" #从腾讯云拉取录播上传视频信息更新到DB
updateLiveStatus: "0 1 * * * ?" #更新视频录播状态
stopLivingVideo: "0 1-5 0 * * ?" #结束前一天直播中/暂停中的视频直播
saveWatchSeconds: "0 0/5 * * * ?" #保存短视频观看时长
collectLastWeek: "0 30 3 * * ?" #统计一周内的数据
collectRecentEndVideo: "0 2/5 * * * ?" #每5分钟统计已结束48小时以内的视频直播数据
saveWatchSeconds: "0 0/5 * * * ?" #保存短视频观看时长
collectRecentEndVideo: "0 1/5 * * * ?" #每5分钟统计已结束48小时以内的视频直播数据
saveGroupMessageRead: "0 2/5 * * * ?" #每5分钟统计已结束48小时以内的视频直播数据
saveGroupUser: "0 3/5 * * * ?"
collectGroupData: "0 4/5 * * * ?"
user:
admin:
roles: 1,3,4,5 #管理员角色id用逗号隔开

View File

@ -1,192 +0,0 @@
package com.upchina.group.service;
import com.hazelcast.map.IMap;
import com.upchina.advisor.entity.AdvisorBasic;
import com.upchina.advisor.service.AdvisorInfoService;
import com.upchina.common.query.OnlyIdQuery;
import com.upchina.common.service.AppUserService;
import com.upchina.common.service.CacheService;
import com.upchina.common.service.SensitiveWordService;
import com.upchina.common.state.StateMachine;
import com.upchina.common.vo.BackendUserVO;
import com.upchina.common.vo.InsertIdVO;
import com.upchina.course.service.PageService;
import com.upchina.group.constant.GroupInfoStatus;
import com.upchina.group.entity.GroupInfo;
import com.upchina.group.mapper.GroupInfoMapper;
import com.upchina.group.query.SaveGroupQuery;
import com.upchina.group.query.UpdateGroupQuery;
import com.upchina.group.query.UpdateGroupStatusQuery;
import com.upchina.rbac.entity.UserDept;
import com.upchina.rbac.service.AuthService;
import com.upchina.rbac.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class GroupInfoServiceTest {
@Mock
private GroupInfoMapper groupInfoMapper;
@Mock
private SensitiveWordService sensitiveWordService;
@Mock
private StateMachine<GroupInfoStatus> groupSM;
@Mock
private AdvisorInfoService advisorInfoService;
@Mock
private UserService userService;
@Mock
private AuthService authService;
@Mock
private PageService pageService;
@Mock
private CacheService cacheService;
@Mock
private AppUserService appUserService;
@Mock
private IMap<String, Object> groupCache;
@InjectMocks
private GroupInfoService groupInfoService;
private BackendUserVO mockBackendUser;
@BeforeEach
void setUp() {
mockBackendUser = new BackendUserVO();
mockBackendUser.setUserId(1);
mockBackendUser.setAdvisorId(100);
}
@Test
void save_ShouldSucceed() {
// 准备测试数据
SaveGroupQuery query = new SaveGroupQuery();
query.setName("测试群组");
query.setRemark("测试备注");
query.setDetail("测试详情");
query.setAdvisorId(100);
// 设置模拟行为
doNothing().when(sensitiveWordService).check(anyString(), anyString(), anyString());
when(groupInfoMapper.insert(any(GroupInfo.class))).thenReturn(1);
// 执行测试
InsertIdVO result = groupInfoService.save(query, mockBackendUser);
// 验证结果
assertNotNull(result);
verify(groupInfoMapper).insert(any(GroupInfo.class));
verify(sensitiveWordService).check(anyString(), anyString(), anyString());
}
@Test
void update_ShouldSucceed() {
// 准备测试数据
UpdateGroupQuery query = new UpdateGroupQuery();
query.setId(1);
query.setName("更新的群组名");
GroupInfo existingGroup = new GroupInfo();
existingGroup.setId(1);
existingGroup.setStatus(GroupInfoStatus.TO_COMMIT.value);
existingGroup.setAdvisorId(100);
// 设置模拟行为 - 修改这里的 mock 设置
when(groupInfoMapper.selectById(1)).thenReturn(existingGroup);
when(groupInfoMapper.updateById(any(GroupInfo.class))).thenReturn(1);
// 执行测试
groupInfoService.update(query, mockBackendUser);
// 验证结果
verify(groupInfoMapper).updateById(any(GroupInfo.class));
verify(groupCache).delete(anyString());
// 验证 sensitiveWordService.check 被调用使用参数捕获器
verify(sensitiveWordService).check(
eq("更新的群组名"), // 名称
isNull(), // 备注
isNull() // 详情
);
}
@Test
void updateStatus_ShouldSucceed() {
// 准备测试数据
UpdateGroupStatusQuery query = new UpdateGroupStatusQuery();
query.setId(1);
query.setEvent(GroupInfoStatus.EVENT_SUBMIT.value);
GroupInfo existingGroup = new GroupInfo();
existingGroup.setId(1);
existingGroup.setStatus(GroupInfoStatus.TO_AUDIT.value);
existingGroup.setAdvisorId(100);
// 设置模拟行为
when(groupInfoMapper.selectById(1)).thenReturn(existingGroup);
when(groupInfoMapper.updateById(any(GroupInfo.class))).thenReturn(1);
when(groupSM.send(any(), any())).thenReturn(GroupInfoStatus.TO_AUDIT);
// 执行测试
groupInfoService.updateStatus(query, mockBackendUser);
// 验证结果
verify(groupInfoMapper).updateById(any(GroupInfo.class));
verify(groupCache).delete(anyString());
}
@Test
void get_ShouldReturnGroupVO() {
// 准备测试数据
OnlyIdQuery query = new OnlyIdQuery(1);
GroupInfo groupInfo = new GroupInfo();
groupInfo.setId(1);
groupInfo.setAdvisorId(100);
Map<Integer, AdvisorBasic> advisorMap = new HashMap<>();
AdvisorBasic advisor = new AdvisorBasic();
advisor.setId(100);
advisorMap.put(100, advisor);
Map<Integer, UserDept> userMap = new HashMap<>();
UserDept userDept = new UserDept();
userDept.setUserId(1);
userDept.setName("测试用户");
userMap.put(1, userDept);
// 设置模拟行为
when(groupInfoMapper.selectById(1)).thenReturn(groupInfo);
when(advisorInfoService.getAdvisorMap()).thenReturn(advisorMap);
when(userService.getUserMap()).thenReturn(userMap);
// 执行测试
var result = groupInfoService.get(query, mockBackendUser);
// 验证结果
assertNotNull(result);
assertEquals(1, result.getId());
}
}

View File

@ -1,134 +0,0 @@
package com.upchina.group.service.admin;
import com.upchina.common.state.StateMachine;
import com.upchina.common.vo.BackendUserVO;
import com.upchina.common.vo.OnlyIdVO;
import com.upchina.group.constant.GroupMessageChannel;
import com.upchina.group.constant.GroupMessageStatus;
import com.upchina.group.constant.GroupMessageType;
import com.upchina.group.entity.GroupInfo;
import com.upchina.group.entity.GroupMessage;
import com.upchina.group.mapper.GroupInfoMapper;
import com.upchina.group.mapper.GroupMessageMapper;
import com.upchina.group.query.message.GroupMessageProductQuery;
import com.upchina.group.query.message.GroupMessageStatusQuery;
import com.upchina.group.query.message.SendGroupMessageAdminQuery;
import com.upchina.group.service.common.GroupCacheService;
import com.upchina.group.service.common.GroupMessageService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class AdminGroupMessageServiceTest {
@Mock
private GroupMessageMapper groupMessageMapper;
@Mock
private GroupInfoMapper groupInfoMapper;
@Mock
private GroupMessageService groupMessageService;
@Mock
private GroupCacheService groupCacheService;
@Mock
private StateMachine<GroupMessageStatus> groupMessageSM;
@InjectMocks
private AdminGroupMessageService adminGroupMessageService;
private BackendUserVO mockBackendUser;
@BeforeEach
void setUp() {
mockBackendUser = new BackendUserVO();
mockBackendUser.setUserId(1);
mockBackendUser.setAdvisorId(100);
}
@Test
void sendAdvisorMessage_ShouldSucceed() {
// 准备测试数据
SendGroupMessageAdminQuery query = new SendGroupMessageAdminQuery();
query.setGroupId(1);
query.setContent("测试消息");
when(groupMessageMapper.insert(any(GroupMessage.class))).thenReturn(1);
// 执行测试
OnlyIdVO result = adminGroupMessageService.sendAdvisorMessage(query, mockBackendUser);
// 验证结果
assertNotNull(result);
verify(groupMessageMapper).insert(any(GroupMessage.class));
verify(groupMessageService).publishGroupMessage(
eq(GroupMessageChannel.ALL),
eq(GroupMessageType.RECOMMEND_PRODUCT),
eq(query.getGroupId()),
any()
);
}
@Test
void sendInteractiveStatusMessage_ShouldSucceed() {
// 准备测试数据
GroupMessageStatusQuery query = new GroupMessageStatusQuery();
query.setGroupId(1);
query.setStatus(1);
GroupInfo mockGroupInfo = new GroupInfo();
mockGroupInfo.setId(1);
when(groupInfoMapper.selectById(query.getGroupId())).thenReturn(mockGroupInfo);
when(groupInfoMapper.updateById(any(GroupInfo.class))).thenReturn(1);
// 执行测试
adminGroupMessageService.sendInteractiveStatusMessage(query, mockBackendUser);
// 验证结果
verify(groupInfoMapper).selectById(query.getGroupId());
verify(groupInfoMapper).updateById(any(GroupInfo.class));
verify(groupMessageService).publishGroupMessage(
eq(GroupMessageChannel.APP),
eq(GroupMessageType.OPEN_INTERACTIVE),
eq(query.getGroupId()),
eq(query.getStatus())
);
}
@Test
void saveProductMessage_ShouldSucceed() {
// 准备测试数据
GroupMessageProductQuery query = new GroupMessageProductQuery();
query.setGroupId(1);
query.setProductId(100);
query.setProductName("测试产品");
query.setProductDesc("产品描述");
query.setProductUrl("http://example.com");
when(groupMessageMapper.insert(any(GroupMessage.class))).thenReturn(1);
// 执行测试
OnlyIdVO result = adminGroupMessageService.saveProductMessage(query, mockBackendUser);
// 验证结果
assertNotNull(result);
verify(groupMessageMapper).insert(any(GroupMessage.class));
verify(groupMessageService).publishGroupMessage(
eq(GroupMessageChannel.ALL),
eq(GroupMessageType.RECOMMEND_PRODUCT),
eq(query.getGroupId()),
any()
);
}
}

View File

@ -1,208 +0,0 @@
package com.upchina.group.service.app;
import com.hazelcast.map.IMap;
import com.upchina.common.constant.IsOrNot;
import com.upchina.common.handler.BizException;
import com.upchina.common.query.OnlyIdQuery;
import com.upchina.common.result.AppPager;
import com.upchina.common.service.CacheService;
import com.upchina.common.service.SensitiveWordService;
import com.upchina.common.vo.FrontUserVO;
import com.upchina.group.constant.GroupMessageChannel;
import com.upchina.group.constant.GroupMessageType;
import com.upchina.group.constant.QueryGroupMessageType;
import com.upchina.group.entity.GroupMessage;
import com.upchina.group.mapper.GroupMessageMapper;
import com.upchina.group.query.message.ListGroupMessageAppQuery;
import com.upchina.group.query.message.SendGroupMessageAppQuery;
import com.upchina.group.service.GroupInfoService;
import com.upchina.group.service.common.GroupCacheService;
import com.upchina.group.service.common.GroupCommonService;
import com.upchina.group.service.common.GroupMessageService;
import com.upchina.group.vo.GroupVO;
import com.upchina.group.vo.message.GroupMessageVO;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.NavigableSet;
import java.util.TreeSet;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class AppGroupMessageServiceTest {
@Mock
private GroupMessageMapper groupMessageMapper;
@Mock
private GroupMessageService groupMessageService;
@Mock
private GroupInfoService groupInfoService;
@Mock
private GroupCacheService groupCacheService;
@Mock
private GroupCommonService groupCommonService;
@Mock
private SensitiveWordService sensitiveWordService;
@Mock
private CacheService cacheService;
@Mock
private IMap<String, Object> groupCache;
@InjectMocks
private AppGroupMessageService appGroupMessageService;
private FrontUserVO mockFrontUser;
private GroupVO mockGroupVO;
@BeforeEach
void setUp() {
mockFrontUser = new FrontUserVO();
mockFrontUser.setUserId("123");
mockGroupVO = new GroupVO();
mockGroupVO.setId(1);
mockGroupVO.setPrivateChatStatus(IsOrNot.IS.value);
}
@Test
void getMessageList_ShouldSucceed() {
// 准备测试数据
ListGroupMessageAppQuery query = new ListGroupMessageAppQuery();
query.setGroupId(1);
query.setType(QueryGroupMessageType.ADVISOR.value);
query.setSize(10);
NavigableSet<Integer> mockSet = new TreeSet<>();
mockSet.add(1);
mockSet.add(2);
GroupMessageVO mockMessageVO = new GroupMessageVO();
mockMessageVO.setId(1);
// 设置模拟行为
when(groupInfoService.getForApp(any(OnlyIdQuery.class), any())).thenReturn(mockGroupVO);
when(groupCommonService.validateUserPermission(anyString(), any(GroupVO.class))).thenReturn(true);
when(cacheService.get(eq(groupCache), anyString(), any())).thenReturn(mockSet);
// 执行测试
AppPager<GroupMessageVO> result = appGroupMessageService.getMessageList(query, mockFrontUser);
// 验证结果
assertNotNull(result);
assertFalse(result.getList().isEmpty());
assertEquals(1, result.getList().get(0).getId());
}
@Test
void getMessageList_WithPrivateType_ShouldSucceed() {
// 准备测试数据
ListGroupMessageAppQuery query = new ListGroupMessageAppQuery();
query.setGroupId(1);
query.setType(QueryGroupMessageType.PRIVATE.value);
query.setSize(10);
NavigableSet<Integer> mockSet = new TreeSet<>();
mockSet.add(1);
GroupMessageVO mockMessageVO = new GroupMessageVO();
mockMessageVO.setId(1);
// 设置模拟行为
when(groupInfoService.getForApp(any(OnlyIdQuery.class), any())).thenReturn(mockGroupVO);
when(groupCommonService.validateUserPermission(anyString(), any(GroupVO.class))).thenReturn(true);
when(cacheService.get(eq(groupCache), anyString(), any())).thenReturn(mockSet);
// 执行测试
AppPager<GroupMessageVO> result = appGroupMessageService.getMessageList(query, mockFrontUser);
// 验证结果
assertNotNull(result);
assertFalse(result.getList().isEmpty());
}
@Test
void sendMessage_ShouldSucceed() {
// 准备测试数据
SendGroupMessageAppQuery query = new SendGroupMessageAppQuery();
query.setGroupId(1);
query.setContent("测试消息");
// 设置模拟行为
doNothing().when(sensitiveWordService).check(anyString());
when(groupMessageMapper.insert(any(GroupMessage.class))).thenReturn(1);
// 执行测试
GroupMessageVO result = appGroupMessageService.sendMessage(query, mockFrontUser);
// 验证结果
assertNotNull(result);
verify(groupMessageService).publishGroupMessage(
eq(GroupMessageChannel.ALL),
eq(GroupMessageType.NORMAL),
eq(query.getGroupId()),
any(GroupMessageVO.class)
);
}
@Test
void sendMessage_WhenGroupClosed_ShouldThrowException() {
// 准备测试数据
SendGroupMessageAppQuery query = new SendGroupMessageAppQuery();
query.setGroupId(1);
query.setContent("测试消息");
// 设置模拟行为
doNothing().when(sensitiveWordService).check(anyString());
// 执行测试并验证异常
assertThrows(BizException.class, () ->
appGroupMessageService.sendMessage(query, mockFrontUser)
);
}
@Test
void sendMessage_WhenUserForbidden_ShouldThrowException() {
// 准备测试数据
SendGroupMessageAppQuery query = new SendGroupMessageAppQuery();
query.setGroupId(1);
query.setContent("测试消息");
// 设置模拟行为
doNothing().when(sensitiveWordService).check(anyString());
// 执行测试并验证异常
assertThrows(BizException.class, () ->
appGroupMessageService.sendMessage(query, mockFrontUser)
);
}
@Test
void getMessageList_WithInvalidGroup_ShouldThrowException() {
// 准备测试数据
ListGroupMessageAppQuery query = new ListGroupMessageAppQuery();
query.setGroupId(1);
query.setType(QueryGroupMessageType.ADVISOR.value);
// 设置模拟行为
when(groupInfoService.getForApp(any(OnlyIdQuery.class), any())).thenReturn(null);
// 执行测试并验证异常
assertThrows(BizException.class, () ->
appGroupMessageService.getMessageList(query, mockFrontUser)
);
}
}