mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 09:46:37 +08:00
压缩历史至 95081aff
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
Copyright 2025 linux.do
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/linux-do/credit/internal/db"
|
||||
"github.com/linux-do/credit/internal/model"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// getDateRange 计算查询的时间范围
|
||||
func getDateRange(days int) (startDate, endDate time.Time) {
|
||||
now := time.Now()
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
return todayStart.AddDate(0, 0, -(days - 1)), todayStart.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
// dailyAmountResult 每日金额查询结果
|
||||
type dailyAmountResult struct {
|
||||
Date time.Time
|
||||
Amount decimal.Decimal
|
||||
}
|
||||
|
||||
// queryDailyAmounts 查询每日金额
|
||||
// isIncome: true=收入(payee), false=支出(payer)
|
||||
func queryDailyAmounts(ctx context.Context, userID uint64, isIncome bool, startDate, endDate time.Time) (map[string]decimal.Decimal, error) {
|
||||
var results []dailyAmountResult
|
||||
var err error
|
||||
|
||||
if isIncome {
|
||||
// 收入查询:payee_user_id = user
|
||||
// 包括:普通收款、红包领取(red_envelope_receive)、红包退款(red_envelope_refund)
|
||||
err = db.DB(ctx).Model(&model.Order{}).
|
||||
Select("DATE_TRUNC('day', created_at) as date, SUM(amount) as amount").
|
||||
Where("payee_user_id = ?", userID).
|
||||
Where("status = ?", model.OrderStatusSuccess).
|
||||
Where("created_at >= ? AND created_at < ?", startDate, endDate).
|
||||
Group("DATE_TRUNC('day', created_at)").
|
||||
Scan(&results).Error
|
||||
} else {
|
||||
// 支出查询:payer_user_id = user,但排除 red_envelope_receive
|
||||
// red_envelope_receive 的 payer_user_id 是红包创建者,但创建者的支出已在 red_envelope_send 时计算
|
||||
err = db.DB(ctx).Model(&model.Order{}).
|
||||
Select("DATE_TRUNC('day', created_at) as date, SUM(amount) as amount").
|
||||
Where("payer_user_id = ?", userID).
|
||||
Where("status = ?", model.OrderStatusSuccess).
|
||||
Where("type != ?", model.OrderTypeRedEnvelopeReceive).
|
||||
Where("created_at >= ? AND created_at < ?", startDate, endDate).
|
||||
Group("DATE_TRUNC('day', created_at)").
|
||||
Scan(&results).Error
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为 map
|
||||
statsMap := make(map[string]decimal.Decimal)
|
||||
for _, r := range results {
|
||||
dateStr := r.Date.Format("2006-01-02")
|
||||
statsMap[dateStr] = r.Amount
|
||||
}
|
||||
|
||||
return statsMap, nil
|
||||
}
|
||||
|
||||
// mergeDailyStats 合并收入和支出统计,填充无数据的日期
|
||||
func mergeDailyStats(startDate time.Time, days int, incomeMap, expenseMap map[string]decimal.Decimal) []DailyStatsItem {
|
||||
result := make([]DailyStatsItem, days)
|
||||
|
||||
for i := range result {
|
||||
dateStr := startDate.AddDate(0, 0, i).Format("2006-01-02")
|
||||
result[i] = DailyStatsItem{
|
||||
Date: dateStr,
|
||||
Income: incomeMap[dateStr],
|
||||
Expense: expenseMap[dateStr],
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// userBalanceStatsResult 用户余额统计查询结果
|
||||
type userBalanceStatsResult struct {
|
||||
TotalCount int64
|
||||
TotalAmount decimal.Decimal
|
||||
AvgAmount decimal.Decimal
|
||||
MinAmount decimal.Decimal
|
||||
MaxAmount decimal.Decimal
|
||||
StdDev decimal.Decimal
|
||||
}
|
||||
|
||||
// calculateUserBalanceStats 计算用户余额统计数据
|
||||
func calculateUserBalanceStats(ctx context.Context) (*userBalanceStatsResult, error) {
|
||||
var result userBalanceStatsResult
|
||||
|
||||
err := db.DB(ctx).Model(&model.User{}).
|
||||
Select(`
|
||||
COUNT(*) as total_count,
|
||||
COALESCE(SUM(available_balance), 0) as total_amount,
|
||||
COALESCE(AVG(available_balance), 0) as avg_amount,
|
||||
COALESCE(MIN(available_balance), 0) as min_amount,
|
||||
COALESCE(MAX(available_balance), 0) as max_amount,
|
||||
COALESCE(STDDEV_SAMP(available_balance), 0) as std_dev
|
||||
`).
|
||||
Scan(&result).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// calculateMedian 计算用户余额中位数
|
||||
func calculateMedian(ctx context.Context) (decimal.Decimal, error) {
|
||||
var median decimal.Decimal
|
||||
|
||||
err := db.DB(ctx).Raw(`
|
||||
SELECT COALESCE(
|
||||
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY available_balance),
|
||||
0
|
||||
) as median
|
||||
FROM users
|
||||
`).Scan(&median).Error
|
||||
|
||||
if err != nil {
|
||||
return decimal.Zero, err
|
||||
}
|
||||
|
||||
return median, nil
|
||||
}
|
||||
Reference in New Issue
Block a user