package hostorg import ( "database/sql" "fmt" "strconv" "strings" "hyapp-admin-server/internal/integration/userclient" "hyapp-admin-server/internal/integration/walletclient" "hyapp-admin-server/internal/middleware" "hyapp-admin-server/internal/modules/shared" "hyapp-admin-server/internal/response" "github.com/gin-gonic/gin" ) type Handler struct { service *Service audit shared.OperationLogger } func New(userClient userclient.Client, walletClient walletclient.Client, userDB *sql.DB, walletDB *sql.DB, adminDB *sql.DB, audit shared.OperationLogger) *Handler { return &Handler{service: NewService(userClient, walletClient, NewReader(userDB, walletDB, adminDB)), audit: audit} } func (h *Handler) ListBDLeaders(c *gin.Context) { query, ok := parseListQuery(c) if !ok { return } items, total, err := h.service.ListBDLeaders(c.Request.Context(), query) if err != nil { response.ServerError(c, "获取 BD Leader 列表失败") return } response.OK(c, response.Page{Items: items, Page: query.Page, PageSize: query.PageSize, Total: total}) } func (h *Handler) ListBDs(c *gin.Context) { query, ok := parseListQuery(c) if !ok { return } items, total, err := h.service.ListBDs(c.Request.Context(), query) if err != nil { response.ServerError(c, "获取 BD 列表失败") return } response.OK(c, response.Page{Items: items, Page: query.Page, PageSize: query.PageSize, Total: total}) } func (h *Handler) ListAgencies(c *gin.Context) { query, ok := parseListQuery(c) if !ok { return } items, total, err := h.service.ListAgencies(c.Request.Context(), query) if err != nil { response.ServerError(c, "获取 Agency 列表失败") return } response.OK(c, response.Page{Items: items, Page: query.Page, PageSize: query.PageSize, Total: total}) } func (h *Handler) ListHosts(c *gin.Context) { query, ok := parseListQuery(c) if !ok { return } items, total, err := h.service.ListHosts(c.Request.Context(), query) if err != nil { response.ServerError(c, "获取主播列表失败") return } response.OK(c, response.Page{Items: items, Page: query.Page, PageSize: query.PageSize, Total: total}) } func (h *Handler) ListCoinSellers(c *gin.Context) { query, ok := parseListQuery(c) if !ok { return } items, total, err := h.service.ListCoinSellers(c.Request.Context(), query) if err != nil { response.ServerError(c, "获取币商列表失败") return } response.OK(c, response.Page{Items: items, Page: query.Page, PageSize: query.PageSize, Total: total}) } func (h *Handler) CreateBDLeader(c *gin.Context) { var req createBDLeaderRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "BD Leader 参数不正确") return } profile, err := h.service.CreateBDLeader(c.Request.Context(), adminActorID(c), middleware.CurrentRequestID(c), req) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "create-bd-leader", "bd_profiles", profile.UserID, fmt.Sprintf("command_id=%s user_id=%d region_id=%d reason=%q", strings.TrimSpace(req.CommandID), profile.UserID, req.RegionID, strings.TrimSpace(req.Reason))) response.Created(c, profile) } func (h *Handler) CreateBD(c *gin.Context) { var req createBDRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "BD 参数不正确") return } profile, err := h.service.CreateBD(c.Request.Context(), adminActorID(c), middleware.CurrentRequestID(c), req) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "create-bd", "bd_profiles", profile.UserID, fmt.Sprintf("command_id=%s user_id=%d parent_leader_user_id=%d reason=%q", strings.TrimSpace(req.CommandID), profile.UserID, profile.ParentLeaderUserID, strings.TrimSpace(req.Reason))) response.Created(c, profile) } func (h *Handler) SetBDStatus(c *gin.Context) { targetUserID, ok := parseInt64ID(c, "user_id") if !ok { return } var req bdStatusRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "BD 状态参数不正确") return } profile, err := h.service.SetBDStatus(c.Request.Context(), adminActorID(c), targetUserID, middleware.CurrentRequestID(c), req) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "set-bd-status", "bd_profiles", profile.UserID, fmt.Sprintf("command_id=%s user_id=%d status=%s reason=%q", strings.TrimSpace(req.CommandID), profile.UserID, profile.Status, strings.TrimSpace(req.Reason))) response.OK(c, profile) } func (h *Handler) DeleteBDLeader(c *gin.Context) { targetUserID, ok := parseInt64ID(c, "user_id") if !ok { return } profile, err := h.service.DeleteBDLeader(c.Request.Context(), targetUserID) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "delete-bd-leader", "bd_profiles", profile.UserID, fmt.Sprintf("user_id=%d", profile.UserID)) response.OK(c, profile) } func (h *Handler) CreateCoinSeller(c *gin.Context) { var req createCoinSellerRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "币商参数不正确") return } profile, err := h.service.CreateCoinSeller(c.Request.Context(), adminActorID(c), middleware.CurrentRequestID(c), req) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "create-coin-seller", "coin_seller_profiles", profile.UserID, fmt.Sprintf("command_id=%s user_id=%d reason=%q", strings.TrimSpace(req.CommandID), profile.UserID, strings.TrimSpace(req.Reason))) response.Created(c, profile) } func (h *Handler) SetCoinSellerStatus(c *gin.Context) { targetUserID, ok := parseInt64ID(c, "user_id") if !ok { return } var req coinSellerStatusRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "币商状态参数不正确") return } profile, err := h.service.SetCoinSellerStatus(c.Request.Context(), adminActorID(c), targetUserID, middleware.CurrentRequestID(c), req) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "set-coin-seller-status", "coin_seller_profiles", profile.UserID, fmt.Sprintf("command_id=%s user_id=%d status=%s reason=%q", strings.TrimSpace(req.CommandID), profile.UserID, profile.Status, strings.TrimSpace(req.Reason))) response.OK(c, profile) } func (h *Handler) CreditCoinSellerStock(c *gin.Context) { sellerUserID, ok := parseInt64ID(c, "user_id") if !ok { return } var req coinSellerStockCreditRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "币商进货参数不正确") return } result, err := h.service.CreditCoinSellerStock(c.Request.Context(), adminActorID(c), sellerUserID, middleware.CurrentRequestID(c), req) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "coin-seller-stock-credit", "coin_seller_stock_records", sellerUserID, fmt.Sprintf("command_id=%s seller_user_id=%d stock_type=%s coin_amount=%d paid_currency_code=%s paid_amount_micro=%d transaction_id=%s reason=%q", strings.TrimSpace(req.CommandID), sellerUserID, result.GetStockType(), result.GetCoinAmount(), result.GetPaidCurrencyCode(), result.GetPaidAmountMicro(), result.GetTransactionId(), strings.TrimSpace(req.Reason))) response.Created(c, coinSellerStockCreditFromProto(result)) } func (h *Handler) GetCoinSellerSalaryRates(c *gin.Context) { regionID, ok := parseInt64ID(c, "region_id") if !ok { return } items, err := h.service.GetCoinSellerSalaryRates(c.Request.Context(), regionID) if err != nil { response.BadRequest(c, err.Error()) return } response.OK(c, gin.H{"regionId": regionID, "tiers": items}) } func (h *Handler) ReplaceCoinSellerSalaryRates(c *gin.Context) { regionID, ok := parseInt64ID(c, "region_id") if !ok { return } var req replaceCoinSellerSalaryRatesRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "币商工资兑换比例参数不正确") return } items, err := h.service.ReplaceCoinSellerSalaryRates(c.Request.Context(), adminActorID(c), regionID, req) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "coin-seller-salary-rates", "coin_seller_salary_exchange_rate_tiers", regionID, fmt.Sprintf("region_id=%d tiers=%d", regionID, len(items))) response.OK(c, gin.H{"regionId": regionID, "tiers": items}) } func (h *Handler) CreateAgency(c *gin.Context) { var req createAgencyRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Agency 参数不正确") return } result, err := h.service.CreateAgency(c.Request.Context(), adminActorID(c), middleware.CurrentRequestID(c), req) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "create-agency", "agencies", result.Agency.AgencyID, fmt.Sprintf("command_id=%s agency_id=%d owner_user_id=%d parent_bd_user_id=%d join_enabled=%t reason=%q", strings.TrimSpace(req.CommandID), result.Agency.AgencyID, result.Agency.OwnerUserID, result.Agency.ParentBDUserID, result.Agency.JoinEnabled, strings.TrimSpace(req.Reason))) response.Created(c, result) } func (h *Handler) CloseAgency(c *gin.Context) { agencyID, ok := parseInt64ID(c, "agency_id") if !ok { return } var req agencyCloseRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "关闭 Agency 参数不正确") return } agency, err := h.service.CloseAgency(c.Request.Context(), adminActorID(c), agencyID, middleware.CurrentRequestID(c), req) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "close-agency", "agencies", agency.AgencyID, fmt.Sprintf("command_id=%s agency_id=%d status=%s reason=%q", strings.TrimSpace(req.CommandID), agency.AgencyID, agency.Status, strings.TrimSpace(req.Reason))) response.OK(c, agency) } func (h *Handler) SetAgencyJoinEnabled(c *gin.Context) { agencyID, ok := parseInt64ID(c, "agency_id") if !ok { return } var req agencyJoinEnabledRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "Agency 入会开关参数不正确") return } agency, err := h.service.SetAgencyJoinEnabled(c.Request.Context(), adminActorID(c), agencyID, middleware.CurrentRequestID(c), req) if err != nil { response.BadRequest(c, err.Error()) return } writeHostOrgAuditLog(c, h.audit, "set-agency-join-enabled", "agencies", agency.AgencyID, fmt.Sprintf("command_id=%s agency_id=%d join_enabled=%t reason=%q", strings.TrimSpace(req.CommandID), agency.AgencyID, agency.JoinEnabled, strings.TrimSpace(req.Reason))) response.OK(c, agency) } func adminActorID(c *gin.Context) int64 { return int64(shared.ActorFromContext(c).UserID) } func parseInt64ID(c *gin.Context, name string) (int64, bool) { id, ok := shared.ParseID(c, name) if !ok { return 0, false } return int64(id), true } func parseListQuery(c *gin.Context) (listQuery, bool) { options := shared.ListOptions(c) query := listQuery{ Page: options.Page, PageSize: options.PageSize, Keyword: options.Keyword, Status: options.Status, SortBy: firstNonBlank(c.Query("sortBy"), c.Query("sort_by")), SortDirection: firstNonBlank(c.Query("sortDirection"), c.Query("sort_direction"), c.Query("order")), } var ok bool if query.RegionID, ok = parseOptionalInt64Query(c, "regionId", "region_id"); !ok { return listQuery{}, false } if query.AgencyID, ok = parseOptionalInt64Query(c, "agencyId", "agency_id"); !ok { return listQuery{}, false } if query.ParentBDUserID, ok = parseOptionalInt64Query(c, "parentBdUserId", "parent_bd_user_id"); !ok { return listQuery{}, false } if query.ParentLeaderUserID, ok = parseOptionalInt64Query(c, "parentLeaderUserId", "parent_leader_user_id"); !ok { return listQuery{}, false } return normalizeListQuery(query), true } func parseOptionalInt64Query(c *gin.Context, primary string, fallback string) (int64, bool) { raw := strings.TrimSpace(c.Query(primary)) if raw == "" { raw = strings.TrimSpace(c.Query(fallback)) } if raw == "" { return 0, true } value, err := strconv.ParseInt(raw, 10, 64) if err != nil || value < 0 { response.BadRequest(c, primary+" 参数不正确") return 0, false } return value, true } func writeHostOrgAuditLog(c *gin.Context, logger shared.OperationLogger, action string, resource string, resourceID int64, detail string) { // HostOrg 的后台审计只能写 hyapp-admin-server 自己的 admin_operation_logs。 // user-service 只接收业务命令并维护关系事实,不能反向保存后台审计数据。 shared.OperationLogWithResourceID(c, logger, action, resource, fmt.Sprintf("%d", resourceID), "success", detail) }