feat(contact): Contact 模型、DAO、常量与 DTO
- model/friendship.go: 好友关系模型(双向存储,4 种状态) - model/friend_group.go: 好友分组模型 - constants/contact.go: 好友关系状态常量 - dto/contact_dto.go: 联系人请求/响应 DTO - dao/friendship_dao.go: 好友关系 CRUD + 搜索 + 共同好友 - dao/friend_group_dao.go: 好友分组 CRUD + 移动好友 Made-with: Cursor
This commit is contained in:
8
backend/go-service/app/constants/contact.go
Normal file
8
backend/go-service/app/constants/contact.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
FriendshipStatusPending = 0 // 待确认(已发送申请)
|
||||
FriendshipStatusAccepted = 1 // 已接受(互为好友)
|
||||
FriendshipStatusRejected = 2 // 已拒绝
|
||||
FriendshipStatusBlocked = 3 // 已拉黑
|
||||
)
|
||||
116
backend/go-service/app/contact/dao/friend_group_dao.go
Normal file
116
backend/go-service/app/contact/dao/friend_group_dao.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/echochat/backend/app/contact/model"
|
||||
"github.com/echochat/backend/pkg/logs"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// FriendGroupDAO 好友分组数据访问对象
|
||||
type FriendGroupDAO struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewFriendGroupDAO 创建 FriendGroupDAO 实例
|
||||
func NewFriendGroupDAO(db *gorm.DB) *FriendGroupDAO {
|
||||
return &FriendGroupDAO{db: db}
|
||||
}
|
||||
|
||||
// CreateGroup 创建好友分组
|
||||
func (d *FriendGroupDAO) CreateGroup(ctx context.Context, userID int64, name string) (*model.FriendGroup, error) {
|
||||
funcName := "dao.friend_group_dao.CreateGroup"
|
||||
logs.Info(ctx, funcName, "创建好友分组",
|
||||
zap.Int64("user_id", userID), zap.String("name", name))
|
||||
|
||||
group := &model.FriendGroup{
|
||||
UserID: userID,
|
||||
Name: name,
|
||||
}
|
||||
err := d.db.WithContext(ctx).Create(group).Error
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "创建分组失败", zap.Error(err))
|
||||
}
|
||||
return group, err
|
||||
}
|
||||
|
||||
// GetGroups 获取用户的所有好友分组
|
||||
func (d *FriendGroupDAO) GetGroups(ctx context.Context, userID int64) ([]model.FriendGroup, error) {
|
||||
funcName := "dao.friend_group_dao.GetGroups"
|
||||
logs.Debug(ctx, funcName, "查询好友分组", zap.Int64("user_id", userID))
|
||||
|
||||
var groups []model.FriendGroup
|
||||
err := d.db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Order("sort_order ASC, id ASC").
|
||||
Find(&groups).Error
|
||||
return groups, err
|
||||
}
|
||||
|
||||
// UpdateGroup 更新分组信息
|
||||
func (d *FriendGroupDAO) UpdateGroup(ctx context.Context, groupID int64, userID int64, name string, sortOrder *int) error {
|
||||
funcName := "dao.friend_group_dao.UpdateGroup"
|
||||
logs.Info(ctx, funcName, "更新好友分组",
|
||||
zap.Int64("group_id", groupID), zap.String("name", name))
|
||||
|
||||
updates := map[string]interface{}{"name": name}
|
||||
if sortOrder != nil {
|
||||
updates["sort_order"] = *sortOrder
|
||||
}
|
||||
|
||||
result := d.db.WithContext(ctx).
|
||||
Model(&model.FriendGroup{}).
|
||||
Where("id = ? AND user_id = ?", groupID, userID).
|
||||
Updates(updates)
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGroup 删除分组(同时将该分组下的好友移出分组)
|
||||
func (d *FriendGroupDAO) DeleteGroup(ctx context.Context, groupID int64, userID int64) error {
|
||||
funcName := "dao.friend_group_dao.DeleteGroup"
|
||||
logs.Info(ctx, funcName, "删除好友分组",
|
||||
zap.Int64("group_id", groupID), zap.Int64("user_id", userID))
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
tx.Model(&model.Friendship{}).
|
||||
Where("user_id = ? AND group_id = ?", userID, groupID).
|
||||
Update("group_id", nil)
|
||||
|
||||
result := tx.Where("id = ? AND user_id = ?", groupID, userID).
|
||||
Delete(&model.FriendGroup{})
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return result.Error
|
||||
})
|
||||
}
|
||||
|
||||
// MoveToGroup 移动好友到指定分组
|
||||
func (d *FriendGroupDAO) MoveToGroup(ctx context.Context, userID, friendID int64, groupID *int64) error {
|
||||
funcName := "dao.friend_group_dao.MoveToGroup"
|
||||
logs.Info(ctx, funcName, "移动好友到分组",
|
||||
zap.Int64("user_id", userID), zap.Int64("friend_id", friendID))
|
||||
|
||||
return d.db.WithContext(ctx).
|
||||
Model(&model.Friendship{}).
|
||||
Where("user_id = ? AND friend_id = ? AND status = 1", userID, friendID).
|
||||
Update("group_id", groupID).Error
|
||||
}
|
||||
|
||||
// GetGroupByID 根据 ID 获取分组
|
||||
func (d *FriendGroupDAO) GetGroupByID(ctx context.Context, groupID, userID int64) (*model.FriendGroup, error) {
|
||||
var group model.FriendGroup
|
||||
err := d.db.WithContext(ctx).
|
||||
Where("id = ? AND user_id = ?", groupID, userID).
|
||||
First(&group).Error
|
||||
return &group, err
|
||||
}
|
||||
346
backend/go-service/app/contact/dao/friendship_dao.go
Normal file
346
backend/go-service/app/contact/dao/friendship_dao.go
Normal file
@@ -0,0 +1,346 @@
|
||||
// Package dao 提供 contact 模块的数据库访问操作
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
authModel "github.com/echochat/backend/app/auth/model"
|
||||
"github.com/echochat/backend/app/constants"
|
||||
"github.com/echochat/backend/app/contact/model"
|
||||
"github.com/echochat/backend/pkg/logs"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// FriendshipDAO 好友关系数据访问对象
|
||||
type FriendshipDAO struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewFriendshipDAO 创建 FriendshipDAO 实例
|
||||
func NewFriendshipDAO(db *gorm.DB) *FriendshipDAO {
|
||||
return &FriendshipDAO{db: db}
|
||||
}
|
||||
|
||||
// CreateRequest 创建好友申请(单向 A→B,status=0)
|
||||
func (d *FriendshipDAO) CreateRequest(ctx context.Context, userID, friendID int64, message string) (*model.Friendship, error) {
|
||||
funcName := "dao.friendship_dao.CreateRequest"
|
||||
logs.Info(ctx, funcName, "创建好友申请",
|
||||
zap.Int64("user_id", userID), zap.Int64("friend_id", friendID))
|
||||
|
||||
f := &model.Friendship{
|
||||
UserID: userID,
|
||||
FriendID: friendID,
|
||||
Status: constants.FriendshipStatusPending,
|
||||
Message: message,
|
||||
}
|
||||
err := d.db.WithContext(ctx).Create(f).Error
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "创建好友申请失败", zap.Error(err))
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
|
||||
// AcceptRequest 接受好友申请(事务内:更新 A→B 为 accepted + 创建 B→A)
|
||||
func (d *FriendshipDAO) AcceptRequest(ctx context.Context, requestID, userID int64) error {
|
||||
funcName := "dao.friendship_dao.AcceptRequest"
|
||||
logs.Info(ctx, funcName, "接受好友申请",
|
||||
zap.Int64("request_id", requestID), zap.Int64("user_id", userID))
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var req model.Friendship
|
||||
if err := tx.First(&req, requestID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if req.FriendID != userID {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if err := tx.Model(&req).Updates(map[string]interface{}{
|
||||
"status": constants.FriendshipStatusAccepted,
|
||||
"updated_at": now,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reverse := &model.Friendship{
|
||||
UserID: userID,
|
||||
FriendID: req.UserID,
|
||||
Status: constants.FriendshipStatusAccepted,
|
||||
}
|
||||
return tx.Create(reverse).Error
|
||||
})
|
||||
}
|
||||
|
||||
// RejectRequest 拒绝好友申请
|
||||
func (d *FriendshipDAO) RejectRequest(ctx context.Context, requestID, userID int64) error {
|
||||
funcName := "dao.friendship_dao.RejectRequest"
|
||||
logs.Info(ctx, funcName, "拒绝好友申请",
|
||||
zap.Int64("request_id", requestID), zap.Int64("user_id", userID))
|
||||
|
||||
result := d.db.WithContext(ctx).
|
||||
Model(&model.Friendship{}).
|
||||
Where("id = ? AND friend_id = ? AND status = ?", requestID, userID, constants.FriendshipStatusPending).
|
||||
Update("status", constants.FriendshipStatusRejected)
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFriendList 获取好友列表(status=1 的记录,JOIN 用户表获取详情)
|
||||
func (d *FriendshipDAO) GetFriendList(ctx context.Context, userID int64, groupID *int64) ([]FriendWithUser, error) {
|
||||
funcName := "dao.friendship_dao.GetFriendList"
|
||||
logs.Debug(ctx, funcName, "查询好友列表", zap.Int64("user_id", userID))
|
||||
|
||||
var results []FriendWithUser
|
||||
query := d.db.WithContext(ctx).
|
||||
Table("contact_friendships f").
|
||||
Select("f.id, f.friend_id as user_id, u.username, u.nickname, u.avatar, f.remark, f.group_id, f.created_at").
|
||||
Joins("JOIN auth_users u ON u.id = f.friend_id").
|
||||
Where("f.user_id = ? AND f.status = ?", userID, constants.FriendshipStatusAccepted)
|
||||
|
||||
if groupID != nil {
|
||||
query = query.Where("f.group_id = ?", *groupID)
|
||||
}
|
||||
|
||||
err := query.Order("u.nickname ASC").Scan(&results).Error
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "查询好友列表失败", zap.Error(err))
|
||||
}
|
||||
return results, err
|
||||
}
|
||||
|
||||
// FriendWithUser 好友列表查询结果(JOIN 用户表)
|
||||
type FriendWithUser struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Remark string `json:"remark"`
|
||||
GroupID *int64 `json:"group_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// GetPendingRequests 获取收到的待处理好友申请
|
||||
func (d *FriendshipDAO) GetPendingRequests(ctx context.Context, userID int64) ([]RequestWithUser, error) {
|
||||
funcName := "dao.friendship_dao.GetPendingRequests"
|
||||
logs.Debug(ctx, funcName, "查询待处理申请", zap.Int64("user_id", userID))
|
||||
|
||||
var results []RequestWithUser
|
||||
err := d.db.WithContext(ctx).
|
||||
Table("contact_friendships f").
|
||||
Select("f.id, f.user_id, u.username, u.nickname, u.avatar, f.message, f.status, f.created_at").
|
||||
Joins("JOIN auth_users u ON u.id = f.user_id").
|
||||
Where("f.friend_id = ? AND f.status = ?", userID, constants.FriendshipStatusPending).
|
||||
Order("f.created_at DESC").
|
||||
Scan(&results).Error
|
||||
|
||||
if err != nil {
|
||||
logs.Error(ctx, funcName, "查询待处理申请失败", zap.Error(err))
|
||||
}
|
||||
return results, err
|
||||
}
|
||||
|
||||
// RequestWithUser 好友申请查询结果(JOIN 申请方用户表)
|
||||
type RequestWithUser struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Message string `json:"message"`
|
||||
Status int `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// DeleteFriend 删除好友(事务内双向删除)
|
||||
func (d *FriendshipDAO) DeleteFriend(ctx context.Context, userID, friendID int64) error {
|
||||
funcName := "dao.friendship_dao.DeleteFriend"
|
||||
logs.Info(ctx, funcName, "删除好友",
|
||||
zap.Int64("user_id", userID), zap.Int64("friend_id", friendID))
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("user_id = ? AND friend_id = ?", userID, friendID).
|
||||
Delete(&model.Friendship{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Where("user_id = ? AND friend_id = ?", friendID, userID).
|
||||
Delete(&model.Friendship{}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRemark 设置好友备注
|
||||
func (d *FriendshipDAO) UpdateRemark(ctx context.Context, userID, friendID int64, remark string) error {
|
||||
funcName := "dao.friendship_dao.UpdateRemark"
|
||||
logs.Info(ctx, funcName, "更新好友备注",
|
||||
zap.Int64("user_id", userID), zap.Int64("friend_id", friendID))
|
||||
|
||||
return d.db.WithContext(ctx).
|
||||
Model(&model.Friendship{}).
|
||||
Where("user_id = ? AND friend_id = ? AND status = ?", userID, friendID, constants.FriendshipStatusAccepted).
|
||||
Update("remark", remark).Error
|
||||
}
|
||||
|
||||
// BlockUser 拉黑用户(事务内:删除双向好友 + 创建单向拉黑记录)
|
||||
func (d *FriendshipDAO) BlockUser(ctx context.Context, userID, targetID int64) error {
|
||||
funcName := "dao.friendship_dao.BlockUser"
|
||||
logs.Info(ctx, funcName, "拉黑用户",
|
||||
zap.Int64("user_id", userID), zap.Int64("target_id", targetID))
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
tx.Where("(user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)",
|
||||
userID, targetID, targetID, userID).
|
||||
Delete(&model.Friendship{})
|
||||
|
||||
block := &model.Friendship{
|
||||
UserID: userID,
|
||||
FriendID: targetID,
|
||||
Status: constants.FriendshipStatusBlocked,
|
||||
}
|
||||
return tx.Create(block).Error
|
||||
})
|
||||
}
|
||||
|
||||
// UnblockUser 取消拉黑
|
||||
func (d *FriendshipDAO) UnblockUser(ctx context.Context, userID, targetID int64) error {
|
||||
funcName := "dao.friendship_dao.UnblockUser"
|
||||
logs.Info(ctx, funcName, "取消拉黑",
|
||||
zap.Int64("user_id", userID), zap.Int64("target_id", targetID))
|
||||
|
||||
return d.db.WithContext(ctx).
|
||||
Where("user_id = ? AND friend_id = ? AND status = ?", userID, targetID, constants.FriendshipStatusBlocked).
|
||||
Delete(&model.Friendship{}).Error
|
||||
}
|
||||
|
||||
// GetBlockList 获取黑名单列表
|
||||
func (d *FriendshipDAO) GetBlockList(ctx context.Context, userID int64) ([]FriendWithUser, error) {
|
||||
funcName := "dao.friendship_dao.GetBlockList"
|
||||
logs.Debug(ctx, funcName, "查询黑名单", zap.Int64("user_id", userID))
|
||||
|
||||
var results []FriendWithUser
|
||||
err := d.db.WithContext(ctx).
|
||||
Table("contact_friendships f").
|
||||
Select("f.id, f.friend_id as user_id, u.username, u.nickname, u.avatar, f.remark, f.group_id, f.created_at").
|
||||
Joins("JOIN auth_users u ON u.id = f.friend_id").
|
||||
Where("f.user_id = ? AND f.status = ?", userID, constants.FriendshipStatusBlocked).
|
||||
Scan(&results).Error
|
||||
return results, err
|
||||
}
|
||||
|
||||
// IsBlocked 检查 userID 是否被 targetID 拉黑
|
||||
func (d *FriendshipDAO) IsBlocked(ctx context.Context, userID, targetID int64) (bool, error) {
|
||||
var count int64
|
||||
err := d.db.WithContext(ctx).
|
||||
Model(&model.Friendship{}).
|
||||
Where("user_id = ? AND friend_id = ? AND status = ?", targetID, userID, constants.FriendshipStatusBlocked).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// IsFriend 检查是否互为好友
|
||||
func (d *FriendshipDAO) IsFriend(ctx context.Context, userID, friendID int64) (bool, error) {
|
||||
var count int64
|
||||
err := d.db.WithContext(ctx).
|
||||
Model(&model.Friendship{}).
|
||||
Where("user_id = ? AND friend_id = ? AND status = ?", userID, friendID, constants.FriendshipStatusAccepted).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// HasPendingRequest 检查是否已有待处理的申请(A→B 或 B→A)
|
||||
func (d *FriendshipDAO) HasPendingRequest(ctx context.Context, userID, friendID int64) (bool, error) {
|
||||
var count int64
|
||||
err := d.db.WithContext(ctx).
|
||||
Model(&model.Friendship{}).
|
||||
Where("((user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)) AND status = ?",
|
||||
userID, friendID, friendID, userID, constants.FriendshipStatusPending).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// GetRequestByID 根据 ID 获取好友申请
|
||||
func (d *FriendshipDAO) GetRequestByID(ctx context.Context, id int64) (*model.Friendship, error) {
|
||||
var f model.Friendship
|
||||
err := d.db.WithContext(ctx).First(&f, id).Error
|
||||
return &f, err
|
||||
}
|
||||
|
||||
// GetCommonFriends 获取共同好友 ID 列表
|
||||
func (d *FriendshipDAO) GetCommonFriends(ctx context.Context, userID, targetID int64) ([]int64, error) {
|
||||
funcName := "dao.friendship_dao.GetCommonFriends"
|
||||
logs.Debug(ctx, funcName, "查询共同好友",
|
||||
zap.Int64("user_id", userID), zap.Int64("target_id", targetID))
|
||||
|
||||
var ids []int64
|
||||
err := d.db.WithContext(ctx).
|
||||
Raw(`SELECT a.friend_id FROM contact_friendships a
|
||||
JOIN contact_friendships b ON a.friend_id = b.friend_id
|
||||
WHERE a.user_id = ? AND a.status = ? AND b.user_id = ? AND b.status = ?`,
|
||||
userID, constants.FriendshipStatusAccepted, targetID, constants.FriendshipStatusAccepted).
|
||||
Scan(&ids).Error
|
||||
return ids, err
|
||||
}
|
||||
|
||||
// SearchUsers 搜索用户(按用户名或昵称模糊匹配,排除自己)
|
||||
func (d *FriendshipDAO) SearchUsers(ctx context.Context, keyword string, excludeUserID int64, page, pageSize int) ([]authModel.User, int64, error) {
|
||||
funcName := "dao.friendship_dao.SearchUsers"
|
||||
logs.Debug(ctx, funcName, "搜索用户", zap.String("keyword", keyword))
|
||||
|
||||
var users []authModel.User
|
||||
var total int64
|
||||
|
||||
query := d.db.WithContext(ctx).
|
||||
Model(&authModel.User{}).
|
||||
Where("id != ? AND status = 1 AND (username LIKE ? OR nickname LIKE ?)",
|
||||
excludeUserID, "%"+keyword+"%", "%"+keyword+"%")
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
err := query.Select("id, username, nickname, avatar").
|
||||
Offset(offset).Limit(pageSize).
|
||||
Find(&users).Error
|
||||
|
||||
return users, total, err
|
||||
}
|
||||
|
||||
// GetFriendIDs 获取用户的所有好友 ID 列表
|
||||
func (d *FriendshipDAO) GetFriendIDs(ctx context.Context, userID int64) ([]int64, error) {
|
||||
var ids []int64
|
||||
err := d.db.WithContext(ctx).
|
||||
Model(&model.Friendship{}).
|
||||
Where("user_id = ? AND status = ?", userID, constants.FriendshipStatusAccepted).
|
||||
Pluck("friend_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
|
||||
// CountFriendsByGroup 统计每个分组的好友数
|
||||
func (d *FriendshipDAO) CountFriendsByGroup(ctx context.Context, userID int64) (map[int64]int, error) {
|
||||
type GroupCount struct {
|
||||
GroupID int64 `gorm:"column:group_id"`
|
||||
Count int `gorm:"column:cnt"`
|
||||
}
|
||||
var counts []GroupCount
|
||||
err := d.db.WithContext(ctx).
|
||||
Model(&model.Friendship{}).
|
||||
Select("group_id, COUNT(*) as cnt").
|
||||
Where("user_id = ? AND status = ? AND group_id IS NOT NULL", userID, constants.FriendshipStatusAccepted).
|
||||
Group("group_id").
|
||||
Scan(&counts).Error
|
||||
|
||||
result := make(map[int64]int)
|
||||
for _, c := range counts {
|
||||
result[c.GroupID] = c.Count
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
16
backend/go-service/app/contact/model/friend_group.go
Normal file
16
backend/go-service/app/contact/model/friend_group.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// FriendGroup 好友分组模型,对应 contact_groups 表
|
||||
type FriendGroup struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 分组唯一标识
|
||||
UserID int64 `gorm:"not null;index:idx_contact_groups_user" json:"user_id"` // 所属用户 ID
|
||||
Name string `gorm:"size:50;not null" json:"name"` // 分组名称
|
||||
SortOrder int `gorm:"not null;default:0" json:"sort_order"` // 排序权重,越小越靠前
|
||||
CreatedAt time.Time `gorm:"not null;autoCreateTime" json:"created_at"` // 创建时间
|
||||
}
|
||||
|
||||
func (FriendGroup) TableName() string {
|
||||
return "contact_groups"
|
||||
}
|
||||
21
backend/go-service/app/contact/model/friendship.go
Normal file
21
backend/go-service/app/contact/model/friendship.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// Friendship 好友关系模型,对应 contact_friendships 表
|
||||
// 双向存储:A→B 和 B→A 各一条记录
|
||||
type Friendship struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 记录唯一标识
|
||||
UserID int64 `gorm:"not null;index:idx_friendships_user_status" json:"user_id"` // 发起方用户 ID
|
||||
FriendID int64 `gorm:"not null;index:idx_friendships_friend_status" json:"friend_id"` // 好友用户 ID
|
||||
Remark string `gorm:"size:50;default:''" json:"remark"` // 好友备注名
|
||||
GroupID *int64 `gorm:"default:null" json:"group_id"` // 所属好友分组 ID
|
||||
Status int `gorm:"not null;default:0;index:idx_friendships_user_status" json:"status"` // 状态:0=待确认,1=已接受,2=已拒绝,3=已拉黑
|
||||
Message string `gorm:"size:200;default:''" json:"message"` // 好友申请附言
|
||||
CreatedAt time.Time `gorm:"not null;autoCreateTime" json:"created_at"` // 创建时间
|
||||
UpdatedAt time.Time `gorm:"not null;autoUpdateTime" json:"updated_at"` // 更新时间
|
||||
}
|
||||
|
||||
func (Friendship) TableName() string {
|
||||
return "contact_friendships"
|
||||
}
|
||||
87
backend/go-service/app/dto/contact_dto.go
Normal file
87
backend/go-service/app/dto/contact_dto.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package dto
|
||||
|
||||
// SendFriendRequestReq 发送好友申请请求
|
||||
type SendFriendRequestReq struct {
|
||||
TargetID int64 `json:"target_id" binding:"required"` // 目标用户 ID
|
||||
Message string `json:"message"` // 申请附言
|
||||
}
|
||||
|
||||
// HandleFriendRequestReq 处理好友申请请求
|
||||
type HandleFriendRequestReq struct {
|
||||
RequestID int64 `json:"request_id" binding:"required"` // 申请记录 ID
|
||||
}
|
||||
|
||||
// UpdateRemarkReq 更新好友备注请求
|
||||
type UpdateRemarkReq struct {
|
||||
Remark string `json:"remark" binding:"max=50"` // 备注名
|
||||
}
|
||||
|
||||
// MoveToGroupReq 移动好友到分组请求
|
||||
type MoveToGroupReq struct {
|
||||
GroupID *int64 `json:"group_id"` // 目标分组 ID,nil 表示移出分组
|
||||
}
|
||||
|
||||
// BlockUserReq 拉黑用户请求
|
||||
type BlockUserReq struct {
|
||||
TargetID int64 `json:"target_id" binding:"required"` // 目标用户 ID
|
||||
}
|
||||
|
||||
// CreateGroupReq 创建好友分组请求
|
||||
type CreateGroupReq struct {
|
||||
Name string `json:"name" binding:"required,max=50"` // 分组名称
|
||||
}
|
||||
|
||||
// UpdateGroupReq 修改好友分组请求
|
||||
type UpdateGroupReq struct {
|
||||
Name string `json:"name" binding:"required,max=50"` // 分组名称
|
||||
SortOrder *int `json:"sort_order"` // 排序权重
|
||||
}
|
||||
|
||||
// SearchUsersReq 搜索用户请求
|
||||
type SearchUsersReq struct {
|
||||
Keyword string `form:"keyword" binding:"required,min=1"` // 搜索关键词
|
||||
Page int `form:"page" binding:"min=1"` // 页码
|
||||
PageSize int `form:"page_size" binding:"min=1,max=50"` // 每页数量
|
||||
}
|
||||
|
||||
// FriendInfo 好友信息(列表展示用)
|
||||
type FriendInfo struct {
|
||||
ID int64 `json:"id"` // friendship 记录 ID
|
||||
UserID int64 `json:"user_id"` // 好友用户 ID
|
||||
Username string `json:"username"` // 用户名
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
Avatar string `json:"avatar"` // 头像
|
||||
Remark string `json:"remark"` // 备注名
|
||||
GroupID *int64 `json:"group_id"` // 所属分组
|
||||
IsOnline bool `json:"is_online"` // 是否在线
|
||||
CreatedAt string `json:"created_at"` // 成为好友时间
|
||||
}
|
||||
|
||||
// FriendRequestInfo 好友申请信息
|
||||
type FriendRequestInfo struct {
|
||||
ID int64 `json:"id"` // 申请记录 ID
|
||||
UserID int64 `json:"user_id"` // 申请方用户 ID
|
||||
Username string `json:"username"` // 申请方用户名
|
||||
Nickname string `json:"nickname"` // 申请方昵称
|
||||
Avatar string `json:"avatar"` // 申请方头像
|
||||
Message string `json:"message"` // 申请附言
|
||||
Status int `json:"status"` // 状态
|
||||
CreatedAt string `json:"created_at"` // 申请时间
|
||||
}
|
||||
|
||||
// GroupInfo 好友分组信息
|
||||
type GroupInfo struct {
|
||||
ID int64 `json:"id"` // 分组 ID
|
||||
Name string `json:"name"` // 分组名称
|
||||
SortOrder int `json:"sort_order"` // 排序权重
|
||||
FriendCount int `json:"friend_count"` // 分组内好友数
|
||||
}
|
||||
|
||||
// SearchUserInfo 搜索结果用户信息
|
||||
type SearchUserInfo struct {
|
||||
ID int64 `json:"id"` // 用户 ID
|
||||
Username string `json:"username"` // 用户名
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
Avatar string `json:"avatar"` // 头像
|
||||
IsFriend bool `json:"is_friend"` // 是否已是好友
|
||||
}
|
||||
Reference in New Issue
Block a user