Files
backend/internal/service/admin_group_service.go
lan d9aa4b46c3
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00

367 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
"errors"
"with_you/internal/dto"
apperrors "with_you/internal/errors"
"with_you/internal/model"
"with_you/internal/repository"
)
// AdminGroupService 管理端群组服务接口
type AdminGroupService interface {
// GetGroupList 获取群组列表(分页、搜索、筛选)
GetGroupList(ctx context.Context, page, pageSize int, keyword, status, ownerID, startDate, endDate string) ([]dto.AdminGroupListResponse, int64, error)
// GetGroupDetail 获取群组详情
GetGroupDetail(ctx context.Context, groupID string) (*dto.AdminGroupDetailResponse, error)
// GetGroupMembers 获取群组成员列表
GetGroupMembers(ctx context.Context, groupID string, page, pageSize int) (*dto.AdminGroupMemberListResponse, error)
// DeleteGroup 解散群组
DeleteGroup(ctx context.Context, groupID string) error
// UpdateGroupStatus 更新群组状态
UpdateGroupStatus(ctx context.Context, groupID string, status string) (*dto.AdminGroupDetailResponse, error)
// RemoveMember 移除群组成员
RemoveMember(ctx context.Context, groupID string, userID string) error
// TransferOwner 转让群主
TransferOwner(ctx context.Context, groupID string, newOwnerID string) (*dto.AdminGroupDetailResponse, error)
}
// adminGroupServiceImpl 管理端群组服务实现
type adminGroupServiceImpl struct {
groupRepo repository.GroupRepository
userRepo repository.UserRepository
}
// NewAdminGroupService 创建管理端群组服务
func NewAdminGroupService(
groupRepo repository.GroupRepository,
userRepo repository.UserRepository,
) AdminGroupService {
return &adminGroupServiceImpl{
groupRepo: groupRepo,
userRepo: userRepo,
}
}
// GetGroupList 获取群组列表
func (s *adminGroupServiceImpl) GetGroupList(ctx context.Context, page, pageSize int, keyword, status, ownerID, startDate, endDate string) ([]dto.AdminGroupListResponse, int64, error) {
groups, total, err := s.groupRepo.GetGroupList(page, pageSize, keyword, status, ownerID, startDate, endDate)
if err != nil {
return nil, 0, err
}
responses := make([]dto.AdminGroupListResponse, 0, len(groups))
for _, group := range groups {
response := dto.AdminGroupListResponse{
ID: group.ID,
Name: group.Name,
Avatar: group.Avatar,
Description: group.Description,
OwnerID: group.OwnerID,
MemberCount: group.MemberCount,
MaxMembers: group.MaxMembers,
JoinType: int(group.JoinType),
MuteAll: group.MuteAll,
Status: "active", // 默认状态为active后续可以根据需要添加状态字段
CreatedAt: dto.FormatTime(group.CreatedAt),
UpdatedAt: dto.FormatTime(group.UpdatedAt),
}
// 获取群主信息
if group.OwnerID != "" {
owner, err := s.userRepo.GetByID(group.OwnerID)
if err == nil && owner != nil {
response.Owner = &dto.UserResponse{
ID: owner.ID,
Username: owner.Username,
Nickname: owner.Nickname,
Avatar: owner.Avatar,
CreatedAt: dto.FormatTime(owner.CreatedAt),
}
}
}
// 获取帖子数量
postCount, _ := s.groupRepo.GetGroupPostCount(group.ID)
response.PostCount = postCount
responses = append(responses, response)
}
return responses, total, nil
}
// GetGroupDetail 获取群组详情
func (s *adminGroupServiceImpl) GetGroupDetail(ctx context.Context, groupID string) (*dto.AdminGroupDetailResponse, error) {
group, err := s.groupRepo.GetByID(groupID)
if err != nil {
return nil, err
}
response := &dto.AdminGroupDetailResponse{
ID: group.ID,
Name: group.Name,
Avatar: group.Avatar,
Description: group.Description,
OwnerID: group.OwnerID,
MemberCount: group.MemberCount,
MaxMembers: group.MaxMembers,
JoinType: int(group.JoinType),
MuteAll: group.MuteAll,
Status: "active",
IsPublic: true, // 默认为公开群组,后续可以根据需要添加字段
CreatedAt: dto.FormatTime(group.CreatedAt),
UpdatedAt: dto.FormatTime(group.UpdatedAt),
}
// 获取群主信息
if group.OwnerID != "" {
owner, err := s.userRepo.GetByID(group.OwnerID)
if err == nil && owner != nil {
response.Owner = dto.ConvertUserToResponse(owner)
}
}
// 获取帖子数量
postCount, _ := s.groupRepo.GetGroupPostCount(group.ID)
response.PostCount = postCount
// 获取最新公告
announcement, _ := s.groupRepo.GetLatestAnnouncement(groupID)
if announcement != nil {
response.Announcement = &dto.GroupAnnouncementResponse{
ID: announcement.ID,
GroupID: announcement.GroupID,
Content: announcement.Content,
AuthorID: announcement.AuthorID,
IsPinned: announcement.IsPinned,
CreatedAt: dto.FormatTime(announcement.CreatedAt),
}
}
return response, nil
}
// GetGroupMembers 获取群组成员列表
func (s *adminGroupServiceImpl) GetGroupMembers(ctx context.Context, groupID string, page, pageSize int) (*dto.AdminGroupMemberListResponse, error) {
// 先检查群组是否存在
_, err := s.groupRepo.GetByID(groupID)
if err != nil {
return nil, errors.New("群组不存在")
}
members, total, err := s.groupRepo.GetMembersWithUserInfo(groupID, page, pageSize)
if err != nil {
return nil, err
}
list := make([]*dto.AdminGroupMemberResponse, 0, len(members))
for _, member := range members {
item := &dto.AdminGroupMemberResponse{
ID: member.ID,
UserID: member.UserID,
Role: member.Role,
Nickname: member.Nickname,
Muted: member.Muted,
JoinedAt: dto.FormatTime(member.JoinTime),
}
if member.User != nil {
item.Username = member.User.Username
item.Nickname = member.User.Nickname
item.Avatar = member.User.Avatar
item.User = &dto.UserResponse{
ID: member.User.ID,
Username: member.User.Username,
Nickname: member.User.Nickname,
Avatar: member.User.Avatar,
CreatedAt: dto.FormatTime(member.User.CreatedAt),
}
}
list = append(list, item)
}
return &dto.AdminGroupMemberListResponse{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
}, nil
}
// DeleteGroup 解散群组
func (s *adminGroupServiceImpl) DeleteGroup(ctx context.Context, groupID string) error {
// 先检查群组是否存在
_, err := s.groupRepo.GetByID(groupID)
if err != nil {
return errors.New("群组不存在")
}
return s.groupRepo.Delete(groupID)
}
// UpdateGroupStatus 更新群组状态
func (s *adminGroupServiceImpl) UpdateGroupStatus(ctx context.Context, groupID string, status string) (*dto.AdminGroupDetailResponse, error) {
// 先检查群组是否存在
group, err := s.groupRepo.GetByID(groupID)
if err != nil {
return nil, errors.New("群组不存在")
}
// 验证状态值
if status != "active" && status != "dissolved" {
return nil, errors.New("无效的状态值")
}
// 如果状态是dissolved则解散群组
if status == "dissolved" {
err = s.groupRepo.Delete(groupID)
if err != nil {
return nil, err
}
} else {
// 更新群组状态
err = s.groupRepo.UpdateGroupStatus(groupID, status)
if err != nil {
return nil, err
}
// 重新获取群组信息
group, err = s.groupRepo.GetByID(groupID)
if err != nil {
return nil, err
}
}
// 构建响应
response := &dto.AdminGroupDetailResponse{
ID: group.ID,
Name: group.Name,
Avatar: group.Avatar,
Description: group.Description,
OwnerID: group.OwnerID,
MemberCount: group.MemberCount,
MaxMembers: group.MaxMembers,
JoinType: int(group.JoinType),
MuteAll: group.MuteAll,
Status: status,
IsPublic: true,
CreatedAt: dto.FormatTime(group.CreatedAt),
UpdatedAt: dto.FormatTime(group.UpdatedAt),
}
// 获取群主信息
if group.OwnerID != "" {
owner, err := s.userRepo.GetByID(group.OwnerID)
if err == nil && owner != nil {
response.Owner = &dto.UserResponse{
ID: owner.ID,
Username: owner.Username,
Nickname: owner.Nickname,
Avatar: owner.Avatar,
CreatedAt: dto.FormatTime(owner.CreatedAt),
}
}
}
return response, nil
}
// RemoveMember 移除群组成员
func (s *adminGroupServiceImpl) RemoveMember(ctx context.Context, groupID string, userID string) error {
// 先检查群组是否存在
_, err := s.groupRepo.GetByID(groupID)
if err != nil {
return errors.New("群组不存在")
}
// 检查用户是否是群成员
member, err := s.groupRepo.GetMember(groupID, userID)
if err != nil {
return errors.New("用户不是群组成员")
}
// 不能移除群主
if member.Role == model.GroupRoleOwner {
return errors.New("不能移除群主")
}
return s.groupRepo.RemoveMember(groupID, userID)
}
// TransferOwner 转让群主
// 委托 groupRepo.TransferOwner 在单个数据库事务内原子完成:
// - 更新 group.owner_id 为新群主
// - 原群主角色降级为普通成员
// - 新群主角色升级为 owner
//
// 任何一步失败都会整体回滚,避免出现「两个群主」或「零群主」的数据不一致。
func (s *adminGroupServiceImpl) TransferOwner(ctx context.Context, groupID string, newOwnerID string) (*dto.AdminGroupDetailResponse, error) {
// 校验群组存在
group, err := s.groupRepo.GetByID(groupID)
if err != nil {
return nil, apperrors.ErrGroupNotFound
}
// 校验新群主是群成员
newOwnerMember, err := s.groupRepo.GetMember(groupID, newOwnerID)
if err != nil {
return nil, apperrors.ErrNotGroupMember
}
// 新群主已是群主则无需转让
if newOwnerMember.Role == model.GroupRoleOwner {
return nil, apperrors.ErrInvalidOperation
}
// 校验当前群主信息存在
if _, err := s.groupRepo.GetMember(groupID, group.OwnerID); err != nil {
return nil, apperrors.ErrGroupNotFound
}
// 原子转让:所有写入在 groupRepo.TransferOwner 的单个事务内完成
if err := s.groupRepo.TransferOwner(groupID, group.OwnerID, newOwnerID); err != nil {
return nil, err
}
// 重新获取群组信息
group, err = s.groupRepo.GetByID(groupID)
if err != nil {
return nil, err
}
// 构建响应
response := &dto.AdminGroupDetailResponse{
ID: group.ID,
Name: group.Name,
Avatar: group.Avatar,
Description: group.Description,
OwnerID: group.OwnerID,
MemberCount: group.MemberCount,
MaxMembers: group.MaxMembers,
JoinType: int(group.JoinType),
MuteAll: group.MuteAll,
Status: "active",
IsPublic: true,
CreatedAt: dto.FormatTime(group.CreatedAt),
UpdatedAt: dto.FormatTime(group.UpdatedAt),
}
// 获取新群主信息
if group.OwnerID != "" {
owner, err := s.userRepo.GetByID(group.OwnerID)
if err == nil && owner != nil {
response.Owner = &dto.UserResponse{
ID: owner.ID,
Username: owner.Username,
Nickname: owner.Nickname,
Avatar: owner.Avatar,
CreatedAt: dto.FormatTime(owner.CreatedAt),
}
}
}
return response, nil
}