diff --git a/api/v1/plant/my_plant.go b/api/v1/plant/my_plant.go index 3b8e63e..44b3c2d 100644 --- a/api/v1/plant/my_plant.go +++ b/api/v1/plant/my_plant.go @@ -151,13 +151,13 @@ func (a *MyPlantApi) CompleteTask(c *gin.Context) { return } userId := auth.GetUserId(c) - err = plantService.CompleteTask(req, userId) + res, err := plantService.CompleteTask(req, userId) if err != nil { global.Logger.Error("完成任务失败", zap.Error(err)) response.FailWithMsg("完成任务失败", c) return } - response.OkWithMsg("完成任务成功", c) + response.OkWithData(res, c) } // DeletePlants diff --git a/api/v1/plant/ocr.go b/api/v1/plant/ocr.go index 478bfd9..e785703 100644 --- a/api/v1/plant/ocr.go +++ b/api/v1/plant/ocr.go @@ -67,3 +67,29 @@ func (o *OcrApi) MyClassifyLog(c *gin.Context) { PageSize: req.PageSize, }, c) } + +// DeleteClassifyLog +// @tags 识别相关 +// @Summary 删除识别记录 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "删除识别记录" +// @Success 200 {object} response.Response{msg=string} "删除识别记录" +// @router /classify/deleteClassifyLog [post] +func (o *OcrApi) DeleteClassifyLog(c *gin.Context) { + var req request.IdsReq + err := c.ShouldBind(&req) + if err != nil { + response.FailWithMsg("请求参数错误", c) + return + } + userId := auth.GetUserId(c) + err = ocrService.DeleteClassifyLog(req, userId) + if err != nil { + global.Logger.Error("删除识别记录失败!", zap.Error(err)) + response.FailWithMsg("删除识别记录失败!", c) + return + } + response.OkWithMsg("删除识别记录成功!", c) +} diff --git a/api/v1/plant/post.go b/api/v1/plant/post.go index 2e5cc75..6e342a9 100644 --- a/api/v1/plant/post.go +++ b/api/v1/plant/post.go @@ -2,6 +2,7 @@ package plant import ( "sundynix-go/global" + common "sundynix-go/model/commom/request" "sundynix-go/model/commom/response" plantReq "sundynix-go/model/plant/request" "sundynix-go/utils/auth" @@ -148,3 +149,50 @@ func (a *PostApi) CommentPost(c *gin.Context) { } response.OkWithMsg("评论成功", c) } + +// DeletePost 删除帖子 +// @Tags 帖子 +// @Summary 删除帖子 +// @Security BearerAuth +// @accept application/json +// @Produce application/json +// @Param data body common.IdsReq true "删除帖子" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /post/delete [post] +func (a *PostApi) DeletePost(c *gin.Context) { + var req common.IdsReq + err := c.ShouldBindJSON(&req) + if err != nil { + response.FailWithMsg("请求参数错误", c) + return + } + err = postService.DeletePost(req.Ids) + if err != nil { + global.Logger.Error("删除帖子失败", zap.Error(err)) + response.FailWithMsg("删除帖子失败", c) + return + } + response.OkWithMsg("删除帖子成功", c) +} + +// StarPost 收藏帖子 +// @Tags 帖子 +// @Summary 收藏帖子 +// @Security BearerAuth +// @Produce application/json +// @Param id query string true "帖子id" +// @Param type query string true "收藏类型 1 收藏 2 取消收藏" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"收藏成功"}" +// @Router /post/star [get] +func (a *PostApi) StarPost(c *gin.Context) { + postId := c.Query("id") + class := c.Query("type") + userId := auth.GetUserId(c) + err := postService.StarPost(userId, postId, class) + if err != nil { + global.Logger.Error("操作失败", zap.Error(err)) + response.FailWithMsg("操作失败", c) + return + } + response.OkWithMsg("操作成功", c) +} diff --git a/api/v1/plant/user_profile.go b/api/v1/plant/user_profile.go index aa4901d..533bdc9 100644 --- a/api/v1/plant/user_profile.go +++ b/api/v1/plant/user_profile.go @@ -1,11 +1,13 @@ package plant import ( + "sundynix-go/global" "sundynix-go/model/commom/response" "sundynix-go/model/plant/request" "sundynix-go/utils/auth" "github.com/gin-gonic/gin" + "go.uber.org/zap" ) type UserProfileApi struct{} @@ -49,3 +51,34 @@ func (a *UserProfileApi) ProfileDetail(c *gin.Context) { } response.OkWithData(res, c) } + +// MyStars 我的收藏 +// @Tags 个人中心 +// @Summary 我的收藏 +// @Security BearerAuth +// @accept application/json +// @Produce application/json +// @Param data body request.StarsPageReq true "分页获取收藏列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /profile/star [post] +func (a *UserProfileApi) MyStars(c *gin.Context) { + var req request.StarsPageReq + err := c.ShouldBindJSON(&req) + if err != nil { + response.FailWithMsg("请求参数错误", c) + return + } + userId := auth.GetUserId(c) + list, total, err := userProfileService.MyStars(req, userId) + if err != nil { + global.Logger.Error("获取收藏失败", zap.Error(err)) + response.FailWithMsg("获取收藏失败", c) + return + } + response.OkWithData(response.PageResult{ + List: list, + Total: total, + Page: req.Current, + PageSize: req.PageSize, + }, c) +} diff --git a/api/v1/plant/wiki.go b/api/v1/plant/wiki.go index f85ada2..108a588 100644 --- a/api/v1/plant/wiki.go +++ b/api/v1/plant/wiki.go @@ -4,6 +4,7 @@ import ( "sundynix-go/global" "sundynix-go/model/commom/response" plantReq "sundynix-go/model/plant/request" + "sundynix-go/utils/auth" "github.com/gin-gonic/gin" "go.uber.org/zap" @@ -78,7 +79,8 @@ func (a *WikiApi) WikiPage(c *gin.Context) { response.FailWithMsg("请求参数错误", c) return } - list, total, err := wikiService.WikiPage(req) + userId := auth.GetUserId(c) + list, total, err := wikiService.WikiPage(req, userId) if err != nil { global.Logger.Error("分页百科失败", zap.Error(err)) response.FailWithMsg("分页百科失败", c) @@ -102,10 +104,33 @@ func (a *WikiApi) WikiPage(c *gin.Context) { // @Router /wiki/detail [get] func (a *WikiApi) WikiDetail(c *gin.Context) { id := c.Query("id") - topic, err := wikiService.Detail(id) + userId := auth.GetUserId(c) + topic, err := wikiService.Detail(id, userId) if err != nil { response.FailWithMsg("获取失败", c) return } response.OkWithData(topic, c) } + +// StarWiki 收藏百科 +// @Tags 百科 +// @Summary 收藏百科 +// @Security BearerAuth +// @Produce application/json +// @Param id query string true "百科id" +// @Param type query string true "收藏类型 1 收藏 2 取消收藏" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"收藏成功"}" +// @Router /wiki/star [get] +func (a *WikiApi) StarWiki(c *gin.Context) { + postId := c.Query("id") + class := c.Query("type") + userId := auth.GetUserId(c) + err := wikiService.StarWiki(userId, postId, class) + if err != nil { + global.Logger.Error("操作失败", zap.Error(err)) + response.FailWithMsg("操作失败", c) + return + } + response.OkWithMsg("操作成功", c) +} diff --git a/config-dev.yaml b/config-dev.yaml index 4362ae9..1bc8704 100644 --- a/config-dev.yaml +++ b/config-dev.yaml @@ -14,14 +14,14 @@ jwt: # 植趣微信小程序 -#mini-program: -# app-id: wxb463820bf36dd5d6 -# app-secret: 731784a74c76c6d31fa00bb847af2c7d +mini-program: + app-id: wxb463820bf36dd5d6 + app-secret: 731784a74c76c6d31fa00bb847af2c7d # 植遇微信小程序 -mini-program: - app-id: wx52dfc635739a9c19 - app-secret: 5aaed22f05352b7cd991870de6600bef +#mini-program: +# app-id: wx52dfc635739a9c19 +# app-secret: 5aaed22f05352b7cd991870de6600bef # 植趣服务号 service-account: diff --git a/initialize/gorm.go b/initialize/gorm.go index e2c2932..0ba651c 100644 --- a/initialize/gorm.go +++ b/initialize/gorm.go @@ -56,6 +56,7 @@ func MigrateTable() { plant.BadgeConfig{}, //徽章配置 plant.UserProfile{}, //用户资料 plant.UserBadge{}, //用户徽章 + plant.UserStar{}, //用户收藏 ) if err != nil { diff --git a/initialize/timer.go b/initialize/timer.go index 411ceab..6036dc1 100644 --- a/initialize/timer.go +++ b/initialize/timer.go @@ -15,7 +15,7 @@ func InitTimer() { option = append(option, cron.WithSeconds()) // 任务一:每天8点30执行 发送植物养护提醒 - _, err := global.Timer.AddTaskByFuncWithSecond("SendCareRemind", "0 30 8 * * *", func() { + _, err := global.Timer.AddTaskByFuncWithSecond("SendCareRemind", "0 15 9 * * *", func() { err1 := task.SendCareMsg() if err1 != nil { global.Logger.Error("定时发送植物养护提醒失败", zap.Error(err1)) diff --git a/middleware/auth.go b/middleware/auth.go index 6bb49f0..ec24fde 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -5,8 +5,8 @@ import ( "sundynix-go/global" "sundynix-go/model/commom/response" "sundynix-go/service" - "sundynix-go/utils" "sundynix-go/utils/auth" + "sundynix-go/utils/timer" "time" "github.com/gin-gonic/gin" @@ -48,7 +48,7 @@ func AuthMiddleware() gin.HandlerFunc { } c.Set("claims", claims) if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime { - dr, _ := utils.ParseDuration(global.Config.JWT.ExpiresTime) + dr, _ := timer.ParseDuration(global.Config.JWT.ExpiresTime) claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(dr)) } c.Next() diff --git a/model/plant/community_post.go b/model/plant/community_post.go index 226fcd8..e60b52d 100644 --- a/model/plant/community_post.go +++ b/model/plant/community_post.go @@ -14,9 +14,11 @@ type Post struct { ViewCount int `json:"viewCount" form:"viewCount" gorm:"default:0;column:view_count;comment:浏览次数"` CommentCount int `json:"commentCount" form:"commentCount" gorm:"default:0;column:comment_count;comment:评论次数"` LikeCount int `json:"likeCount" form:"likeCount" gorm:"default:0;column:like_count;comment:点赞次数"` + StarCount int `json:"starCount" form:"starCount" gorm:"default:0;column:star_count;comment:收藏次数"` Location string `json:"location" form:"location" gorm:"column:location;size:100;comment:位置"` - HasReviewed int `json:"hasReviewed" form:"hasReviewed" gorm:"column:has_reviewed;default:0;comment:是否审核通过"` + HasReviewed int `json:"hasReviewed" form:"hasReviewed" gorm:"column:has_reviewed;default:0;comment:是否审核通过"` HasLiked int `json:"hasLiked" form:"hasLiked" gorm:"-"` + HasStar int `json:"hasStar" form:"hasStar" gorm:"-"` //图片 ImgList []*system.Oss `json:"imgList" form:"imgList" gorm:"many2many:post_oss;comment:图片列表"` //评论 diff --git a/model/plant/my_plant.go b/model/plant/my_plant.go index 9893d7a..0c78f1c 100644 --- a/model/plant/my_plant.go +++ b/model/plant/my_plant.go @@ -35,13 +35,14 @@ func (p *MyPlant) AfterCreate(tx *gorm.DB) (err error) { for _, v := range p.CarePlans { dueDate := today.AddDate(0, 0, v.Period) task := CareTask{ - UserId: p.UserId, - PlantId: p.Id, - PlanId: v.Id, - Name: v.Name, - Icon: v.Icon, - DueDate: dueDate, - Status: 1, + UserId: p.UserId, + PlantId: p.Id, + PlanId: v.Id, + Name: v.Name, + Icon: v.Icon, + TargetAction: v.TargetAction, + DueDate: dueDate, + Status: 1, } err = tx.Create(&task).Error if err != nil { diff --git a/model/plant/my_plant_care_plan.go b/model/plant/my_plant_care_plan.go index a7d9882..41806c6 100644 --- a/model/plant/my_plant_care_plan.go +++ b/model/plant/my_plant_care_plan.go @@ -10,11 +10,12 @@ import ( // CarePlan 养护计划 type CarePlan struct { global.BaseModel - UserId string `json:"userId" form:"userId" gorm:"size:50;column:user_id;comment:用户id"` - PlantId string `json:"plantId" form:"plantId" gorm:"size:50;column:plant_id;comment:植物id"` - Icon string `json:"icon" form:"icon" gorm:"type:text;"` - Name string `json:"name"` - Period int `json:"period" form:"period" gorm:"column:period;comment:周期"` + UserId string `json:"userId" form:"userId" gorm:"size:50;column:user_id;comment:用户id"` + PlantId string `json:"plantId" form:"plantId" gorm:"index;size:50;column:plant_id;comment:植物id"` + Icon string `json:"icon" form:"icon" gorm:"type:text;"` + Name string `json:"name"` + Period int `json:"period" form:"period" gorm:"column:period;comment:周期"` + TargetAction string `gorm:"type:varchar(32);index;not null;column:target_action;comment:触发动作代码" json:"targetAction"` } // AfterUpdate 钩子函数 修改计划后重新生成任务 diff --git a/model/plant/my_plant_care_task.go b/model/plant/my_plant_care_task.go index 147245d..068ac94 100644 --- a/model/plant/my_plant_care_task.go +++ b/model/plant/my_plant_care_task.go @@ -7,12 +7,13 @@ import ( type CareTask struct { global.BaseModel - PlantId string `json:"plantId" gorm:"index"` - PlanId string `json:"planId" gorm:"index"` - UserId string `json:"userId" gorm:"index"` - Name string `json:"name"` - Icon string `json:"icon" gorm:"type:text"` - DueDate time.Time `json:"dueDate"` // 应做时间(用于判断逾期) - CompletedAt *time.Time `json:"completedAt" gorm:"column:completed_at"` // 完成时间(为nil表示未完成) - Status int `json:"status"` // 1:待执行 2:已完成 3:已跳过 + PlantId string `json:"plantId" gorm:"index"` + PlanId string `json:"planId" gorm:"index"` + UserId string `json:"userId" gorm:"index"` + Name string `json:"name"` + Icon string `json:"icon" gorm:"type:text"` + TargetAction string `gorm:"type:varchar(32);index;column:target_action;comment:触发动作代码" json:"targetAction"` + DueDate time.Time `json:"dueDate"` // 应做时间(用于判断逾期) + CompletedAt *time.Time `json:"completedAt" gorm:"column:completed_at"` // 完成时间(为nil表示未完成) + Status int `json:"status"` // 1:待执行 2:已完成 3:已跳过 } diff --git a/model/plant/request/my_plant.go b/model/plant/request/my_plant.go index 40024ba..b0a5e9d 100644 --- a/model/plant/request/my_plant.go +++ b/model/plant/request/my_plant.go @@ -1,15 +1,17 @@ package request type CarePlan struct { - Icon string `json:"icon"` // icon信息 - Name string `json:"name"` // 农事名称 - Period int `json:"period"` // 周期 + Icon string `json:"icon"` // icon信息 + Name string `json:"name"` // 农事名称 + Period int `json:"period"` // 周期 + TargetAction string `json:"targetAction"` // 触动的动作 } type CreateCarePlan struct { - PlantId string `json:"plantId" binding:"required"` - Icon string `json:"icon"` // icon信息 - Name string `json:"name"` // 农事名称 - Period int `json:"period"` // 周期 + PlantId string `json:"plantId" binding:"required"` + Icon string `json:"icon"` // icon信息 + Name string `json:"name"` // 农事名称 + Period int `json:"period"` // 周期 + TargetAction string `json:"targetAction"` // 触动的动作 } type AddPlans struct { CarePlan []CreateCarePlan `json:"carePlan"` @@ -29,10 +31,11 @@ type CreateMyPlant struct { } type UpdatePlan struct { - Id string `json:"id" binding:"required"` - Icon string `json:"icon"` // icon信息 - Name string `json:"name"` // 农事名称 - Period int `json:"period"` // 周期 + Id string `json:"id" binding:"required"` + Icon string `json:"icon"` // icon信息 + Name string `json:"name"` // 农事名称 + Period int `json:"period"` // 周期 + TargetAction string `json:"targetAction"` // 触动的动作 } // UpdateMyPlant 修改植物 diff --git a/model/plant/request/profile.go b/model/plant/request/profile.go index 1c41d54..65df5f3 100644 --- a/model/plant/request/profile.go +++ b/model/plant/request/profile.go @@ -1,6 +1,13 @@ package request +import common "sundynix-go/model/commom/request" + type UpdateProfile struct { Nickname string `json:"nickname"` AvatarId string `json:"avatarId"` } + +type StarsPageReq struct { + common.PageInfo + Class int `json:"class"` //分类 0 全部 1百科 2社交动态 +} diff --git a/model/plant/response/task.go b/model/plant/response/task.go new file mode 100644 index 0000000..cbf176a --- /dev/null +++ b/model/plant/response/task.go @@ -0,0 +1,11 @@ +package response + +import "sundynix-go/model/plant" + +// TaskCompletionResult 定义返回给前端的组合对象 +type TaskCompletionResult struct { + NewBadge *plant.BadgeConfig `json:"newBadge"` // 本次新获得的徽章,无则为 nil + CurrentLevel *plant.LevelConfig `json:"currentLevel"` // 当前最新的等级信息 + IsLevelUp bool `json:"isLevelUp"` // 是否升级了 + IsGetBadge bool `json:"isGetBadge"` // 本次任务是否获得徽章 +} diff --git a/model/plant/user_badge.go b/model/plant/user_badge.go index fd37906..2cdc9e1 100644 --- a/model/plant/user_badge.go +++ b/model/plant/user_badge.go @@ -8,6 +8,6 @@ import ( type UserBadge struct { global.BaseModel UserId string `gorm:"type:varchar(50);index;not null;column:user_id;comment:用户id" json:"userId"` - BadgeId uint `gorm:"index:idx_user_badge,unique;not null;column:badge_id;comment:徽章配置ID" json:"badgeId"` + BadgeId string `gorm:"index:idx_user_badge,unique;not null;column:badge_id;comment:徽章配置ID" json:"badgeId"` AcquiredAt time.Time `gorm:"autoCreateTime;column:acquired_at;comment:获得时间" json:"acquiredAt"` } diff --git a/model/plant/user_profile.go b/model/plant/user_profile.go index 2cbacce..a542e73 100644 --- a/model/plant/user_profile.go +++ b/model/plant/user_profile.go @@ -11,12 +11,17 @@ type UserProfile struct { MiniOpenId string `gorm:"size:80;column:mini_open_id" json:"miniOpenId" form:"miniOpenId"` Nickname string `json:"nickname" gorm:"column:nick_name"` AvatarId string `json:"avatarId" gorm:"column:avatar_id"` - LevelId string `json:"levelId" gorm:"column:level_id"` // 当前等级id - CurrentSunlight int64 `json:"currentSunlight" gorm:"column:current_sunlight"` // 当前持有阳光值 (可消耗) - TotalSunlight int64 `json:"totalSunlight" gorm:"column:total_sunlight"` // 历史累计阳光值 (用于计算等级) - PlantCount int64 `json:"plantCount" gorm:"column:plant_count"` - CareCount int64 `json:"careCount" gorm:"column:care_count"` - PostCount int64 `json:"postCount" gorm:"column:post_count"` + LevelId string `json:"levelId" gorm:"column:level_id"` // 当前等级id + CurrentSunlight int64 `json:"currentSunlight" gorm:"not null;default:0;column:current_sunlight"` // 当前持有阳光值 (可消耗) + TotalSunlight int64 `json:"totalSunlight" gorm:"not null;default:0;column:total_sunlight"` // 历史累计阳光值 (用于计算等级) + PlantCount int64 `json:"plantCount" gorm:"not null;default:0;column:plant_count"` + CareCount int64 `json:"careCount" gorm:"not null;default:0;column:care_count"` + PostCount int64 `json:"postCount" gorm:"not null;default:0;column:post_count"` + WaterCount int64 `json:"waterCount" gorm:"not null;default:0;column:water_count"` // 浇水次数 + FertilizeCount int64 `json:"fertilizeCount" gorm:"not null;default:0;column:fertilize_count"` // 施肥次数 + RepotCount int64 `json:"repotCount" gorm:"not null;default:0;column:repot_count"` // 换盆次数 + PruneCount int64 `json:"pruneCount" gorm:"not null;default:0;column:prune_count"` // 剪枝次数 + PhotoCount int64 `json:"photoCount" gorm:"not null;default:0;column:photo_count"` // 拍照次数 Avatar *system.Oss `json:"avatar" gorm:"foreignKey:AvatarId"` Level *LevelConfig `json:"level" gorm:"foreignKey:LevelId"` } diff --git a/model/plant/user_star.go b/model/plant/user_star.go new file mode 100644 index 0000000..1bf9f1a --- /dev/null +++ b/model/plant/user_star.go @@ -0,0 +1,13 @@ +package plant + +import "sundynix-go/global" + +type UserStar struct { + global.BaseModel + UserId string `json:"userId" gorm:"index"` + Type int `json:"type" gorm:"index"` //1.百科 //2.社交动态 + WikiId string `json:"wikiId" gorm:"index"` + PostId string `json:"postId" gorm:"index"` + Wiki *Wiki `json:"wiki" gorm:"foreignKey:WikiId"` + Post *Post `json:"post" gorm:"foreignKey:PostId"` +} diff --git a/model/plant/wiki.go b/model/plant/wiki.go index 69ce3dd..20bd3fc 100644 --- a/model/plant/wiki.go +++ b/model/plant/wiki.go @@ -39,7 +39,8 @@ type Wiki struct { FloweringShape string `json:"floweringShape" form:"floweringShape" gorm:"size:100;column:flowering_shape;comment:开花形状"` FlowerDiameter int `json:"flowerDiameter" form:"flowerDiameter" gorm:"size:10;column:flower_diameter;comment:花直径"` //果 - Fruit string `json:"fruit" form:"fruit" gorm:"size:200;column:fruit;comment:果实"` + Fruit string `json:"fruit" form:"fruit" gorm:"size:200;column:fruit;comment:果实"` + HasStar int `json:"hasStar" form:"hasStar" gorm:"-"` //相关推荐 RelatedWiki []*Wiki `gorm:"many2many:wiki_related;" json:"relatedWiki"` diff --git a/router/plant/ocr_router.go b/router/plant/ocr_router.go index 6aaf644..72ff4b8 100644 --- a/router/plant/ocr_router.go +++ b/router/plant/ocr_router.go @@ -9,6 +9,7 @@ func (c *OcrRouter) InitOcrRouter(Router *gin.RouterGroup) { { ocrRouter.POST("/plant", ocrApi.ClassifyPlant) ocrRouter.POST("/myClassifyLog", ocrApi.MyClassifyLog) + ocrRouter.POST("/deleteClassifyLog", ocrApi.DeleteClassifyLog) } } diff --git a/router/plant/post_router.go b/router/plant/post_router.go index 1d370c0..0f0c9b8 100644 --- a/router/plant/post_router.go +++ b/router/plant/post_router.go @@ -12,7 +12,9 @@ func (p *PostRouter) InitPostRouter(Router *gin.RouterGroup) { postRouter.POST("page", postApi.PostPage) // 帖子列表 postRouter.POST("myPost", postApi.MyPost) // 我的发布 postRouter.GET("like", postApi.LikePost) // 点赞或者取消点赞 + postRouter.GET("star", postApi.StarPost) // 收藏或者取消收藏帖子 postRouter.POST("comment", postApi.CommentPost) // 评论 + postRouter.POST("delete", postApi.DeletePost) // 删除帖子 //postRouter.POST("deleteComment", postApi.delementComment) // 取消评论 } diff --git a/router/plant/user_profile_router.go b/router/plant/user_profile_router.go index 27dbcfe..f614f40 100644 --- a/router/plant/user_profile_router.go +++ b/router/plant/user_profile_router.go @@ -7,8 +7,10 @@ type UserProfileRouter struct{} func (c *UserProfileRouter) InitUserProfileRouter(Router *gin.RouterGroup) { userProfileRouter := Router.Group("profile") { - userProfileRouter.POST("/update", userProfileApi.UpdateProfile) - userProfileRouter.GET("/detail", userProfileApi.ProfileDetail) + userProfileRouter.POST("update", userProfileApi.UpdateProfile) + userProfileRouter.GET("detail", userProfileApi.ProfileDetail) + + userProfileRouter.POST("star", userProfileApi.MyStars) //我的收藏 } } diff --git a/router/plant/wiki_router.go b/router/plant/wiki_router.go index 66f87fb..f6b6ca8 100644 --- a/router/plant/wiki_router.go +++ b/router/plant/wiki_router.go @@ -12,5 +12,8 @@ func (p *WikiRouter) InitWikiRouter(Router *gin.RouterGroup) { wikiRouter.POST("/page", wikiApi.WikiPage) wikiRouter.GET("/detail", wikiApi.WikiDetail) + //用户端 + wikiRouter.GET("/star", wikiApi.StarWiki) //收藏或者取消收藏 + } } diff --git a/service/plant/my_plant.go b/service/plant/my_plant.go index e6e64f3..1ea62e3 100644 --- a/service/plant/my_plant.go +++ b/service/plant/my_plant.go @@ -1,6 +1,8 @@ package plant import ( + "context" + "errors" "sundynix-go/global" common "sundynix-go/model/commom/request" "sundynix-go/model/plant" @@ -32,13 +34,14 @@ func (s *MyPlantService) AddPlant(req plantReq.CreateMyPlant, userId string) err var carePlans []*plant.CarePlan for _, v := range req.CarePlans { carePlans = append(carePlans, &plant.CarePlan{ - UserId: userId, - Name: v.Name, - Icon: v.Icon, - Period: v.Period, + UserId: userId, + Name: v.Name, + Icon: v.Icon, + Period: v.Period, + TargetAction: v.TargetAction, }) } - //3.保存数据 + //3.保存数据 myPlant钩子函数自动处理创建careTask myPlant := plant.MyPlant{ UserId: userId, Name: req.Name, @@ -150,9 +153,10 @@ func (s *MyPlantService) UpdatePlant(req plantReq.UpdateMyPlant) error { today := timer.GetZeroTime() for _, plan := range req.CarePlans { err = tx.Model(&plant.CarePlan{}).Where("id = ?", plan.Id).Updates(map[string]interface{}{ - "icon": plan.Icon, - "name": plan.Name, - "period": plan.Period, + "icon": plan.Icon, + "name": plan.Name, + "period": plan.Period, + "target_action": plan.TargetAction, }).Error if err != nil { return err @@ -234,7 +238,8 @@ func (s *MyPlantService) TodayTask(userId string) ([]plantRes.PlantTaskVO, error } // CompleteTask 完成任务 -func (s *MyPlantService) CompleteTask(req plantReq.CompleteTask, userId string) error { +func (s *MyPlantService) CompleteTask(req plantReq.CompleteTask, userId string) (*plantRes.TaskCompletionResult, error) { + var result plantRes.TaskCompletionResult err := global.DB.Transaction(func(tx *gorm.DB) error { var task plant.CareTask if err := tx.Where("id = ?", req.TaskId).First(&task).Error; err != nil { @@ -257,13 +262,14 @@ func (s *MyPlantService) CompleteTask(req plantReq.CompleteTask, userId string) today := timer.GetZeroTime() nextDueDate := today.AddDate(0, 0, plan.Period) newTask := plant.CareTask{ - UserId: plan.UserId, - PlantId: plan.PlantId, - PlanId: plan.Id, - Name: plan.Name, - Icon: plan.Icon, - DueDate: nextDueDate, - Status: 1, + UserId: plan.UserId, + PlantId: plan.PlantId, + PlanId: plan.Id, + Name: plan.Name, + Icon: plan.Icon, + TargetAction: plan.TargetAction, + DueDate: nextDueDate, + Status: 1, } if err := tx.Create(&newTask).Error; err != nil { return err @@ -280,51 +286,134 @@ func (s *MyPlantService) CompleteTask(req plantReq.CompleteTask, userId string) if err := tx.Create(&record).Error; err != nil { return err } + //4.等级与阳光计算 + var profile plant.UserProfile + if err := tx.Set("gorm:query_option", "FOR UPDATE").Where("user_id = ?", userId).First(&profile).Error; err != nil { + return err + } + fieldMap := map[string]string{ + "ACT_WATER": "water_count", + "ACT_FERTILIZE": "fertilize_count", + "ACT_REPOT": "repot_count", + "ACT_PRUNE": "prune_count", + } + column, ok := fieldMap[task.TargetAction] + if !ok { + column = "care_count" // 默认备用 + } + const TaskReward = 50 + newTotalSunlight := profile.TotalSunlight + TaskReward + newCurrentSunlight := profile.CurrentSunlight + TaskReward + // 5. 等级判定 (根据累计阳光查出当前应有的等级) + var latestLevel plant.LevelConfig + if err := tx.Where("min_sunlight <= ?", newTotalSunlight). + Order("min_sunlight DESC").First(&latestLevel).Error; err != nil { + return errors.New("等级配置异常") + } + result.CurrentLevel = &latestLevel + result.IsLevelUp = latestLevel.Id != profile.LevelId + // 6. 执行 Profile 更新 (任务阳光 + 计数自增 + 等级同步) + profileData := map[string]interface{}{ + column: gorm.Expr(column + " + 1"), + "current_sunlight": newCurrentSunlight, + "total_sunlight": newTotalSunlight, + "level_id": latestLevel.Id, + } + if err := tx.Model(&plant.UserProfile{}).Where("user_id = ?", userId).Updates(profileData).Error; err != nil { + return err + } + // 7. 徽章判定逻辑 + // 7.1 计算当前动作的最新逻辑数值 + var currentActionVal int64 + switch task.TargetAction { + case "ACT_WATER": + currentActionVal = profile.WaterCount + 1 + case "ACT_FERTILIZE": + currentActionVal = profile.FertilizeCount + 1 + case "ACT_REPOT": + currentActionVal = profile.RepotCount + 1 + case "ACT_PRUNE": + currentActionVal = profile.PruneCount + 1 + default: + currentActionVal = 0 + } + // 7.2 查询已拥有的徽章 ID (用于去重) + var ownedBadgeIds []string + tx.Model(&plant.UserBadge{}).Where("user_id = ?", userId).Pluck("badge_id", &ownedBadgeIds) + ownedMap := make(map[string]bool) + for _, id := range ownedBadgeIds { + ownedMap[id] = true + } + // 7.3 筛选当前 Action 下满足条件的最高级徽章 + var badgeConfigs []plant.BadgeConfig + tx.Where("target_action = ?", task.TargetAction).Preload("Icon").Find(&badgeConfigs) + + for i := range badgeConfigs { + conf := &badgeConfigs[i] + // 如果没拿过且数值达标 + if !ownedMap[conf.Id] && currentActionVal >= conf.Threshold { + // 寻找 Tier 最大的那个 + if result.NewBadge == nil || conf.Tier > result.NewBadge.Tier { + result.NewBadge = conf + result.IsGetBadge = true + } + } + } + // 8. 奖励入库:如果产生了新徽章 + if result.NewBadge != nil { + // 写入获得记录 + ub := plant.UserBadge{ + UserId: userId, + BadgeId: result.NewBadge.Id, // 注意这里 Id 是 string + } + if err := tx.Create(&ub).Error; err != nil { + return err + } + // 发放徽章额外奖励阳光 + if err := tx.Model(&plant.UserProfile{}).Where("user_id = ?", userId). + Update("current_sunlight", gorm.Expr("current_sunlight + ?", result.NewBadge.RewardSunlight)).Error; err != nil { + return err + } + } return nil }) - if err == nil { - go func() { - //5.更新用户profile - var profile plant.UserProfile - if asyncErr := global.DB.Where("user_id = ?", userId).First(&profile).Error; err != nil { - global.Logger.Error("完成任务异步操作-----查询用户profile失败", zap.Error(asyncErr)) - } - totalSunlight := profile.TotalSunlight + 50 - updateData := map[string]interface{}{ - "care_count": profile.CareCount + 1, - "current_sunlight": profile.CurrentSunlight + 50, - "total_sunlight": totalSunlight, - } - //5.1 判断是否到达下个等级 - var currentLevel plant.LevelConfig - levelErr := global.DB.Where("min_sunlight <= ?", totalSunlight).Order("min_sunlight DESC").First(¤tLevel).Error - if levelErr != nil { - global.Logger.Error("完成任务异步操作-----查询用户等级失败", zap.Error(levelErr)) - } - updateData["level_id"] = currentLevel.Id - //5.2 更新用户profile - updateProfileErr := global.DB.Model(&plant.UserProfile{}).Where("user_id = ?", userId).Updates(updateData).Error - if updateProfileErr != nil { - global.Logger.Error("完成任务异步操作-----更新用户profile失败", zap.Error(updateProfileErr)) - } - }() - } - - return err + return &result, err } // DeletePlants 删除植物 func (s *MyPlantService) DeletePlants(req common.IdsReq) error { return global.DB.Transaction(func(tx *gorm.DB) error { - var plants []plant.MyPlant - if err := tx.Where("id in ?", req.Ids).Find(&plants).Error; err != nil { + var imgIds []string + tx.Table("sundynix_my_plant_oss").Where("my_plant_id IN ?", req.Ids).Pluck("oss_id", &imgIds) + // 2. 清理中间表记录 (解开多对多关系) + // 使用 Exec 直接操作中间表比循环 Clear 快得多 + if err := tx.Exec("DELETE FROM sundynix_my_plant_oss WHERE my_plant_id IN ?", req.Ids).Error; err != nil { return err } - // 删除图片 养护计划 养护任务 养护记录 成长记录 - err := tx.Select("ImgList", "CarePlans", "CareTasks", "CareRecords").Unscoped().Delete(&plants).Error - if err != nil { + // 3. 物理删除图片记录本身 + if len(imgIds) > 0 { + if err := tx.Unscoped().Where("id IN ?", imgIds).Delete(&system.Oss{}).Error; err != nil { + return err + } + } + //4.批量删除养护计划 养护任务 养护记录 成长记录 + if err := tx.Unscoped().Where("plant_id in ?", req.Ids).Delete(&plant.CarePlan{}).Error; err != nil { return err } + if err := tx.Unscoped().Where("plant_id in ?", req.Ids).Delete(&plant.CareTask{}).Error; err != nil { + return err + } + if err := tx.Unscoped().Where("plant_id in ?", req.Ids).Delete(&plant.CareRecord{}).Error; err != nil { + return err + } + if err := tx.Unscoped().Where("plant_id in ?", req.Ids).Delete(&plant.GrowthRecord{}).Error; err != nil { + return err + } + //5.删除植物本身 + if err := tx.Unscoped().Where("id in ?", req.Ids).Delete(&plant.MyPlant{}).Error; err != nil { + return err + } + return nil }) } @@ -365,11 +454,12 @@ func (s *MyPlantService) AddCarePlan(req plantReq.AddPlans) error { } //1.新增计划 newPlan := plant.CarePlan{ - UserId: myPlant.UserId, - PlantId: myPlant.Id, - Name: plan.Name, - Icon: plan.Icon, - Period: plan.Period, + UserId: myPlant.UserId, + PlantId: myPlant.Id, + Name: plan.Name, + Icon: plan.Icon, + Period: plan.Period, + TargetAction: plan.TargetAction, } err = tx.Create(&newPlan).Error if err != nil { @@ -379,13 +469,14 @@ func (s *MyPlantService) AddCarePlan(req plantReq.AddPlans) error { today := timer.GetZeroTime() dueDate := today.AddDate(0, 0, plan.Period) task := plant.CareTask{ - UserId: myPlant.UserId, - PlantId: myPlant.Id, - PlanId: newPlan.Id, - Name: plan.Name, - Icon: plan.Icon, - DueDate: dueDate, - Status: 1, + UserId: myPlant.UserId, + PlantId: myPlant.Id, + PlanId: newPlan.Id, + Name: plan.Name, + Icon: plan.Icon, + TargetAction: plan.TargetAction, + DueDate: dueDate, + Status: 1, } err = tx.Create(&task).Error if err != nil { @@ -459,3 +550,51 @@ func (s *MyPlantService) AddGrowthRecord(req plantReq.CreateGrowthRecord, userId return nil }) } + +// 完成任务后异步执行 更新用户等级 sunlight +func (s *MyPlantService) handleCompleteTaskThenUpdateProfile(ctx context.Context, userId string) { + //5.更新用户profile + var profile plant.UserProfile + if asyncErr := global.DB.Where("user_id = ?", userId).First(&profile).Error; asyncErr != nil { + global.Logger.Error("完成任务异步操作-----查询用户profile失败", zap.Error(asyncErr)) + } + totalSunlight := profile.TotalSunlight + 50 + updateData := map[string]interface{}{ + "care_count": profile.CareCount + 1, + "current_sunlight": profile.CurrentSunlight + 50, + "total_sunlight": totalSunlight, + } + //5.1 判断是否到达下个等级 + var currentLevel plant.LevelConfig + levelErr := global.DB.Where("min_sunlight <= ?", totalSunlight).Order("min_sunlight DESC").First(¤tLevel).Error + if levelErr != nil { + global.Logger.Error("完成任务异步操作-----查询用户等级失败", zap.Error(levelErr)) + } + updateData["level_id"] = currentLevel.Id + //5.2 更新用户profile + updateProfileErr := global.DB.Model(&plant.UserProfile{}).Where("user_id = ?", userId).Updates(updateData).Error + if updateProfileErr != nil { + global.Logger.Error("完成任务异步操作-----更新用户profile失败", zap.Error(updateProfileErr)) + } +} + +// todo 完成任务后异步执行 处理徽章 +func (s *MyPlantService) handleCompleteTaskThenHandleBadge(ctx context.Context, taskId, userId string) { + //1.查询任务 + var task plant.CareTask + if asyncErr := global.DB.Where("id = ?", taskId).First(&task).Error; asyncErr != nil { + global.Logger.Error("完成任务异步操作-----查询任务失败", zap.Error(asyncErr)) + } + //2.查询徽章 + var badges []plant.BadgeConfig + if asyncErr := global.DB.Where("target_action = ?", task.TargetAction).Order("tier ASC").Find(&badges).Error; asyncErr != nil { + global.Logger.Error("完成任务异步操作-----查询徽章失败", zap.Error(asyncErr)) + } + //3.用户的徽章 + var userBadges []plant.UserBadge + if asyncErr := global.DB.Where("user_id = ?", userId).Find(&userBadges).Error; asyncErr != nil { + global.Logger.Error("完成任务异步操作-----查询用户徽章失败", zap.Error(asyncErr)) + } + //4.用户个人资料 + +} diff --git a/service/plant/ocr.go b/service/plant/ocr.go index bc9d014..afaa5e8 100644 --- a/service/plant/ocr.go +++ b/service/plant/ocr.go @@ -114,6 +114,11 @@ func (s *OcrService) MyClassifyLog(req request.PageInfo, id string) (list interf } +// DeleteClassifyLog 删除植物识别记录 +func (s *OcrService) DeleteClassifyLog(req request.IdsReq, userId string) error { + return global.DB.Where("id in ? and user_id = ?", req.Ids, userId).Unscoped().Delete(&plant.ClassifyRecord{}).Error +} + func getAccessToken() string { rpcUrl := "https://aip.baidubce.com/oauth/2.0/token" postData := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s", global.Config.BaiduImgClassify.ApiKey, global.Config.BaiduImgClassify.SecretKey) diff --git a/service/plant/post.go b/service/plant/post.go index 45669c9..a77e9bd 100644 --- a/service/plant/post.go +++ b/service/plant/post.go @@ -62,11 +62,6 @@ func (s *PostService) PostPage(req plantReq.PostPage, userId string) (list inter Preload("Publisher", func(db *gorm.DB) *gorm.DB { return db.Preload("Avatar") }). - Preload("LikeList", func(db *gorm.DB) *gorm.DB { - return db.Preload("Liker", func(db *gorm.DB) *gorm.DB { - return db.Preload("Avatar") - }) - }). Preload("CommentList", func(db *gorm.DB) *gorm.DB { return db.Preload("Commentator", func(db *gorm.DB) *gorm.DB { return db.Preload("Avatar") @@ -96,11 +91,21 @@ func (s *PostService) PostPage(req plantReq.PostPage, userId string) (list inter if err != nil { return } + //批量查询当前用户的收藏 + var stars []*plant.UserStar + err = global.DB.Where("user_id = ? and post_id in ?", userId, postIds).Find(&stars).Error + if err != nil { + return + } // 构建id映射 likesMap := make(map[string]bool) for _, v := range postLikeList { likesMap[v.PostId] = true } + starsMap := make(map[string]bool) + for _, v := range stars { + starsMap[v.PostId] = true + } // 是否点赞 for i := range posts { if likesMap[posts[i].Id] { @@ -109,6 +114,11 @@ func (s *PostService) PostPage(req plantReq.PostPage, userId string) (list inter posts[i].HasLiked = 0 } + if starsMap[posts[i].Id] { + posts[i].HasStar = 1 + } else { + posts[i].HasStar = 0 + } } return posts, total, err @@ -169,7 +179,6 @@ func (s *PostService) MyPost(req plantReq.PostPage, userId string) (list interfa } else { posts[i].HasLiked = 0 } - } return posts, total, err @@ -210,7 +219,9 @@ func (s *PostService) LikePost(userId, postId, class string) error { if err != nil { return err } - err = tx.Model(&post).Update("like_count", post.LikeCount-1).Error + err = tx.Model(&post). + Where("like_count > 0"). // 只有大于 0 才会执行减法 + Update("like_count", post.LikeCount-1).Error if err != nil { return err } @@ -244,3 +255,89 @@ func (s *PostService) CommentPost(req plantReq.CreateComment, userId string) err return tx.Create(&comment).Error }) } + +// DeletePost 删除帖子 +func (s *PostService) DeletePost(ids []string) error { + return global.DB.Transaction(func(tx *gorm.DB) error { + var imgIds []string + tx.Table("sundynix_post_oss").Where("post_id IN ?", ids).Pluck("oss_id", &imgIds) + + // 2. 清理中间表记录 (解开多对多关系) + // 使用 Exec 直接操作中间表比循环 Clear 快得多 + if err := tx.Exec("DELETE FROM sundynix_post_oss WHERE post_id IN ?", ids).Error; err != nil { + return err + } + + // 3. 物理删除图片记录本身 + if len(imgIds) > 0 { + if err := tx.Unscoped().Where("id IN ?", imgIds).Delete(&system.Oss{}).Error; err != nil { + return err + } + } + // 4. 批量删除点赞 (PostLike) + if err := tx.Unscoped().Where("post_id IN ?", ids).Delete(&plant.PostLike{}).Error; err != nil { + return err + } + // 5. 批量删除评论 (PostComment) + if err := tx.Unscoped().Where("post_id IN ?", ids).Delete(&plant.PostComment{}).Error; err != nil { + return err + } + + // 6. 最后删除主表 Post + if err := tx.Unscoped().Where("id IN ?", ids).Delete(&plant.Post{}).Error; err != nil { + return err + } + + return nil + }) +} + +// StarPost 收藏帖子 +func (s *PostService) StarPost(userId string, postId string, class string) error { + if class == "1" { + return global.DB.Transaction(func(tx *gorm.DB) error { + var post plant.Post + err := tx.Where("id = ?", postId).First(&post).Error + if err != nil { + return err + } + //1.更新点赞数 + err = tx.Model(&post).Update("star_count", post.LikeCount+1).Error + if err != nil { + return err + } + //2.添加到我的收藏 + star := plant.UserStar{ + UserId: userId, + Type: 2, + PostId: postId, + } + return tx.Create(&star).Error + }) + } else if class == "2" { + return global.DB.Transaction(func(tx *gorm.DB) error { + var star plant.UserStar + err := tx.Where("post_id = ? and user_id = ?", postId, userId).First(&star).Error + if err != nil { + return err + } + //1.更新收藏数 + var post plant.Post + err = tx.Where("id = ?", postId).First(&post).Error + if err != nil { + return err + } + err = tx.Model(&post). + Where("star_count > ?", 0). // 只有大于 0 才会执行减法 + Update("star_count", gorm.Expr("star_count - 1")). + Error + + if err != nil { + return err + } + //2.删除收藏 + return tx.Unscoped().Delete(&star).Error + }) + } + return nil +} diff --git a/service/plant/user_profile.go b/service/plant/user_profile.go index 041a9f9..fc0a681 100644 --- a/service/plant/user_profile.go +++ b/service/plant/user_profile.go @@ -45,3 +45,29 @@ func (s *UserProfileService) ProfileDetail(userId string) (plant.UserProfile, er } return res, nil } + +// MyStars 我的收藏 +func (s *UserProfileService) MyStars(req plantReq.StarsPageReq, userId string) (list interface{}, total int64, err error) { + limit := req.PageSize + offset := req.PageSize * (req.Current - 1) + db := global.DB.Model(&plant.UserStar{}).Preload("Wiki", func(db *gorm.DB) *gorm.DB { + return db.Preload("ImgList", func(db *gorm.DB) *gorm.DB { + return db.Order("created_at desc") + }) + }).Preload("Post", func(db *gorm.DB) *gorm.DB { + return db.Preload("ImgList", func(db *gorm.DB) *gorm.DB { + return db.Order("created_at desc") + }) + }) + var stars []*plant.UserStar + db = db.Where("user_id = ?", userId) + if req.Class != 0 { + db = db.Where("type = ?", req.Class) //1.百科 //2.社交动态 + } + err = db.Count(&total).Error + if err != nil { + return + } + err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&stars).Error + return stars, total, err +} diff --git a/service/plant/wiki.go b/service/plant/wiki.go index 6fac675..f566c4e 100644 --- a/service/plant/wiki.go +++ b/service/plant/wiki.go @@ -149,7 +149,7 @@ func (s *WikiService) UpdateWiki(req plantReq.UpdateWiki) error { } // WikiPage 分页 -func (s *WikiService) WikiPage(req plantReq.WikiPage) (list interface{}, total int64, err error) { +func (s *WikiService) WikiPage(req plantReq.WikiPage, userId string) (list interface{}, total int64, err error) { limit := req.PageSize offset := req.PageSize * (req.Current - 1) db := global.DB.Model(&plant.Wiki{}).Preload("ImgList", func(db *gorm.DB) *gorm.DB { @@ -173,11 +173,35 @@ func (s *WikiService) WikiPage(req plantReq.WikiPage) (list interface{}, total i return } err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&wikis).Error + + // 优化 N+1 查询 + var wikiIds []string + for _, v := range wikis { + wikiIds = append(wikiIds, v.Id) + } + //批量查询当前用户的收藏 + var stars []*plant.UserStar + err = global.DB.Where("user_id = ? and wiki_id in ?", userId, wikiIds).Find(&stars).Error + if err != nil { + return + } + starsMap := make(map[string]bool) + for _, v := range stars { + starsMap[v.WikiId] = true + } + // 是否收藏 + for i := range wikis { + if starsMap[wikis[i].Id] { + wikis[i].HasStar = 1 + } else { + wikis[i].HasStar = 0 + } + } return wikis, total, err } // Detail 详情 -func (s *WikiService) Detail(id string) (w plant.Wiki, err error) { +func (s *WikiService) Detail(id, userId string) (w plant.Wiki, err error) { var wiki plant.Wiki err = global.DB.Where("id = ?", id). Preload("Classes", func(db *gorm.DB) *gorm.DB { @@ -193,5 +217,44 @@ func (s *WikiService) Detail(id string) (w plant.Wiki, err error) { if err != nil { return } + var stars []plant.UserStar + err = global.DB.Where("user_id = ? and wiki_id = ?", userId, id).Find(&stars).Error + if err != nil { + return + } + if len(stars) > 0 { + wiki.HasStar = 1 + } return wiki, nil } + +// StarWiki 收藏 +func (s *WikiService) StarWiki(userId, wikiId, class string) error { + if class == "1" { + return global.DB.Transaction(func(tx *gorm.DB) error { + var wiki plant.Wiki + err := tx.Where("id = ?", wikiId).First(&wiki).Error + if err != nil { + return err + } + //2.添加到我的收藏 + star := plant.UserStar{ + UserId: userId, + Type: 1, + WikiId: wikiId, + } + return tx.Create(&star).Error + }) + } else if class == "2" { + return global.DB.Transaction(func(tx *gorm.DB) error { + var star plant.UserStar + err := tx.Where("wiki_id = ? and user_id = ?", wikiId, userId).First(&star).Error + if err != nil { + return err + } + //2.删除收藏 + return tx.Unscoped().Delete(&star).Error + }) + } + return nil +} diff --git a/service/system/sys_jwt.go b/service/system/sys_jwt.go index 572340a..9394fb3 100644 --- a/service/system/sys_jwt.go +++ b/service/system/sys_jwt.go @@ -3,7 +3,7 @@ package system import ( "context" "sundynix-go/global" - "sundynix-go/utils" + "sundynix-go/utils/timer" ) type JwtService struct{} @@ -12,7 +12,7 @@ var JwtServiceApp = new(JwtService) // 登出,禁用jwt func (s *JwtService) PutBlacklist(userId string, token string) (err error) { - expire, err := utils.ParseDuration(global.Config.JWT.ExpiresTime) + expire, err := timer.ParseDuration(global.Config.JWT.ExpiresTime) if err != nil { return err } diff --git a/task/care_message_send_task.go b/task/care_message_send_task.go index 73fa23f..5cbb375 100644 --- a/task/care_message_send_task.go +++ b/task/care_message_send_task.go @@ -104,9 +104,10 @@ func SendCareMsg() error { } if smr.Errcode != 0 { + global.Logger.Error("订阅消息发送失败!" + userId + "open-id" + user.MiniOpenId) return fmt.Errorf("微信服务器返回错误: errcode=%d, errmsg=%s", smr.Errcode, smr.Errmsg) } - global.Logger.Info("订阅消息发送成功!") + global.Logger.Info("订阅消息发送成功!" + userId + "open-id" + user.MiniOpenId) } } } diff --git a/utils/async/async_task.go b/utils/async/async_task.go index 82323da..dab2c22 100644 --- a/utils/async/async_task.go +++ b/utils/async/async_task.go @@ -2,37 +2,63 @@ package async import ( "context" + "runtime/debug" "sundynix-go/global" + "sync" + "time" "go.uber.org/zap" ) -// AsyncTask 定义了异步任务的函数原型 type AsyncTask func(ctx context.Context) -// TaskRunner 任务收集器 +type namedTask struct { + name string + fn AsyncTask +} + type TaskRunner struct { - tasks []AsyncTask + mu sync.Mutex + tasks []namedTask } -// Add 添加一个任务到队列中 -func (tr *TaskRunner) Add(task AsyncTask) { - tr.tasks = append(tr.tasks, task) +// Add 添加任务 +func (tr *TaskRunner) Add(name string, task AsyncTask) { + if task == nil { + return + } + tr.mu.Lock() + defer tr.mu.Unlock() + tr.tasks = append(tr.tasks, namedTask{name: name, fn: task}) } -// RunAll 安全地启动所有任务 +// RunAll 安全执行 func (tr *TaskRunner) RunAll() { - for _, task := range tr.tasks { - t := task // 避免闭包变量捕获问题 + tr.mu.Lock() + todoTasks := tr.tasks + tr.tasks = nil + tr.mu.Unlock() + + for _, task := range todoTasks { + t := task go func() { defer func() { if r := recover(); r != nil { - global.Logger.Info("[AsyncError] 任务执行崩溃", zap.Any("recover", r)) + // 使用全局 Zap 记录结构化日志 + // 这里的 global.Logger 替换为你实际的全局变量名 + global.Logger.Error("异步任务异常崩溃", + zap.String("task_name", t.name), + zap.Any("panic_info", r), + zap.String("stack", string(debug.Stack())), + ) } }() - // 异步任务通常使用 Background,避免受主请求超时影响 - // 也可以自定义一个更长的超时 context - t(context.Background()) + + // 异步任务执行,设置独立的超时控制 + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + t.fn(ctx) }() } } diff --git a/utils/auth/jwt.go b/utils/auth/jwt.go index 2ae70b0..5e76fb7 100644 --- a/utils/auth/jwt.go +++ b/utils/auth/jwt.go @@ -4,7 +4,7 @@ import ( "errors" "sundynix-go/global" "sundynix-go/model/system/request" - "sundynix-go/utils" + "sundynix-go/utils/timer" "time" "github.com/golang-jwt/jwt/v5" @@ -32,8 +32,8 @@ func NewJWT() *JWT { // CreateClaims 创建Claims func (j *JWT) CreateClaims(baseClaims request.BaseClaims) request.CustomClaims { - bf, _ := utils.ParseDuration(global.Config.JWT.BufferTime) - ep, _ := utils.ParseDuration(global.Config.JWT.ExpiresTime) + bf, _ := timer.ParseDuration(global.Config.JWT.BufferTime) + ep, _ := timer.ParseDuration(global.Config.JWT.ExpiresTime) claims := request.CustomClaims{ BaseClaims: baseClaims, BufferTime: int64(bf / time.Second), // 缓冲时间1天 缓冲时间内会获得新的token刷新令牌 此时一个用户会存在两个有效令牌 但是前端只留一个 另一个会丢失 diff --git a/utils/human_duration.go b/utils/timer/human_duration.go similarity index 97% rename from utils/human_duration.go rename to utils/timer/human_duration.go index abdb3b1..fd9a59b 100644 --- a/utils/human_duration.go +++ b/utils/timer/human_duration.go @@ -1,4 +1,4 @@ -package utils +package timer import ( "strconv" diff --git a/utils/wechat/access_token.go b/utils/wechat/access_token.go index 6400903..e3f873c 100644 --- a/utils/wechat/access_token.go +++ b/utils/wechat/access_token.go @@ -15,7 +15,7 @@ import ( // GetMiniAccessToken 获取小程序的access_token func GetMiniAccessToken() string { - ak, err := global.Redis.Get(context.Background(), "mini_access_token").Result() + ak, err := global.Redis.Get(context.Background(), "zeeq_mini_access_token").Result() if errors.Is(err, redis.Nil) { // 从微信服务器获取 //重新从微信服务器获取 @@ -44,7 +44,7 @@ func GetMiniAccessToken() string { } ak = data["access_token"].(string) ex := data["expires_in"].(float64) - global.Redis.Set(context.Background(), "mini_access_token", ak, time.Duration(ex)*time.Second) //秒 + global.Redis.Set(context.Background(), "zeeq_mini_access_token", ak, time.Duration(ex)*time.Second) //秒 } else if err != nil { log.Fatalf("Error getting access token from Redis: %s", err) }