@@ -1,17 +1,22 @@
package tech.easyflow.admin.service.dashboard.impl ;
import com.easyagents.flow.core.chain.ChainStatus ;
import com.mybatisflex.core.query.QueryWrapper ;
import com.mybatisflex.core.row.Db ;
import com.mybatisflex.core.row.Row ;
import org.springframework.stereotype.Service ;
import org.springframework.util.StringUtils ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import tech.easyflow.admin.model.dashboard.DashboardChatStatusVo ;
import tech.easyflow.admin.model.dashboard.DashboardDistributionItemVo ;
import tech.easyflow.admin.model.dashboard.DashboardOverviewQuery ;
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.service.dashboard.DashboardService ;
import tech.easyflow.chatlog.domain.dto.ChatAssistantUsageRank ;
import tech.easyflow.chatlog.domain.dto.ChatDashboardSummary ;
import tech.easyflow.chatlog.domain.dto.ChatDashboardTrend ;
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 ;
@@ -40,24 +45,38 @@ import java.util.stream.Collectors;
@Service
public class DashboardServiceImpl implements DashboardService {
private static final Logger log = LoggerFactory . getLogger ( DashboardServiceImpl . class ) ;
private static final ZoneId DEFAULT_ZONE_ID = ZoneId . systemDefault ( ) ;
private static final String CHAT_UNAVAILABLE_MESSAGE = " 聊天数据不可用 " ;
private static final int DEFAULT_ASSISTANT_RANK_LIMIT = 5 ;
@Resource
private SysAccountRoleService sysAccountRoleService ;
@Resource
private SysRoleService sysRoleService ;
@Resource
private ChatDashboardQueryService chatDashboardQueryService ;
/**
* 获取工作台总览信息。
*
* @param loginAccount 当前登录账号
* @param query 查询条件
* @return 工作台总览
*/
@Override
public DashboardOverviewVo getOverview ( LoginAccount loginAccount , DashboardOverviewQuery query ) {
DashboardQueryContext context = buildContext ( loginAccount , query ) ;
DashboardSummaryVo summary = buildSummary ( context ) ;
List < DashboardTrendItemVo > trends = buildTrends ( context ) ;
List < DashboardDistributionItemVo > distribution = buildDistribution ( context , summary ) ;
ChatDashboardPayload chatPayload = buildChatPayload ( context , summary );
DashboardOverviewVo result = new DashboardOverviewVo ( ) ;
result . setSummary ( summary ) ;
result . setTrends ( trend s ) ;
result . setDistribution ( distribution ) ;
result . setChatStatus ( chatPayload . chatStatu s ) ;
result . setTrends ( chatPayload . trends ) ;
result . setDistribution ( chatPayload . distribution ) ;
DashboardOverviewQuery normalizedQuery = new DashboardOverviewQuery ( ) ;
normalizedQuery . setRange ( context . range ) ;
@@ -66,6 +85,12 @@ public class DashboardServiceImpl implements DashboardService {
return result ;
}
/**
* 构建顶部汇总卡片。
*
* @param context 查询上下文
* @return 汇总结果
*/
private DashboardSummaryVo buildSummary ( DashboardQueryContext context ) {
DashboardSummaryVo summary = new DashboardSummaryVo ( ) ;
summary . setUserTotal ( countScopedTable ( " tb_sys_account " , " a " , true , context ) ) ;
@@ -73,72 +98,127 @@ public class DashboardServiceImpl implements DashboardService {
summary . setBotTotal ( countScopedTable ( " tb_bot " , " b " , false , context ) ) ;
summary . setWorkflowTotal ( countScopedTable ( " tb_workflow " , " w " , false , context ) ) ;
summary . setKnowledgeBaseTotal ( countScopedTable ( " tb_document_collection " , " d " , false , context ) ) ;
summary . setChatMessageTotal ( 0L ) ;
summary . setChatSessionTotal ( 0L ) ;
summary . setActiveAssistantTotal ( 0L ) ;
return summary ;
}
private List < DashboardTrendItemVo > buildTrends ( DashboardQueryContext context ) {
List < TimeBucket > buckets = buildBuckets ( context . range ) ;
String bucketFormat = " today " . equals ( context . range ) ? " %Y-%m-%d %H:00:00 " : " %Y-%m-%d " ;
/**
* 构建聊天统计载荷。
*
* @param context 查询上下文
* @param summary 汇总结果
* @return 聊天统计载荷
*/
private ChatDashboardPayload buildChatPayload ( DashboardQueryContext context , DashboardSummaryVo summary ) {
DashboardChatStatusVo chatStatus = new DashboardChatStatusVo ( ) ;
chatStatus . setAvailable ( Boolean . TRUE ) ;
chatStatus . setMessage ( " " ) ;
Map < String , Long > activeUserMap = queryActiveUserTrend ( context , bucketFormat ) ;
if ( ! chatDashboardQueryService . available ( ) ) {
chatStatus . setAvailable ( Boolean . FALSE ) ;
chatStatus . setMessage ( CHAT_UNAVAILABLE_MESSAGE ) ;
summary . setChatMessageTotal ( 0L ) ;
summary . setChatSessionTotal ( 0L ) ;
summary . setActiveAssistantTotal ( 0L ) ;
return new ChatDashboardPayload ( chatStatus , new ArrayList < > ( ) , new ArrayList < > ( ) ) ;
}
LocalDate startDate = context . startTime . toLocalDate ( ) ;
LocalDate endDate = context . endTime . toLocalDate ( ) ;
try {
ChatDashboardSummary chatSummary = chatDashboardQueryService . querySummary ( startDate , endDate , context . tenantFilterId ) ;
summary . setChatMessageTotal ( chatSummary . messageTotal ( ) ) ;
summary . setChatSessionTotal ( chatSummary . sessionTotal ( ) ) ;
summary . setActiveAssistantTotal ( chatSummary . activeAssistantTotal ( ) ) ;
List < ChatDashboardTrend > rawTrends = " today " . equals ( context . range )
? chatDashboardQueryService . queryHourlyTrends ( context . startTime , context . endTime , context . tenantFilterId )
: chatDashboardQueryService . queryTrends ( startDate , endDate , context . tenantFilterId ) ;
List < DashboardTrendItemVo > trends = buildTrendItems ( context . range , rawTrends ) ;
List < ChatAssistantUsageRank > rawRanks = chatDashboardQueryService . queryAssistantUsageRanks (
startDate ,
endDate ,
context . tenantFilterId ,
DEFAULT_ASSISTANT_RANK_LIMIT
) ;
List < DashboardDistributionItemVo > distribution = buildAssistantDistribution ( rawRanks ) ;
return new ChatDashboardPayload ( chatStatus , trends , distribution ) ;
} catch ( Exception ex ) {
log . warn ( " 加载工作台聊天统计失败, 已降级为不可用状态, range={}, tenantId={} " ,
context . range ,
context . tenantFilterId ,
ex ) ;
chatStatus . setAvailable ( Boolean . FALSE ) ;
chatStatus . setMessage ( CHAT_UNAVAILABLE_MESSAGE ) ;
summary . setChatMessageTotal ( 0L ) ;
summary . setChatSessionTotal ( 0L ) ;
summary . setActiveAssistantTotal ( 0L ) ;
return new ChatDashboardPayload ( chatStatus , new ArrayList < > ( ) , new ArrayList < > ( ) ) ;
}
}
/**
* 构建聊天趋势项,缺失日期补 0。
*
* @param range 时间范围
* @param rawTrends 原始趋势
* @return 趋势项
*/
private List < DashboardTrendItemVo > buildTrendItems ( String range , List < ChatDashboardTrend > rawTrends ) {
List < TimeBucket > buckets = buildBuckets ( range ) ;
Map < String , ChatDashboardTrend > trendMap = new HashMap < > ( ) ;
for ( ChatDashboardTrend rawTrend : rawTrends ) {
trendMap . put ( rawTrend . bucketKey ( ) , rawTrend ) ;
}
List < DashboardTrendItemVo > items = new ArrayList < > ( buckets . size ( ) ) ;
for ( TimeBucket bucket : buckets ) {
long activeUserTotal = activeUser Map. getOrDefault ( bucket . key , 0L );
ChatDashboardTrend trend = trend Map. get ( bucket . key ) ;
DashboardTrendItemVo item = new DashboardTrendItemVo ( ) ;
item . setKey ( bucket . key ) ;
item . setLabel ( bucket . label ) ;
item . setActiveUserTotal ( activeUserTotal ) ;
item . setActiveUserTotal ( 0L ) ;
item . setChatMessageTotal ( trend = = null ? 0L : trend . messageTotal ( ) ) ;
item . setChatSessionTotal ( trend = = null ? 0L : trend . sessionTotal ( ) ) ;
items . add ( item ) ;
}
return items ;
}
private List < DashboardDistributionItemVo > buildDistribution ( DashboardQueryContext context , DashboardSummaryVo summary ) {
return buildResourceDistribution ( summary ) ;
}
private List < DashboardDistributionItemVo > buildResourceDistribution ( DashboardSummaryVo summary ) {
List < DashboardDistributionItemVo > items = new ArrayList < > ( ) ;
items . add ( buildPlatformItem ( " userTotal " , " 用户总量 " , summary . getUserTotal ( ) ) ) ;
items . add ( buildPlatformItem ( " activeUserTotal " , " 活跃用户 " , summary . getActiveUserTotal ( ) ) ) ;
items . add ( buildPlatformItem ( " botTotal " , " 助手数量 " , summary . getBotTotal ( ) ) ) ;
items . add ( buildPlatformItem ( " workflowTotal " , " 工作流数量 " , summary . getWorkflowTotal ( ) ) ) ;
items . add ( buildPlatformItem ( " knowledgeBaseTotal " , " 知识库数量 " , summary . getKnowledgeBaseTotal ( ) ) ) ;
/**
* 构建智能体使用排行。
*
* @param ranks 原始排行数据
* @return 页面排行项
*/
private List < DashboardDistributionItemVo > buildAssistantDistribution ( List < ChatAssistantUsageRank > ranks ) {
List < DashboardDistributionItemVo > items = new ArrayList < > ( ranks . size ( ) ) ;
for ( ChatAssistantUsageRank rank : ranks ) {
DashboardDistributionItemVo item = new DashboardDistributionItemVo ( ) ;
item . setKey ( rank . assistantId ( ) = = null ? " " : rank . assistantId ( ) . toString ( ) ) ;
item . setAssistantId ( rank . assistantId ( ) ) ;
item . setLabel ( resolveAssistantLabel ( rank . assistantId ( ) , rank . assistantName ( ) ) ) ;
item . setMessageTotal ( rank . messageTotal ( ) ) ;
item . setSessionTotal ( rank . sessionTotal ( ) ) ;
item . setAvgMessagePerSession ( calculateAvg ( rank . messageTotal ( ) , rank . sessionTotal ( ) ) ) ;
item . setValue ( rank . messageTotal ( ) ) ;
items . add ( item ) ;
}
return items ;
}
private DashboardDistributionItemVo buildPlatformItem ( String key , String label , Long value ) {
DashboardDistributionItemVo item = new DashboardDistributionItemVo ( ) ;
item . setKey ( key ) ;
item . setLabel ( label ) ;
item . setValue ( defaultLong ( value ) ) ;
return item ;
}
private Map < String , Long > queryActiveUserTrend ( DashboardQueryContext context , String bucketFormat ) {
StringBuilder sql = new StringBuilder ( ) ;
List < Object > params = new ArrayList < > ( ) ;
sql . append ( " SELECT DATE_FORMAT(l.created, ' " ) . append ( bucketFormat ) . append ( " ') AS bucket_key, " )
. append ( " COUNT(DISTINCT l.account_id) AS total " )
. append ( " FROM tb_sys_log l " )
. append ( " INNER JOIN tb_sys_account a ON a.id = l.account_id AND a.is_deleted IS NULL " )
. append ( " WHERE l.created >= ? AND l.created < ? " ) ;
params . add ( toDate ( context . startTime ) ) ;
params . add ( toDate ( context . endTime ) ) ;
appendOptionalTenantFilter ( sql , params , context . tenantFilterId , " a.tenant_id " ) ;
appendOptionalDeptFilter ( sql , params , context . deptFilterId , " a.dept_id " ) ;
sql . append ( " GROUP BY bucket_key ORDER BY bucket_key ASC " ) ;
Map < String , Long > data = new HashMap < > ( ) ;
for ( Row row : Db . selectListBySql ( sql . toString ( ) , params . toArray ( ) ) ) {
data . put ( asString ( row . get ( " bucket_key " ) ) , asLong ( row . get ( " total " ) ) ) ;
}
return data ;
}
/**
* 按租户统计平台资源数量。
*
* @param tableName 表名
* @param alias 别名
* @param containsLogicDelete 是否包含逻辑删除条件
* @param context 查询上下文
* @return 统计值
*/
private long countScopedTable ( String tableName , String alias , boolean containsLogicDelete , DashboardQueryContext context ) {
StringBuilder sql = new StringBuilder ( ) ;
List < Object > params = new ArrayList < > ( ) ;
@@ -152,6 +232,12 @@ public class DashboardServiceImpl implements DashboardService {
return queryForLong ( sql . toString ( ) , params ) ;
}
/**
* 统计当前时间范围内活跃用户数。
*
* @param context 查询上下文
* @return 活跃用户数
*/
private long countActiveUsers ( DashboardQueryContext context ) {
StringBuilder sql = new StringBuilder ( ) ;
List < Object > params = new ArrayList < > ( ) ;
@@ -167,11 +253,26 @@ public class DashboardServiceImpl implements DashboardService {
return queryForLong ( sql . toString ( ) , params ) ;
}
/**
* 执行 count SQL 并返回 long 值。
*
* @param sql SQL
* @param params 参数
* @return long 值
*/
private long queryForLong ( String sql , List < Object > params ) {
Object result = Db . selectObject ( sql , params . toArray ( ) ) ;
return asLong ( result ) ;
}
/**
* 追加租户过滤。
*
* @param sql SQL 构造器
* @param params 参数列表
* @param tenantId 租户 ID
* @param columnName 列名
*/
private void appendOptionalTenantFilter ( StringBuilder sql , List < Object > params , BigInteger tenantId , String columnName ) {
if ( tenantId ! = null ) {
sql . append ( " AND " ) . append ( columnName ) . append ( " = ? " ) ;
@@ -179,6 +280,14 @@ public class DashboardServiceImpl implements DashboardService {
}
}
/**
* 追加部门过滤。
*
* @param sql SQL 构造器
* @param params 参数列表
* @param deptId 部门 ID
* @param columnName 列名
*/
private void appendOptionalDeptFilter ( StringBuilder sql , List < Object > params , BigInteger deptId , String columnName ) {
if ( deptId ! = null ) {
sql . append ( " AND " ) . append ( columnName ) . append ( " = ? " ) ;
@@ -186,6 +295,13 @@ public class DashboardServiceImpl implements DashboardService {
}
}
/**
* 构建查询上下文。
*
* @param loginAccount 当前登录账号
* @param query 查询条件
* @return 查询上下文
*/
private DashboardQueryContext buildContext ( LoginAccount loginAccount , DashboardOverviewQuery query ) {
DashboardQueryContext context = new DashboardQueryContext ( ) ;
context . range = normalizeRange ( query = = null ? null : query . getRange ( ) ) ;
@@ -203,11 +319,16 @@ public class DashboardServiceImpl implements DashboardService {
context . endTime = LocalDateTime . of ( today . plusDays ( 1 ) , LocalTime . MIN ) ;
}
context . tenantFilterId = context . superAdmin ? null : loginAccount . getTenantId ( ) ;
context . tenantFilterId = context . superAdmin | | loginAccount = = null ? null : loginAccount . getTenantId ( ) ;
return context ;
}
/**
* 判断当前登录账号是否为超管。
*
* @param loginAccount 当前登录账号
* @return true 表示超管
*/
private boolean isSuperAdmin ( LoginAccount loginAccount ) {
if ( loginAccount = = null | | loginAccount . getId ( ) = = null ) {
return false ;
@@ -228,6 +349,12 @@ public class DashboardServiceImpl implements DashboardService {
return sysRoleService . count ( roleWrapper ) > 0 ;
}
/**
* 归一化时间范围参数。
*
* @param range 原始时间范围
* @return 规范化后的时间范围
*/
private String normalizeRange ( String range ) {
if ( ! StringUtils . hasText ( range ) ) {
return " 7d " ;
@@ -238,10 +365,15 @@ public class DashboardServiceImpl implements DashboardService {
throw new BusinessException ( " 不支持的时间范围: " + range ) ;
}
/**
* 构建时间桶。
*
* @param range 时间范围
* @return 时间桶列表
*/
private List < TimeBucket > buildBuckets ( String range ) {
List < TimeBucket > buckets = new ArrayList < > ( ) ;
LocalDate today = LocalDate . now ( DEFAULT_ZONE_ID ) ;
if ( " today " . equals ( range ) ) {
DateTimeFormatter keyFormatter = DateTimeFormatter . ofPattern ( " yyyy-MM-dd HH:00:00 " ) ;
DateTimeFormatter labelFormatter = DateTimeFormatter . ofPattern ( " HH:00 " ) ;
@@ -264,18 +396,22 @@ public class DashboardServiceImpl implements DashboardService {
return buckets ;
}
/**
* 把 LocalDateTime 转换为 Date。
*
* @param dateTime 时间
* @return Date
*/
private Date toDate ( LocalDateTime dateTime ) {
return Date . from ( dateTime . atZone ( DEFAULT_ZONE_ID ) . toInstant ( ) ) ;
}
private long defaultLong ( Long value ) {
return value = = null ? 0L : value ;
}
private String asString ( Object value ) {
return value = = null ? " " : String . valueOf ( value ) ;
}
/**
* 解析对象为 long 值。
*
* @param value 原始对象
* @return long 值
*/
private long asLong ( Object value ) {
if ( value = = null ) {
return 0L ;
@@ -286,6 +422,37 @@ public class DashboardServiceImpl implements DashboardService {
return Long . parseLong ( String . valueOf ( value ) ) ;
}
/**
* 计算平均每会话消息数。
*
* @param messageTotal 消息总数
* @param sessionTotal 会话总数
* @return 平均值
*/
private double calculateAvg ( long messageTotal , long sessionTotal ) {
if ( sessionTotal < = 0 ) {
return 0D ;
}
return ( double ) messageTotal / ( double ) sessionTotal ;
}
/**
* 解析智能体展示名称。
*
* @param assistantId 智能体 ID
* @param assistantName 智能体名称
* @return 展示名称
*/
private String resolveAssistantLabel ( BigInteger assistantId , String assistantName ) {
if ( StringUtils . hasText ( assistantName ) ) {
return assistantName . trim ( ) ;
}
return assistantId = = null ? " 智能体-未知 " : " 智能体- " + assistantId ;
}
/**
* 工作台查询上下文。
*/
private static class DashboardQueryContext {
private String range ;
private BigInteger tenantFilterId ;
@@ -295,6 +462,9 @@ public class DashboardServiceImpl implements DashboardService {
private LocalDateTime endTime ;
}
/**
* 时间桶。
*/
private static class TimeBucket {
private final String key ;
private final String label ;
@@ -304,4 +474,14 @@ public class DashboardServiceImpl implements DashboardService {
this . label = label ;
}
}
/**
* 聊天统计页面载荷。
*/
private record ChatDashboardPayload (
DashboardChatStatusVo chatStatus ,
List < DashboardTrendItemVo > trends ,
List < DashboardDistributionItemVo > distribution
) {
}
}