service.xpcool.com/internal/service/house/dashboard/dashboard.go
夏犀麟 4b3c00270f
Some checks failed
Build and Deploy (service.xpcool.com) / build-and-deploy (push) Failing after 31s
feat(i18n): 中文化校验提示与服务层错误信息
将 api 层校验规则提示语、service 层 gerror.Wrap 与 response.Error
错误信息、panic 未注册提示统一改为中文,并同步中文化 cmd 路由注释
与变更记录。仅涉及注释、文档与字符串改动,无业务逻辑变更。
2026-09-13 23:22:46 +08:00

142 lines
5.0 KiB
Go

// Package house_dashboard 提供看房数据看板聚合服务。
package house_dashboard
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"service.xpcool.com/internal/dao"
"service.xpcool.com/internal/model/dto"
)
// IDashboard 看板聚合领域服务接口。
type IDashboard interface {
Overview(context.Context, string) (dto.DashboardOverview, error)
MapPoints(context.Context, string, float64, float64, int) ([]dto.DashboardMapPoint, error)
PriceTrend(context.Context, uint64, string, int) ([]dto.DashboardTrendPoint, error)
AggregateRegion(context.Context) ([]dto.DashboardRegionAgg, error)
}
type dashboard struct{}
var localDashboard IDashboard
func NewDashboard() IDashboard { return &dashboard{} }
// Dashboard 返回已注册的看板服务实现。
func Dashboard() IDashboard {
if localDashboard == nil {
panic("Dashboard 实现未注册")
}
return localDashboard
}
// RegisterDashboard 注册看板服务实现。
func RegisterDashboard(i IDashboard) { localDashboard = i }
// Overview 统计概览:小区数、在售房源、笋盘、低可信、均价、平均挂牌天数。
func (s *dashboard) Overview(ctx context.Context, region string) (dto.DashboardOverview, error) {
var out dto.DashboardOverview
cm := dao.HouseCommunity.Ctx(ctx)
if region != "" {
cm = cm.Where("region", region)
}
cnt, err := cm.Count()
if err != nil {
return out, gerror.Wrap(err, "统计小区总数失败")
}
out.CommunityCount = cnt
// 房源相关指标统一经小区联表,支持按区域过滤。
lm := dao.HouseListing.Ctx(ctx).As("l").LeftJoin("house_community c", "l.community_id=c.id")
if region != "" {
lm = lm.Where("c.region", region)
}
out.ListingCount, _ = lm.Clone().Where("l.status", 1).Count()
out.BargainCount, _ = lm.Clone().Where("l.is_bargain", 1).Count()
out.LowConfidence, _ = lm.Clone().Where("l.confidence", 1).Count()
var agg struct {
AvgUnitPrice float64 `orm:"avg_unit_price"`
AvgTotalPrice float64 `orm:"avg_total_price"`
AvgListDays float64 `orm:"avg_list_days"`
}
if err := lm.Clone().Where("l.status", 1).Fields(
"COALESCE(AVG(l.unit_price),0) AS avg_unit_price, COALESCE(AVG(l.total_price),0) AS avg_total_price, COALESCE(AVG(l.on_market_days),0) AS avg_list_days",
).Scan(&agg); err != nil {
return out, gerror.Wrap(err, "聚合概览指标失败")
}
out.AvgUnitPrice = agg.AvgUnitPrice
out.AvgTotalPrice = agg.AvgTotalPrice
out.AvgListDays = agg.AvgListDays
return out, nil
}
// MapPoints 地图点位:按小区聚合均价与在售/笋盘数量。
func (s *dashboard) MapPoints(ctx context.Context, region string, priceMin, priceMax float64, status int) ([]dto.DashboardMapPoint, error) {
m := dao.HouseListing.Ctx(ctx).As("l").
LeftJoin("house_community c", "l.community_id=c.id").
Fields("l.community_id AS community_id, c.name AS name, c.region AS region, c.lng AS lng, c.lat AS lat, COALESCE(AVG(l.unit_price),0) AS avg_unit_price, COUNT(*) AS listing_count, COALESCE(SUM(l.is_bargain),0) AS bargain_count").
Group("l.community_id, c.name, c.region, c.lng, c.lat")
if region != "" {
m = m.Where("c.region", region)
}
if priceMin > 0 {
m = m.WhereGTE("l.total_price", priceMin)
}
if priceMax > 0 {
m = m.WhereLTE("l.total_price", priceMax)
}
if status > 0 {
m = m.Where("l.status", status)
}
var list []dto.DashboardMapPoint
if err := m.Scan(&list); err != nil {
return nil, gerror.Wrap(err, "查询地图点位失败")
}
return list, nil
}
// PriceTrend 价格趋势:按快照日期聚合挂牌/成交均价,返回时间升序。
func (s *dashboard) PriceTrend(ctx context.Context, communityId uint64, region string, limit int) ([]dto.DashboardTrendPoint, error) {
m := dao.HousePriceSnapshot.Ctx(ctx).As("p")
if communityId > 0 {
m = m.Where("p.community_id", communityId)
}
if region != "" {
m = m.LeftJoin("house_community c", "p.community_id=c.id").Where("c.region", region)
}
m = m.Fields("p.snap_date AS date, COALESCE(AVG(p.list_price),0) AS avg_list_price, COALESCE(AVG(p.deal_price),0) AS avg_deal_price").
Group("p.snap_date").
OrderDesc("p.snap_date")
if limit > 0 {
m = m.Limit(limit)
}
var list []dto.DashboardTrendPoint
if err := m.Scan(&list); err != nil {
return nil, gerror.Wrap(err, "查询价格趋势失败")
}
// 反转为时间升序,便于前端画趋势线。
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
list[i], list[j] = list[j], list[i]
}
return list, nil
}
// AggregateRegion 区域聚合:按区县统计在售均价与数量。
func (s *dashboard) AggregateRegion(ctx context.Context) ([]dto.DashboardRegionAgg, error) {
m := dao.HouseListing.Ctx(ctx).As("l").
LeftJoin("house_community c", "l.community_id=c.id").
Fields("c.region AS region, COALESCE(AVG(l.unit_price),0) AS avg_unit_price, COUNT(*) AS listing_count, COALESCE(SUM(l.is_bargain),0) AS bargain_count").
Where("l.status", 1).
WhereGT("c.region", "").
Group("c.region").
OrderAsc("c.region")
var list []dto.DashboardRegionAgg
if err := m.Scan(&list); err != nil {
return nil, gerror.Wrap(err, "查询区域聚合失败")
}
return list, nil
}