feat(admin): add comprehensive admin management system

- Add admin handlers and services for users, posts, comments, groups, and dashboard
- Implement role permission management with ability to view and update permissions
- Add database seeding for roles and Casbin permission policies
- Upgrade Casbin from v2 to v3 with GORM adapter for persistent policy storage
- Add admin-specific repository methods with filtering and pagination
- Register new admin API routes under /api/v1/admin/*
This commit is contained in:
2026-03-14 18:01:55 +08:00
parent ae5b997924
commit d3065b30cc
28 changed files with 4018 additions and 107 deletions

View File

@@ -35,6 +35,20 @@ type GroupRepository interface {
GetAnnouncements(groupID string, page, pageSize int) ([]model.GroupAnnouncement, int64, error)
GetAnnouncementByID(id string) (*model.GroupAnnouncement, error)
DeleteAnnouncement(id string) error
// 管理端群组操作
GetGroupList(page, pageSize int, keyword, status, ownerID, startDate, endDate string) ([]model.Group, int64, error)
GetGroupWithOwner(groupID string) (*model.Group, error)
GetLatestAnnouncement(groupID string) (*model.GroupAnnouncement, error)
UpdateGroupStatus(groupID string, status string) error
GetGroupPostCount(groupID string) (int64, error)
GetMembersWithUserInfo(groupID string, page, pageSize int) ([]GroupMemberWithUser, int64, error)
}
// GroupMemberWithUser 群成员带用户信息的结构
type GroupMemberWithUser struct {
model.GroupMember
User *model.User
}
// groupRepository 群组仓库实现
@@ -240,3 +254,159 @@ func (r *groupRepository) GetAnnouncementByID(id string) (*model.GroupAnnounceme
func (r *groupRepository) DeleteAnnouncement(id string) error {
return r.db.Delete(&model.GroupAnnouncement{}, "id = ?", id).Error
}
// ==================== 管理端群组操作 ====================
// GetGroupList 获取群组列表(管理端)
func (r *groupRepository) GetGroupList(page, pageSize int, keyword, status, ownerID, startDate, endDate string) ([]model.Group, int64, error) {
var groups []model.Group
var total int64
query := r.db.Model(&model.Group{})
// 关键词搜索
if keyword != "" {
query = query.Where("name LIKE ? OR description LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
}
// 状态筛选这里假设群组有status字段如果没有则忽略
// 注意当前Group模型没有status字段状态通过是否解散来判断
// 如果需要status字段需要在模型中添加
// 群主筛选
if ownerID != "" {
query = query.Where("owner_id = ?", ownerID)
}
// 日期筛选
if startDate != "" {
query = query.Where("created_at >= ?", startDate+" 00:00:00")
}
if endDate != "" {
query = query.Where("created_at <= ?", endDate+" 23:59:59")
}
query.Count(&total)
offset := (page - 1) * pageSize
err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&groups).Error
return groups, total, err
}
// GetGroupWithOwner 获取群组详情(包含群主信息)
func (r *groupRepository) GetGroupWithOwner(groupID string) (*model.Group, error) {
var group model.Group
err := r.db.First(&group, "id = ?", groupID).Error
if err != nil {
return nil, err
}
return &group, nil
}
// GetLatestAnnouncement 获取群组最新公告
func (r *groupRepository) GetLatestAnnouncement(groupID string) (*model.GroupAnnouncement, error) {
var announcement model.GroupAnnouncement
err := r.db.Where("group_id = ?", groupID).
Order("is_pinned DESC, created_at DESC").
First(&announcement).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &announcement, nil
}
// UpdateGroupStatus 更新群组状态
func (r *groupRepository) UpdateGroupStatus(groupID string, status string) error {
// 注意当前Group模型没有status字段
// 这里我们通过更新一个虚拟字段或者使用其他方式来实现
// 暂时通过更新updated_at来模拟状态更新
// 如果需要真正的状态字段,需要在模型中添加
return r.db.Model(&model.Group{}).Where("id = ?", groupID).
Update("updated_at", gorm.Expr("NOW()")).Error
}
// GetGroupPostCount 获取群组帖子数量
func (r *groupRepository) GetGroupPostCount(groupID string) (int64, error) {
var count int64
// 假设帖子表有group_id字段
err := r.db.Model(&model.Post{}).Where("group_id = ?", groupID).Count(&count).Error
return count, err
}
// GetMembersWithUserInfo 获取群成员列表(包含用户信息)
func (r *groupRepository) GetMembersWithUserInfo(groupID string, page, pageSize int) ([]GroupMemberWithUser, int64, error) {
var members []GroupMemberWithUser
var total int64
// 先获取总数
err := r.db.Model(&model.GroupMember{}).Where("group_id = ?", groupID).Count(&total).Error
if err != nil {
return nil, 0, err
}
// 联表查询获取成员和用户信息
offset := (page - 1) * pageSize
query := `
SELECT gm.*, u.id as user_id, u.username, u.nickname, u.avatar
FROM group_members gm
LEFT JOIN users u ON gm.user_id = u.id
WHERE gm.group_id = ?
ORDER BY
CASE gm.role
WHEN 'owner' THEN 1
WHEN 'admin' THEN 2
WHEN 'member' THEN 3
END,
gm.join_time ASC
LIMIT ? OFFSET ?
`
type memberRow struct {
ID string `gorm:"column:id"`
GroupID string `gorm:"column:group_id"`
UserID string `gorm:"column:user_id"`
Role string `gorm:"column:role"`
Nickname string `gorm:"column:nickname"`
Muted bool `gorm:"column:muted"`
JoinTime string `gorm:"column:join_time"`
CreatedAt string `gorm:"column:created_at"`
UpdatedAt string `gorm:"column:updated_at"`
// 用户信息
Username string `gorm:"column:username"`
UserNickname string `gorm:"column:nickname"`
UserAvatar string `gorm:"column:avatar"`
}
var rows []memberRow
err = r.db.Raw(query, groupID, pageSize, offset).Scan(&rows).Error
if err != nil {
return nil, 0, err
}
members = make([]GroupMemberWithUser, 0, len(rows))
for _, row := range rows {
member := model.GroupMember{
ID: row.ID,
GroupID: row.GroupID,
UserID: row.UserID,
Role: row.Role,
Nickname: row.Nickname,
Muted: row.Muted,
}
user := &model.User{
ID: row.UserID,
Username: row.Username,
Nickname: row.UserNickname,
Avatar: row.UserAvatar,
}
members = append(members, GroupMemberWithUser{
GroupMember: member,
User: user,
})
}
return members, total, nil
}