refactor(utils): 修复密码哈希比较逻辑错误 feat(user): 新增按状态筛选优惠券接口 docs: 添加虚拟发货与任务中心相关文档 fix(wechat): 修正Code2Session上下文传递问题 test: 补充订单折扣与积分转换测试用例 build: 更新配置文件与构建脚本 style: 清理多余的空行与注释
245 lines
6.1 KiB
Go
245 lines
6.1 KiB
Go
package channel
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"bindbox-game/internal/pkg/logger"
|
|
"bindbox-game/internal/repository/mysql"
|
|
"bindbox-game/internal/repository/mysql/dao"
|
|
"bindbox-game/internal/repository/mysql/model"
|
|
)
|
|
|
|
type Service interface {
|
|
Create(ctx context.Context, in CreateInput) (*model.Channels, error)
|
|
Modify(ctx context.Context, id int64, in ModifyInput) error
|
|
Delete(ctx context.Context, id int64) error
|
|
List(ctx context.Context, in ListInput) (items []*ChannelWithStat, total int64, err error)
|
|
GetStats(ctx context.Context, channelID int64, days int) (*StatsOutput, error)
|
|
}
|
|
|
|
type service struct {
|
|
logger logger.CustomLogger
|
|
readDB *dao.Query
|
|
writeDB *dao.Query
|
|
}
|
|
|
|
func New(l logger.CustomLogger, db mysql.Repo) Service {
|
|
return &service{logger: l, readDB: dao.Use(db.GetDbR()), writeDB: dao.Use(db.GetDbW())}
|
|
}
|
|
|
|
type CreateInput struct {
|
|
Name string
|
|
Code string
|
|
Type string
|
|
Remarks string
|
|
}
|
|
|
|
type ModifyInput struct {
|
|
Name *string
|
|
Type *string
|
|
Remarks *string
|
|
}
|
|
|
|
type ListInput struct {
|
|
Name string
|
|
Page int
|
|
PageSize int
|
|
}
|
|
|
|
type ChannelWithStat struct {
|
|
*model.Channels
|
|
UserCount int64 `json:"user_count"`
|
|
}
|
|
|
|
type StatsOutput struct {
|
|
Overview StatsOverview `json:"overview"`
|
|
Daily []StatsDailyItem `json:"daily"`
|
|
}
|
|
|
|
type StatsOverview struct {
|
|
TotalUsers int64 `json:"total_users"`
|
|
TotalOrders int64 `json:"total_orders"`
|
|
TotalGMV int64 `json:"total_gmv"`
|
|
}
|
|
|
|
type StatsDailyItem struct {
|
|
Date string `json:"date"`
|
|
UserCount int64 `json:"user_count"`
|
|
OrderCount int64 `json:"order_count"`
|
|
GMV int64 `json:"gmv"`
|
|
}
|
|
|
|
func (s *service) Create(ctx context.Context, in CreateInput) (*model.Channels, error) {
|
|
m := &model.Channels{Name: in.Name, Code: in.Code, Type: in.Type, Remarks: in.Remarks}
|
|
if err := s.writeDB.Channels.WithContext(ctx).Create(m); err != nil {
|
|
return nil, err
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func (s *service) Modify(ctx context.Context, id int64, in ModifyInput) error {
|
|
updater := s.writeDB.Channels.WithContext(ctx).Where(s.writeDB.Channels.ID.Eq(id))
|
|
set := map[string]any{}
|
|
if in.Name != nil {
|
|
set["name"] = *in.Name
|
|
}
|
|
if in.Type != nil {
|
|
set["type"] = *in.Type
|
|
}
|
|
if in.Remarks != nil {
|
|
set["remarks"] = *in.Remarks
|
|
}
|
|
if len(set) == 0 {
|
|
return nil
|
|
}
|
|
_, err := updater.Updates(set)
|
|
return err
|
|
}
|
|
|
|
func (s *service) Delete(ctx context.Context, id int64) error {
|
|
_, err := s.writeDB.Channels.WithContext(ctx).Where(s.writeDB.Channels.ID.Eq(id)).Delete()
|
|
return err
|
|
}
|
|
|
|
func (s *service) List(ctx context.Context, in ListInput) (items []*ChannelWithStat, total int64, err error) {
|
|
if in.Page <= 0 {
|
|
in.Page = 1
|
|
}
|
|
if in.PageSize <= 0 {
|
|
in.PageSize = 20
|
|
}
|
|
q := s.readDB.Channels.WithContext(ctx)
|
|
if in.Name != "" {
|
|
q = q.Where(s.readDB.Channels.Name.Like("%" + in.Name + "%"))
|
|
}
|
|
|
|
total, err = q.Count()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// List channels
|
|
channels, err := q.Order(s.readDB.Channels.ID.Desc()).Limit(in.PageSize).Offset((in.Page - 1) * in.PageSize).Find()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Get user counts
|
|
var channelIDs []int64
|
|
for _, c := range channels {
|
|
channelIDs = append(channelIDs, c.ID)
|
|
}
|
|
|
|
stats := make(map[int64]int64)
|
|
if len(channelIDs) > 0 {
|
|
type Result struct {
|
|
ChannelID int64
|
|
Count int64
|
|
}
|
|
var results []Result
|
|
// Using raw query for grouping
|
|
err = s.readDB.Users.WithContext(ctx).UnderlyingDB().Table("users").
|
|
Select("channel_id, count(*) as count").
|
|
Where("channel_id IN ?", channelIDs).
|
|
Group("channel_id").
|
|
Scan(&results).Error
|
|
if err == nil {
|
|
for _, r := range results {
|
|
stats[r.ChannelID] = r.Count
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, c := range channels {
|
|
items = append(items, &ChannelWithStat{
|
|
Channels: c,
|
|
UserCount: stats[c.ID],
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *service) GetStats(ctx context.Context, channelID int64, months int) (*StatsOutput, error) {
|
|
if months <= 0 {
|
|
months = 12
|
|
}
|
|
now := time.Now()
|
|
// Calculate start date (first day of the month N months ago)
|
|
startMonth := now.AddDate(0, -months+1, 0)
|
|
startDate := time.Date(startMonth.Year(), startMonth.Month(), 1, 0, 0, 0, 0, startMonth.Location())
|
|
|
|
out := &StatsOutput{}
|
|
|
|
// 1. Overview
|
|
// Users
|
|
userCount, _ := s.readDB.Users.WithContext(ctx).Where(s.readDB.Users.ChannelID.Eq(channelID)).Count()
|
|
out.Overview.TotalUsers = userCount
|
|
|
|
// Orders & GMV
|
|
type OrderStat struct {
|
|
Count int64
|
|
GMV int64
|
|
}
|
|
var os OrderStat
|
|
s.readDB.Orders.WithContext(ctx).UnderlyingDB().Table("orders").
|
|
Joins("JOIN users ON users.id = orders.user_id").
|
|
Select("count(*) as count, coalesce(sum(actual_amount),0) as gmv").
|
|
Where("users.channel_id = ? AND orders.status = 2", channelID).
|
|
Scan(&os)
|
|
out.Overview.TotalOrders = os.Count
|
|
out.Overview.TotalGMV = os.GMV
|
|
|
|
// 2. Monthly Stats
|
|
dateMap := make(map[string]*StatsDailyItem)
|
|
var dateList []string
|
|
for i := 0; i < months; i++ {
|
|
d := startDate.AddDate(0, i, 0).Format("2006-01")
|
|
dateList = append(dateList, d)
|
|
dateMap[d] = &StatsDailyItem{Date: d}
|
|
}
|
|
|
|
// Monthly Users
|
|
type MonthlyUser struct {
|
|
Date string
|
|
Count int64
|
|
}
|
|
var monthlyUsers []MonthlyUser
|
|
s.readDB.Users.WithContext(ctx).UnderlyingDB().Table("users").
|
|
Select("DATE_FORMAT(created_at, '%Y-%m') as date, count(*) as count").
|
|
Where("channel_id = ? AND created_at >= ?", channelID, startDate).
|
|
Group("date").Scan(&monthlyUsers)
|
|
|
|
for _, u := range monthlyUsers {
|
|
if item, ok := dateMap[u.Date]; ok {
|
|
item.UserCount = u.Count
|
|
}
|
|
}
|
|
|
|
// Monthly Orders
|
|
type MonthlyOrder struct {
|
|
Date string
|
|
Count int64
|
|
GMV int64
|
|
}
|
|
var monthlyOrders []MonthlyOrder
|
|
s.readDB.Orders.WithContext(ctx).UnderlyingDB().Table("orders").
|
|
Joins("JOIN users ON users.id = orders.user_id").
|
|
Select("DATE_FORMAT(orders.created_at, '%Y-%m') as date, count(*) as count, coalesce(sum(actual_amount),0) as gmv").
|
|
Where("users.channel_id = ? AND orders.status = 2 AND orders.created_at >= ?", channelID, startDate).
|
|
Group("date").Scan(&monthlyOrders)
|
|
|
|
for _, o := range monthlyOrders {
|
|
if item, ok := dateMap[o.Date]; ok {
|
|
item.OrderCount = o.Count
|
|
item.GMV = o.GMV
|
|
}
|
|
}
|
|
|
|
for _, d := range dateList {
|
|
out.Daily = append(out.Daily, *dateMap[d])
|
|
}
|
|
|
|
return out, nil
|
|
}
|