Some checks failed
Build and Deploy (service.xpcool.com) / build-and-deploy (push) Failing after 5m7s
- 新增 9 张房屋相关数据表(社区/楼宇/房源/价格快照/交易/设施/社区设施/学区/偏好) - 添加菜单权限种子数据并绑定超级管理员角色 - 生成 DAO 层代码和实体对象 - 实现房屋模块 API 接口(社区/房源/看板)和控制器服务层 - 支持多平台软关联匹配、笋盘标记和低可信度标记功能 - 更新超级管理员账号为 xxcool/xxCool@2026 - 调整 RBAC 菜单结构,移除管理员管理功能,新增日志管理菜单 - 修复 RBAC 安全漏洞,确保禁用角色权限失效 - 重构认证模块,将登录相关接口迁移到统一包结构下 - 移除废弃的管理模块和工具类接口定义 - 为通用工具包添加中文注释和文档说明
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 implementation not registered")
|
|
}
|
|
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, "count community")
|
|
}
|
|
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, "aggregate overview")
|
|
}
|
|
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, "query map points")
|
|
}
|
|
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, "query price trend")
|
|
}
|
|
// 反转为时间升序,便于前端画趋势线。
|
|
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, "query region aggregate")
|
|
}
|
|
return list, nil
|
|
}
|