feat: 增强管理端工作台用户活跃榜能力
- 新增用户活跃榜接口、筛选与导出能力 - 支持按智能体过滤排行榜并补充前后端测试
This commit is contained in:
@@ -1,15 +1,25 @@
|
||||
package tech.easyflow.admin.controller.dashboard;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardOverviewQuery;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardOverviewVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardUserRankItemVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardUserRankQuery;
|
||||
import tech.easyflow.admin.service.dashboard.DashboardService;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理端工作台统计接口。
|
||||
*/
|
||||
@@ -28,4 +38,26 @@ public class DashboardController {
|
||||
public Result<DashboardOverviewVo> overview(DashboardOverviewQuery query) {
|
||||
return Result.ok(dashboardService.getOverview(SaTokenUtil.getLoginAccount(), query));
|
||||
}
|
||||
|
||||
@GetMapping("/user-ranks")
|
||||
@SaCheckPermission("/api/v1/dashboard/query")
|
||||
public Result<List<DashboardUserRankItemVo>> userRanks(DashboardUserRankQuery query) {
|
||||
return Result.ok(dashboardService.getUserRanks(SaTokenUtil.getLoginAccount(), query));
|
||||
}
|
||||
|
||||
@GetMapping(value = "/user-ranks/export", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
@SaCheckPermission("/api/v1/dashboard/query")
|
||||
public void exportUserRanks(DashboardUserRankQuery query, HttpServletResponse response) throws Exception {
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
||||
String fileName = URLEncoder.encode("dashboard_user_ranks_" + timestamp, "UTF-8")
|
||||
.replaceAll("\\+", "%20");
|
||||
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
|
||||
dashboardService.exportUserRanks(
|
||||
SaTokenUtil.getLoginAccount(),
|
||||
query,
|
||||
response.getOutputStream()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ public class DashboardOverviewVo {
|
||||
|
||||
private List<DashboardDistributionItemVo> distribution;
|
||||
|
||||
private List<DashboardUserRankItemVo> userRanks;
|
||||
|
||||
private DashboardOverviewQuery query;
|
||||
|
||||
private Date updatedAt;
|
||||
@@ -64,14 +62,6 @@ public class DashboardOverviewVo {
|
||||
this.assistantTrends = assistantTrends;
|
||||
}
|
||||
|
||||
public List<DashboardUserRankItemVo> getUserRanks() {
|
||||
return userRanks;
|
||||
}
|
||||
|
||||
public void setUserRanks(List<DashboardUserRankItemVo> userRanks) {
|
||||
this.userRanks = userRanks;
|
||||
}
|
||||
|
||||
public DashboardOverviewQuery getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -9,14 +9,25 @@ public class DashboardUserRankItemVo {
|
||||
|
||||
private BigInteger userId;
|
||||
|
||||
/**
|
||||
* 最终展示名称。
|
||||
*/
|
||||
private String label;
|
||||
|
||||
/**
|
||||
* 登录账号。
|
||||
*/
|
||||
private String loginName;
|
||||
|
||||
/**
|
||||
* 昵称。
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
private Long sessionTotal;
|
||||
|
||||
private Long messageTotal;
|
||||
|
||||
private Long assistantTotal;
|
||||
|
||||
public BigInteger getUserId() {
|
||||
return userId;
|
||||
}
|
||||
@@ -33,6 +44,22 @@ public class DashboardUserRankItemVo {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getLoginName() {
|
||||
return loginName;
|
||||
}
|
||||
|
||||
public void setLoginName(String loginName) {
|
||||
this.loginName = loginName;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public void setNickname(String nickname) {
|
||||
this.nickname = nickname;
|
||||
}
|
||||
|
||||
public Long getSessionTotal() {
|
||||
return sessionTotal;
|
||||
}
|
||||
@@ -48,12 +75,4 @@ public class DashboardUserRankItemVo {
|
||||
public void setMessageTotal(Long messageTotal) {
|
||||
this.messageTotal = messageTotal;
|
||||
}
|
||||
|
||||
public Long getAssistantTotal() {
|
||||
return assistantTotal;
|
||||
}
|
||||
|
||||
public void setAssistantTotal(Long assistantTotal) {
|
||||
this.assistantTotal = assistantTotal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package tech.easyflow.admin.model.dashboard;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 用户活跃榜查询参数。
|
||||
*/
|
||||
public class DashboardUserRankQuery {
|
||||
|
||||
private String range;
|
||||
private String startDate;
|
||||
private String endDate;
|
||||
private BigInteger assistantId;
|
||||
|
||||
public String getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
public void setRange(String range) {
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
public String getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public void setStartDate(String startDate) {
|
||||
this.startDate = startDate;
|
||||
}
|
||||
|
||||
public String getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public void setEndDate(String endDate) {
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public BigInteger getAssistantId() {
|
||||
return assistantId;
|
||||
}
|
||||
|
||||
public void setAssistantId(BigInteger assistantId) {
|
||||
this.assistantId = assistantId;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,42 @@ package tech.easyflow.admin.service.dashboard;
|
||||
|
||||
import tech.easyflow.admin.model.dashboard.DashboardOverviewQuery;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardOverviewVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardUserRankItemVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardUserRankQuery;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作台统计服务。
|
||||
*/
|
||||
public interface DashboardService {
|
||||
|
||||
/**
|
||||
* 获取工作台总览。
|
||||
*
|
||||
* @param loginAccount 当前登录账号
|
||||
* @param query 查询条件
|
||||
* @return 工作台总览
|
||||
*/
|
||||
DashboardOverviewVo getOverview(LoginAccount loginAccount, DashboardOverviewQuery query);
|
||||
|
||||
/**
|
||||
* 获取用户活跃榜。
|
||||
*
|
||||
* @param loginAccount 当前登录账号
|
||||
* @param query 查询条件
|
||||
* @return 用户活跃榜
|
||||
*/
|
||||
List<DashboardUserRankItemVo> getUserRanks(LoginAccount loginAccount, DashboardUserRankQuery query);
|
||||
|
||||
/**
|
||||
* 导出用户活跃榜。
|
||||
*
|
||||
* @param loginAccount 当前登录账号
|
||||
* @param query 查询条件
|
||||
* @param outputStream 输出流
|
||||
*/
|
||||
void exportUserRanks(LoginAccount loginAccount, DashboardUserRankQuery query, OutputStream outputStream);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package tech.easyflow.admin.service.dashboard.impl;
|
||||
|
||||
import cn.idev.excel.EasyExcel;
|
||||
import cn.idev.excel.write.style.column.SimpleColumnWidthStyleStrategy;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.row.Db;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import tech.easyflow.ai.entity.Bot;
|
||||
import tech.easyflow.ai.service.BotService;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardChatStatusVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardAssistantTrendPointVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardAssistantTrendSeriesVo;
|
||||
@@ -15,6 +19,7 @@ import tech.easyflow.admin.model.dashboard.DashboardOverviewVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardSummaryVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardTrendItemVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardUserRankItemVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardUserRankQuery;
|
||||
import tech.easyflow.admin.service.dashboard.DashboardService;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatActiveUserRank;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatAssistantSessionTrend;
|
||||
@@ -25,13 +30,16 @@ import tech.easyflow.chatlog.service.ChatDashboardQueryService;
|
||||
import tech.easyflow.common.constant.Constants;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.entity.SysAccountRole;
|
||||
import tech.easyflow.system.entity.SysRole;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
import tech.easyflow.system.service.SysAccountService;
|
||||
import tech.easyflow.system.service.SysAccountRoleService;
|
||||
import tech.easyflow.system.service.SysRoleService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -58,6 +66,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
private static final int DEFAULT_ASSISTANT_RANK_LIMIT = 5;
|
||||
private static final int DEFAULT_ASSISTANT_TREND_LIMIT = 8;
|
||||
private static final int DEFAULT_USER_RANK_LIMIT = 5;
|
||||
private static final int USER_RANK_EXPORT_COLUMN_WIDTH = 20;
|
||||
|
||||
@Resource
|
||||
private SysAccountRoleService sysAccountRoleService;
|
||||
@@ -71,6 +80,12 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
@Resource
|
||||
private ChatDashboardQueryService chatDashboardQueryService;
|
||||
|
||||
@Resource
|
||||
private BotService botService;
|
||||
|
||||
@Resource
|
||||
private CategoryPermissionService categoryPermissionService;
|
||||
|
||||
/**
|
||||
* 获取工作台总览信息。
|
||||
*
|
||||
@@ -90,7 +105,6 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
result.setTrends(chatPayload.trends);
|
||||
result.setAssistantTrends(chatPayload.assistantTrends);
|
||||
result.setDistribution(chatPayload.distribution);
|
||||
result.setUserRanks(chatPayload.userRanks);
|
||||
|
||||
DashboardOverviewQuery normalizedQuery = new DashboardOverviewQuery();
|
||||
normalizedQuery.setRange(context.range);
|
||||
@@ -101,6 +115,37 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户活跃榜。
|
||||
*
|
||||
* @param loginAccount 当前登录账号
|
||||
* @param query 查询条件
|
||||
* @return 用户活跃榜
|
||||
*/
|
||||
@Override
|
||||
public List<DashboardUserRankItemVo> getUserRanks(LoginAccount loginAccount, DashboardUserRankQuery query) {
|
||||
DashboardQueryContext context = buildContext(loginAccount, query);
|
||||
return queryUserRanks(context, DEFAULT_USER_RANK_LIMIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用户活跃榜。
|
||||
*
|
||||
* @param loginAccount 当前登录账号
|
||||
* @param query 查询条件
|
||||
* @param outputStream 输出流
|
||||
*/
|
||||
@Override
|
||||
public void exportUserRanks(LoginAccount loginAccount, DashboardUserRankQuery query, OutputStream outputStream) {
|
||||
DashboardQueryContext context = buildContext(loginAccount, query);
|
||||
List<DashboardUserRankItemVo> userRanks = queryUserRanks(context, null);
|
||||
EasyExcel.write(outputStream)
|
||||
.head(buildUserRankExportHead())
|
||||
.registerWriteHandler(new SimpleColumnWidthStyleStrategy(USER_RANK_EXPORT_COLUMN_WIDTH))
|
||||
.sheet("用户活跃榜")
|
||||
.doWrite(buildUserRankExportRows(userRanks));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建顶部汇总卡片。
|
||||
*
|
||||
@@ -144,7 +189,6 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
chatStatus,
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>()
|
||||
);
|
||||
}
|
||||
@@ -176,14 +220,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
List<DashboardDistributionItemVo> distribution = buildAssistantDistribution(
|
||||
rawRanks.subList(0, Math.min(DEFAULT_ASSISTANT_RANK_LIMIT, rawRanks.size()))
|
||||
);
|
||||
List<ChatActiveUserRank> rawUserRanks = chatDashboardQueryService.queryActiveUserRanks(
|
||||
startDate,
|
||||
endDate,
|
||||
context.tenantFilterId,
|
||||
DEFAULT_USER_RANK_LIMIT
|
||||
);
|
||||
List<DashboardUserRankItemVo> userRanks = buildUserRanks(rawUserRanks);
|
||||
return new ChatDashboardPayload(chatStatus, trends, assistantTrends, distribution, userRanks);
|
||||
return new ChatDashboardPayload(chatStatus, trends, assistantTrends, distribution);
|
||||
} catch (Exception ex) {
|
||||
log.warn("加载工作台聊天统计失败,已降级为不可用状态,range={}, tenantId={}",
|
||||
context.range,
|
||||
@@ -199,7 +236,6 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
chatStatus,
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>()
|
||||
);
|
||||
}
|
||||
@@ -328,6 +364,27 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
return seriesList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户活跃排行。
|
||||
*
|
||||
* @param context 查询上下文
|
||||
* @param limit 返回条数,空表示全部
|
||||
* @return 用户活跃排行
|
||||
*/
|
||||
private List<DashboardUserRankItemVo> queryUserRanks(DashboardQueryContext context, Integer limit) {
|
||||
if (!chatDashboardQueryService.available()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<ChatActiveUserRank> rawUserRanks = chatDashboardQueryService.queryActiveUserRanks(
|
||||
context.startTime.toLocalDate(),
|
||||
context.endTime.toLocalDate(),
|
||||
context.tenantFilterId,
|
||||
context.assistantId,
|
||||
limit
|
||||
);
|
||||
return buildUserRanks(rawUserRanks);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用户活跃排行。
|
||||
*
|
||||
@@ -335,20 +392,62 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
* @return 页面排行项
|
||||
*/
|
||||
private List<DashboardUserRankItemVo> buildUserRanks(List<ChatActiveUserRank> ranks) {
|
||||
if (ranks == null || ranks.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<DashboardUserRankItemVo> items = new ArrayList<>(ranks.size());
|
||||
Map<BigInteger, String> displayNameMap = resolveUserDisplayNameMap(ranks);
|
||||
Map<BigInteger, AccountIdentitySnapshot> identityMap = resolveAccountIdentityMap(ranks);
|
||||
for (ChatActiveUserRank rank : ranks) {
|
||||
ResolvedUserIdentity identity = resolveUserIdentity(
|
||||
rank.userId(),
|
||||
rank.userAccount(),
|
||||
identityMap.get(rank.userId())
|
||||
);
|
||||
DashboardUserRankItemVo item = new DashboardUserRankItemVo();
|
||||
item.setUserId(rank.userId());
|
||||
item.setLabel(resolveUserLabel(rank.userId(), rank.userAccount(), displayNameMap));
|
||||
item.setLabel(identity.label);
|
||||
item.setLoginName(identity.loginName);
|
||||
item.setNickname(identity.nickname);
|
||||
item.setSessionTotal(rank.sessionTotal());
|
||||
item.setMessageTotal(rank.messageTotal());
|
||||
item.setAssistantTotal(rank.assistantTotal());
|
||||
items.add(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建导出表头。
|
||||
*
|
||||
* @return 表头
|
||||
*/
|
||||
private List<List<String>> buildUserRankExportHead() {
|
||||
List<List<String>> head = new ArrayList<>(4);
|
||||
head.add(List.of("登录账号"));
|
||||
head.add(List.of("昵称"));
|
||||
head.add(List.of("会话数"));
|
||||
head.add(List.of("消息数"));
|
||||
return head;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建导出数据行。
|
||||
*
|
||||
* @param userRanks 用户排行
|
||||
* @return 数据行
|
||||
*/
|
||||
private List<List<String>> buildUserRankExportRows(List<DashboardUserRankItemVo> userRanks) {
|
||||
List<List<String>> rows = new ArrayList<>(userRanks.size());
|
||||
for (DashboardUserRankItemVo item : userRanks) {
|
||||
List<String> row = new ArrayList<>(4);
|
||||
row.add(defaultIfBlank(item.getLoginName()));
|
||||
row.add(defaultIfBlank(item.getNickname()));
|
||||
row.add(String.valueOf(item.getSessionTotal() == null ? 0L : item.getSessionTotal()));
|
||||
row.add(String.valueOf(item.getMessageTotal() == null ? 0L : item.getMessageTotal()));
|
||||
rows.add(row);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按租户统计平台资源数量。
|
||||
*
|
||||
@@ -442,8 +541,47 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
* @return 查询上下文
|
||||
*/
|
||||
private DashboardQueryContext buildContext(LoginAccount loginAccount, DashboardOverviewQuery query) {
|
||||
return buildContext(
|
||||
loginAccount,
|
||||
query == null ? null : query.getRange(),
|
||||
query == null ? null : query.getStartDate(),
|
||||
query == null ? null : query.getEndDate()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用户榜查询上下文。
|
||||
*
|
||||
* @param loginAccount 当前登录账号
|
||||
* @param query 用户榜查询
|
||||
* @return 查询上下文
|
||||
*/
|
||||
private DashboardQueryContext buildContext(LoginAccount loginAccount, DashboardUserRankQuery query) {
|
||||
DashboardQueryContext context = buildContext(
|
||||
loginAccount,
|
||||
query == null ? null : query.getRange(),
|
||||
query == null ? null : query.getStartDate(),
|
||||
query == null ? null : query.getEndDate()
|
||||
);
|
||||
context.assistantId = validateAssistantId(loginAccount, query == null ? null : query.getAssistantId());
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建基础查询上下文。
|
||||
*
|
||||
* @param loginAccount 当前登录账号
|
||||
* @param range 时间范围
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @return 查询上下文
|
||||
*/
|
||||
private DashboardQueryContext buildContext(LoginAccount loginAccount,
|
||||
String range,
|
||||
String startDate,
|
||||
String endDate) {
|
||||
DashboardQueryContext context = new DashboardQueryContext();
|
||||
context.range = normalizeRange(query == null ? null : query.getRange());
|
||||
context.range = normalizeRange(range);
|
||||
context.superAdmin = isSuperAdmin(loginAccount);
|
||||
|
||||
LocalDate today = LocalDate.now(DEFAULT_ZONE_ID);
|
||||
@@ -463,8 +601,8 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
context.queryStartDate = today.minusDays(29).toString();
|
||||
context.queryEndDate = today.toString();
|
||||
} else {
|
||||
LocalDate customStartDate = parseRequiredDate(query == null ? null : query.getStartDate(), "开始日期不能为空");
|
||||
LocalDate customEndDate = parseRequiredDate(query == null ? null : query.getEndDate(), "结束日期不能为空");
|
||||
LocalDate customStartDate = parseRequiredDate(startDate, "开始日期不能为空");
|
||||
LocalDate customEndDate = parseRequiredDate(endDate, "结束日期不能为空");
|
||||
if (customStartDate.isAfter(customEndDate)) {
|
||||
throw new BusinessException("开始日期不能晚于结束日期");
|
||||
}
|
||||
@@ -645,7 +783,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
* @param ranks 活跃排行
|
||||
* @return 名称映射
|
||||
*/
|
||||
private Map<BigInteger, String> resolveUserDisplayNameMap(List<ChatActiveUserRank> ranks) {
|
||||
private Map<BigInteger, AccountIdentitySnapshot> resolveAccountIdentityMap(List<ChatActiveUserRank> ranks) {
|
||||
List<BigInteger> userIds = ranks.stream()
|
||||
.map(ChatActiveUserRank::userId)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
@@ -654,7 +792,24 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
if (userIds.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return sysAccountService.resolveDisplayNameMap(userIds);
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
.select(SysAccount::getId, SysAccount::getLoginName, SysAccount::getNickname)
|
||||
.in(SysAccount::getId, userIds);
|
||||
List<SysAccount> accounts = sysAccountService.list(queryWrapper);
|
||||
Map<BigInteger, AccountIdentitySnapshot> identityMap = new HashMap<>(userIds.size());
|
||||
if (accounts == null) {
|
||||
return identityMap;
|
||||
}
|
||||
for (SysAccount account : accounts) {
|
||||
if (account == null || account.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
identityMap.put(
|
||||
account.getId(),
|
||||
new AccountIdentitySnapshot(trimToNull(account.getLoginName()), trimToNull(account.getNickname()))
|
||||
);
|
||||
}
|
||||
return identityMap;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -676,20 +831,75 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
* @param userAccount 聊天侧账号快照
|
||||
* @param displayNameMap 系统账号名称映射
|
||||
* @return 展示名称
|
||||
* @param snapshot 系统账号快照
|
||||
* @return 用户身份
|
||||
*/
|
||||
private String resolveUserLabel(BigInteger userId, String userAccount, Map<BigInteger, String> displayNameMap) {
|
||||
if (userId != null) {
|
||||
String displayName = displayNameMap.get(userId);
|
||||
if (StringUtils.hasText(displayName) && !displayName.equals(userId.toString())) {
|
||||
return displayName;
|
||||
private ResolvedUserIdentity resolveUserIdentity(BigInteger userId,
|
||||
String userAccount,
|
||||
AccountIdentitySnapshot snapshot) {
|
||||
String loginName = snapshot == null ? null : snapshot.loginName;
|
||||
String nickname = snapshot == null ? null : snapshot.nickname;
|
||||
String trimmedUserAccount = trimToNull(userAccount);
|
||||
if (StringUtils.hasText(loginName)) {
|
||||
if (StringUtils.hasText(nickname)) {
|
||||
return new ResolvedUserIdentity(loginName, nickname, loginName + "(" + nickname + ")");
|
||||
}
|
||||
return new ResolvedUserIdentity(loginName, nickname, loginName);
|
||||
}
|
||||
if (StringUtils.hasText(userAccount)) {
|
||||
return userAccount.trim();
|
||||
if (StringUtils.hasText(trimmedUserAccount)) {
|
||||
return new ResolvedUserIdentity(trimmedUserAccount, nickname, trimmedUserAccount);
|
||||
}
|
||||
return userId == null ? "用户-未知" : "用户-" + userId;
|
||||
return new ResolvedUserIdentity(null, nickname, userId == null ? "用户-未知" : "用户-" + userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验智能体筛选条件。
|
||||
*
|
||||
* @param loginAccount 当前登录账号
|
||||
* @param assistantId 智能体 ID
|
||||
* @return 规范化后的智能体 ID
|
||||
*/
|
||||
private BigInteger validateAssistantId(LoginAccount loginAccount, BigInteger assistantId) {
|
||||
if (assistantId == null) {
|
||||
return null;
|
||||
}
|
||||
Bot bot = botService.getById(assistantId);
|
||||
if (bot == null || !Integer.valueOf(1).equals(bot.getStatus())) {
|
||||
throw new BusinessException("聊天助手不存在或未启用");
|
||||
}
|
||||
boolean visible = categoryPermissionService.canAccessCategory(
|
||||
loginAccount,
|
||||
"BOT",
|
||||
bot.getCreatedBy(),
|
||||
bot.getCategoryId()
|
||||
);
|
||||
if (!visible) {
|
||||
throw new BusinessException("聊天助手不存在或未启用");
|
||||
}
|
||||
return assistantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 归一化文本。
|
||||
*
|
||||
* @param value 原始文本
|
||||
* @return 去空白后的文本
|
||||
*/
|
||||
private String trimToNull(String value) {
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 把空文本转为空串。
|
||||
*
|
||||
* @param value 原始文本
|
||||
* @return 输出文本
|
||||
*/
|
||||
private String defaultIfBlank(String value) {
|
||||
return StringUtils.hasText(value) ? value.trim() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -699,6 +909,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
private String range;
|
||||
private BigInteger tenantFilterId;
|
||||
private BigInteger deptFilterId;
|
||||
private BigInteger assistantId;
|
||||
private boolean superAdmin;
|
||||
private LocalDateTime startTime;
|
||||
private LocalDateTime endTime;
|
||||
@@ -719,6 +930,34 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统账号身份快照。
|
||||
*/
|
||||
private static class AccountIdentitySnapshot {
|
||||
private final String loginName;
|
||||
private final String nickname;
|
||||
|
||||
private AccountIdentitySnapshot(String loginName, String nickname) {
|
||||
this.loginName = loginName;
|
||||
this.nickname = nickname;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户展示身份。
|
||||
*/
|
||||
private static class ResolvedUserIdentity {
|
||||
private final String loginName;
|
||||
private final String nickname;
|
||||
private final String label;
|
||||
|
||||
private ResolvedUserIdentity(String loginName, String nickname, String label) {
|
||||
this.loginName = loginName;
|
||||
this.nickname = nickname;
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天统计页面载荷。
|
||||
*/
|
||||
@@ -726,8 +965,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
DashboardChatStatusVo chatStatus,
|
||||
List<DashboardTrendItemVo> trends,
|
||||
List<DashboardAssistantTrendSeriesVo> assistantTrends,
|
||||
List<DashboardDistributionItemVo> distribution,
|
||||
List<DashboardUserRankItemVo> userRanks
|
||||
List<DashboardDistributionItemVo> distribution
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package tech.easyflow.admin.service.dashboard.impl;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
import tech.easyflow.ai.entity.Bot;
|
||||
import tech.easyflow.ai.service.BotService;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardAssistantTrendSeriesVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardDistributionItemVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardOverviewQuery;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardSummaryVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardTrendItemVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardUserRankItemVo;
|
||||
import tech.easyflow.admin.model.dashboard.DashboardUserRankQuery;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatActiveUserRank;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatAssistantSessionTrend;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatAssistantUsageRank;
|
||||
@@ -15,8 +20,15 @@ import tech.easyflow.chatlog.domain.dto.ChatDashboardSummary;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatDashboardTrend;
|
||||
import tech.easyflow.chatlog.service.ChatDashboardQueryService;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
import tech.easyflow.system.service.SysAccountService;
|
||||
import tech.easyflow.system.service.SysAccountRoleService;
|
||||
import tech.easyflow.system.service.SysRoleService;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -25,13 +37,15 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -71,7 +85,7 @@ public class DashboardServiceImplTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 today 返回 24 个小时点位,且排行名称与均值回退正确。
|
||||
* 验证 today 返回 24 个小时点位,且 overview 不再触发用户榜查询。
|
||||
*
|
||||
* @throws Exception 反射调用失败
|
||||
*/
|
||||
@@ -91,12 +105,7 @@ public class DashboardServiceImplTest {
|
||||
.thenReturn(List.of(new ChatAssistantUsageRank(BigInteger.ONE, "", 2L, 3L, 9L)));
|
||||
when(chatDashboardQueryService.queryAssistantHourlyTrends(any(), any(), any(), any()))
|
||||
.thenReturn(List.of(new ChatAssistantSessionTrend(BigInteger.ONE, "", currentHourKey, 3L)));
|
||||
when(chatDashboardQueryService.queryActiveUserRanks(any(), any(), any(), any(Integer.class)))
|
||||
.thenReturn(List.of(new ChatActiveUserRank(BigInteger.valueOf(2), "demo-user", 3L, 9L, 1L)));
|
||||
SysAccountService sysAccountService = mock(SysAccountService.class);
|
||||
when(sysAccountService.resolveDisplayNameMap(any())).thenReturn(Map.of(BigInteger.valueOf(2), "演示用户"));
|
||||
setField(service, "chatDashboardQueryService", chatDashboardQueryService);
|
||||
setField(service, "sysAccountService", sysAccountService);
|
||||
|
||||
Object context = newContext("today", BigInteger.valueOf(9));
|
||||
DashboardSummaryVo summary = new DashboardSummaryVo();
|
||||
@@ -107,7 +116,6 @@ public class DashboardServiceImplTest {
|
||||
List<DashboardAssistantTrendSeriesVo> assistantTrends =
|
||||
(List<DashboardAssistantTrendSeriesVo>) readField(payload, "assistantTrends");
|
||||
List<DashboardDistributionItemVo> distribution = (List<DashboardDistributionItemVo>) readField(payload, "distribution");
|
||||
List<DashboardUserRankItemVo> userRanks = (List<DashboardUserRankItemVo>) readField(payload, "userRanks");
|
||||
|
||||
Assert.assertTrue(Boolean.TRUE.equals(invokeGetter(chatStatus, "getAvailable")));
|
||||
Assert.assertEquals(trends.size(), 24);
|
||||
@@ -132,40 +140,31 @@ public class DashboardServiceImplTest {
|
||||
Assert.assertEquals(distribution.get(0).getLabel(), "智能体-1");
|
||||
Assert.assertEquals(distribution.get(0).getAvgMessagePerSession(), Double.valueOf(3D));
|
||||
Assert.assertEquals(distribution.get(0).getAvgSessionPerUser(), Double.valueOf(1.5D));
|
||||
Assert.assertEquals(userRanks.get(0).getLabel(), "演示用户");
|
||||
Assert.assertEquals(userRanks.get(0).getAssistantTotal(), Long.valueOf(1L));
|
||||
verify(chatDashboardQueryService, never()).queryActiveUserRanks(any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证当系统账号名称仅回退为纯 ID 时,仍优先继续回退到聊天侧账号。
|
||||
*
|
||||
* @throws Exception 反射调用失败
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void shouldFallbackToUserAccountWhenSystemDisplayNameIsOnlyId() throws Exception {
|
||||
DashboardServiceImpl service = new DashboardServiceImpl();
|
||||
ChatDashboardQueryService chatDashboardQueryService = mock(ChatDashboardQueryService.class);
|
||||
when(chatDashboardQueryService.available()).thenReturn(true);
|
||||
when(chatDashboardQueryService.querySummary(any(), any(), any()))
|
||||
.thenReturn(new ChatDashboardSummary(1L, 1L, 1L, 1L));
|
||||
when(chatDashboardQueryService.queryHourlyTrends(any(), any(), any()))
|
||||
.thenReturn(List.of());
|
||||
when(chatDashboardQueryService.queryAssistantUsageRanks(any(), any(), any(), any(Integer.class)))
|
||||
.thenReturn(List.of());
|
||||
when(chatDashboardQueryService.queryActiveUserRanks(any(), any(), any(), any(Integer.class)))
|
||||
when(chatDashboardQueryService.queryActiveUserRanks(any(), any(), any(), any(), any()))
|
||||
.thenReturn(List.of(new ChatActiveUserRank(BigInteger.valueOf(9), "chat-user", 1L, 1L, 1L)));
|
||||
SysAccountService sysAccountService = mock(SysAccountService.class);
|
||||
when(sysAccountService.resolveDisplayNameMap(any())).thenReturn(Map.of(BigInteger.valueOf(9), "9"));
|
||||
when(sysAccountService.list(any(QueryWrapper.class))).thenReturn(List.of(buildSysAccount(9L, "", "仅昵称")));
|
||||
setField(service, "chatDashboardQueryService", chatDashboardQueryService);
|
||||
setField(service, "sysAccountService", sysAccountService);
|
||||
|
||||
Object context = newContext("today", BigInteger.ONE);
|
||||
DashboardSummaryVo summary = new DashboardSummaryVo();
|
||||
Object payload = invokeBuildChatPayload(service, context, summary);
|
||||
DashboardUserRankQuery query = new DashboardUserRankQuery();
|
||||
query.setRange("7d");
|
||||
|
||||
List<DashboardUserRankItemVo> userRanks = (List<DashboardUserRankItemVo>) readField(payload, "userRanks");
|
||||
List<DashboardUserRankItemVo> userRanks = service.getUserRanks(new LoginAccount(), query);
|
||||
Assert.assertEquals(userRanks.get(0).getLabel(), "chat-user");
|
||||
Assert.assertEquals(userRanks.get(0).getLoginName(), "chat-user");
|
||||
Assert.assertEquals(userRanks.get(0).getNickname(), "仅昵称");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,12 +193,7 @@ public class DashboardServiceImplTest {
|
||||
new ChatAssistantSessionTrend(BigInteger.ONE, "助手-A", LocalDate.now().toString(), 4L),
|
||||
new ChatAssistantSessionTrend(null, "未知助手", LocalDate.now().minusDays(3).toString(), 2L)
|
||||
));
|
||||
when(chatDashboardQueryService.queryActiveUserRanks(any(), any(), any(), any(Integer.class)))
|
||||
.thenReturn(List.of());
|
||||
SysAccountService sysAccountService = mock(SysAccountService.class);
|
||||
when(sysAccountService.resolveDisplayNameMap(any())).thenReturn(Map.of());
|
||||
setField(service, "chatDashboardQueryService", chatDashboardQueryService);
|
||||
setField(service, "sysAccountService", sysAccountService);
|
||||
|
||||
Object context = newContext("7d", BigInteger.ONE);
|
||||
DashboardSummaryVo summary = new DashboardSummaryVo();
|
||||
@@ -238,10 +232,7 @@ public class DashboardServiceImplTest {
|
||||
.thenReturn(List.of(new ChatDashboardTrend(currentHourKey, 2L, 6L, 1L)));
|
||||
when(chatDashboardQueryService.queryAssistantUsageRanks(any(), any(), any(), any(Integer.class)))
|
||||
.thenReturn(List.of());
|
||||
when(chatDashboardQueryService.queryActiveUserRanks(any(), any(), any(), any(Integer.class)))
|
||||
.thenReturn(List.of());
|
||||
setField(service, "chatDashboardQueryService", chatDashboardQueryService);
|
||||
setField(service, "sysAccountService", mock(SysAccountService.class));
|
||||
|
||||
Object context = newContext(
|
||||
"custom",
|
||||
@@ -321,10 +312,7 @@ public class DashboardServiceImplTest {
|
||||
new ChatAssistantSessionTrend(BigInteger.ONE, "助手-1", startDate.toString(), 2L),
|
||||
new ChatAssistantSessionTrend(BigInteger.ONE, "助手-1", endDate.toString(), 4L)
|
||||
));
|
||||
when(chatDashboardQueryService.queryActiveUserRanks(any(), any(), any(), any(Integer.class)))
|
||||
.thenReturn(List.of());
|
||||
setField(service, "chatDashboardQueryService", chatDashboardQueryService);
|
||||
setField(service, "sysAccountService", mock(SysAccountService.class));
|
||||
|
||||
Object context = newContext("30d", BigInteger.ONE);
|
||||
DashboardSummaryVo summary = new DashboardSummaryVo();
|
||||
@@ -344,6 +332,150 @@ public class DashboardServiceImplTest {
|
||||
verify(chatDashboardQueryService).queryAssistantUsageRanks(any(), any(), any(), eq(8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户榜筛选会透传 assistantId。
|
||||
*/
|
||||
@Test
|
||||
public void shouldQueryUserRanksWithAssistantFilter() throws Exception {
|
||||
DashboardServiceImpl service = new DashboardServiceImpl();
|
||||
ChatDashboardQueryService chatDashboardQueryService = mock(ChatDashboardQueryService.class);
|
||||
BotService botService = mock(BotService.class);
|
||||
CategoryPermissionService categoryPermissionService = mock(CategoryPermissionService.class);
|
||||
SysAccountService sysAccountService = mock(SysAccountService.class);
|
||||
SysAccountRoleService sysAccountRoleService = mock(SysAccountRoleService.class);
|
||||
SysRoleService sysRoleService = mock(SysRoleService.class);
|
||||
|
||||
Bot bot = new Bot();
|
||||
bot.setId(BigInteger.TEN);
|
||||
bot.setStatus(1);
|
||||
bot.setCreatedBy(BigInteger.ONE);
|
||||
bot.setCategoryId(BigInteger.valueOf(8));
|
||||
|
||||
when(chatDashboardQueryService.available()).thenReturn(true);
|
||||
when(chatDashboardQueryService.queryActiveUserRanks(any(), any(), any(), eq(BigInteger.TEN), eq(5)))
|
||||
.thenReturn(List.of(new ChatActiveUserRank(BigInteger.valueOf(2), "demo-user", 2L, 4L, 1L)));
|
||||
when(botService.getById(BigInteger.TEN)).thenReturn(bot);
|
||||
when(categoryPermissionService.canAccessCategory(any(LoginAccount.class), eq("BOT"), eq(BigInteger.ONE), eq(BigInteger.valueOf(8))))
|
||||
.thenReturn(true);
|
||||
when(sysAccountService.list(any(QueryWrapper.class))).thenReturn(List.of(buildSysAccount(2L, "demo-user", "演示用户")));
|
||||
when(sysAccountRoleService.list(any(QueryWrapper.class))).thenReturn(Collections.emptyList());
|
||||
|
||||
setField(service, "chatDashboardQueryService", chatDashboardQueryService);
|
||||
setField(service, "botService", botService);
|
||||
setField(service, "categoryPermissionService", categoryPermissionService);
|
||||
setField(service, "sysAccountService", sysAccountService);
|
||||
setField(service, "sysAccountRoleService", sysAccountRoleService);
|
||||
setField(service, "sysRoleService", sysRoleService);
|
||||
|
||||
DashboardUserRankQuery query = new DashboardUserRankQuery();
|
||||
query.setRange("7d");
|
||||
query.setAssistantId(BigInteger.TEN);
|
||||
|
||||
LoginAccount loginAccount = new LoginAccount();
|
||||
loginAccount.setId(BigInteger.valueOf(12));
|
||||
loginAccount.setTenantId(BigInteger.valueOf(33));
|
||||
|
||||
List<DashboardUserRankItemVo> userRanks = service.getUserRanks(loginAccount, query);
|
||||
Assert.assertEquals(userRanks.size(), 1);
|
||||
Assert.assertEquals(userRanks.get(0).getLabel(), "demo-user(演示用户)");
|
||||
verify(chatDashboardQueryService).queryActiveUserRanks(any(), any(), any(), eq(BigInteger.TEN), eq(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证未启用智能体会被拒绝。
|
||||
*/
|
||||
@Test(expectedExceptions = BusinessException.class, expectedExceptionsMessageRegExp = "聊天助手不存在或未启用")
|
||||
public void shouldRejectDisabledAssistantFilter() {
|
||||
DashboardServiceImpl service = new DashboardServiceImpl();
|
||||
BotService botService = mock(BotService.class);
|
||||
ChatDashboardQueryService chatDashboardQueryService = mock(ChatDashboardQueryService.class);
|
||||
|
||||
Bot bot = new Bot();
|
||||
bot.setId(BigInteger.TEN);
|
||||
bot.setStatus(0);
|
||||
|
||||
when(botService.getById(BigInteger.TEN)).thenReturn(bot);
|
||||
when(chatDashboardQueryService.available()).thenReturn(true);
|
||||
|
||||
setFieldSilently(service, "botService", botService);
|
||||
setFieldSilently(service, "chatDashboardQueryService", chatDashboardQueryService);
|
||||
setFieldSilently(service, "categoryPermissionService", mock(CategoryPermissionService.class));
|
||||
setFieldSilently(service, "sysAccountService", mock(SysAccountService.class));
|
||||
|
||||
DashboardUserRankQuery query = new DashboardUserRankQuery();
|
||||
query.setRange("7d");
|
||||
query.setAssistantId(BigInteger.TEN);
|
||||
service.getUserRanks(new LoginAccount(), query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证当前作用域不可见的智能体会被拒绝。
|
||||
*/
|
||||
@Test(expectedExceptions = BusinessException.class, expectedExceptionsMessageRegExp = "聊天助手不存在或未启用")
|
||||
public void shouldRejectInvisibleAssistantFilter() {
|
||||
DashboardServiceImpl service = new DashboardServiceImpl();
|
||||
BotService botService = mock(BotService.class);
|
||||
ChatDashboardQueryService chatDashboardQueryService = mock(ChatDashboardQueryService.class);
|
||||
CategoryPermissionService categoryPermissionService = mock(CategoryPermissionService.class);
|
||||
|
||||
Bot bot = new Bot();
|
||||
bot.setId(BigInteger.TEN);
|
||||
bot.setStatus(1);
|
||||
bot.setCreatedBy(BigInteger.ONE);
|
||||
bot.setCategoryId(BigInteger.valueOf(8));
|
||||
|
||||
when(botService.getById(BigInteger.TEN)).thenReturn(bot);
|
||||
when(chatDashboardQueryService.available()).thenReturn(true);
|
||||
when(categoryPermissionService.canAccessCategory(any(LoginAccount.class), eq("BOT"), eq(BigInteger.ONE), eq(BigInteger.valueOf(8))))
|
||||
.thenReturn(false);
|
||||
|
||||
setFieldSilently(service, "botService", botService);
|
||||
setFieldSilently(service, "chatDashboardQueryService", chatDashboardQueryService);
|
||||
setFieldSilently(service, "categoryPermissionService", categoryPermissionService);
|
||||
setFieldSilently(service, "sysAccountService", mock(SysAccountService.class));
|
||||
|
||||
DashboardUserRankQuery query = new DashboardUserRankQuery();
|
||||
query.setRange("7d");
|
||||
query.setAssistantId(BigInteger.TEN);
|
||||
service.getUserRanks(new LoginAccount(), query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证导出结果包含表头与账号昵称列。
|
||||
*/
|
||||
@Test
|
||||
public void shouldExportDashboardUserRanksWithSeparatedIdentityColumns() throws Exception {
|
||||
DashboardServiceImpl service = new DashboardServiceImpl();
|
||||
ChatDashboardQueryService chatDashboardQueryService = mock(ChatDashboardQueryService.class);
|
||||
SysAccountService sysAccountService = mock(SysAccountService.class);
|
||||
|
||||
when(chatDashboardQueryService.available()).thenReturn(true);
|
||||
when(chatDashboardQueryService.queryActiveUserRanks(any(), any(), any(), isNull(), isNull()))
|
||||
.thenReturn(List.of(new ChatActiveUserRank(BigInteger.valueOf(7), "export-user", 3L, 6L, 1L)));
|
||||
when(sysAccountService.list(any(QueryWrapper.class))).thenReturn(List.of(buildSysAccount(7L, "export-user", "导出演示")));
|
||||
|
||||
setField(service, "chatDashboardQueryService", chatDashboardQueryService);
|
||||
setField(service, "sysAccountService", sysAccountService);
|
||||
|
||||
DashboardUserRankQuery query = new DashboardUserRankQuery();
|
||||
query.setRange("7d");
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
service.exportUserRanks(new LoginAccount(), query, outputStream);
|
||||
|
||||
try (org.apache.poi.ss.usermodel.Workbook workbook =
|
||||
WorkbookFactory.create(new ByteArrayInputStream(outputStream.toByteArray()))) {
|
||||
org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(0);
|
||||
Assert.assertEquals(sheet.getRow(0).getCell(0).getStringCellValue(), "登录账号");
|
||||
Assert.assertEquals(sheet.getRow(0).getCell(1).getStringCellValue(), "昵称");
|
||||
Assert.assertEquals(sheet.getRow(0).getCell(2).getStringCellValue(), "会话数");
|
||||
Assert.assertEquals(sheet.getRow(0).getCell(3).getStringCellValue(), "消息数");
|
||||
Assert.assertEquals(sheet.getRow(1).getCell(0).getStringCellValue(), "export-user");
|
||||
Assert.assertEquals(sheet.getRow(1).getCell(1).getStringCellValue(), "导出演示");
|
||||
Assert.assertEquals(sheet.getRow(1).getCell(2).getStringCellValue(), "3");
|
||||
Assert.assertEquals(sheet.getRow(1).getCell(3).getStringCellValue(), "6");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造查询上下文。
|
||||
*
|
||||
@@ -475,4 +607,20 @@ public class DashboardServiceImplTest {
|
||||
}
|
||||
throw new IllegalArgumentException("未找到字段: " + fieldName);
|
||||
}
|
||||
|
||||
private void setFieldSilently(Object target, String fieldName, Object value) {
|
||||
try {
|
||||
setField(target, fieldName, value);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private SysAccount buildSysAccount(long id, String loginName, String nickname) {
|
||||
SysAccount account = new SysAccount();
|
||||
account.setId(BigInteger.valueOf(id));
|
||||
account.setLoginName(loginName);
|
||||
account.setNickname(nickname);
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user