service.xpcool.com/internal/service/house/listing/listing.go
夏犀麟 4aca0c7f6f
Some checks failed
Build and Deploy (service.xpcool.com) / build-and-deploy (push) Failing after 5m7s
feat(house): 实现看房模块后端功能
- 新增 9 张房屋相关数据表(社区/楼宇/房源/价格快照/交易/设施/社区设施/学区/偏好)
- 添加菜单权限种子数据并绑定超级管理员角色
- 生成 DAO 层代码和实体对象
- 实现房屋模块 API 接口(社区/房源/看板)和控制器服务层
- 支持多平台软关联匹配、笋盘标记和低可信度标记功能
- 更新超级管理员账号为 xxcool/xxCool@2026
- 调整 RBAC 菜单结构,移除管理员管理功能,新增日志管理菜单
- 修复 RBAC 安全漏洞,确保禁用角色权限失效
- 重构认证模块,将登录相关接口迁移到统一包结构下
- 移除废弃的管理模块和工具类接口定义
- 为通用工具包添加中文注释和文档说明
2026-08-26 23:42:38 +08:00

180 lines
5.3 KiB
Go

// Package house_listing 提供房源/挂牌领域服务。
package house_listing
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"service.xpcool.com/internal/consts"
"service.xpcool.com/internal/dao"
"service.xpcool.com/internal/library/response"
"service.xpcool.com/internal/model/do"
"service.xpcool.com/internal/model/dto"
)
// IListing 房源/挂牌领域服务接口。
type IListing interface {
List(context.Context, dto.HouseListingFilter) ([]dto.HouseListingVO, int, error)
Create(context.Context, dto.HouseListingInput) (uint64, error)
Update(context.Context, dto.HouseListingInput) error
Delete(context.Context, uint64) error
BatchMark(context.Context, []uint64, string, int) (int64, error)
}
type listing struct{}
var localListing IListing
func NewListing() IListing { return &listing{} }
// Listing 返回已注册的房源服务实现。
func Listing() IListing {
if localListing == nil {
panic("Listing implementation not registered")
}
return localListing
}
// RegisterListing 注册房源服务实现。
func RegisterListing(i IListing) { localListing = i }
// List 分页查询房源,关联小区名,支持多维筛选(管理列表与看板共用)。
func (s *listing) List(ctx context.Context, f dto.HouseListingFilter) ([]dto.HouseListingVO, int, error) {
m := dao.HouseListing.Ctx(ctx).As("l").LeftJoin("house_community c", "l.community_id=c.id")
m = m.Fields("l.*, c.name AS community_name")
if f.CommunityId > 0 {
m = m.Where("l.community_id", f.CommunityId)
}
if f.Keyword != "" {
m = m.Where("l.house_no LIKE ? OR l.layout LIKE ?", "%"+f.Keyword+"%", "%"+f.Keyword+"%")
}
if f.Layout != "" {
m = m.Where("l.layout", f.Layout)
}
if f.Source != "" {
m = m.Where("l.source", f.Source)
}
if f.Region != "" {
m = m.Where("c.region", f.Region)
}
if f.PriceMin > 0 {
m = m.WhereGTE("l.total_price", f.PriceMin)
}
if f.PriceMax > 0 {
m = m.WhereLTE("l.total_price", f.PriceMax)
}
if f.AreaMin > 0 {
m = m.WhereGTE("l.area", f.AreaMin)
}
if f.AreaMax > 0 {
m = m.WhereLTE("l.area", f.AreaMax)
}
if f.Status > 0 {
m = m.Where("l.status", f.Status)
}
if f.IsBargain > 0 {
m = m.Where("l.is_bargain", 1)
}
if f.Confidence > 0 {
m = m.Where("l.confidence", 1)
}
total, err := m.Clone().Count()
if err != nil {
return nil, 0, gerror.Wrap(err, "count listing")
}
var list []dto.HouseListingVO
if err = m.Clone().Page(f.Page, f.Size).OrderDesc("l.id").Scan(&list); err != nil {
return nil, 0, gerror.Wrap(err, "query listing list")
}
return list, total, nil
}
// Create 新增房源。
func (s *listing) Create(ctx context.Context, in dto.HouseListingInput) (uint64, error) {
id, err := dao.HouseListing.Ctx(ctx).Data(do.HouseListing{
CommunityId: in.CommunityId,
BuildingId: in.BuildingId,
HouseNo: in.HouseNo,
Layout: in.Layout,
Area: in.Area,
UsableArea: in.UsableArea,
Orientation: in.Orientation,
Floor: in.Floor,
TotalFloors: in.TotalFloors,
Decoration: in.Decoration,
TotalPrice: in.TotalPrice,
UnitPrice: in.UnitPrice,
ListPrice: in.ListPrice,
Source: in.Source,
SourceHouseId: in.SourceHouseId,
SourceUrl: in.SourceUrl,
MatchGroupId: in.MatchGroupId,
OnMarketDays: in.OnMarketDays,
PriceChangeCount: in.PriceChangeCount,
Status: in.Status,
Confidence: in.Confidence,
IsBargain: in.IsBargain,
}).InsertAndGetId()
if err != nil {
return 0, gerror.Wrap(err, "insert listing")
}
return uint64(id), nil
}
// Update 更新房源。
func (s *listing) Update(ctx context.Context, in dto.HouseListingInput) error {
_, err := dao.HouseListing.Ctx(ctx).Where(do.HouseListing{Id: in.Id}).Data(do.HouseListing{
CommunityId: in.CommunityId,
BuildingId: in.BuildingId,
HouseNo: in.HouseNo,
Layout: in.Layout,
Area: in.Area,
UsableArea: in.UsableArea,
Orientation: in.Orientation,
Floor: in.Floor,
TotalFloors: in.TotalFloors,
Decoration: in.Decoration,
TotalPrice: in.TotalPrice,
UnitPrice: in.UnitPrice,
ListPrice: in.ListPrice,
MatchGroupId: in.MatchGroupId,
Status: in.Status,
Confidence: in.Confidence,
IsBargain: in.IsBargain,
}).Update()
if err != nil {
return gerror.Wrap(err, "update listing")
}
return nil
}
// Delete 软删除房源。
func (s *listing) Delete(ctx context.Context, id uint64) error {
if _, err := dao.HouseListing.Ctx(ctx).Where(do.HouseListing{Id: id}).Delete(); err != nil {
return gerror.Wrap(err, "delete listing")
}
return nil
}
// BatchMark 批量标记房源(置信度/笋盘/状态),字段白名单防注入。
func (s *listing) BatchMark(ctx context.Context, ids []uint64, field string, value int) (int64, error) {
var data do.HouseListing
switch field {
case "confidence":
data = do.HouseListing{Confidence: value}
case "isBargain":
data = do.HouseListing{IsBargain: value}
case "status":
data = do.HouseListing{Status: value}
default:
return 0, response.Error(consts.CodeInvalidParam, "unsupported mark field")
}
res, err := dao.HouseListing.Ctx(ctx).WhereIn("id", ids).Data(data).Update()
if err != nil {
return 0, gerror.Wrap(err, "batch mark listing")
}
affected, _ := res.RowsAffected()
return affected, nil
}