首次提交
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/inchstep/server/internal/models"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AdminService struct {
|
||||
db *gorm.DB
|
||||
userSvc *UserService
|
||||
}
|
||||
|
||||
func NewAdminService(db *gorm.DB, userSvc *UserService) *AdminService {
|
||||
return &AdminService{db: db, userSvc: userSvc}
|
||||
}
|
||||
|
||||
// SeedAdmin 初始化超级管理员账号
|
||||
func (s *AdminService) SeedAdmin() error {
|
||||
var count int64
|
||||
s.db.Model(&models.Admin{}).Count(&count)
|
||||
if count > 0 {
|
||||
return nil // 已有管理员,跳过
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte("admin123456"), 12)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nick := "超级管理员"
|
||||
admin := &models.Admin{
|
||||
ID: generateID(),
|
||||
Username: "admin",
|
||||
PasswordHash: string(hash),
|
||||
Nickname: &nick,
|
||||
Role: models.RoleSuperAdmin,
|
||||
Status: models.AdminStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
return s.db.Create(admin).Error
|
||||
}
|
||||
|
||||
// Login 管理员登录
|
||||
func (s *AdminService) Login(username, password string) (*models.Admin, error) {
|
||||
var admin models.Admin
|
||||
if err := s.db.Where("username = ?", username).First(&admin).Error; err != nil {
|
||||
return nil, errors.New("用户名或密码错误")
|
||||
}
|
||||
if admin.Status != models.AdminStatusActive {
|
||||
return nil, errors.New("账号已被禁用")
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(admin.PasswordHash), []byte(password)); err != nil {
|
||||
return nil, errors.New("用户名或密码错误")
|
||||
}
|
||||
return &admin, nil
|
||||
}
|
||||
|
||||
// GetDashboardStats 获取管理后台统计数据
|
||||
func (s *AdminService) GetDashboardStats() (map[string]interface{}, error) {
|
||||
var totalUsers, todayUsers int64
|
||||
s.db.Model(&models.User{}).Count(&totalUsers)
|
||||
todayStart := time.Now().Truncate(24 * time.Hour)
|
||||
s.db.Model(&models.User{}).Where("created_at >= ?", todayStart).Count(&todayUsers)
|
||||
|
||||
var totalPosts, totalItems, totalRecords int64
|
||||
s.db.Model(&models.Post{}).Where("privacy = ?", 1).Count(&totalPosts)
|
||||
s.db.Model(&models.CheckInItem{}).Count(&totalItems)
|
||||
s.db.Model(&models.CheckInRecord{}).Count(&totalRecords)
|
||||
|
||||
return map[string]interface{}{
|
||||
"totalUsers": totalUsers,
|
||||
"totalPosts": totalPosts,
|
||||
"totalItems": totalItems,
|
||||
"totalRecords": totalRecords,
|
||||
"newUsersToday": todayUsers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListUsers 获取用户列表(分页)
|
||||
func (s *AdminService) ListUsers(page, pageSize int) ([]models.User, int64, error) {
|
||||
var total int64
|
||||
s.db.Model(&models.User{}).Count(&total)
|
||||
var users []models.User
|
||||
err := s.db.Order("created_at desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&users).Error
|
||||
return users, total, err
|
||||
}
|
||||
|
||||
// ListPosts 获取动态列表
|
||||
func (s *AdminService) ListPosts(page, pageSize int) ([]models.Post, int64, error) {
|
||||
var total int64
|
||||
s.db.Model(&models.Post{}).Count(&total)
|
||||
var posts []models.Post
|
||||
err := s.db.Preload("User").Order("created_at desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&posts).Error
|
||||
return posts, total, err
|
||||
}
|
||||
|
||||
// ListAdmins 获取管理员列表
|
||||
func (s *AdminService) ListAdmins() ([]models.Admin, error) {
|
||||
var admins []models.Admin
|
||||
err := s.db.Find(&admins).Error
|
||||
return admins, err
|
||||
}
|
||||
|
||||
// CreateAdmin 创建管理员
|
||||
func (s *AdminService) CreateAdmin(username, password, role string, nickname *string) (*models.Admin, error) {
|
||||
var existing models.Admin
|
||||
if err := s.db.Where("username = ?", username).First(&existing).Error; err == nil {
|
||||
return nil, errors.New("用户名已存在")
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
admin := &models.Admin{
|
||||
ID: generateID(),
|
||||
Username: username,
|
||||
PasswordHash: string(hash),
|
||||
Nickname: nickname,
|
||||
Role: models.AdminRole(role),
|
||||
Status: models.AdminStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
return admin, s.db.Create(admin).Error
|
||||
}
|
||||
|
||||
// DisableAdmin 禁用管理员
|
||||
func (s *AdminService) DisableAdmin(id string) error {
|
||||
return s.db.Model(&models.Admin{}).Where("id = ?", id).Update("status", models.AdminStatusDisabled).Error
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/inchstep/server/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CheckInService struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewCheckInService(db *gorm.DB) *CheckInService {
|
||||
return &CheckInService{db: db}
|
||||
}
|
||||
|
||||
// CreateItem 创建打卡项
|
||||
func (s *CheckInService) CreateItem(userID string, item *models.CheckInItem) error {
|
||||
item.ID = generateID()
|
||||
item.UserID = userID
|
||||
item.Status = models.StatusActive
|
||||
item.CreatedAt = time.Now()
|
||||
return s.db.Create(item).Error
|
||||
}
|
||||
|
||||
// GetUserItems 获取用户打卡项列表
|
||||
func (s *CheckInService) GetUserItems(userID string) ([]models.CheckInItem, error) {
|
||||
var items []models.CheckInItem
|
||||
err := s.db.Where("user_id = ? AND status != ?", userID, models.StatusDeleted).
|
||||
Order("created_at desc").Find(&items).Error
|
||||
return items, err
|
||||
}
|
||||
|
||||
// GetItemByID 获取打卡项详情
|
||||
func (s *CheckInService) GetItemByID(id, userID string) (*models.CheckInItem, error) {
|
||||
var item models.CheckInItem
|
||||
err := s.db.Where("id = ? AND user_id = ?", id, userID).First(&item).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
// UpdateItem 更新打卡项
|
||||
func (s *CheckInService) UpdateItem(id, userID string, updates map[string]interface{}) (*models.CheckInItem, error) {
|
||||
allowed := map[string]bool{"name": true, "icon": true, "category": true, "daily_goal": true,
|
||||
"cycle_type": true, "privacy": true}
|
||||
filtered := make(map[string]interface{})
|
||||
for k, v := range updates {
|
||||
if allowed[k] {
|
||||
filtered[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
result := s.db.Model(&models.CheckInItem{}).Where("id = ? AND user_id = ?", id, userID).Updates(filtered)
|
||||
if result.RowsAffected == 0 {
|
||||
return nil, errors.New("打卡项不存在或无权限")
|
||||
}
|
||||
return s.GetItemByID(id, userID)
|
||||
}
|
||||
|
||||
// DeleteItem 软删除打卡项
|
||||
func (s *CheckInService) DeleteItem(id, userID string) error {
|
||||
return s.db.Model(&models.CheckInItem{}).
|
||||
Where("id = ? AND user_id = ?", id, userID).
|
||||
Update("status", models.StatusDeleted).Error
|
||||
}
|
||||
|
||||
// PauseItem 暂停打卡项
|
||||
func (s *CheckInService) PauseItem(id, userID string) error {
|
||||
return s.db.Model(&models.CheckInItem{}).
|
||||
Where("id = ? AND user_id = ?", id, userID).
|
||||
Update("status", models.StatusPaused).Error
|
||||
}
|
||||
|
||||
// ResumeItem 恢复打卡项
|
||||
func (s *CheckInService) ResumeItem(id, userID string) error {
|
||||
return s.db.Model(&models.CheckInItem{}).
|
||||
Where("id = ? AND user_id = ?", id, userID).
|
||||
Update("status", models.StatusActive).Error
|
||||
}
|
||||
|
||||
// CreateRecord 创建打卡记录
|
||||
func (s *CheckInService) CreateRecord(userID, itemID string, checkDate time.Time, note, images *string) (*models.CheckInRecord, error) {
|
||||
// 验证打卡项
|
||||
item, err := s.GetItemByID(itemID, userID)
|
||||
if err != nil {
|
||||
return nil, errors.New("打卡项不存在或无权限")
|
||||
}
|
||||
if item.Status != models.StatusActive {
|
||||
return nil, errors.New("打卡项当前不可用(已暂停或归档)")
|
||||
}
|
||||
|
||||
// 检查当日是否已打卡
|
||||
startOfDay := time.Date(checkDate.Year(), checkDate.Month(), checkDate.Day(), 0, 0, 0, 0, checkDate.Location())
|
||||
endOfDay := startOfDay.Add(24 * time.Hour)
|
||||
|
||||
var count int64
|
||||
s.db.Model(&models.CheckInRecord{}).
|
||||
Where("item_id = ? AND user_id = ? AND check_date >= ? AND check_date < ?",
|
||||
itemID, userID, startOfDay, endOfDay).Count(&count)
|
||||
if count > 0 {
|
||||
return nil, errors.New("今日已打卡")
|
||||
}
|
||||
|
||||
record := &models.CheckInRecord{
|
||||
ID: generateID(),
|
||||
ItemID: itemID,
|
||||
UserID: userID,
|
||||
CheckDate: checkDate,
|
||||
Status: models.RecordNormal,
|
||||
Note: note,
|
||||
Images: images,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if err := s.db.Create(record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// MakeupRecord 补卡(7 天内,每月 3 次免费,超出扣积分)
|
||||
func (s *CheckInService) MakeupRecord(userID, itemID string, checkDate time.Time, costPoints int) (*models.CheckInRecord, error) {
|
||||
now := time.Now()
|
||||
diff := now.Sub(checkDate)
|
||||
if diff.Hours() > 7*24 || diff.Hours() < 0 {
|
||||
return nil, errors.New("只能补签最近 7 天的记录")
|
||||
}
|
||||
|
||||
// 检查本月已用补卡次数
|
||||
currentMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
var makeupCount int64
|
||||
s.db.Model(&models.CheckInRecord{}).
|
||||
Where("user_id = ? AND status = ? AND created_at >= ?",
|
||||
userID, models.RecordMakeup, currentMonthStart).Count(&makeupCount)
|
||||
|
||||
// 计算需要消耗的积分
|
||||
freeCount := int64(3)
|
||||
if makeupCount >= freeCount {
|
||||
var user models.User
|
||||
if err := s.db.First(&user, "id = ?", userID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user.Points < costPoints {
|
||||
return nil, errors.New("积分不足,补卡需要 5 积分")
|
||||
}
|
||||
s.db.Model(&user).Update("points", user.Points-costPoints)
|
||||
}
|
||||
|
||||
record := &models.CheckInRecord{
|
||||
ID: generateID(),
|
||||
ItemID: itemID,
|
||||
UserID: userID,
|
||||
CheckDate: checkDate,
|
||||
Status: models.RecordMakeup,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
return record, s.db.Create(record).Error
|
||||
}
|
||||
|
||||
// GetRecords 获取打卡记录列表
|
||||
func (s *CheckInService) GetRecords(userID, itemID string, start, end time.Time) ([]models.CheckInRecord, error) {
|
||||
var records []models.CheckInRecord
|
||||
query := s.db.Where("user_id = ?", userID)
|
||||
if itemID != "" {
|
||||
query = query.Where("item_id = ?", itemID)
|
||||
}
|
||||
err := query.Where("check_date >= ? AND check_date < ?", start, end).
|
||||
Order("check_date desc").Find(&records).Error
|
||||
return records, err
|
||||
}
|
||||
|
||||
// GetStreak 计算连续打卡天数(时区感知)
|
||||
func (s *CheckInService) GetStreak(itemID, userID string) (int, error) {
|
||||
var records []models.CheckInRecord
|
||||
if err := s.db.Where("item_id = ? AND user_id = ?", itemID, userID).
|
||||
Order("check_date desc").Find(&records).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
streak := 0
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
|
||||
for i, r := range records {
|
||||
recordDate := r.CheckDate.Truncate(24 * time.Hour)
|
||||
if i == 0 {
|
||||
if recordDate.Before(today.Add(-48 * time.Hour)) {
|
||||
return 0, nil // 最近一次打卡超过 2 天前,连续已断
|
||||
}
|
||||
streak = 1
|
||||
continue
|
||||
}
|
||||
|
||||
prevDate := records[i-1].CheckDate.Truncate(24 * time.Hour)
|
||||
if prevDate.Sub(recordDate).Hours() == 24 {
|
||||
streak++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return streak, nil
|
||||
}
|
||||
|
||||
// GetMonthlyStats 获取月度统计
|
||||
func (s *CheckInService) GetMonthlyStats(userID string, year, month int) (map[string]interface{}, error) {
|
||||
startOfMonth := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.Local)
|
||||
endOfMonth := startOfMonth.AddDate(0, 1, 0)
|
||||
|
||||
var totalItems int64
|
||||
s.db.Model(&models.CheckInItem{}).Where("user_id = ? AND status = ?", userID, models.StatusActive).Count(&totalItems)
|
||||
|
||||
var totalRecords int64
|
||||
s.db.Model(&models.CheckInRecord{}).
|
||||
Where("user_id = ? AND check_date >= ? AND check_date < ?", userID, startOfMonth, endOfMonth).
|
||||
Count(&totalRecords)
|
||||
|
||||
// 按分类统计
|
||||
type CategoryCount struct {
|
||||
Category string
|
||||
Count int64
|
||||
}
|
||||
var catStats []CategoryCount
|
||||
s.db.Model(&models.CheckInItem{}).
|
||||
Select("category, count(*) as count").
|
||||
Where("user_id = ? AND status = ?", userID, models.StatusActive).
|
||||
Group("category").Find(&catStats)
|
||||
|
||||
return map[string]interface{}{
|
||||
"totalItems": totalItems,
|
||||
"totalRecords": totalRecords,
|
||||
"byCategory": catStats,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
// GetTodayStatus 获取用户今天已打卡的打卡项 ID 列表
|
||||
func (s *CheckInService) GetTodayStatus(userID string) (map[string]bool, error) {
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
endOfToday := today.Add(24 * time.Hour)
|
||||
|
||||
var records []models.CheckInRecord
|
||||
if err := s.db.Where("user_id = ? AND check_date >= ? AND check_date < ?",
|
||||
userID, today, endOfToday).Find(&records).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]bool)
|
||||
for _, r := range records {
|
||||
result[r.ItemID] = true
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/inchstep/server/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PointsService struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewPointsService(db *gorm.DB) *PointsService {
|
||||
return &PointsService{db: db}
|
||||
}
|
||||
|
||||
const (
|
||||
PointsCheckIn = 1
|
||||
PointsStreak7 = 5
|
||||
PointsStreak30 = 20
|
||||
PointsGoalComplete = 10
|
||||
PointsPostShare = 2
|
||||
PointsComment = 1
|
||||
PointsInviteUser = 15
|
||||
)
|
||||
|
||||
// AddPoints 为用户增加积分
|
||||
func (s *PointsService) AddPoints(userID string, amount int, reason string) error {
|
||||
return s.db.Model(&models.User{}).Where("id = ?", userID).
|
||||
UpdateColumn("points", gorm.Expr("points + ?", amount)).Error
|
||||
}
|
||||
|
||||
// CheckAndAwardStreak 检查并发放连续打卡奖励
|
||||
func (s *PointsService) CheckAndAwardStreak(userID string, streak int) error {
|
||||
switch streak {
|
||||
case 7:
|
||||
return s.AddPoints(userID, PointsStreak7, "连续打卡 7 天")
|
||||
case 30:
|
||||
return s.AddPoints(userID, PointsStreak30, "连续打卡 30 天")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AwardCheckIn 打卡奖励
|
||||
func (s *PointsService) AwardCheckIn(userID string) error {
|
||||
return s.AddPoints(userID, PointsCheckIn, "每日打卡")
|
||||
}
|
||||
|
||||
func ptr(s string) *string { return &s }
|
||||
|
||||
// predefinedAchievements 预定义成就列表
|
||||
var predefinedAchievements = []models.Achievement{
|
||||
{ID: "first_checkin", Name: "初露锋芒", Rarity: "bronze", Condition: ptr("完成第 1 次打卡")},
|
||||
{ID: "streak_7", Name: "七日坚持", Rarity: "bronze", Condition: ptr("连续 7 天打卡")},
|
||||
{ID: "streak_15", Name: "半月之约", Rarity: "bronze", Condition: ptr("连续 15 天打卡")},
|
||||
{ID: "streak_30", Name: "月满则盈", Rarity: "silver", Condition: ptr("连续 30 天打卡")},
|
||||
{ID: "streak_100", Name: "百日筑基", Rarity: "gold", Condition: ptr("连续 100 天打卡")},
|
||||
{ID: "all_round", Name: "全面开花", Rarity: "silver", Condition: ptr("同时有 5 个打卡项保持活跃")},
|
||||
{ID: "social_star", Name: "社交达人", Rarity: "silver", Condition: ptr("获得 50 个点赞")},
|
||||
{ID: "group_star", Name: "小组之星", Rarity: "silver", Condition: ptr("在小组内连续打卡满 30 天")},
|
||||
{ID: "makeup_master", Name: "神补刀", Rarity: "bronze", Condition: ptr("使用补卡功能 10 次")},
|
||||
{ID: "year_champion", Name: "年度坚持王", Rarity: "gold", Condition: ptr("全年打卡完成率 ≥ 80%")},
|
||||
}
|
||||
|
||||
// InitAchievements 初始化成就数据
|
||||
func (s *PointsService) InitAchievements() error {
|
||||
for _, a := range predefinedAchievements {
|
||||
s.db.Where("id = ?", a.ID).FirstOrCreate(&a)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnlockAchievement 解锁成就
|
||||
func (s *PointsService) UnlockAchievement(userID, achievementID string) (*models.UserAchievement, error) {
|
||||
ua := &models.UserAchievement{
|
||||
UserID: userID,
|
||||
AchievementID: achievementID,
|
||||
UnlockedAt: time.Now(),
|
||||
}
|
||||
if err := s.db.Create(ua).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ua, nil
|
||||
}
|
||||
|
||||
// GetUserAchievements 获取用户已解锁的成就
|
||||
func (s *PointsService) GetUserAchievements(userID string) ([]models.UserAchievement, error) {
|
||||
var list []models.UserAchievement
|
||||
err := s.db.Where("user_id = ?", userID).Preload("Achievement").Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
|
||||
// GetAllAchievements 获取所有成就
|
||||
func (s *PointsService) GetAllAchievements() ([]models.Achievement, error) {
|
||||
var list []models.Achievement
|
||||
err := s.db.Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
|
||||
// DeductPoints 扣除积分
|
||||
func (s *PointsService) DeductPoints(userID string, amount int) error {
|
||||
var user models.User
|
||||
if err := s.db.First(&user, "id = ?", userID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if user.Points < amount {
|
||||
return gorm.ErrRecordNotFound // 积分不足
|
||||
}
|
||||
return s.db.Model(&user).UpdateColumn("points", gorm.Expr("points - ?", amount)).Error
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/inchstep/server/internal/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SocialService struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewSocialService(db *gorm.DB) *SocialService {
|
||||
return &SocialService{db: db}
|
||||
}
|
||||
|
||||
// SendFriendRequest 发送好友请求
|
||||
func (s *SocialService) SendFriendRequest(userID, friendID string) error {
|
||||
if userID == friendID {
|
||||
return errors.New("不能添加自己为好友")
|
||||
}
|
||||
f := &models.Friend{
|
||||
UserID: userID,
|
||||
FriendID: friendID,
|
||||
Status: "pending",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
return s.db.Create(f).Error
|
||||
}
|
||||
|
||||
// AcceptFriendRequest 接受好友请求
|
||||
func (s *SocialService) AcceptFriendRequest(userID, friendID string) error {
|
||||
return s.db.Model(&models.Friend{}).
|
||||
Where("user_id = ? AND friend_id = ?", friendID, userID).
|
||||
Update("status", "accepted").Error
|
||||
}
|
||||
|
||||
// RemoveFriend 删除好友
|
||||
func (s *SocialService) RemoveFriend(userID, friendID string) error {
|
||||
return s.db.Where("(user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)",
|
||||
userID, friendID, friendID, userID).Delete(&models.Friend{}).Error
|
||||
}
|
||||
|
||||
// GetFriends 获取好友列表
|
||||
func (s *SocialService) GetFriends(userID string) ([]models.User, error) {
|
||||
var friendIDs []string
|
||||
s.db.Model(&models.Friend{}).Select("friend_id").
|
||||
Where("user_id = ? AND status = ?", userID, "accepted").Find(&friendIDs)
|
||||
|
||||
var users []models.User
|
||||
if len(friendIDs) > 0 {
|
||||
s.db.Where("id IN ?", friendIDs).Find(&users)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// CreatePost 创建动态
|
||||
func (s *SocialService) CreatePost(post *models.Post) error {
|
||||
post.ID = generateID()
|
||||
post.CreatedAt = time.Now()
|
||||
return s.db.Create(post).Error
|
||||
}
|
||||
|
||||
// GetFeed 获取动态广场
|
||||
func (s *SocialService) GetFeed(page, pageSize int) ([]models.Post, int64, error) {
|
||||
var total int64
|
||||
s.db.Model(&models.Post{}).Where("privacy = ?", 1).Count(&total)
|
||||
|
||||
var posts []models.Post
|
||||
err := s.db.Where("privacy = ?", 1).Preload("User").
|
||||
Order("created_at desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&posts).Error
|
||||
return posts, total, err
|
||||
}
|
||||
|
||||
// LikePost 点赞动态
|
||||
func (s *SocialService) LikePost(postID string) error {
|
||||
return s.db.Model(&models.Post{}).Where("id = ?", postID).
|
||||
UpdateColumn("likes", gorm.Expr("likes + ?", 1)).Error
|
||||
}
|
||||
|
||||
// CommentPost 评论动态
|
||||
func (s *SocialService) CommentPost(postID string) error {
|
||||
return s.db.Model(&models.Post{}).Where("id = ?", postID).
|
||||
UpdateColumn("comments", gorm.Expr("comments + ?", 1)).Error
|
||||
}
|
||||
|
||||
// CreateGroup 创建小组
|
||||
func (s *SocialService) CreateGroup(userID string, group *models.Group) error {
|
||||
group.ID = generateID()
|
||||
group.CreatedBy = userID
|
||||
group.CreatedAt = time.Now()
|
||||
tx := s.db.Begin()
|
||||
|
||||
if err := tx.Create(group).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
member := &models.GroupMember{
|
||||
GroupID: group.ID,
|
||||
UserID: userID,
|
||||
Role: "admin",
|
||||
JoinedAt: time.Now(),
|
||||
}
|
||||
if err := tx.Create(member).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
// JoinGroup 加入小组
|
||||
func (s *SocialService) JoinGroup(groupID, userID string) error {
|
||||
member := &models.GroupMember{
|
||||
GroupID: groupID,
|
||||
UserID: userID,
|
||||
Role: "member",
|
||||
JoinedAt: time.Now(),
|
||||
}
|
||||
return s.db.Create(member).Error
|
||||
}
|
||||
|
||||
// LeaveGroup 退出小组
|
||||
func (s *SocialService) LeaveGroup(groupID, userID string) error {
|
||||
return s.db.Where("group_id = ? AND user_id = ?", groupID, userID).
|
||||
Delete(&models.GroupMember{}).Error
|
||||
}
|
||||
|
||||
// GetGroupMembers 获取小组成员
|
||||
func (s *SocialService) GetGroupMembers(groupID string) ([]models.GroupMember, error) {
|
||||
var members []models.GroupMember
|
||||
err := s.db.Where("group_id = ?", groupID).Find(&members).Error
|
||||
return members, err
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/inchstep/server/internal/models"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUserService(db *gorm.DB) *UserService {
|
||||
return &UserService{db: db}
|
||||
}
|
||||
|
||||
func generateID() string {
|
||||
b := make([]byte, 16)
|
||||
rand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
func (s *UserService) RegisterByPhone(phone string) (*models.User, error) {
|
||||
var existing models.User
|
||||
if err := s.db.Where("phone = ?", phone).First(&existing).Error; err == nil {
|
||||
return nil, errors.New("该手机号已注册")
|
||||
}
|
||||
user := &models.User{
|
||||
ID: generateID(),
|
||||
Phone: &phone,
|
||||
Privacy: models.PrivacyFriendsOnly,
|
||||
Points: 0,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
if err := s.db.Create(user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *UserService) GetByPhone(phone string) (*models.User, error) {
|
||||
var user models.User
|
||||
if err := s.db.Where("phone = ?", phone).First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (s *UserService) GetByID(id string) (*models.User, error) {
|
||||
var user models.User
|
||||
if err := s.db.First(&user, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateProfile(id string, updates map[string]interface{}) (*models.User, error) {
|
||||
allowed := map[string]bool{"nickname": true, "avatar": true, "bio": true, "motto": true, "privacy": true}
|
||||
filtered := make(map[string]interface{})
|
||||
for k, v := range updates {
|
||||
if allowed[k] {
|
||||
filtered[k] = v
|
||||
}
|
||||
}
|
||||
if err := s.db.Model(&models.User{}).Where("id = ?", id).Updates(filtered).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.GetByID(id)
|
||||
}
|
||||
|
||||
func (s *UserService) DeleteAccount(id string) error {
|
||||
return s.db.Model(&models.User{}).Where("id = ?", id).Updates(map[string]interface{}{
|
||||
"phone": nil,
|
||||
"nickname": nil,
|
||||
"avatar": nil,
|
||||
"bio": nil,
|
||||
"motto": nil,
|
||||
"privacy": models.PrivacyPrivate,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func HashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func CheckPassword(hash, password string) bool {
|
||||
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
|
||||
}
|
||||
Reference in New Issue
Block a user