package app import ( "database/sql" "net/http" "strings" ) func (a *App) searchUsers(w http.ResponseWriter, r *http.Request) { keyword := strings.TrimSpace(r.URL.Query().Get("q")) if keyword == "" { reply(w, map[string]any{"items": []profileView{}, "total": 0}) return } pattern := "%" + keyword + "%" page, pageSize, offset := pageOptions(r) var total int if err := a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM users u JOIN user_profiles p ON p.user_id=u.id JOIN user_privacy_settings privacy ON privacy.user_id=u.id WHERE u.status=1 AND u.id<>? AND privacy.allow_search=1 AND (p.nickname LIKE ? OR u.public_id LIKE ?) AND NOT EXISTS(SELECT 1 FROM user_blocks b WHERE (b.user_id=? AND b.blocked_user_id=u.id) OR (b.user_id=u.id AND b.blocked_user_id=?))`, current(r).ID, pattern, pattern, current(r).ID, current(r).ID).Scan(&total); err != nil { fail(w, 500, 50001, "搜索失败") return } rows, err := a.db.QueryContext(r.Context(), `SELECT u.id FROM users u JOIN user_profiles p ON p.user_id=u.id JOIN user_privacy_settings privacy ON privacy.user_id=u.id WHERE u.status=1 AND u.id<>? AND privacy.allow_search=1 AND (p.nickname LIKE ? OR u.public_id LIKE ?) AND NOT EXISTS(SELECT 1 FROM user_blocks b WHERE (b.user_id=? AND b.blocked_user_id=u.id) OR (b.user_id=u.id AND b.blocked_user_id=?)) ORDER BY p.is_vip DESC,p.last_active_at DESC LIMIT ? OFFSET ?`, current(r).ID, pattern, pattern, current(r).ID, current(r).ID, pageSize, offset) if err != nil { fail(w, 500, 50001, "搜索失败") return } defer rows.Close() items := []profileView{} for rows.Next() { var id int64 _ = rows.Scan(&id) if item, loadErr := a.loadProfile(r, id, current(r).ID); loadErr == nil { item.AvatarThumbnail = avatarThumbnailURL(item.Avatar) items = append(items, item) } } reply(w, map[string]any{"items": items, "total": total, "keyword": keyword, "page": page, "pageSize": pageSize, "hasMore": offset+len(items) < total}) } func (a *App) followingList(w http.ResponseWriter, r *http.Request) { a.relationshipList(w, r, "following") } func (a *App) followerList(w http.ResponseWriter, r *http.Request) { a.relationshipList(w, r, "followers") } func (a *App) visitorList(w http.ResponseWriter, r *http.Request) { if !a.resolveMembershipEntitlements(r.Context(), current(r).ID).CanViewVisitors { fail(w, http.StatusForbidden, 10006, "开通会员后可查看访客记录") return } a.relationshipList(w, r, "visitors") } func (a *App) relationshipList(w http.ResponseWriter, r *http.Request, listType string) { var rows *sql.Rows var err error page, pageSize, offset := pageOptions(r) var total int switch listType { case "following": _ = a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM user_follows WHERE user_id=?`, current(r).ID).Scan(&total) rows, err = a.db.QueryContext(r.Context(), `SELECT target_user_id FROM user_follows WHERE user_id=? ORDER BY created_at DESC LIMIT ? OFFSET ?`, current(r).ID, pageSize, offset) case "followers": _ = a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM user_follows WHERE target_user_id=?`, current(r).ID).Scan(&total) rows, err = a.db.QueryContext(r.Context(), `SELECT user_id FROM user_follows WHERE target_user_id=? ORDER BY created_at DESC LIMIT ? OFFSET ?`, current(r).ID, pageSize, offset) default: _ = a.db.QueryRowContext(r.Context(), `SELECT COUNT(DISTINCT viewer_user_id) FROM profile_visits WHERE target_user_id=?`, current(r).ID).Scan(&total) rows, err = a.db.QueryContext(r.Context(), `SELECT viewer_user_id FROM profile_visits WHERE target_user_id=? GROUP BY viewer_user_id ORDER BY MAX(visited_at) DESC LIMIT ? OFFSET ?`, current(r).ID, pageSize, offset) } if err != nil { fail(w, 500, 50001, "查询失败") return } defer rows.Close() items := []profileView{} for rows.Next() { var id int64 _ = rows.Scan(&id) if item, loadErr := a.loadProfile(r, id, current(r).ID); loadErr == nil { item.AvatarThumbnail = avatarThumbnailURL(item.Avatar) items = append(items, item) } } reply(w, map[string]any{"items": items, "total": total, "type": listType, "page": page, "pageSize": pageSize, "hasMore": offset+len(items) < total}) } type privacyView struct { NearbyVisible bool `json:"nearbyVisible"` DistanceVisible bool `json:"distanceVisible"` OnlineVisible bool `json:"onlineVisible"` LastActiveVisible bool `json:"lastActiveVisible"` AllowStrangerMessage bool `json:"allowStrangerMessage"` AllowProfileVisitRecord bool `json:"allowProfileVisitRecord"` InvisibleVisit bool `json:"invisibleVisit"` AllowSearch bool `json:"allowSearch"` } func (a *App) getPrivacy(w http.ResponseWriter, r *http.Request) { var values [8]int err := a.db.QueryRowContext(r.Context(), `SELECT nearby_visible,distance_visible,online_visible,last_active_visible,allow_stranger_message,allow_profile_visit_record,invisible_visit,allow_search FROM user_privacy_settings WHERE user_id=?`, current(r).ID).Scan(&values[0], &values[1], &values[2], &values[3], &values[4], &values[5], &values[6], &values[7]) if err != nil { fail(w, 500, 50001, "查询失败") return } reply(w, privacyView{NearbyVisible: values[0] == 1, DistanceVisible: values[1] == 1, OnlineVisible: values[2] == 1, LastActiveVisible: values[3] == 1, AllowStrangerMessage: values[4] == 1, AllowProfileVisitRecord: values[5] == 1, InvisibleVisit: values[6] == 1, AllowSearch: values[7] == 1}) } func (a *App) updatePrivacy(w http.ResponseWriter, r *http.Request) { var req privacyView if decode(r, &req) != nil { fail(w, 400, 20001, "隐私设置格式错误") return } if req.InvisibleVisit && !a.resolveMembershipEntitlements(r.Context(), current(r).ID).CanInvisibleVisit { fail(w, http.StatusForbidden, 10006, "当前会员等级不支持隐身访问") return } _, err := a.db.ExecContext(r.Context(), `UPDATE user_privacy_settings SET nearby_visible=?,distance_visible=?,online_visible=?,last_active_visible=?,allow_stranger_message=?,allow_profile_visit_record=?,invisible_visit=?,allow_search=? WHERE user_id=?`, req.NearbyVisible, req.DistanceVisible, req.OnlineVisible, req.LastActiveVisible, req.AllowStrangerMessage, req.AllowProfileVisitRecord, req.InvisibleVisit, req.AllowSearch, current(r).ID) if err != nil { fail(w, 500, 50001, "保存失败") return } reply(w, req) } func (a *App) blockUser(w http.ResponseWriter, r *http.Request) { target, err := pathID(r) if err != nil || target == current(r).ID { fail(w, 400, 20001, "无效用户") return } var targetExists int if err = a.db.QueryRowContext(r.Context(), `SELECT EXISTS(SELECT 1 FROM users WHERE id=? AND status=1 AND deleted_at IS NULL)`, target).Scan(&targetExists); err != nil || targetExists != 1 { fail(w, http.StatusNotFound, 30001, "用户不存在") return } tx, err := a.db.BeginTx(r.Context(), nil) if err != nil { fail(w, 500, 50001, "拉黑失败") return } defer tx.Rollback() _, err = tx.ExecContext(r.Context(), `INSERT IGNORE INTO user_blocks(user_id,blocked_user_id,reason)VALUES(?,?,'user_action')`, current(r).ID, target) if err == nil { _, err = tx.ExecContext(r.Context(), `DELETE FROM user_follows WHERE (user_id=? AND target_user_id=?) OR (user_id=? AND target_user_id=?)`, current(r).ID, target, target, current(r).ID) } if err == nil { _, err = tx.ExecContext(r.Context(), `DELETE FROM user_likes WHERE (user_id=? AND target_user_id=?) OR (user_id=? AND target_user_id=?)`, current(r).ID, target, target, current(r).ID) } if err == nil { err = tx.Commit() } if err != nil { fail(w, 500, 50001, "拉黑失败") return } reply(w, map[string]bool{"success": true}) } func (a *App) unblockUser(w http.ResponseWriter, r *http.Request) { target, err := pathID(r) if err != nil { fail(w, 400, 20001, "无效用户") return } _, err = a.db.ExecContext(r.Context(), `DELETE FROM user_blocks WHERE user_id=? AND blocked_user_id=?`, current(r).ID, target) if err != nil { fail(w, 500, 50001, "解除拉黑失败") return } reply(w, map[string]bool{"success": true}) } func (a *App) loadPost(r *http.Request, id int64) (postView, error) { var item postView var userID int64 var vip int err := a.db.QueryRowContext(r.Context(), `SELECT p.id,p.user_id,p.content,p.location_text,p.like_count,p.comment_count,p.created_at,u.public_id,pr.nickname,pr.avatar_url,pr.gender,pr.is_vip,EXISTS(SELECT 1 FROM post_likes l WHERE l.post_id=p.id AND l.user_id=?) FROM posts p JOIN users u ON u.id=p.user_id JOIN user_profiles pr ON pr.user_id=p.user_id WHERE p.id=? AND p.status=1 AND (p.visibility=1 OR p.user_id=? OR (p.visibility=2 AND EXISTS(SELECT 1 FROM user_follows audience WHERE audience.user_id=? AND audience.target_user_id=p.user_id)))`, current(r).ID, id, current(r).ID, current(r).ID).Scan(&item.ID, &userID, &item.Content, &item.Location, &item.LikeCount, &item.CommentCount, &item.CreatedAt, &item.User.PublicID, &item.User.Nickname, &item.User.Avatar, &item.User.Gender, &vip, &item.Liked) if err != nil { return item, err } item.User.ID = userID item.User.VIP = vip == 1 item.User.AvatarThumbnail = avatarThumbnailURL(item.User.Avatar) item.CanEdit = userID == current(r).ID item.CanDelete = userID == current(r).ID item.Media = []string{} item.MediaThumbnails = []string{} rows, _ := a.db.QueryContext(r.Context(), `SELECT media_url FROM post_media WHERE post_id=? ORDER BY sort_order`, id) if rows != nil { defer rows.Close() for rows.Next() { var media string _ = rows.Scan(&media) item.Media = append(item.Media, media) item.MediaThumbnails = append(item.MediaThumbnails, mediaThumbnailURL(media)) } } return item, nil } func (a *App) postDetail(w http.ResponseWriter, r *http.Request) { id, err := pathID(r) if err != nil { fail(w, 400, 20001, "动态编号无效") return } item, err := a.loadPost(r, id) if err != nil { fail(w, 404, 30001, "动态不存在") return } reply(w, item) } func (a *App) userPosts(w http.ResponseWriter, r *http.Request) { userID, err := pathID(r) if err != nil { fail(w, 400, 20001, "用户编号无效") return } page, pageSize, offset := pageOptions(r) var total int if err := a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM posts p WHERE p.user_id=? AND p.status=1 AND (p.visibility=1 OR p.user_id=? OR (p.visibility=2 AND EXISTS(SELECT 1 FROM user_follows audience WHERE audience.user_id=? AND audience.target_user_id=p.user_id)))`, userID, current(r).ID, current(r).ID).Scan(&total); err != nil { fail(w, 500, 50001, "查询失败") return } rows, err := a.db.QueryContext(r.Context(), `SELECT p.id FROM posts p WHERE p.user_id=? AND p.status=1 AND (p.visibility=1 OR p.user_id=? OR (p.visibility=2 AND EXISTS(SELECT 1 FROM user_follows audience WHERE audience.user_id=? AND audience.target_user_id=p.user_id))) ORDER BY p.created_at DESC LIMIT ? OFFSET ?`, userID, current(r).ID, current(r).ID, pageSize, offset) if err != nil { fail(w, 500, 50001, "查询失败") return } defer rows.Close() ids := []int64{} for rows.Next() { var id int64 _ = rows.Scan(&id) ids = append(ids, id) } items := []postView{} for _, id := range ids { if item, loadErr := a.loadPost(r, id); loadErr == nil { items = append(items, item) } } reply(w, map[string]any{"items": items, "total": total, "userId": userID, "page": page, "pageSize": pageSize, "hasMore": offset+len(items) < total}) }