feat(admin): 管理端在线监控 + 好友关系管理后端

- admin/service/online_service.go: 在线用户列表/计数
- admin/controller/online_controller.go: GET /admin/online/users|count
- admin/service/contact_manage_service.go: 好友关系分页查询/管理员删除
- admin/controller/contact_manage_controller.go: GET|DELETE /admin/contacts
- 更新 admin router/provider/wire 集成新控制器

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-03-02 17:13:06 +08:00
parent 1c87a466a1
commit a77669ee99
9 changed files with 215 additions and 11 deletions

View File

@@ -0,0 +1,50 @@
package controller
import (
"strconv"
"github.com/echochat/backend/app/admin/service"
"github.com/echochat/backend/pkg/utils"
"github.com/gin-gonic/gin"
)
type ContactManageController struct {
contactManageService *service.ContactManageService
}
func NewContactManageController(contactManageService *service.ContactManageService) *ContactManageController {
return &ContactManageController{contactManageService: contactManageService}
}
// GetAllContacts GET /api/v1/admin/contacts
func (ctl *ContactManageController) GetAllContacts(c *gin.Context) {
ctx := c.Request.Context()
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
contacts, total, err := ctl.contactManageService.GetAllFriendships(ctx, page, pageSize)
if err != nil {
utils.ResponseError(c, "获取好友关系列表失败")
return
}
utils.ResponseOK(c, gin.H{
"list": contacts,
"total": total,
})
}
// DeleteContact DELETE /api/v1/admin/contacts/:id
func (ctl *ContactManageController) DeleteContact(c *gin.Context) {
ctx := c.Request.Context()
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
utils.ResponseBadRequest(c, "ID 格式错误")
return
}
if err := ctl.contactManageService.DeleteFriendship(ctx, id); err != nil {
utils.ResponseError(c, "删除好友关系失败")
return
}
utils.ResponseOK(c, nil)
}

View File

@@ -0,0 +1,37 @@
package controller
import (
"github.com/echochat/backend/app/admin/service"
"github.com/echochat/backend/pkg/utils"
"github.com/gin-gonic/gin"
)
type OnlineController struct {
onlineManageService *service.OnlineManageService
}
func NewOnlineController(onlineManageService *service.OnlineManageService) *OnlineController {
return &OnlineController{onlineManageService: onlineManageService}
}
// GetOnlineUsers GET /api/v1/admin/online/users
func (ctl *OnlineController) GetOnlineUsers(c *gin.Context) {
ctx := c.Request.Context()
userIDs, err := ctl.onlineManageService.GetOnlineUsers(ctx)
if err != nil {
utils.ResponseError(c, "获取在线用户列表失败")
return
}
utils.ResponseOK(c, userIDs)
}
// GetOnlineCount GET /api/v1/admin/online/count
func (ctl *OnlineController) GetOnlineCount(c *gin.Context) {
ctx := c.Request.Context()
count, err := ctl.onlineManageService.GetOnlineCount(ctx)
if err != nil {
utils.ResponseError(c, "获取在线用户数失败")
return
}
utils.ResponseOK(c, gin.H{"count": count})
}

View File

@@ -14,4 +14,8 @@ var AdminSet = wire.NewSet(
dao.NewUserManageDAO,
service.NewUserManageService,
controller.NewUserManageController,
service.NewOnlineManageService,
controller.NewOnlineController,
service.NewContactManageService,
controller.NewContactManageController,
)

View File

@@ -12,7 +12,9 @@ import (
// 所有管理端 API 需要 JWT + admin 角色双重中间件
func RegisterRoutes(
r *gin.Engine,
ctrl *controller.UserManageController,
userCtrl *controller.UserManageController,
onlineCtrl *controller.OnlineController,
contactManageCtrl *controller.ContactManageController,
jwtAuth gin.HandlerFunc,
) {
// 管理端路由组JWT 认证 + admin/super_admin 角色检查
@@ -20,13 +22,21 @@ func RegisterRoutes(
adminGroup.Use(jwtAuth, middleware.RequireRole(constants.RoleAdmin, constants.RoleSuperAdmin))
{
// 用户管理
adminGroup.GET("/users", ctrl.GetUserList)
adminGroup.GET("/users/:id", ctrl.GetUserDetail)
adminGroup.PUT("/users/:id/status", ctrl.UpdateUserStatus)
adminGroup.PUT("/users/:id/roles", ctrl.SetRoles)
adminGroup.POST("/users", ctrl.CreateUser)
adminGroup.GET("/users", userCtrl.GetUserList)
adminGroup.GET("/users/:id", userCtrl.GetUserDetail)
adminGroup.PUT("/users/:id/status", userCtrl.UpdateUserStatus)
adminGroup.PUT("/users/:id/roles", userCtrl.SetRoles)
adminGroup.POST("/users", userCtrl.CreateUser)
// 角色管理
adminGroup.GET("/roles", ctrl.GetAllRoles)
adminGroup.GET("/roles", userCtrl.GetAllRoles)
// 在线监控
adminGroup.GET("/online/users", onlineCtrl.GetOnlineUsers)
adminGroup.GET("/online/count", onlineCtrl.GetOnlineCount)
// 好友关系管理
adminGroup.GET("/contacts", contactManageCtrl.GetAllContacts)
adminGroup.DELETE("/contacts/:id", contactManageCtrl.DeleteContact)
}
}

View File

@@ -0,0 +1,67 @@
package service
import (
"context"
"github.com/echochat/backend/app/contact/model"
"github.com/echochat/backend/pkg/logs"
"go.uber.org/zap"
"gorm.io/gorm"
)
type ContactManageService struct {
db *gorm.DB
}
func NewContactManageService(db *gorm.DB) *ContactManageService {
return &ContactManageService{db: db}
}
type AdminFriendship struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
UserUsername string `json:"user_username"`
FriendID int64 `json:"friend_id"`
FriendUsername string `json:"friend_username"`
Status int `json:"status"`
CreatedAt string `json:"created_at"`
}
// GetAllFriendships 获取所有好友关系(分页)
func (s *ContactManageService) GetAllFriendships(ctx context.Context, page, pageSize int) ([]AdminFriendship, int64, error) {
funcName := "service.contact_manage_service.GetAllFriendships"
logs.Debug(ctx, funcName, "管理端查询好友关系")
var total int64
s.db.WithContext(ctx).Model(&model.Friendship{}).Count(&total)
var results []AdminFriendship
offset := (page - 1) * pageSize
err := s.db.WithContext(ctx).
Table("contact_friendships f").
Select("f.id, f.user_id, u1.username as user_username, f.friend_id, u2.username as friend_username, f.status, TO_CHAR(f.created_at, 'YYYY-MM-DD HH24:MI:SS') as created_at").
Joins("JOIN auth_users u1 ON u1.id = f.user_id").
Joins("JOIN auth_users u2 ON u2.id = f.friend_id").
Order("f.created_at DESC").
Offset(offset).Limit(pageSize).
Scan(&results).Error
return results, total, err
}
// DeleteFriendship 管理员删除好友关系(双向删除)
func (s *ContactManageService) DeleteFriendship(ctx context.Context, friendshipID int64) error {
funcName := "service.contact_manage_service.DeleteFriendship"
logs.Info(ctx, funcName, "管理员删除好友关系", zap.Int64("friendship_id", friendshipID))
var f model.Friendship
if err := s.db.WithContext(ctx).First(&f, friendshipID).Error; err != nil {
return err
}
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
tx.Where("user_id = ? AND friend_id = ?", f.UserID, f.FriendID).Delete(&model.Friendship{})
tx.Where("user_id = ? AND friend_id = ?", f.FriendID, f.UserID).Delete(&model.Friendship{})
return nil
})
}

View File

@@ -0,0 +1,26 @@
package service
import (
"context"
wsApp "github.com/echochat/backend/app/ws"
"github.com/echochat/backend/pkg/logs"
)
type OnlineManageService struct {
onlineService *wsApp.OnlineService
}
func NewOnlineManageService(onlineService *wsApp.OnlineService) *OnlineManageService {
return &OnlineManageService{onlineService: onlineService}
}
func (s *OnlineManageService) GetOnlineUsers(ctx context.Context) ([]int64, error) {
funcName := "service.online_manage_service.GetOnlineUsers"
logs.Debug(ctx, funcName, "获取在线用户列表")
return s.onlineService.GetOnlineUserIDs(ctx)
}
func (s *OnlineManageService) GetOnlineCount(ctx context.Context) (int64, error) {
return s.onlineService.GetOnlineCount(ctx)
}

View File

@@ -24,7 +24,9 @@ type App struct {
AuthService *service.AuthService // Auth 认证服务
AuthController *authController.AuthController // 前台认证控制器
AdminAuthController *authController.AdminAuthController // 后台认证控制器
UserManageController *adminController.UserManageController // 管理端用户管理控制器
UserManageController *adminController.UserManageController // 管理端用户管理控制器
OnlineController *adminController.OnlineController // 管理端在线监控控制器
ContactManageController *adminController.ContactManageController // 管理端好友关系管理控制器
WSHandler *wsApp.Handler // WebSocket 连接处理器
Hub *ws.Hub // WebSocket Hub 连接管理
PubSub *ws.PubSub // Redis Pub/Sub 消息路由
@@ -41,6 +43,8 @@ func NewApp(
authCtrl *authController.AuthController,
adminAuthCtrl *authController.AdminAuthController,
userManageCtrl *adminController.UserManageController,
onlineCtrl *adminController.OnlineController,
contactManageCtrl *adminController.ContactManageController,
wsHandler *wsApp.Handler,
hub *ws.Hub,
pubsub *ws.PubSub,
@@ -54,7 +58,9 @@ func NewApp(
AuthService: authService,
AuthController: authCtrl,
AdminAuthController: adminAuthCtrl,
UserManageController: userManageCtrl,
UserManageController: userManageCtrl,
OnlineController: onlineCtrl,
ContactManageController: contactManageCtrl,
WSHandler: wsHandler,
Hub: hub,
PubSub: pubsub,

View File

@@ -48,11 +48,15 @@ func InitializeApp(cfg *config.Config) (*App, error) {
hub := ws.ProvideHub()
pubSub := ws.ProvidePubSub(client, hub)
onlineService := ws.ProvideOnlineService(client, hub, pubSub)
onlineManageService := service2.NewOnlineManageService(onlineService)
onlineController := controller2.NewOnlineController(onlineManageService)
contactManageService := service2.NewContactManageService(gormDB)
contactManageController := controller2.NewContactManageController(contactManageService)
handler := ws.ProvideWSHandler(hub, pubSub, jwtConfig, onlineService)
friendshipDAO := dao3.NewFriendshipDAO(gormDB)
friendGroupDAO := dao3.NewFriendGroupDAO(gormDB)
contactService := service3.NewContactService(friendshipDAO, friendGroupDAO, pubSub)
contactController := controller3.NewContactController(contactService)
app := NewApp(cfg, gormDB, client, authService, authController, adminAuthController, userManageController, handler, hub, pubSub, onlineService, contactController)
app := NewApp(cfg, gormDB, client, authService, authController, adminAuthController, userManageController, onlineController, contactManageController, handler, hub, pubSub, onlineService, contactController)
return app, nil
}