refactor(archive-service): 重构员工会话存档统计逻辑,优化查询性能

1. 新增StaffMapper分页查询与计数方法,替换原MyBatis-Plus分页写法
2. 新增批量统计方法,合并多轮单员工查询为批量SQL查询
3. 重构员工列表查询逻辑,减少数据库交互次数
4. 拆分基础VO转换与统计填充方法,优化代码结构
This commit is contained in:
jqb 2026-07-10 16:44:07 +08:00
parent c4d993a9f5
commit 9f86fc9f76
4 changed files with 114 additions and 33 deletions

View File

@ -106,6 +106,21 @@ public interface ArchiveMessageMapper extends BaseMapper<ArchiveMessage> {
@Select("SELECT DISTINCT from_user FROM archive_messages WHERE corp_id = #{corpId} AND from_role = 'INTERNAL'") @Select("SELECT DISTINCT from_user FROM archive_messages WHERE corp_id = #{corpId} AND from_role = 'INTERNAL'")
List<String> selectStaffIdsWithArchive(@Param("corpId") String corpId); List<String> selectStaffIdsWithArchive(@Param("corpId") String corpId);
@Select("<script>" +
"SELECT from_user AS staff_id, COUNT(*) AS msg_count, " +
"SUM(CASE WHEN msgtime &gt;= UNIX_TIMESTAMP(CURDATE()) * 1000 " +
" AND msgtime &lt; UNIX_TIMESTAMP(DATE_ADD(CURDATE(), INTERVAL 1 DAY)) * 1000 THEN 1 ELSE 0 END) AS today_msg_count, " +
"MAX(msgtime) AS last_msg_time " +
"FROM archive_messages " +
"WHERE corp_id = #{corpId} AND from_user IN " +
"<foreach collection='staffIds' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach> " +
"GROUP BY from_user" +
"</script>")
List<Map<String, Object>> selectStaffArchiveStats(@Param("corpId") String corpId,
@Param("staffIds") List<String> staffIds);
/** /**
* 查询尚未上传到 COS 的媒体消息 * 查询尚未上传到 COS 的媒体消息
*/ */

View File

@ -6,6 +6,9 @@ import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
/** /**
* 客户数据访问层 * 客户数据访问层
*/ */
@ -14,4 +17,17 @@ public interface CustomerMapper extends BaseMapper<Customer> {
@Select("SELECT * FROM customers WHERE customer_id = #{customerId} AND corp_id = #{corpId} LIMIT 1") @Select("SELECT * FROM customers WHERE customer_id = #{customerId} AND corp_id = #{corpId} LIMIT 1")
Customer selectByCustomerId(@Param("customerId") String customerId, @Param("corpId") String corpId); Customer selectByCustomerId(@Param("customerId") String customerId, @Param("corpId") String corpId);
@Select("<script>" +
"SELECT s.staff_id, COUNT(*) AS customer_count " +
"FROM customers c " +
"JOIN (" +
" <foreach collection='staffIds' item='id' separator=' UNION ALL '>" +
" SELECT #{id} AS staff_id" +
" </foreach>" +
") s ON c.corp_id = #{corpId} AND JSON_CONTAINS(c.staff_ids, JSON_QUOTE(s.staff_id)) " +
"GROUP BY s.staff_id" +
"</script>")
List<Map<String, Object>> countCustomersByStaffIds(@Param("corpId") String corpId,
@Param("staffIds") List<String> staffIds);
} }

View File

@ -6,9 +6,43 @@ import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper @Mapper
public interface StaffMapper extends BaseMapper<Staff> { public interface StaffMapper extends BaseMapper<Staff> {
@Select("SELECT * FROM staffs WHERE staff_id = #{staffId} AND corp_id = #{corpId} LIMIT 1") @Select("SELECT * FROM staffs WHERE staff_id = #{staffId} AND corp_id = #{corpId} LIMIT 1")
Staff selectByStaffId(@Param("staffId") String staffId, @Param("corpId") String corpId); Staff selectByStaffId(@Param("staffId") String staffId, @Param("corpId") String corpId);
@Select("<script>" +
"SELECT s.* FROM staffs s " +
"WHERE s.corp_id = #{corpId} " +
"<if test='keyword != null and keyword != \"\"'>" +
" AND (s.staff_id LIKE CONCAT('%', #{keyword}, '%') OR s.name LIKE CONCAT('%', #{keyword}, '%')) " +
"</if>" +
"<if test='hasArchive'>" +
" AND EXISTS (SELECT 1 FROM archive_messages m WHERE m.corp_id = s.corp_id AND m.from_user = s.staff_id LIMIT 1) " +
"</if>" +
"ORDER BY s.created_at DESC " +
"LIMIT #{offset}, #{size}" +
"</script>")
List<Staff> selectStaffPage(@Param("corpId") String corpId,
@Param("keyword") String keyword,
@Param("hasArchive") boolean hasArchive,
@Param("offset") int offset,
@Param("size") int size);
@Select("<script>" +
"SELECT COUNT(*) FROM staffs s " +
"WHERE s.corp_id = #{corpId} " +
"<if test='keyword != null and keyword != \"\"'>" +
" AND (s.staff_id LIKE CONCAT('%', #{keyword}, '%') OR s.name LIKE CONCAT('%', #{keyword}, '%')) " +
"</if>" +
"<if test='hasArchive'>" +
" AND EXISTS (SELECT 1 FROM archive_messages m WHERE m.corp_id = s.corp_id AND m.from_user = s.staff_id LIMIT 1) " +
"</if>" +
"</script>")
Long countStaffPage(@Param("corpId") String corpId,
@Param("keyword") String keyword,
@Param("hasArchive") boolean hasArchive);
} }

View File

@ -51,24 +51,17 @@ public class ArchiveSessionService {
* 员工列表 * 员工列表
*/ */
public PageResult<StaffSessionVO> listStaffs(String keyword, int page, int size, boolean hasArchive) { public PageResult<StaffSessionVO> listStaffs(String keyword, int page, int size, boolean hasArchive) {
LambdaQueryWrapper<Staff> wrapper = new LambdaQueryWrapper<>(); int offset = (page - 1) * size;
wrapper.eq(Staff::getCorpId, corpId); Long total = staffMapper.countStaffPage(corpId, keyword, hasArchive);
if (keyword != null && !keyword.isEmpty()) { if (total == null || total == 0) {
wrapper.and(w -> w.like(Staff::getStaffId, keyword).or().like(Staff::getName, keyword));
}
// 只展示有会话存档的员工
if (hasArchive) {
List<String> archiveStaffIds = archiveMessageMapper.selectStaffIdsWithArchive(corpId);
if (archiveStaffIds == null || archiveStaffIds.isEmpty()) {
return PageResult.of(page, size, 0L, Collections.emptyList()); return PageResult.of(page, size, 0L, Collections.emptyList());
} }
wrapper.in(Staff::getStaffId, archiveStaffIds);
}
wrapper.orderByDesc(Staff::getCreatedAt);
Page<Staff> pageResult = staffMapper.selectPage(new Page<>(page, size), wrapper); List<Staff> staffList = staffMapper.selectStaffPage(corpId, keyword, hasArchive, offset, size);
List<StaffSessionVO> list = pageResult.getRecords().stream().map(this::convertStaffVO).collect(Collectors.toList()); List<StaffSessionVO> list = staffList.stream().map(this::toBaseStaffVO).collect(Collectors.toList());
return PageResult.of(page, size, pageResult.getTotal(), list); List<String> staffIds = staffList.stream().map(Staff::getStaffId).collect(Collectors.toList());
fillStaffStats(list, staffIds);
return PageResult.of(page, size, total, list);
} }
/** /**
@ -228,6 +221,12 @@ public class ArchiveSessionService {
// ============ 私有方法 ============ // ============ 私有方法 ============
private StaffSessionVO convertStaffVO(Staff staff) { private StaffSessionVO convertStaffVO(Staff staff) {
StaffSessionVO vo = toBaseStaffVO(staff);
fillStaffStats(Collections.singletonList(vo), Collections.singletonList(staff.getStaffId()));
return vo;
}
private StaffSessionVO toBaseStaffVO(Staff staff) {
StaffSessionVO vo = new StaffSessionVO(); StaffSessionVO vo = new StaffSessionVO();
vo.setStaffId(staff.getStaffId()); vo.setStaffId(staff.getStaffId());
vo.setName(staff.getName()); vo.setName(staff.getName());
@ -237,24 +236,41 @@ public class ArchiveSessionService {
vo.setStatus(staff.getStatus()); vo.setStatus(staff.getStatus());
vo.setCreatedAt(staff.getCreatedAt()); vo.setCreatedAt(staff.getCreatedAt());
vo.setUpdatedAt(staff.getUpdatedAt()); vo.setUpdatedAt(staff.getUpdatedAt());
return vo;
}
// 查询统计 private void fillStaffStats(List<StaffSessionVO> vos, List<String> staffIds) {
if (vos == null || vos.isEmpty() || staffIds == null || staffIds.isEmpty()) {
return;
}
try { try {
Long msgCount = archiveMessageMapper.selectCount( List<Map<String, Object>> statsList = archiveMessageMapper.selectStaffArchiveStats(corpId, staffIds);
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ArchiveMessage>() Map<String, Map<String, Object>> statsMap = statsList.stream()
.eq(ArchiveMessage::getCorpId, corpId) .collect(Collectors.toMap(m -> (String) m.get("staff_id"), m -> m, (a, b) -> a));
.eq(ArchiveMessage::getFromUser, staff.getStaffId())
); List<Map<String, Object>> custList = customerMapper.countCustomersByStaffIds(corpId, staffIds);
vo.setMsgCount(msgCount != null ? msgCount.intValue() : 0); Map<String, Integer> custMap = custList.stream()
vo.setTodayMsgCount(archiveMessageMapper.countTodayMsgByStaff(staff.getStaffId(), corpId)); .collect(Collectors.toMap(m -> (String) m.get("staff_id"), m -> ((Number) m.get("customer_count")).intValue(), (a, b) -> a));
vo.setCustomerCount(archiveMessageMapper.countCustomersByStaff(staff.getStaffId(), corpId));
Long lastMsgTime = archiveMessageMapper.selectLastMsgTimeByUser(staff.getStaffId(), corpId); for (StaffSessionVO vo : vos) {
Map<String, Object> stats = statsMap.get(vo.getStaffId());
if (stats != null) {
vo.setMsgCount(((Number) stats.get("msg_count")).intValue());
vo.setTodayMsgCount(((Number) stats.get("today_msg_count")).intValue());
Long lastMsgTime = stats.get("last_msg_time") instanceof Number ? ((Number) stats.get("last_msg_time")).longValue() : null;
vo.setLastMsgTime(lastMsgTime); vo.setLastMsgTime(lastMsgTime);
vo.setLastMsgTimeStr(formatTime(lastMsgTime)); vo.setLastMsgTimeStr(formatTime(lastMsgTime));
} catch (Exception e) { } else {
log.debug("查询员工统计失败: {}", e.getMessage()); vo.setMsgCount(0);
vo.setTodayMsgCount(0);
vo.setLastMsgTime(null);
vo.setLastMsgTimeStr(formatTime(null));
}
vo.setCustomerCount(custMap.getOrDefault(vo.getStaffId(), 0));
}
} catch (Exception e) {
log.warn("查询员工统计失败: {}", e.getMessage(), e);
} }
return vo;
} }
private CustomerSessionVO convertCustomerVO(Customer customer) { private CustomerSessionVO convertCustomerVO(Customer customer) {