From 0ecad7f23d1fe243fc2fc33e4372364e7b4ee5f7 Mon Sep 17 00:00:00 2001 From: engigu Date: Sat, 20 Sep 2025 14:09:07 +0800 Subject: [PATCH] feat: speed up dashboard page loading --- models/send_tasks_logs.go | 96 +++++++++++++++ pkg/util/jwt.go | 4 +- routers/api/v1/statistic.go | 41 ++++++- service/statistic_service/statistic.go | 15 +++ .../components/pages/dashboard/Dashboard.vue | 113 +++++++++++++----- 5 files changed, 235 insertions(+), 34 deletions(-) diff --git a/models/send_tasks_logs.go b/models/send_tasks_logs.go index a26a27b..f185a67 100644 --- a/models/send_tasks_logs.go +++ b/models/send_tasks_logs.go @@ -129,6 +129,25 @@ type StatisticData struct { WayCateData []WayCateData `json:"way_cate_data" gorm:"many2many:way_cate_data;"` } +// BasicStatisticData 基础统计数据 +type BasicStatisticData struct { + TodaySuccNum int `json:"today_succ_num"` + TodayFailedNum int `json:"today_failed_num"` + TodayTotalNum int `json:"today_total_num"` + MessageTotalNum int `json:"message_total_num"` + HostedMessageTotalNum int `json:"hosted_message_total_num"` +} + +// TrendStatisticData 趋势统计数据 +type TrendStatisticData struct { + LatestSendData []LatestSendData `json:"latest_send_data"` +} + +// ChannelStatisticData 渠道统计数据 +type ChannelStatisticData struct { + WayCateData []WayCateData `json:"way_cate_data"` +} + type LatestSendData struct { Day string `json:"day"` Num int `json:"num"` @@ -204,3 +223,80 @@ func GetStatisticData() (StatisticData, error) { statistic.WayCateData = wayCateData return statistic, nil } + +// GetBasicStatisticData 获取基础统计数据 +func GetBasicStatisticData() (BasicStatisticData, error) { + var statistic BasicStatisticData + logt := GetSchema(SendTasksLogs{}) + hostedt := GetSchema(HostedMessage{}) + currDay := util.GetNowTimeStr()[:10] + + // 今日统计数据 + query := db. + Table(logt). + Select(` + COUNT(*) AS today_total_num, + SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS today_succ_num, + SUM(CASE WHEN status != 1 or status is null THEN 1 ELSE 0 END) AS today_failed_num`). + Where("DATE(created_on) = ?", currDay) + + query.Take(&statistic) + + // 全部消息统计数据 + totalQuery := db.Table(logt).Select(`COUNT(*) AS message_total_num`) + totalQuery.Take(&statistic) + + // 托管消息统计数据 + hostedMessageTotalQuery := db.Table(hostedt).Select(`COUNT(*) AS hosted_message_total_num`) + hostedMessageTotalQuery.Take(&statistic) + + return statistic, nil +} + +// GetTrendStatisticData 获取趋势统计数据 +func GetTrendStatisticData() (TrendStatisticData, error) { + var statistic TrendStatisticData + var latestData []LatestSendData + logt := GetSchema(SendTasksLogs{}) + + // 最近30天数据 + days := 30 + now := util.GetNowTime() + past := now.AddDate(0, 0, -days) + pastDate := past.Format("2006-01-02") + next := now.AddDate(0, 0, 1) + nextDate := next.Format("2006-01-02") + queryData := db. + Table(logt). + Select(` + CAST(DATE(created_on) AS CHAR) AS day, + SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS day_succ_num, + SUM(CASE WHEN status != 1 or status is null THEN 1 ELSE 0 END) AS day_failed_num, + COUNT(*) AS num`). + Where(fmt.Sprintf(" created_on >= '%s' and created_on <= '%s' ", pastDate, nextDate)). + Group("day"). + Order("day") + + queryData.Scan(&latestData) + statistic.LatestSendData = latestData + return statistic, nil +} + +// GetChannelStatisticData 获取渠道统计数据 +func GetChannelStatisticData() (ChannelStatisticData, error) { + var statistic ChannelStatisticData + var wayCateData []WayCateData + inst := GetSchema(SendTasksIns{}) + wayst := GetSchema(SendWays{}) + + // 消息实例分类数目 + db. + Table(inst). + Select(fmt.Sprintf("%s.name as way_name, count(%s.id) as count_num", wayst, wayst)). + Joins(fmt.Sprintf("JOIN %s ON %s.way_id = %s.id", wayst, inst, wayst)). + Group(fmt.Sprintf("%s.id", wayst)). + Scan(&wayCateData) + + statistic.WayCateData = wayCateData + return statistic, nil +} diff --git a/pkg/util/jwt.go b/pkg/util/jwt.go index d0f2b47..d742b79 100644 --- a/pkg/util/jwt.go +++ b/pkg/util/jwt.go @@ -2,9 +2,10 @@ package util import ( "fmt" - "github.com/golang-jwt/jwt/v5" "message-nest/pkg/setting" "time" + + "github.com/golang-jwt/jwt/v5" ) type UserClaims struct { @@ -16,6 +17,7 @@ type UserClaims struct { func GenerateToken(username, password string) (string, error) { expHours := 1 * 24 * time.Hour + //expHours := 1 * time.Minute SetClaims := UserClaims{ Username: username, Password: EncodeMD5(password), diff --git a/routers/api/v1/statistic.go b/routers/api/v1/statistic.go index f9d6605..0d56585 100644 --- a/routers/api/v1/statistic.go +++ b/routers/api/v1/statistic.go @@ -14,12 +14,41 @@ func GetStatisticData(c *gin.Context) { appG = app.Gin{C: c} ) + // 获取类型参数 + statisticType := c.Query("type") + msgService := statistic_service.StatisticService{} - data, err := msgService.GetStatisticData() - if err != nil { - appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取失败!原因:%s", err), nil) - return + + switch statisticType { + case "basic": + data, err := msgService.GetBasicStatisticData() + if err != nil { + appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取基础统计失败!原因:%s", err), nil) + return + } + appG.CResponse(http.StatusOK, "获取基础统计成功", data) + case "trend": + data, err := msgService.GetTrendStatisticData() + if err != nil { + appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取趋势统计失败!原因:%s", err), nil) + return + } + appG.CResponse(http.StatusOK, "获取趋势统计成功", data) + case "channels": + data, err := msgService.GetChannelStatisticData() + if err != nil { + appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取渠道统计失败!原因:%s", err), nil) + return + } + appG.CResponse(http.StatusOK, "获取渠道统计成功", data) + default: + // 默认返回完整统计数据(保持向后兼容) + data, err := msgService.GetStatisticData() + if err != nil { + appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取失败!原因:%s", err), nil) + return + } + appG.CResponse(http.StatusOK, "获取成功", data) } - - appG.CResponse(http.StatusOK, "获取成功", data) } + diff --git a/service/statistic_service/statistic.go b/service/statistic_service/statistic.go index 835d446..3b6cdb0 100644 --- a/service/statistic_service/statistic.go +++ b/service/statistic_service/statistic.go @@ -10,3 +10,18 @@ type StatisticService struct { func (sw *StatisticService) GetStatisticData() (models.StatisticData, error) { return models.GetStatisticData() } + +// GetBasicStatisticData 获取基础统计数据 +func (sw *StatisticService) GetBasicStatisticData() (models.BasicStatisticData, error) { + return models.GetBasicStatisticData() +} + +// GetTrendStatisticData 获取趋势统计数据 +func (sw *StatisticService) GetTrendStatisticData() (models.TrendStatisticData, error) { + return models.GetTrendStatisticData() +} + +// GetChannelStatisticData 获取渠道统计数据 +func (sw *StatisticService) GetChannelStatisticData() (models.ChannelStatisticData, error) { + return models.GetChannelStatisticData() +} diff --git a/web/src/components/pages/dashboard/Dashboard.vue b/web/src/components/pages/dashboard/Dashboard.vue index fdb53b7..b24baf3 100644 --- a/web/src/components/pages/dashboard/Dashboard.vue +++ b/web/src/components/pages/dashboard/Dashboard.vue @@ -4,6 +4,7 @@ import { DatabaseIcon, BarChartIcon, SendIcon, CheckCircleIcon, XCircleIcon } fr // import { LineChart } from "@/components/ui/chart-line" import { onMounted, reactive } from 'vue'; import { request } from '@/api/api'; +import { toast } from 'vue-sonner'; // import VueApexCharts from 'vue3-apexcharts' import ApexCharts from 'apexcharts' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' @@ -21,54 +22,113 @@ interface CateData { } let state = reactive({ - data: { + basicData: { message_total_num: 0, hosted_message_total_num: 0, today_total_num: 0, today_succ_num: 0, today_failed_num: 0, - way_cate_data: [] as CateData[], + }, + trendData: { latest_send_data: [] as SendData[], + }, + channelData: { + way_cate_data: [] as CateData[], + }, + loading: { + basic: false, + trend: false, + channels: false, } }); -const getStatisticData = async () => { +// 获取基础统计数据 +const getBasicStatisticData = async () => { + state.loading.basic = true; try { - const rsp = await request.get('/statistic'); + const rsp = await request.get('/statistic?type=basic'); if (rsp && rsp.data && rsp.data.code == 200) { - state.data = rsp.data.data; - // 数据加载完成后重新渲染图表 + state.basicData = rsp.data.data; + } else { + toast.error(rsp?.data?.msg || '获取基础统计数据失败'); + } + } catch (error) { + toast.error('获取基础统计数据时发生错误'); + } finally { + state.loading.basic = false; + } +} + +// 获取趋势统计数据 +const getTrendStatisticData = async () => { + state.loading.trend = true; + try { + const rsp = await request.get('/statistic?type=trend'); + if (rsp && rsp.data && rsp.data.code == 200) { + state.trendData = rsp.data.data; + // 数据加载完成后重新渲染折线图 setTimeout(() => { renderLineChart(); + }, 100); + } else { + toast.error(rsp?.data?.msg || '获取趋势统计数据失败'); + } + } catch (error) { + toast.error('获取趋势统计数据时发生错误'); + } finally { + state.loading.trend = false; + } +} + +// 获取渠道统计数据 +const getChannelStatisticData = async () => { + state.loading.channels = true; + try { + const rsp = await request.get('/statistic?type=channels'); + if (rsp && rsp.data && rsp.data.code == 200) { + state.channelData = rsp.data.data; + // 数据加载完成后重新渲染饼图 + setTimeout(() => { renderPieChart(); }, 100); } else { - console.error('Failed to load statistics:', rsp?.data?.msg || 'Unknown error'); + toast.error(rsp?.data?.msg || '获取渠道统计数据失败'); } } catch (error) { - console.error('Error loading statistics:', error); + toast.error('获取渠道统计数据时发生错误'); + } finally { + state.loading.channels = false; } } +// 并行加载所有统计数据 +const loadAllStatisticData = async () => { + await Promise.all([ + getBasicStatisticData(), + getTrendStatisticData(), + getChannelStatisticData() + ]); +} + const renderLineChart = () => { const options = { series: [ { name: '发送总数', - data: state.data.latest_send_data.length > 0 - ? state.data.latest_send_data.map(item => item.num || 0) + data: state.trendData.latest_send_data.length > 0 + ? state.trendData.latest_send_data.map(item => item.num || 0) : [] }, { name: '发送成功数', - data: state.data.latest_send_data.length > 0 - ? state.data.latest_send_data.map(item => item.day_succ_num || 0) + data: state.trendData.latest_send_data.length > 0 + ? state.trendData.latest_send_data.map(item => item.day_succ_num || 0) : [] }, { name: '发送失败数', - data: state.data.latest_send_data.length > 0 - ? state.data.latest_send_data.map(item => item.day_failed_num || 0) + data: state.trendData.latest_send_data.length > 0 + ? state.trendData.latest_send_data.map(item => item.day_failed_num || 0) : [] }, ], @@ -115,8 +175,8 @@ const renderLineChart = () => { } }, xaxis: { - categories: state.data.latest_send_data.length > 0 - ? state.data.latest_send_data.map(item => item.day) + categories: state.trendData.latest_send_data.length > 0 + ? state.trendData.latest_send_data.map(item => item.day) : [], axisBorder: { show: false @@ -268,8 +328,8 @@ const renderLineChart = () => { const renderPieChart = () => { const options = { - series: state.data.way_cate_data.length > 0 - ? state.data.way_cate_data.map(item => item.count_num) + series: state.channelData.way_cate_data.length > 0 + ? state.channelData.way_cate_data.map(item => item.count_num) : [], chart: { type: 'pie', @@ -290,8 +350,8 @@ const renderPieChart = () => { } } }, - labels: state.data.way_cate_data.length > 0 - ? state.data.way_cate_data.map(item => item.way_name) + labels: state.channelData.way_cate_data.length > 0 + ? state.channelData.way_cate_data.map(item => item.way_name) : [], colors: ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'], legend: { @@ -353,8 +413,7 @@ const renderPieChart = () => { } onMounted(() => { - getStatisticData(); - // renderLineChart(); + loadAllStatisticData(); }) @@ -363,11 +422,11 @@ onMounted(() => {