275 lines
7.1 KiB
Go
275 lines
7.1 KiB
Go
package report
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"hyapp-admin-server/internal/integration/roomclient"
|
|
)
|
|
|
|
const (
|
|
targetTypeUser = "user"
|
|
targetTypeRoom = "room"
|
|
)
|
|
|
|
type Service struct {
|
|
roomClient roomclient.Client
|
|
userDB *sql.DB
|
|
}
|
|
|
|
func NewService(userDB *sql.DB, roomClient roomclient.Client) *Service {
|
|
return &Service{userDB: userDB, roomClient: roomClient}
|
|
}
|
|
|
|
func (s *Service) ListReports(ctx context.Context, appCode string, query listQuery) ([]reportDTO, int64, error) {
|
|
query = normalizeListQuery(query)
|
|
if s == nil || s.userDB == nil {
|
|
return nil, 0, fmt.Errorf("user mysql is not configured")
|
|
}
|
|
|
|
whereSQL, args := reportWhere(appCode, query)
|
|
var total int64
|
|
if err := s.userDB.QueryRowContext(ctx, `
|
|
SELECT COUNT(*)
|
|
FROM user_reports r
|
|
LEFT JOIN users u ON u.app_code = r.app_code AND u.user_id = r.target_user_id
|
|
`+whereSQL,
|
|
args...,
|
|
).Scan(&total); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
rows, err := s.userDB.QueryContext(ctx, `
|
|
SELECT r.report_id, r.reporter_user_id, r.target_type, r.target_user_id, r.room_id,
|
|
r.report_type, r.reason, CAST(r.image_urls_json AS CHAR), r.status, r.request_id,
|
|
r.created_at_ms, r.updated_at_ms,
|
|
COALESCE(u.current_display_user_id, ''), COALESCE(u.default_display_user_id, ''),
|
|
COALESCE(u.username, ''), COALESCE(u.avatar, '')
|
|
FROM user_reports r
|
|
LEFT JOIN users u ON u.app_code = r.app_code AND u.user_id = r.target_user_id
|
|
`+whereSQL+`
|
|
ORDER BY r.created_at_ms DESC, r.report_id DESC
|
|
LIMIT ? OFFSET ?`,
|
|
append(args, query.PageSize, offset(query.Page, query.PageSize))...,
|
|
)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
items := make([]reportDTO, 0, query.PageSize)
|
|
for rows.Next() {
|
|
var item reportDTO
|
|
var reporterUserID int64
|
|
var targetUserID sql.NullInt64
|
|
var imageURLsJSON string
|
|
var displayUserID string
|
|
var defaultDisplayUserID string
|
|
var username string
|
|
var avatar string
|
|
if err := rows.Scan(
|
|
&item.ReportID,
|
|
&reporterUserID,
|
|
&item.TargetType,
|
|
&targetUserID,
|
|
&item.RoomID,
|
|
&item.ReportType,
|
|
&item.Reason,
|
|
&imageURLsJSON,
|
|
&item.Status,
|
|
&item.RequestID,
|
|
&item.CreatedAtMS,
|
|
&item.UpdatedAtMS,
|
|
&displayUserID,
|
|
&defaultDisplayUserID,
|
|
&username,
|
|
&avatar,
|
|
); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
imageURLs, err := parseImageURLs(imageURLsJSON)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
item.ReporterUserID = formatID(reporterUserID)
|
|
item.ImageURLs = imageURLs
|
|
item.Target = reportTargetDTO{Type: item.TargetType}
|
|
if targetUserID.Valid && targetUserID.Int64 > 0 {
|
|
item.UserID = formatID(targetUserID.Int64)
|
|
item.Target.User = &reportUserDTO{
|
|
Avatar: avatar,
|
|
DefaultDisplayUserID: defaultDisplayUserID,
|
|
DisplayUserID: displayUserID,
|
|
UserID: item.UserID,
|
|
Username: username,
|
|
}
|
|
}
|
|
if item.TargetType == targetTypeRoom {
|
|
item.Target.Room = &reportRoomDTO{RoomID: item.RoomID}
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
if err := s.fillRoomTargets(ctx, items); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
return items, total, nil
|
|
}
|
|
|
|
func normalizeListQuery(query listQuery) listQuery {
|
|
if query.Page < 1 {
|
|
query.Page = 1
|
|
}
|
|
if query.PageSize < 1 {
|
|
query.PageSize = 20
|
|
}
|
|
if query.PageSize > 100 {
|
|
query.PageSize = 100
|
|
}
|
|
query.Keyword = strings.TrimSpace(query.Keyword)
|
|
query.TargetType = normalizeTargetType(query.TargetType)
|
|
query.ReportType = strings.ToLower(strings.TrimSpace(query.ReportType))
|
|
if query.StartAtMS < 0 {
|
|
query.StartAtMS = 0
|
|
}
|
|
if query.EndAtMS < 0 {
|
|
query.EndAtMS = 0
|
|
}
|
|
return query
|
|
}
|
|
|
|
func normalizeTargetType(value string) string {
|
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
|
case targetTypeUser, "用户":
|
|
return targetTypeUser
|
|
case targetTypeRoom, "房间":
|
|
return targetTypeRoom
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func reportWhere(appCode string, query listQuery) (string, []any) {
|
|
conditions := []string{"r.app_code = ?"}
|
|
args := []any{appCode}
|
|
if query.TargetType != "" {
|
|
conditions = append(conditions, "r.target_type = ?")
|
|
args = append(args, query.TargetType)
|
|
}
|
|
if query.ReportType != "" {
|
|
conditions = append(conditions, "r.report_type = ?")
|
|
args = append(args, query.ReportType)
|
|
}
|
|
if query.StartAtMS > 0 {
|
|
conditions = append(conditions, "r.created_at_ms >= ?")
|
|
args = append(args, query.StartAtMS)
|
|
}
|
|
if query.EndAtMS > 0 {
|
|
conditions = append(conditions, "r.created_at_ms < ?")
|
|
args = append(args, query.EndAtMS)
|
|
}
|
|
if query.Keyword != "" {
|
|
like := "%" + query.Keyword + "%"
|
|
conditions = append(conditions, `(r.report_id LIKE ? OR r.room_id LIKE ? OR CAST(r.target_user_id AS CHAR) LIKE ? OR CAST(r.reporter_user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.default_display_user_id LIKE ? OR u.username LIKE ?)`)
|
|
args = append(args, like, like, like, like, like, like, like)
|
|
}
|
|
return "WHERE " + strings.Join(conditions, " AND "), args
|
|
}
|
|
|
|
func parseImageURLs(raw string) ([]string, error) {
|
|
if strings.TrimSpace(raw) == "" {
|
|
return []string{}, nil
|
|
}
|
|
var urls []string
|
|
if err := json.Unmarshal([]byte(raw), &urls); err != nil {
|
|
return nil, err
|
|
}
|
|
if urls == nil {
|
|
return []string{}, nil
|
|
}
|
|
return urls, nil
|
|
}
|
|
|
|
func (s *Service) fillRoomTargets(ctx context.Context, items []reportDTO) error {
|
|
if s == nil || s.roomClient == nil || len(items) == 0 {
|
|
return nil
|
|
}
|
|
roomIDs := make([]string, 0, len(items))
|
|
for _, item := range items {
|
|
if item.TargetType == targetTypeRoom && strings.TrimSpace(item.RoomID) != "" {
|
|
roomIDs = append(roomIDs, item.RoomID)
|
|
}
|
|
}
|
|
rooms, err := s.queryRooms(ctx, uniqueStrings(roomIDs))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for index := range items {
|
|
if items[index].TargetType != targetTypeRoom {
|
|
continue
|
|
}
|
|
room := reportRoomDTO{RoomID: items[index].RoomID}
|
|
if found, ok := rooms[items[index].RoomID]; ok {
|
|
room = found
|
|
}
|
|
items[index].Target.Room = &room
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) queryRooms(ctx context.Context, roomIDs []string) (map[string]reportRoomDTO, error) {
|
|
rooms := make(map[string]reportRoomDTO, len(roomIDs))
|
|
for _, roomID := range roomIDs {
|
|
room, err := s.roomClient.GetRoom(ctx, roomclient.GetRoomRequest{RoomID: roomID})
|
|
if err != nil {
|
|
// 举报列表是治理入口,房间被删或房间服务短暂不可读时仍保留举报事实本身。
|
|
if ctx.Err() != nil {
|
|
return nil, ctx.Err()
|
|
}
|
|
continue
|
|
}
|
|
rooms[roomID] = reportRoomDTO{
|
|
CoverURL: room.CoverURL,
|
|
RoomID: room.RoomID,
|
|
RoomShortID: room.RoomShortID,
|
|
Title: room.Title,
|
|
}
|
|
}
|
|
return rooms, nil
|
|
}
|
|
|
|
func formatID(value int64) string {
|
|
if value <= 0 {
|
|
return ""
|
|
}
|
|
return strconv.FormatInt(value, 10)
|
|
}
|
|
|
|
func uniqueStrings(values []string) []string {
|
|
seen := make(map[string]struct{}, len(values))
|
|
unique := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[value]; ok {
|
|
continue
|
|
}
|
|
seen[value] = struct{}{}
|
|
unique = append(unique, value)
|
|
}
|
|
return unique
|
|
}
|
|
|
|
func offset(page int, pageSize int) int {
|
|
return (page - 1) * pageSize
|
|
}
|