Some checks failed
Build and Deploy (service.xpcool.com) / build-and-deploy (push) Failing after 31s
将 api 层校验规则提示语、service 层 gerror.Wrap 与 response.Error 错误信息、panic 未注册提示统一改为中文,并同步中文化 cmd 路由注释 与变更记录。仅涉及注释、文档与字符串改动,无业务逻辑变更。
142 lines
5.0 KiB
Go
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
|
|
}
|