1.添加七牛云&AI的是否启用信息
api/site_api/enter.go
Go
package site_api
import (
"blog_server/common/res"
"blog_server/conf"
"blog_server/core"
"blog_server/global"
"blog_server/middlware"
"fmt"
"github.com/PuerkitoBio/goquery"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"os"
)
type SiteApi struct {
}
type SiteInfoRequest struct {
Name string `uri:"name" binding:"required"`
}
type QiNiu struct {
Enable bool `json:"enable"`
}
type AI struct {
Enable bool `json:"enable"`
}
type SiteInfoResponse struct {
QiNiu QiNiu `json:"qi_niu"`
AI AI `json:"ai"`
conf.Site
}
func (SiteApi) SiteInfoView(c *gin.Context) {
var cr SiteInfoRequest
if err := c.ShouldBindUri(&cr); err != nil {
res.FailWithError(err, c)
return
}
if cr.Name == "site" {
global.Config.Site.About.Version = global.Version
res.OkWithData(SiteInfoResponse{
Site: global.Config.Site,
QiNiu: QiNiu{
Enable: global.Config.Qiniu.Enable,
},
AI: AI{
Enable: global.Config.AI.Enable,
},
}, c)
return
}
//判断是否是管理员
middlware.AdminMiddleware(c)
_, ok := c.Get("claims")
if !ok {
return
}
var data any
switch cr.Name {
case "email":
rep := global.Config.Email
rep.AuthCode = "******"
data = rep
case "qq":
rep := global.Config.QQ
rep.AppId = "******"
data = rep
case "qiniu":
rep := global.Config.Qiniu
rep.SecretKey = "******"
data = rep
case "ai":
rep := global.Config.AI
rep.SecretKey = "******"
data = rep
default:
res.FailWithMsg("不存在的配置", c)
return
}
res.OkWithData(data, c)
return
}
func (SiteApi) SiteUpdateView(c *gin.Context) {
var cr SiteInfoRequest
err := c.ShouldBindUri(&cr)
if err != nil {
res.FailWithError(err, c)
return
}
var rep any
switch cr.Name {
case "site":
var data conf.Site
err = c.ShouldBindJSON(&data)
rep = data
case "email":
var data conf.Email
err = c.ShouldBindJSON(&data)
rep = data
case "qq":
var data conf.QQ
err = c.ShouldBindJSON(&data)
rep = data
case "qiniu":
var data conf.QiNiu
err = c.ShouldBindJSON(&data)
rep = data
case "ai":
var data conf.AI
err = c.ShouldBindJSON(&data)
rep = data
default:
res.FailWithMsg("不存在的配置", c)
return
}
if err != nil {
res.FailWithError(err, c)
return
}
switch s := rep.(type) {
case conf.Site:
//判断站点信息更新前端文件部分
err = UploadSite(s) //暂时没有处理函数,先设置为空,之后补充
if err != nil {
res.FailWithError(err, c)
return
}
global.Config.Site = s
case conf.Email:
if s.AuthCode == "******" {
s.AuthCode = global.Config.Email.AuthCode
}
global.Config.Email = s
case conf.QQ:
if s.AppId == "******" {
s.AppId = global.Config.QQ.AppId
}
global.Config.QQ = s
case conf.QiNiu:
if s.SecretKey == "******" {
s.SecretKey = global.Config.Qiniu.SecretKey
}
global.Config.Qiniu = s
case conf.AI:
if s.SecretKey == "******" {
s.SecretKey = global.Config.Qiniu.SecretKey
}
global.Config.AI = s
}
core.SetConf()
res.FailWithMsg("更新站成功", c)
return
}
func (SiteApi) SiteInfoQQView(c *gin.Context) {
res.OkWithData(global.Config.QQ.Url(), c)
}
func UploadSite(site conf.Site) error {
if site.Project.Icon == "" && site.Project.Title == "" && site.Project.WebPath == "" && site.Seo.KeyWords == "" && site.Seo.Description == "" {
return nil
}
if site.Project.WebPath == "" {
return errors.New("请配置前端地址")
}
file, err := os.Open(site.Project.WebPath)
if err != nil {
return errors.New(fmt.Sprintf("%s 文件不存在", site.Project.WebPath))
}
doc, err := goquery.NewDocumentFromReader(file)
if err != nil {
logrus.Errorf("goquery 解析失败 %s", err)
return errors.New("goquery 解析失败")
}
if site.Project.Title != "" {
doc.Find("title").SetText(site.Project.Title)
}
if site.Project.Icon != "" {
selection := doc.Find("link[rel='icon']").Length()
if selection == 0 {
//没有-》创建
doc.Find("head").AppendHtml(fmt.Sprintf("<link rel=\"icon\" href=\"%s\">", site.Project.Icon))
} else {
//有修改
doc.Find("link[rel='icon']").SetAttr("href", site.Project.Icon)
}
}
if site.Seo.KeyWords != "" {
selection := doc.Find("meta[name='keywords']").Length()
if selection == 0 {
//没有-》创建
doc.Find("head").AppendHtml(fmt.Sprintf(" <meta name=\"keywords\" content=\"%s\">", site.Seo.KeyWords))
} else {
//有修改
doc.Find("meta[name='keywords']").SetAttr("content", site.Seo.KeyWords)
}
}
if site.Seo.Description != "" {
selection := doc.Find("meta[name='description']").Length()
if selection == 0 {
//没有-》创建
doc.Find("head").AppendHtml(fmt.Sprintf(" <meta name=\"description\" content=\"%s\">", site.Seo.Description))
} else {
//有修改
doc.Find("meta[name='description']").SetAttr("content", site.Seo.Description)
}
}
html, err := doc.Html()
if err != nil {
logrus.Errorf("生成html失败:%s", err)
return errors.New("生成html失败")
}
err = os.WriteFile(site.Project.WebPath, []byte(html), 0666)
if err != nil {
logrus.Errorf("写入html失败:%s", err)
return errors.New("写入html失败")
}
return nil
}
2.用户列表接口【api】
修改用户模型
Go
type UserModel struct {
Model
Username string `gorm:"size:32" json:"username"`
Nickname string `gorm:"32" json:"nickname"`
Avatar string `gorm:"size:255" json:"avatar"`
Abstract string `gorm:"size:255" json:"abstract"`
RegisterSource int8 `gorm:"default:1" json:"register_source"` //注册来源
Password string `gorm:"size:64" json:"-"`
Email string `gorm:"size:255" json:"email"`
OpenID string `gorm:"size:64" json:"open_id"` //第三方登录的唯一ID
Role enum.RoleType `gorm:"default:1" json:"role"` //1管理员 2普通用户 3游客
UserConfModel *UserConfModel `gorm:"foreignKey:UserID" json:"-"` //反向映射
IP string `json:"ip"`
Addr string `json:"addr"`
ArticleList []ArticleModel `gorm:"foreignKey:UserID" json:"-"`
LoginList []UserLoginModel `gorm:"foreignKey:UserID" json:"-"`
}
api/user_api/user_list.go
Go
package user_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"github.com/gin-gonic/gin"
"time"
)
type UserListRequest struct {
common.PageInfo
UserID uint `form:"user_id"`
}
type UserListResponse struct {
ID uint `json:"id"`
Nickname string `json:"nickname"`
Username string `json:"username"`
Avatar string `json:"avatar"`
IP string `json:"ip"`
Addr string `json:"addr"`
ArticleCount int `json:"article_count"` //发文数
IndexCount int `json:"index_count"` //主页访问数
CreatedAt time.Time `json:"created_at"` //注册时间
LastLoginDate time.Time `json:"last_login_date"` //最后登录时间
Role enum.RoleType `json:"role"`
}
func (UserApi) UserListView(c *gin.Context) {
cr := middlware.GetBind[UserListRequest](c)
_list, count, _ := common.ListQuery(models.UserModel{}, common.Options{
Likes: []string{"nickname", "username"},
PreLoads: []string{"ArticleList", "LoginList"},
PageInfo: cr.PageInfo,
})
var list = make([]UserListResponse, 0)
for _, model := range _list {
item := UserListResponse{
ID: model.ID,
Nickname: model.Nickname,
Username: model.Username,
Avatar: model.Avatar,
IP: model.IP,
Addr: model.Addr,
ArticleCount: len(model.ArticleList),
CreatedAt: model.CreatedAt,
Role: model.Role,
}
if len(model.LoginList) > 0 {
item.LastLoginDate = model.LoginList[len(model.LoginList)-1].CreatedAt
}
list = append(list, item)
}
res.OkWithList(list, count, c)
}
r.GET("user", middlware.AdminMiddleware, middlware.BindQueryMiddleware[user_api.UserListRequest], app.UserListView)
3.文章置顶接口【api】
api/user_api/user_article_top.go(记得将所有models的主键加上)
Go
package user_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
)
//用户置顶文章、管理员置顶文章接口 少接口
//用户只能置顶自己已发布的一篇文章
//管理员可以置顶记发布的文章
type UserArticleTopRequest struct {
ArticleID uint `json:"article_id" binding:"required"`
Type int8 `json:"type" binding:"required,oneof=1 2"`
}
func (UserApi) UserArticleTopView(c *gin.Context) {
cr := middlware.GetBind[UserArticleTopRequest](c)
var model models.ArticleModel
err := global.DB.Take(&model, cr.ArticleID).Error
if err != nil {
res.FailWithMsg("文章不存在", c)
return
}
claims := jwts.GetClaims(c)
switch cr.Type {
case 1:
//用户置顶文章
//验证文章是不是自己的,且是自己发布的
if model.UserID != claims.UserId {
res.FailWithMsg("用户只能置顶自己的文章", c)
return
}
if model.Status != enum.ArticleStatusPublishes {
res.FailWithMsg("用户只能置顶已发布的文章", c)
return
}
//判断之前自己有没有制定过
var userTopArticleList []models.UserTopArticleModel
global.DB.Find(&userTopArticleList, "user_id =?", claims.UserId)
//查不到 自己从来没有置顶过文章
if len(userTopArticleList) == 0 {
//置顶
global.DB.Create(&models.UserTopArticleModel{UserID: claims.UserId, ArticleID: cr.ArticleID})
res.OkWithMsg("置顶文章成功", c)
return
}
//查到=1
if len(userTopArticleList) == 1 {
uta := userTopArticleList[0]
if uta.ArticleID != cr.ArticleID {
res.FailWithMsg("普通用户只能置顶一篇文章", c)
return
}
}
// 是自己的这篇文章 取消置顶
uta := userTopArticleList[0]
global.DB.Delete(&uta)
res.OkWithMsg("取消置顶文章成功", c)
case 2:
//管理员置顶文章
if claims.RoleId != enum.AdminRole {
res.FailWithMsg("权限不足", c)
return
}
if model.Status != enum.ArticleStatusPublishes {
res.FailWithMsg("管理员只能置顶已发布的文章", c)
return
}
var userTopArticle []models.UserTopArticleModel
err = global.DB.Take(&userTopArticle, "user_id =? and article_id =?", claims.UserId, cr.ArticleID).Error
if err != nil {
global.DB.Create(&models.UserTopArticleModel{UserID: claims.UserId, ArticleID: cr.ArticleID})
res.OkWithMsg("置顶文章成功", c)
return
}
global.DB.Delete(&userTopArticle)
res.OkWithMsg("取消置顶文章成功", c)
}
}
r.POST("user/article/top", middlware.AuthMiddleware, middlware.BindMiddleware[user_api.UserArticleTopRequest], app.UserArticleTopView)
4.文章详情中添加显示分类title
api/article_api/article_detail.go
Go
package article_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/services/redis_service/redis_article"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
)
type ArticleDetailResponse struct {
models.ArticleModel
Username string `json:"username"`
UserAvatar string `json:"user_avatar"`
Nickname string `json:"nickname"`
CategoryTitle *string `json:"category_title"`
}
func (ArticleApi) ArticleDetailView(c *gin.Context) {
cr := middlware.GetBind[models.IdRequest](c)
var article models.ArticleModel
err := global.DB.Preload("UserModel").Preload("CategoryModel").Take(&article, cr.ID).Error
if err != nil {
res.FailWithMsg("文章不存在", c)
return
}
claims, err := jwts.ParseTokenByGin(c)
if err != nil {
if article.Status != enum.ArticleStatusPublishes {
res.FailWithMsg("文章不存在", c)
return
}
}
switch claims.RoleId {
case enum.UserRole:
if claims.UserId != article.UserID {
//登录者看的不是自己的
if article.Status != enum.ArticleStatusPublishes {
res.FailWithMsg("文章不存在", c)
return
}
}
}
//从缓存中获取浏览量和点赞数
collectCount := redis_article.GetCacheCollect(article.ID)
lookCount := redis_article.GetCacheLook(article.ID)
diggCount := redis_article.GetCacheDigg(article.ID)
commentCount := redis_article.GetCacheComment(article.ID) //获取文章评论
article.DiggCount = article.DiggCount + diggCount
article.LookCount = article.LookCount + lookCount
article.CollectCount = article.CollectCount + collectCount
article.CommentCount = article.CommentCount + commentCount //评论数统计
data := ArticleDetailResponse{
ArticleModel: article,
Username: article.UserModel.Username,
UserAvatar: article.UserModel.Avatar,
Nickname: article.UserModel.Nickname,
}
if article.CategoryModel != nil {
data.CategoryTitle = &article.CategoryModel.Title
}
res.OkWithData(data, c)
}
5.用户注销【api】
api/user_api/user_logout.go
Go
package user_api
import (
"blog_server/common/res"
redis_jwt "blog_server/services/redis_service/redis-jwt"
"github.com/gin-gonic/gin"
)
func (UserApi) LogoutView(c *gin.Context) {
token := c.Request.Header.Get("Authorization")
redis_jwt.TokenBlack(token, redis_jwt.UserBlackType)
res.OkWithMsg("注销成功", c)
}
r.DELETE("user/logout", middlware.AuthMiddleware, app.LogoutView)
6.用户详情添加展示角色
api/user_api/user_detail.go
Go
package user_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/models"
"blog_server/models/enum"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
"math"
"time"
)
type UserDetailResponse struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"created_at"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Abstract string `json:"abstract"`
RegisterSource int8 `json:"register_source"` //注册来源
CodeAge int `json:"code_age"` //码龄
Role enum.RoleType `json:"role"`
models.UserConfModel
}
func (UserApi) UserDetailView(c *gin.Context) {
claims := jwts.GetClaims(c) //使用时在路由时必须加上权限中间件才能正常往下走,否则会报错
var user models.UserModel
err := global.DB.Preload("UserConfModel").Take(&user, claims.UserId).Error
if err != nil {
res.FailWithMsg("用户不存在", c)
return
}
sub := time.Now().Sub(user.CreatedAt)
codeAge := int(math.Ceil(sub.Hours() / 24 / 265))
var data = UserDetailResponse{
ID: user.ID,
CreatedAt: user.CreatedAt,
Username: user.Username,
Nickname: user.Nickname,
Role: user.Role,
Avatar: user.Avatar,
Abstract: user.Abstract,
RegisterSource: user.RegisterSource,
CodeAge: codeAge,
}
if user.UserConfModel != nil {
data.UserConfModel = *user.UserConfModel
}
res.OkWithData(data, c)
}
7.优化mps(解决taglist存放的是字符串的格式)
utils/mps/enter.go
Go
package mps
import (
"encoding/json"
"reflect"
)
func StructToMap(data any, t string) (mp map[string]any) {
mp = make(map[string]any)
v := reflect.ValueOf(data)
for i := 0; i < v.NumField(); i++ {
val := v.Field(i)
tag := v.Type().Field(i).Tag.Get(t)
//过滤掉tag为空和"-"的
if tag == "" || tag == "-" {
continue
}
if val.IsNil() {
continue
}
if val.Kind() == reflect.Ptr {
v1 := val.Elem().Interface()
if val.Elem().Kind() == reflect.Slice {
//如果是切片,使用json解析
byteData, _ := json.Marshal(v1)
mp[tag] = string(byteData)
} else {
mp[tag] = v1
}
continue
}
mp[tag] = val.Interface()
}
return
}
8.用户详情添加显示是否密码登录和邮箱显示
api/user_api/user_detail.go
Go
package user_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/models"
"blog_server/models/enum"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
"math"
"time"
)
type UserDetailResponse struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"created_at"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Abstract string `json:"abstract"`
RegisterSource int8 `json:"register_source"` //注册来源
CodeAge int `json:"code_age"` //码龄
Role enum.RoleType `json:"role"`
models.UserConfModel
Email string `json:"email"`
UsePassword bool `json:"usePassword"`
}
func (UserApi) UserDetailView(c *gin.Context) {
claims := jwts.GetClaims(c) //使用时在路由时必须加上权限中间件才能正常往下走,否则会报错
var user models.UserModel
err := global.DB.Preload("UserConfModel").Take(&user, claims.UserId).Error
if err != nil {
res.FailWithMsg("用户不存在", c)
return
}
sub := time.Now().Sub(user.CreatedAt)
codeAge := int(math.Ceil(sub.Hours() / 24 / 265))
var data = UserDetailResponse{
ID: user.ID,
CreatedAt: user.CreatedAt,
Username: user.Username,
Nickname: user.Nickname,
Role: user.Role,
Avatar: user.Avatar,
Abstract: user.Abstract,
RegisterSource: user.RegisterSource,
CodeAge: codeAge,
Email: user.Email,
}
if user.Password != "" {
data.UsePassword = true
}
if user.UserConfModel != nil {
data.UserConfModel = *user.UserConfModel
}
res.OkWithData(data, c)
}
9.扫描json的字符串的方法处理空字符串
models/ctype/list.go
Go
package ctype
import (
"database/sql/driver"
"strings"
)
type List []string
func (j *List) Scan(value interface{}) error {
val, ok := value.([]uint8)
if ok {
if string(val) == "" {
//如果是空的字符串就将这个值赋值为null,
*j = []string{}
return nil
}
*j = strings.Split(string(val), ",")
}
return nil
}
func (j List) Value() (driver.Value, error) {
return strings.Join(j, ","), nil
}
10.评论列表添加展示我与好友的关系
api/comment_api/comment_list.go
Go
package comment_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/models/enum/relationships_enum_type"
"blog_server/services/focus_service"
"blog_server/services/redis_service/redis_comment"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
"time"
)
type CommentListRequest struct {
common.PageInfo
ArticleID uint `form:"article_id"`
UserID uint `form:"user_id"`
Type int8 `form:"type" binding:"required"` //1-查找我发文章的评论 2-查·我发布的评论 3-管理员查看所有评论
}
type commentListResponse struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"created_at"`
Content string `json:"content"`
UserID uint `json:"user_id"`
UserNickname string `json:"user_nickname"`
UserAvatar string `json:"user_avatar"`
ArticleId uint `json:"article_id"`
ArticleTitle string `json:"article_title"`
ArticleCover string `json:"article_cover"`
DiggCount uint `json:"digg_count"`
Relation relationships_enum_type.Relation `json:"relation,omitempty"`
IsMe bool `json:"is_me"`
}
func (CommentApi) CommentListView(c *gin.Context) {
cr := middlware.GetBind[CommentListRequest](c)
query := global.DB.Where("")
claims := jwts.GetClaims(c)
switch cr.Type {
case 1: //1-查找我发文章的评论
var articleIDList []uint
global.DB.Model(models.ArticleModel{}).Where("user_id = ? and status = ?", claims.UserId, enum.ArticleStatusPublishes).Select("id").Scan(&articleIDList)
query.Where("article_id in ?", articleIDList)
cr.UserID = 0
case 2: //2-查·我发布的评论
cr.UserID = claims.UserId
case 3: //3-管理员查看所有评论
}
_list, count, _ := common.ListQuery(models.CommentModel{
ArticleID: cr.ArticleID,
UserID: cr.UserID,
}, common.Options{
PageInfo: cr.PageInfo,
Likes: []string{"content"},
PreLoads: []string{"UserModel", "ArticleModel"},
Where: query,
})
//查找我和查询ID的关系
var RelationMap = map[uint]relationships_enum_type.Relation{}
if cr.Type == 1 {
var userIDList []uint
for _, model := range _list {
userIDList = append(userIDList, model.UserID)
}
RelationMap = focus_service.CalculatingPatchFriendRelationships(claims.UserId, userIDList)
}
var list = make([]commentListResponse, 0)
for _, model := range _list {
list = append(list, commentListResponse{
ID: model.ID,
CreatedAt: model.CreatedAt,
Content: model.Content,
UserID: model.UserID,
UserNickname: model.UserModel.Nickname,
UserAvatar: model.UserModel.Avatar,
ArticleId: model.ArticleID,
ArticleTitle: model.ArticleModel.Title,
ArticleCover: model.ArticleModel.Cover,
DiggCount: model.DiggCount + uint(redis_comment.GetCacheDigg(model.ID)),
Relation: RelationMap[model.UserID],
IsMe: model.UserID == claims.UserId,
})
}
res.OkWithList(list, count, c)
}
r.GET("user/base", app.UserBaseInfoView)
11.用户基本信息接口【api】
user_model.go 添加计算码龄
Go
func (u *UserModel) CodeAge() int {
sub := time.Now().Sub(u.CreatedAt)
return int(math.Ceil(sub.Hours() / 24 / 365))
}
services/cron_service/sync_user.go
Go
package cron_service
import (
"blog_server/global"
"blog_server/models"
"blog_server/services/redis_service/redis_user"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
func SyncUser() {
lookMap := redis_user.GetAllCacheLook()
var list []models.UserConfModel
for _, model := range list {
look := lookMap[model.UserID]
if look == 0 {
continue
}
err := global.DB.Model(&model).Updates(map[string]any{
"look_count": gorm.Expr("look_count + ?", look),
}).Error
if err != nil {
logrus.Errorf("更新失败:%s", err)
continue
}
logrus.Infof("%s redis-mysql数据更新成功", model.UserID)
}
//走完之后清空
redis_user.Clear()
}
services/cron_service/enter.go
Go
package cron_service
import (
"github.com/robfig/cron/v3"
"time"
)
func Cron() {
//crontab := cron.New() 默认从分开始进行时间调度
timezone, _ := time.LoadLocation("Asia/Shanghai")
crontab := cron.New(cron.WithSeconds(), cron.WithLocation(timezone))
crontab.AddFunc("0 0 2 * * *", SyncArticle) //每天两点同步文章数据
crontab.AddFunc("0 30 2 * * *", SyncUser) //每天两点30同步文章数据
crontab.AddFunc("0 0 3 * * *", SyncComment) //每天三点同步文章数据
crontab.Start()
}
services/redis_service/redis_user/enter.go
Go
package redis_user
import (
"blog_server/global"
"github.com/sirupsen/logrus"
"strconv"
)
type userCacheType string
const (
userCacheLook userCacheType = "user_look_key"
)
// ============缓存设置=====================
// 传入设置的key,如果increase为true就设置为数量加1,反之则为减1
func set(t userCacheType, userID uint, n int) {
num, _ := global.Redis.HGet(string(t), strconv.Itoa(int(userID))).Int()
num += n
global.Redis.HSet(string(t), strconv.Itoa(int(userID)), num)
}
func SetCacheLook(userID uint, increase bool) {
var n = 1
if !increase {
n = -1
}
set(userCacheLook, userID, n)
}
// ============缓存获取====================
func get(t userCacheType, userID uint) int {
num, _ := global.Redis.HGet(string(t), strconv.Itoa(int(userID))).Int()
return num
}
func GetCacheLook(userID uint) int {
return get(userCacheLook, userID)
}
func GetAll(t userCacheType) (mps map[uint]int) {
res, err := global.Redis.HGetAll(string(t)).Result()
if err != nil {
return
}
mps = make(map[uint]int)
for key, nums := range res {
intkey, err := strconv.Atoi(key)
if err != nil {
continue
}
intnum, err := strconv.Atoi(nums)
if err != nil {
continue
}
mps[uint(intkey)] = intnum
}
return mps
}
func GetAllCacheLook() (mps map[uint]int) {
return GetAll(userCacheLook)
}
// 清空数据
func Clear() {
err := global.Redis.Del("user_look_key").Err()
if err != nil {
logrus.Error(err)
}
}
api/user_api/user_base_info.go
Go
package user_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/models"
"blog_server/services/redis_service/redis_user"
"github.com/gin-gonic/gin"
)
type UserBaseInfoResponse struct {
UserID uint `json:"user_id"`
CodeAge int `json:"code_age"`
Avatar string `json:"avatar"`
Nickname string `json:"nickname"`
LookCount int `json:"look_count"`
ArticleCount int `json:"article_count"`
FansCount int `json:"fans_count"`
FocusCount int `json:"focus_count"`
Place string `json:"place"` //ip归属地
OpenCollect bool `json:"open_collect"` //公开我的收藏
OpenFollow bool `json:"open_follow"` //公开我的关注
OpenFans bool `json:"open_fans"` //公开我的粉丝
HomeStyleID uint64 `json:"home_style_id"` //主页样式的id
}
func (UserApi) UserBaseInfoView(c *gin.Context) {
var cr models.IdRequest
err := c.ShouldBindQuery(&cr)
if err != nil {
res.FailWithError(err, c)
return
}
var user models.UserModel
err = global.DB.Preload("UserConfModel").Preload("ArticleList").Take(&user, cr.ID).Error
if err != nil {
res.FailWithMsg("不存在的用户", c)
return
}
data := UserBaseInfoResponse{
UserID: user.ID,
CodeAge: user.CodeAge(),
Avatar: user.Avatar,
Nickname: user.Nickname,
LookCount: user.UserConfModel.IndexCount + redis_user.GetCacheLook(cr.ID),
ArticleCount: len(user.ArticleList),
FansCount: 1,
FocusCount: 1,
Place: user.Addr,
OpenCollect: user.UserConfModel.OpenCollect,
OpenFollow: user.UserConfModel.OpenFollow,
OpenFans: user.UserConfModel.OpenFans,
HomeStyleID: user.UserConfModel.HomeStyleID,
}
var focusList []models.UserFocusModel
global.DB.Find(&focusList, "user_id =? or focus_user_id = ?", cr.ID, cr.ID)
for _, model := range focusList {
if model.UserID == cr.ID {
data.FansCount++
}
if model.FocusUserID == cr.ID {
data.FocusCount++
}
}
redis_user.SetCacheLook(cr.ID, true)
res.OkWithData(data, c)
}
用户配置表添加主页访问次数
Go
// 用户配置表
type UserConfModel struct {
UserID uint `gorm:"primaryKey;unique" json:"user_id"`
UserModel UserModel `gorm:"foreignKey:UserID" json:"-"`
LikeTags []string `gorm:"type:longtext;serializer:json" json:"like_tags"` //兴趣标签
UpdateUsernameDate *time.Time `json:"update_username_date"` //上次用户名修改时间
OpenCollect bool `json:"open_collect"` //公开我的收藏
OpenFollow bool `json:"open_follow"` //公开我的关注
OpenFans bool `json:"open_fans"` //公开我的粉丝
HomeStyleID uint64 `json:"home_style_id"` //主页样式的id
IndexCount int `json:"index_count"` //主页的访问次数
}
12.根据收藏夹ID查文章
api/article_api/article_list.go
Go
package article_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/services/redis_service/redis_article"
"blog_server/utils/jwts"
"blog_server/utils/sql"
"fmt"
"github.com/gin-gonic/gin"
)
//可以查某个用户发布的文章,只能查已发布的,不需要登录,支持分类查询
// 可以查某个人用户收藏的文章,前提是这个用户开了对应隐私设置
//用户侧 查自己发布的文章,只能查已发布的,, 需要登录, 支持分类查询
// 也能查自己收藏的文章,不会受到自己的隐私设置
// 支持按照状态查询,已发布,草稿箱,待审核
//管理员侧 查全部,支持按照用户搜索,状态过滤,文章标题模糊匹配,分类过滤
type ArticleListRequest struct {
common.PageInfo
Type int8 `form:"type" binding:"required,oneof=1 2 3"` //1-用户查别人的, 2-查自己的 3-管理员查询
CategoryID *uint `form:"category_id"`
UserID uint `form:"user_id"`
Status enum.ArticleStatusType `form:"status"` //状态 草稿,审核中,已发布
CollectID uint `form:"collect_id"`
}
type ArticleListResponse struct {
models.ArticleModel
UserTop bool `json:"user_top"` //是否是用户置顶
AdminTop bool `json:"admin_top"` //是否是管理员置顶
CategoryTitle *string `json:"category_title"` //使用指针,可以使在json序列化中进行判断,""为空值。nill为没有传递
UserNickname string `json:"user_nickname"`
UserAvatar string `json:"user_avatar"`
}
func (ArticleApi) ArticleListView(c *gin.Context) {
var topArticleIdList []uint //用户置顶列表
var orderColumnMap = map[string]bool{
"look_count desc": true,
"digg_count desc": true,
"comment_count desc": true,
"collect_count desc": true,
"look_count": true,
"digg_count": true,
"comment_count": true,
"collect_count": true,
}
cr := middlware.GetBind[ArticleListRequest](c)
switch cr.Type {
case 1:
//查别人的用户id就是必填的
if cr.UserID == 0 {
res.FailWithMsg("用户id必填", c)
return
}
if cr.Page > 2 || cr.Limit > 10 {
res.FailWithMsg("查询更多请登录", c)
return
}
cr.Status = 0
cr.Order = ""
if cr.CollectID != 0 {
if cr.UserID == 0 {
res.FailWithMsg("请传入用户ID", c)
return
}
var userConf models.UserConfModel
err := global.DB.Take(&userConf, "user_id = ?", cr.UserID).Error
if err != nil {
res.FailWithMsg("用户不存在", c)
return
}
if !userConf.OpenCollect {
//不公开我的收藏
//在页面上,我的收藏页面不可见
//收藏列表接口不能访问,不能用这个收藏夹 id 查文章
res.FailWithMsg("用户未开启我的收藏", c)
return
}
}
case 2:
//查自己
claims, err := jwts.ParseTokenByGin(c)
if err != nil {
res.FailWithMsg("请登录", c)
return
}
cr.UserID = claims.UserId
case 3:
//管理员
claims, err := jwts.ParseTokenByGin(c)
if !(err == nil && claims.RoleId == enum.AdminRole) {
res.FailWithMsg("角色错误", c)
return
}
}
//根据收藏夹ID查文章
query := global.DB.Where("")
if cr.CollectID != 0 {
var articleIDList []uint
global.DB.Model(models.UserArticleCollectMode{}).Where("collect_id =? ", cr.CollectID).Select("article_id").Scan(&articleIDList)
query.Where("id in ?", articleIDList)
}
if cr.Order != "" {
_, ok := orderColumnMap[cr.Order]
if !ok {
res.FailWithMsg("不支持的排序方式", c)
return
}
}
//处理用户置顶
var userTopMap = map[uint]bool{}
var adminTopMap = map[uint]bool{}
if cr.UserID != 0 {
var userTopArticleList []models.UserTopArticleModel
global.DB.Preload("UserModel").Order("created_at desc").Find(&userTopArticleList, "user_id = ?", cr.UserID)
for _, i2 := range userTopArticleList {
topArticleIdList = append(topArticleIdList, i2.ArticleID)
if i2.UserModel.Role == enum.AdminRole {
adminTopMap[i2.ArticleID] = true
}
userTopMap[i2.ArticleID] = true
}
}
//判断是否有置顶,有的话话再按照置顶列表进行排序,否则就按照时间倒叙排序
var options = common.Options{
Likes: []string{"title"},
PageInfo: cr.PageInfo,
DefaultOrder: "created_at desc",
Where: query,
PreLoads: []string{"CategoryModel", "UserModel"},
}
if len(topArticleIdList) > 0 {
options.DefaultOrder = fmt.Sprintf("%s,created_at desc", sql.ConvertSliceOrderSql(topArticleIdList)) // [1 2 3] = >(1,2,3)或者 id= 1 desc,id = 2 desc 使用辅助函数实现ConvertSliceOrderSql
}
_list, count, _ := common.ListQuery(models.ArticleModel{
UserID: cr.UserID,
CategoryID: cr.CategoryID,
Status: cr.Status,
}, options)
var list = make([]ArticleListResponse, 0)
//从缓存中获取浏览量和点赞数
collectMap := redis_article.GetAllCacheCollect()
lookMap := redis_article.GetAllCacheLook()
diggMap := redis_article.GetAllCacheDigg()
commentMap := redis_article.GetAllCacheComment() //获取评论
for _, model := range _list {
model.Content = ""
model.DiggCount = model.DiggCount + diggMap[model.ID]
model.LookCount = model.LookCount + lookMap[model.ID]
model.CollectCount = model.CollectCount + collectMap[model.ID]
model.CommentCount = model.CommentCount + commentMap[model.ID] //评论数统计
data := ArticleListResponse{
ArticleModel: model,
UserTop: userTopMap[model.ID],
AdminTop: adminTopMap[model.ID],
UserNickname: model.UserModel.Nickname,
UserAvatar: model.UserModel.Avatar,
}
//如果有关联的分类表,就将分类表中的title传递过来(使用指针,如果是)
if model.CategoryModel != nil {
data.CategoryTitle = &model.CategoryModel.Title
}
list = append(list, data)
}
res.OkWithList(list, count, c)
}
13.粉丝关注列表添加好友关系字段
api/focus_api/enter.go
Go
package focus_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum/relationships_enum_type"
"blog_server/services/focus_service"
"blog_server/utils/jwts"
"fmt"
"github.com/gin-gonic/gin"
"time"
)
type FocusApi struct {
}
type FocusUserRequest struct {
FocusUserID uint `json:"focus_user_id" binding:"required"`
}
// ============================= FocusUserView 登录人关注用户==========================================
func (FocusApi) FocusUserView(c *gin.Context) {
cr := middlware.GetBind[FocusUserRequest](c)
//查关注的用户是否存在
var user models.UserModel
err := global.DB.Take(&user, cr.FocusUserID).Error
if err != nil {
res.FailWithMsg("关注的用户不存在", c)
return
}
//查找之前是否已经关注过他了
claims := jwts.GetClaims(c)
if cr.FocusUserID == claims.UserId {
res.FailWithMsg("你时刻都在关注自己!", c)
return
}
var focus models.UserFocusModel
err = global.DB.Take(&focus, "user_id = ? and focus_user_id = ?", claims.UserId, user.ID).Error
if err == nil {
res.FailWithMsg("你已经关注了该用户", c)
return
}
//每天关注是不是应该有限度
//每天取关是否有限度
//关注
global.DB.Create(&models.UserFocusModel{
UserID: claims.UserId,
FocusUserID: cr.FocusUserID,
})
res.OkWithMsg("关注成功", c)
}
type FocusUserListRequest struct {
common.PageInfo
FocusUserID uint `form:"focus_user_id"`
UserID uint `form:"user_id"`
}
type UserListResponse struct {
UserID uint `form:"user_id"`
UserNickname string `json:"user_nickname"`
UserAvatar string `json:"user_avatar"`
UserAbstract string `json:"user_abstract"`
Relationship relationships_enum_type.Relation `json:"relationship"`
CreatedAt time.Time `json:"created_at"`
}
// ============================= FocusUserListView 查看我的关注列表&查看某用户的关注列表根据 有无user_id判断===============================
func (FocusApi) FocusUserListView(c *gin.Context) {
cr := middlware.GetBind[FocusUserListRequest](c)
claims, err := jwts.ParseTokenByGin(c)
if cr.UserID != 0 {
//有用户id则查该用户的用户列表
var userConf models.UserConfModel
er := global.DB.Take(&userConf, "user_id = ?", cr.UserID).Error
if er != nil {
res.FailWithMsg("用户配置信息不存在", c)
return
}
if !userConf.OpenFollow {
res.FailWithMsg("该用户未公开我的关注", c)
return
}
//如果你没有登录,就不允许你查询第二页
if err != nil || claims == nil {
if cr.Limit > 10 || cr.Page > 1 {
res.FailWithMsg("未登录用户只能查询第一页", c)
return
}
}
} else {
if err != nil {
res.FailWithMsg("请登录", c)
return
}
cr.UserID = claims.UserId
}
//模糊匹配
query := global.DB.Where("")
if cr.Key != "" {
var userIDList []uint
global.DB.Model(&models.UserModel{}).Where("nickname like ?", fmt.Sprintf("%%%s%%", cr.Key)).Select("id").Scan(&userIDList)
if len(userIDList) > 0 {
query.Where("focus_user_id in ?", userIDList)
}
}
_list, count, _ := common.ListQuery(models.UserFocusModel{
FocusUserID: cr.FocusUserID,
UserID: cr.UserID,
}, common.Options{
PageInfo: cr.PageInfo,
PreLoads: []string{"FocusUserModel"},
Where: query,
})
var m = map[uint]relationships_enum_type.Relation{}
if err == nil && claims != nil {
var userIDList []uint
for _, i2 := range _list {
userIDList = append(userIDList, i2.FocusUserID)
}
m = focus_service.CalculatingPatchFriendRelationships(claims.UserId, userIDList)
}
var list = make([]UserListResponse, 0)
for _, model := range _list {
list = append(list, UserListResponse{
UserID: model.FocusUserID,
UserNickname: model.FocusUserModel.Nickname,
UserAvatar: model.FocusUserModel.Avatar,
Relationship: m[model.FocusUserID],
UserAbstract: model.FocusUserModel.Abstract,
CreatedAt: model.CreatedAt,
})
}
res.OkWithList(list, count, c)
}
type FansUserListRequest struct {
common.PageInfo
FocusUserID uint `form:"focus_user_id"`
UserID uint `form:"user_id"`
}
//type FansUserListResponse struct {
// FansUserID uint `form:"fans_user_id"`
// FansUserNickname string `json:"fans_user_nickname"`
// FansUserAvatar string `json:"fans_user_avatar"`
// FansUserAbstract string `json:"fans_user_abstract"`
// CreatedAt time.Time `json:"created_at"`
//}
// ============================= FansUserListView 我的粉丝列表和用户的粉丝列表===============================
func (FocusApi) FansUserListView(c *gin.Context) {
cr := middlware.GetBind[FansUserListRequest](c)
claims, err := jwts.ParseTokenByGin(c)
if cr.UserID != 0 {
//有用户id则查该用户的用户列表
var userConf models.UserConfModel
er := global.DB.Take(&userConf, "user_id = ?", cr.UserID).Error
if er != nil {
res.FailWithMsg("用户配置信息不存在", c)
return
}
if !userConf.OpenFans {
res.FailWithMsg("该用户未公开我的粉丝", c)
return
}
//如果你没有登录,就不允许你查询第二页
if err != nil || claims == nil {
if cr.Limit > 10 || cr.Page > 1 {
res.FailWithMsg("未登录用户只能查询第一页", c)
return
}
}
} else {
if err != nil {
res.FailWithMsg("请登录", c)
return
}
cr.UserID = claims.UserId
}
//模糊匹配
query := global.DB.Where("")
if cr.Key != "" {
var userIDList []uint
global.DB.Model(&models.UserModel{}).Where("nickname like ?", fmt.Sprintf("%%%s%%", cr.Key)).Select("id").Scan(&userIDList)
if len(userIDList) > 0 {
query.Where("user_id in ?", userIDList)
}
}
_list, count, _ := common.ListQuery(models.UserFocusModel{
FocusUserID: cr.UserID,
UserID: cr.FocusUserID,
}, common.Options{
PageInfo: cr.PageInfo,
PreLoads: []string{"UserModel"},
Where: query,
})
var m = map[uint]relationships_enum_type.Relation{}
if err == nil && claims != nil {
var userIDList []uint
for _, i2 := range _list {
userIDList = append(userIDList, i2.UserID)
}
m = focus_service.CalculatingPatchFriendRelationships(claims.UserId, userIDList)
}
var list = make([]UserListResponse, 0)
for _, model := range _list {
list = append(list, UserListResponse{
UserID: model.UserID,
UserNickname: model.UserModel.Nickname,
UserAvatar: model.UserModel.Avatar,
UserAbstract: model.UserModel.Abstract,
CreatedAt: model.CreatedAt,
Relationship: m[model.UserID],
})
}
res.OkWithList(list, count, c)
}
// ============================= // UnFocusUserView 登录人取关=========================================
func (FocusApi) UnFocusUserView(c *gin.Context) {
cr := middlware.GetBind[FocusUserRequest](c)
//查关注的用户是否存在
var user models.UserModel
err := global.DB.Take(&user, cr.FocusUserID).Error
if err != nil {
res.FailWithMsg("取关用户不存在", c)
return
}
//查找之前是否已经关注过他了
claims := jwts.GetClaims(c)
if cr.FocusUserID == claims.UserId {
res.FailWithMsg("你时刻都在关注自己!", c)
return
}
var focus models.UserFocusModel
err = global.DB.Take(&focus, "user_id = ? and focus_user_id = ?", claims.UserId, user.ID).Error
if err != nil {
res.FailWithMsg("未关注该用户", c)
return
}
//取关
global.DB.Delete(&focus)
res.OkWithMsg("取消关注成功", c)
}
14.用户基本信息添加好友关系字段响应
api/user_api/user_base_info.go
Go
package user_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/models"
"blog_server/models/enum/relationships_enum_type"
"blog_server/services/focus_service"
"blog_server/services/redis_service/redis_user"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
)
type UserBaseInfoResponse struct {
UserID uint `json:"user_id"`
CodeAge int `json:"code_age"`
Avatar string `json:"avatar"`
Nickname string `json:"nickname"`
LookCount int `json:"look_count"`
ArticleCount int `json:"article_count"`
FansCount int `json:"fans_count"`
FocusCount int `json:"focus_count"`
Place string `json:"place"` //ip归属地
OpenCollect bool `json:"open_collect"` //公开我的收藏
OpenFollow bool `json:"open_follow"` //公开我的关注
OpenFans bool `json:"open_fans"` //公开我的粉丝
HomeStyleID uint64 `json:"home_style_id"` //主页样式的id
Relationship relationships_enum_type.Relation `json:"relationship"`
}
func (UserApi) UserBaseInfoView(c *gin.Context) {
var cr models.IdRequest
err := c.ShouldBindQuery(&cr)
if err != nil {
res.FailWithError(err, c)
return
}
var user models.UserModel
err = global.DB.Preload("UserConfModel").Preload("ArticleList").Take(&user, cr.ID).Error
if err != nil {
res.FailWithMsg("不存在的用户", c)
return
}
data := UserBaseInfoResponse{
UserID: user.ID,
CodeAge: user.CodeAge(),
Avatar: user.Avatar,
Nickname: user.Nickname,
LookCount: user.UserConfModel.IndexCount + redis_user.GetCacheLook(cr.ID),
ArticleCount: len(user.ArticleList),
FansCount: 1,
FocusCount: 1,
Place: user.Addr,
OpenCollect: user.UserConfModel.OpenCollect,
OpenFollow: user.UserConfModel.OpenFollow,
OpenFans: user.UserConfModel.OpenFans,
HomeStyleID: user.UserConfModel.HomeStyleID,
}
claims, err := jwts.ParseTokenByGin(c)
if err == nil && claims != nil {
data.Relationship = focus_service.CalculatingFriendRelationships(claims.UserId, cr.ID)
}
var focusList []models.UserFocusModel
global.DB.Find(&focusList, "user_id =? or focus_user_id = ?", cr.ID, cr.ID)
for _, model := range focusList {
if model.UserID == cr.ID {
data.FansCount++
}
if model.FocusUserID == cr.ID {
data.FocusCount++
}
}
redis_user.SetCacheLook(cr.ID, true)
res.OkWithData(data, c)
}
15.文章详情添加是否点赞,是否收藏字段
api/article_api/article_detail.go
Go
package article_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/services/redis_service/redis_article"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
)
type ArticleDetailResponse struct {
models.ArticleModel
Username string `json:"username"`
UserAvatar string `json:"user_avatar"`
Nickname string `json:"nickname"`
CategoryTitle *string `json:"category_title"`
IsDigg bool `json:"is_digg"` //是否点赞
IsCollect bool `json:"is_collect"` //是否收藏
}
func (ArticleApi) ArticleDetailView(c *gin.Context) {
cr := middlware.GetBind[models.IdRequest](c)
var article models.ArticleModel
err := global.DB.Preload("UserModel").Preload("CategoryModel").Take(&article, cr.ID).Error
if err != nil {
res.FailWithMsg("文章不存在", c)
return
}
claims, err := jwts.ParseTokenByGin(c)
if err != nil {
if article.Status != enum.ArticleStatusPublishes {
res.FailWithMsg("文章不存在", c)
return
}
}
data := ArticleDetailResponse{
ArticleModel: article,
Username: article.UserModel.Username,
UserAvatar: article.UserModel.Avatar,
Nickname: article.UserModel.Nickname,
}
if err == nil && claims != nil {
switch claims.RoleId {
case enum.UserRole:
if claims.UserId != article.UserID {
//登录者看的不是自己的
if article.Status != enum.ArticleStatusPublishes {
res.FailWithMsg("文章不存在", c)
return
}
}
}
//查用户是否收藏了,文章,是否点赞了文章
var userDiggModel models.ArticleDiggModel
err = global.DB.Take(&userDiggModel, "user_id =? and article_id =?", claims.UserId, article.ID).Error
if err == nil {
data.IsDigg = true
}
var userCollectModel models.UserArticleCollectMode
err = global.DB.Take(&userCollectModel, "user_id =? and article_id =?", claims.UserId, article.ID).Error
if err == nil {
data.IsCollect = true
}
}
//从缓存中获取浏览量和点赞数
collectCount := redis_article.GetCacheCollect(article.ID)
lookCount := redis_article.GetCacheLook(article.ID)
diggCount := redis_article.GetCacheDigg(article.ID)
commentCount := redis_article.GetCacheComment(article.ID) //获取文章评论
data.DiggCount = article.DiggCount + diggCount
data.LookCount = article.LookCount + lookCount
data.CollectCount = article.CollectCount + collectCount
data.CommentCount = article.CommentCount + commentCount //评论数统计
if article.CategoryModel != nil {
data.CategoryTitle = &article.CategoryModel.Title
}
res.OkWithData(data, c)
}
16.文章收藏,文章点赞取消部分优化(因为表加了主键便方便优化了)
api/article_api/article_digg.go
Go
package article_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/services/message_service"
"blog_server/services/redis_service/redis_article"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
)
func (ArticleApi) ArticleDiggView(c *gin.Context) {
cr := middlware.GetBind[models.IdRequest](c)
var article models.ArticleModel
err := global.DB.Take(&article, "status = ? and id = ?", enum.ArticleStatusPublishes, cr.ID).Error //查询已发布的
if err != nil {
res.FailWithMsg("文章不存在", c)
return
}
claims := jwts.GetClaims(c)
//查一下之前有没有点赞
var UserDiggArticle models.ArticleDiggModel
err = global.DB.Take(&UserDiggArticle, "user_id = ? and article_id = ?", claims.UserId, article.ID).Error
if err != nil {
//不存在-》点赞
model := models.ArticleDiggModel{
UserID: claims.UserId,
ArticleID: article.ID,
}
err = global.DB.Create(&model).Error
if err != nil {
res.FailWithMsg("点赞失败", c)
return
}
//将点赞添加到缓存
redis_article.SetCacheDigg(cr.ID, true)
//给文章拥有者发消息
message_service.InsertDiggArticleMessage(model)
res.OkWithMsg("点赞成功", c)
return
}
//global.DB.Debug().Where("user_id = ? and article_id = ?", claims.UserId, article.ID).
// Delete(&models.ArticleDiggModel{})
global.DB.Delete(&UserDiggArticle)
redis_article.SetCacheDigg(cr.ID, false)
res.OkWithMsg("取消点赞成功", c)
return
}
api/article_api/article_collect.go
Go
package article_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/services/message_service"
"blog_server/services/redis_service/redis_article"
"blog_server/utils/jwts"
"fmt"
"github.com/gin-gonic/gin"
)
type ArticleCollectRequest struct {
ArticleId uint `json:"article_id" binding:"required"`
CollectID uint `json:"collect_id"`
}
func (ArticleApi) ArticleCollectView(c *gin.Context) {
cr := middlware.GetBind[ArticleCollectRequest](c)
var article models.ArticleModel
err := global.DB.Take(&article, "status = ? and id = ?", enum.ArticleStatusPublishes, cr.ArticleId).Error //查询已发布的
if err != nil {
res.FailWithMsg("文章不存在", c)
return
}
var collectModel models.CollectModel
claims, err := jwts.ParseTokenByGin(c)
if cr.CollectID == 0 {
//默认收藏夹
err = global.DB.Take(&collectModel, "user_id =? and is_default = ?", claims.UserId, 1).Error
if err != nil {
//创建一个默认收藏夹
collectModel.Title = "默认收藏夹"
collectModel.UserID = claims.UserId
collectModel.IsDefault = true
global.DB.Create(&collectModel)
}
cr.CollectID = collectModel.ID
} else {
//判断收藏夹是否存在并且是否是自己创建的
err = global.DB.Take(&collectModel, "user_id =?", claims.UserId).Error
if err != nil {
res.FailWithMsg("收藏夹不存在", c)
return
}
}
//判断是否收藏
var articleCollect models.UserArticleCollectMode
err = global.DB.Where(models.UserArticleCollectMode{
UserID: claims.UserId,
ArticleID: cr.ArticleId,
CollectID: cr.CollectID,
}).Take(&articleCollect).Error
if err != nil {
//没有就收藏
model := models.UserArticleCollectMode{
UserID: claims.UserId,
ArticleID: cr.ArticleId,
CollectID: cr.CollectID,
}
err = global.DB.Create(&model).Error
if err != nil {
res.FailWithMsg("收藏失败", c)
return
}
//对收藏夹进行加1
redis_article.SetCacheCollect(cr.CollectID, true)
//给文章拥有者发消息
message_service.InsertCollectArticleMessage(model)
res.FailWithMsg("收藏成功", c)
//global.DB.Model(&collectModel).Update("article_count", gorm.Expr("article_count + 1"))
return
}
//取消收藏
//err = global.DB.Where(models.UserArticleCollectMode{
// UserID: claims.UserId,
// ArticleID: cr.ArticleId,
// CollectID: cr.CollectID,
//}).Delete(&models.UserArticleCollectMode{}).Error
err = global.DB.Delete(&articleCollect).Error
if err != nil {
res.FailWithMsg("取消收藏失败", c)
return
}
res.FailWithMsg("取消收藏成功", c)
//对收藏夹进行-1
//global.DB.Model(&collectModel).Update("article_count", gorm.Expr("article_count - 1"))
//收藏数同步缓存
redis_article.SetCacheCollect(cr.CollectID, false)
return
}
func (ArticleApi) ArticleCollectPatchRemove(c *gin.Context) {
var cr = middlware.GetBind[models.IdRemoveRequest](c)
claims := jwts.GetClaims(c)
var userCollectList []models.UserArticleCollectMode
global.DB.Find(&userCollectList, "id in ? and user_id = ?", cr.IDList, claims.UserId)
if len(userCollectList) > 0 {
global.DB.Debug().Delete(&userCollectList)
}
res.FailWithMsg(fmt.Sprintf("批量删除文章收藏%d个", len(userCollectList)), c)
}
问题:但是文章收藏数的计算有问题待处理(只能减不能加)
17.收藏夹的列表添加查询这个文件夹是否被这文章使用
api/article_api/collect.go
Go
package article_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/utils/jwts"
"fmt"
"github.com/gin-gonic/gin"
)
//=====收藏夹的创建和更新,如果如果有ID就是更新,没有ID就是创建=====
type CollectCreateRequest struct {
ID uint `json:"id"`
Title string `son:"title" binding:"required"`
Abstract string `json:"abstract"`
Cover string `json:"cover"`
}
func (ArticleApi) CollectCreateView(c *gin.Context) {
cr := middlware.GetBind[CollectCreateRequest](c)
claims := jwts.GetClaims(c)
var model models.CollectModel
if cr.ID == 0 {
//创建
err := global.DB.Take(&model, "user_id = ? and title = ?", claims.UserId, cr.Title).Error
if err == nil {
res.FailWithMsg("收藏夹名称重复", c)
return
}
err = global.DB.Create(&models.CollectModel{
Title: cr.Title,
UserID: claims.UserId,
Abstract: cr.Abstract,
Cover: cr.Cover,
}).Error
if err != nil {
res.FailWithMsg("创建收藏夹失败", c)
return
}
res.OkWithMsg("创建收藏夹成功", c)
return
}
err := global.DB.Take(&model, "user_id = ? and id = ?", claims.UserId, cr.ID).Error
if err != nil {
res.FailWithMsg("收藏夹不存在", c)
return
}
err = global.DB.Model(&model).Updates(map[string]any{
"title": cr.Title,
"abstract": cr.Abstract,
"cover": cr.Cover,
}).Error
if err != nil {
res.FailWithMsg("更新收藏夹失败", c)
return
}
res.OkWithMsg("更新收藏夹成功", c)
}
// =====收藏夹列表=====
type CollectListRequest struct {
common.PageInfo
UserID uint `form:"user_id"`
Type int8 `form:"type" binding:"required,oneof=1 2 3"` //1-查自己 2-查别人 3-后台
ArticleID uint `form:"article_id"`
}
type CollectListResponse struct {
models.CollectModel
ArticleCount int `json:"article_count"`
Nickname string `json:"nickname,omitempty"`
Avatar string `json:"avatar,omitempty"`
ArticleUse bool `json:"article_use,omitempty"` //这个收藏夹是否被使用,即里面是否有文章
}
func (ArticleApi) CollectListView(c *gin.Context) {
cr := middlware.GetBind[CollectListRequest](c)
var preload = []string{"ArticleList"}
switch cr.Type {
case 1:
claims, err := jwts.ParseTokenByGin(c)
if err != nil {
res.FailWithError(err, c)
return
}
cr.UserID = claims.UserId
case 2:
//不公开我的收藏
//在页面上,我的收藏页面不可见
//收藏列表接口不能访问,不能用这个收藏夹 id 查文章
var userConf models.UserConfModel
err := global.DB.Take(&userConf, "user_id = ?", cr.UserID).Error
if err != nil {
res.FailWithMsg("用户不存在", c)
return
}
if !userConf.OpenCollect {
res.FailWithMsg("用户未开启我的收藏", c)
return
}
case 3:
claims, err := jwts.ParseTokenByGin(c)
if err != nil {
res.FailWithError(err, c)
return
}
if claims.RoleId != enum.AdminRole {
res.FailWithMsg("权限不足!", c)
return
}
preload = append(preload, "UserModel")
}
_list, count, _ := common.ListQuery(models.CollectModel{
UserID: cr.UserID,
}, common.Options{
PageInfo: cr.PageInfo,
Likes: []string{"title"},
PreLoads: preload,
})
var list = make([]CollectListResponse, 0)
for _, i2 := range _list {
item := CollectListResponse{
CollectModel: i2,
ArticleCount: len(i2.ArticleList),
Nickname: i2.UserModel.Nickname,
Avatar: i2.UserModel.Avatar,
}
for _, model := range i2.ArticleList {
if model.ArticleID == cr.ArticleID {
item.ArticleUse = true
break
}
}
list = append(list, item)
}
res.OkWithList(list, count, c)
}
func (ArticleApi) CollectRemoveView(c *gin.Context) {
var cr = middlware.GetBind[models.IdRemoveRequest](c)
var list []models.CollectModel
query := global.DB.Where("id in ?", cr.IDList)
claims := jwts.GetClaims(c)
if claims.RoleId != enum.AdminRole {
query.Where("user_id = ?", claims.UserId)
}
global.DB.Where(query).Find(&list)
if len(list) > 0 {
err := global.DB.Delete(&list).Error
if err != nil {
res.FailWithMsg("删除收藏夹失败!", c)
return
}
}
msg := fmt.Sprintf("删除收藏夹成功,共删除%d条", len(list))
res.OkWithMsg(msg, c)
}
18.评论数添加是否点赞和好友关系字段
api/comment_api/comment_tree.go
Go
package comment_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/models/enum/relationships_enum_type"
"blog_server/services/comment_service"
"blog_server/services/focus_service"
"blog_server/utils"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
)
func (CommentApi) CommentTreeView(c *gin.Context) {
var cr = middlware.GetBind[models.IdRequest](c)
var article models.ArticleModel
err := global.DB.Take(&article, "status = ? and id =?", enum.ArticleStatusPublishes, cr.ID).Error
if err != nil {
res.FailWithMsg("文章不存在", c)
return
}
var userRelationMap = map[uint]relationships_enum_type.Relation{}
var userDiggCommentMap = map[uint]bool{}
claims, err := jwts.ParseTokenByGin(c)
if err == nil && claims != nil {
//登陆了
var commentList []models.CommentModel //文章的评论列表
global.DB.Find(&commentList, "article_id = ?", cr.ID)
if len(commentList) > 0 {
//查我的点赞的评论列表
var commentIDList []uint
var userIDList []uint
for _, model := range commentList {
commentIDList = append(commentIDList, model.ID)
userIDList = append(userIDList, model.UserID)
}
userIDList = utils.Unique(userIDList) //对用户id去重
userRelationMap = focus_service.CalculatingPatchFriendRelationships(claims.UserId, userIDList)
var commentDiggList []models.CommentDiggModel
global.DB.Find(&commentDiggList, "user_id =? and comment_id in ?", claims.UserId, commentIDList)
for _, model := range commentDiggList {
userDiggCommentMap[model.CommentID] = true
}
}
}
//查找根评论
var commentList []models.CommentModel
global.DB.Order("created_at desc").Find(&commentList, "article_id = ? and parent_id is null", cr.ID)
var list = make([]comment_service.CommentResponse, 0)
for _, model := range commentList {
response := comment_service.GetCommentTreeV3(model.ID, userRelationMap,
userDiggCommentMap)
list = append(list, *response)
}
res.OkWithList(list, len(list), c)
}
services/comment_service/enter.go
Go
package comment_service
import (
"blog_server/global"
"blog_server/models"
"blog_server/models/enum/relationships_enum_type"
"blog_server/services/redis_service/redis_comment"
"time"
)
// 获取一个评论的根评论
func GetRootComment(commentID uint) (model *models.CommentModel) {
var comment models.CommentModel
err := global.DB.Take(&comment, commentID).Error
if err != nil {
return nil
}
if comment.ParentID == nil {
//没有父评论了,那么自己就是根评论
return &comment
}
return GetRootComment(*comment.RootParentID)
}
// 获取多有的父评论
func GetParents(commentID uint) (list []models.CommentModel) {
var comment models.CommentModel
err := global.DB.Take(&comment, commentID).Error
if err != nil {
return
}
list = append(list, comment)
if comment.ParentID != nil {
list = append(list, GetParents(*comment.ParentID)...)
}
return
}
// 获取评论树
func GetCommentTree(model *models.CommentModel) {
global.DB.Preload("SubCommentList").Take(model)
for _, commentModel := range model.SubCommentList {
GetCommentTree(commentModel)
}
}
// 获取评论树版本2
func GetCommentTreeV2(id uint) (model *models.CommentModel) {
model = &models.CommentModel{
Model: models.Model{ID: id},
}
global.DB.Preload("SubCommentList").Take(model)
for i := 0; i < len(model.SubCommentList); i++ {
commentModel := model.SubCommentList[i]
item := GetCommentTreeV2(commentModel.ID)
model.SubCommentList[i] = item
}
return
}
// 评论一维化
func GetCommentOneDimensional(id uint) (list []models.CommentModel) {
model := models.CommentModel{
Model: models.Model{ID: id}}
global.DB.Preload("SubCommentList").Take(&model)
list = append(list, model)
for _, commentModel := range model.SubCommentList {
subList := GetCommentOneDimensional(commentModel.ID)
list = append(list, subList...)
}
return
}
type CommentResponse struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"created_at"`
Content string `json:"content"`
UserID uint `json:"user_id"`
UserNickname string `json:"user_nickname"`
UserAvatar string `json:"user_avatar"`
ArticleID uint `json:"article_id"`
ParentID *uint `json:"parent_id"`
DiggCount uint `json:"digg_count"`
ApplyCount int `json:"apply_count"`
SubComments []*CommentResponse `json:"sub_comments"`
IsDigg bool `json:"is_digg"`
Relationship relationships_enum_type.Relation `json:"relationship"`
}
// 获取评论树版本3返回用户等信息
func GetCommentTreeV3(id uint, userRelationMap map[uint]relationships_enum_type.Relation, userDiggMap map[uint]bool) (res *CommentResponse) {
return getCommentTreeV3(id, 1, userRelationMap, userDiggMap)
}
func getCommentTreeV3(id uint, line int, userRelationMap map[uint]relationships_enum_type.Relation, userDiggMap map[uint]bool) (res *CommentResponse) {
model := &models.CommentModel{
Model: models.Model{ID: id},
}
global.DB.Preload("UserModel").Preload("SubCommentList").Take(model)
res = &CommentResponse{
ID: model.ID,
CreatedAt: model.CreatedAt,
Content: model.Content,
UserID: model.UserID,
UserNickname: model.UserModel.Nickname,
UserAvatar: model.UserModel.Avatar,
ArticleID: model.ArticleID,
ParentID: model.ParentID,
DiggCount: model.DiggCount + uint(redis_comment.GetCacheDigg(model.ID)), // 获取评论点赞数
ApplyCount: redis_comment.GetCacheApply(model.ID), // 获取评论数
SubComments: make([]*CommentResponse, 0),
IsDigg: userDiggMap[model.ID],
Relationship: userRelationMap[model.UserID],
}
if line >= global.Config.Site.Article.CommentLine {
return
}
for _, commentModel := range model.SubCommentList {
res.SubComments = append(res.SubComments, getCommentTreeV3(commentModel.ID, line+1, userRelationMap, userDiggMap))
}
return
}
19.评论点赞删除优化
api/comment_api/comment_digg.go
Go
package comment_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/services/message_service"
"blog_server/services/redis_service/redis_comment"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
)
func (CommentApi) CommentDiggView(c *gin.Context) {
cr := middlware.GetBind[models.IdRequest](c)
var comment models.CommentModel
err := global.DB.Take(&comment, cr.ID).Error //查询已发布的
if err != nil {
res.FailWithMsg("评论不存在", c)
return
}
claims := jwts.GetClaims(c)
//查一下之前有没有点赞
var UserDiggComment models.CommentDiggModel
err = global.DB.Take(&UserDiggComment, "user_id = ? and comment_id = ?", claims.UserId, comment.ID).Error
if err != nil {
//不存在-》点赞
model := models.CommentDiggModel{
UserID: claims.UserId,
CommentID: comment.ID,
}
err = global.DB.Create(&model).Error
if err != nil {
res.FailWithMsg("点赞失败", c)
return
}
redis_comment.SetCacheDigg(cr.ID, 1)
//给评论拥有者发消息
message_service.InsertDiggCommentMessage(model)
res.OkWithMsg("点赞成功", c)
return
}
global.DB.Delete(&UserDiggComment)
redis_comment.SetCacheDigg(cr.ID, -1)
res.OkWithMsg("取消点赞成功", c)
return
}
20.添加标签列表接口
api/search_api/tag_agg.go
Go
package search_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/olivere/elastic/v7"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
"sort"
)
type TagAggResponse struct {
Tag string `json:"tag"`
ArticleCount int `json:"articleCount"`
}
type AggType struct {
DocCountErrorUpperBound int `json:"doc_count_error_upper_bound"`
SumOtherDocCount int `json:"sum_other_doc_count"`
Buckets []struct {
Key string `json:"key"`
DocCount int `json:"doc_count"`
} `json:"buckets"`
}
type AggCount struct {
Value int `json:"value"`
}
// 使用es聚合查询&&mysl查询标签云
func (SearchApi) TagAggView(c *gin.Context) {
var cr = middlware.GetBind[common.PageInfo](c)
var list = make([]TagAggResponse, 0)
if global.ESClient == nil {
//如果没有配置es,服务降级使用mysql查询。分页查询暂时,没做
var articleList []models.ArticleModel
global.DB.Find(&articleList, "tag_list <> '' ")
var tagMap = map[string]int{}
for _, model := range articleList {
for _, tag := range model.TagList {
count, ok := tagMap[tag]
if !ok {
tagMap[tag] = 1
continue
}
tagMap[tag] = count + 1
}
}
for tag, count := range tagMap {
list = append(list, TagAggResponse{
Tag: tag,
ArticleCount: count,
})
}
//排序
sort.Slice(list, func(i, j int) bool {
return list[i].ArticleCount > list[j].ArticleCount
})
res.OkWithList(list, len(list), c)
return
}
agg := elastic.NewTermsAggregation().Field("tag_list")
agg.SubAggregation("page", elastic.NewBucketSortAggregation().From(cr.GetOffset()).Size(cr.Limit))
agg.SubAggregation("sum", elastic.NewSumAggregation().Field("tag_list"))
query := elastic.NewBoolQuery()
query.MustNot(elastic.NewTermQuery("tag_list", ""))
result, err := global.ESClient.Search(models.ArticleModel{}.Index()).Query(query).Aggregation("tags", agg).Aggregation("tag_count", elastic.NewCardinalityAggregation().Field("tag_list")).Size(0).Do(context.Background())
if err != nil {
logrus.Errorf("查询失败 %s", err)
res.FailWithMsg("查询失败", c)
return
}
var count AggCount
json.Unmarshal(result.Aggregations["tag_count"], &count)
var t AggType
var val = result.Aggregations["tags"]
err = json.Unmarshal(val, &t)
if err != nil {
logrus.Errorf("解析json失败 %s", err)
res.FailWithMsg("查询失败", c)
return
}
for _, bucket := range t.Buckets {
list = append(list, TagAggResponse{
Tag: bucket.Key,
ArticleCount: bucket.DocCount,
})
}
res.OkWithList(list, count.Value, c)
}
r.GET("search/tags", middlware.BindQueryMiddleware[common.PageInfo], app.TagAggView)
21.首页作者推荐
api/article_api/auth_recommend.go
Go
package article_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum/relationships_enum_type"
"blog_server/services/focus_service"
"blog_server/utils/jwts"
"fmt"
"github.com/gin-gonic/gin"
)
type AuthRecommendResponse struct {
UserID uint `json:"user_id"`
UserNickname string `json:"user_nickname"`
UserAvatar string `json:"user_avatar"`
UserAbstract string `json:"user_abstract"`
}
// 作者推荐
// 查询我还没有关注的,且发了文章的用户,
func (ArticleApi) AuthRecommendView(c *gin.Context) {
cr := middlware.GetBind[common.PageInfo](c)
var count int
var userIDList []uint
global.DB.Model(models.ArticleModel{}).Group("user_id").Select("count(*)").Scan(&count)
global.DB.Model(models.ArticleModel{}).Group("user_id").
Offset(cr.GetOffset()).Limit(cr.GetLimit()).
Select("user_id").Scan(&userIDList)
claims, err := jwts.ParseTokenByGin(c)
if err == nil && claims != nil {
m := focus_service.CalculatingPatchFriendRelationships(claims.UserId, userIDList)
userIDList = []uint{}
for u, relation := range m {
if relation == relationships_enum_type.RelationStranger || relation == relationships_enum_type.RelationFocus {
fmt.Println("##:", u)
userIDList = append(userIDList, u)
}
}
}
fmt.Println("@@@@", userIDList)
var userList []models.UserModel
global.DB.Debug().Find(&userList, "id in ?", userIDList)
var list = make([]AuthRecommendResponse, 0)
for _, model := range userList {
list = append(list, AuthRecommendResponse{
UserID: model.ID,
UserNickname: model.Nickname,
UserAvatar: model.Avatar,
UserAbstract: model.Abstract,
})
}
res.OkWithList(list, count, c)
}
22.文章推荐
api/article_api/article_recommend.go
Go
package article_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"github.com/gin-gonic/gin"
)
type ArticleRecommendResponse struct {
ID uint `json:"id" gorm:"column:id"`
Title string `json:"title" gorm:"column:title"`
LookCount int `json:"lookCount" gorm:"column:lookCount"`
}
// 推荐每天浏览量最高的文章
func (ArticleApi) ArticleReCommendView(c *gin.Context) {
cr := middlware.GetBind[common.PageInfo](c)
var list = make([]ArticleRecommendResponse, 0)
global.DB.Debug().Model(models.ArticleModel{}).Order("look_count desc").Where("date(created_at) = date(now())").Limit(cr.Limit).Select("id", "title", "look_count").Scan(&list)
res.OkWithList(list, len(list), c)
}
rg.GET("article/auth_recommend", middlware.BindQueryMiddleware[common.PageInfo], app.AuthRecommendView) rg.GET("article/article_recommend", middlware.BindQueryMiddleware[common.PageInfo], app.ArticleReCommendView)
23.banner添加字段type,
api/banner_api/enter.go
Go
package banner_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/global"
"blog_server/models"
"fmt"
"github.com/gin-gonic/gin"
)
type BannerCreateRequest struct {
Cover string `json:"cover" binding:"required"`
Href string `json:"href"`
Show bool `json:"show"`
Type int8 `json:"type" binding:"required,oneof=1 2"`
}
type BannerApi struct {
}
func (BannerApi) BannerCreateView(c *gin.Context) {
var cr BannerCreateRequest
err := c.ShouldBindJSON(&cr)
if err != nil {
res.FailWithError(err, c)
return
}
err = global.DB.Create(&models.BannerModel{
Cover: cr.Cover,
Href: cr.Href,
Show: cr.Show,
Type: cr.Type,
}).Error
if err != nil {
res.FailWithError(err, c)
return
}
res.OkWithMsg("添加banner成功", c)
}
type BannerListRequest struct {
common.PageInfo
Show bool `form:"show"`
Type int8 `form:"type"` //1-banner 2-独家推广
}
func (BannerApi) BannerListView(c *gin.Context) {
var cr BannerListRequest
c.ShouldBindQuery(&cr)
list, count, _ := common.ListQuery(models.BannerModel{
Show: cr.Show,
Type: cr.Type,
}, common.Options{
PageInfo: cr.PageInfo,
})
res.OkWithList(list, count, c)
}
func (BannerApi) BannerRemoveView(c *gin.Context) {
var cr models.IdRemoveRequest
err := c.ShouldBindJSON(&cr)
if err != nil {
res.FailWithError(err, c)
return
}
var list []models.BannerModel
global.DB.Find(&list, "id in ?", cr.IDList)
if len(list) > 0 {
global.DB.Delete(&list)
}
res.OkWithMsg(fmt.Sprintf("删除banner%d个,成功%d个", len(cr.IDList), len(list)), c)
}
func (BannerApi) BannerUpdateView(c *gin.Context) {
var id models.IdRequest
err := c.ShouldBindUri(&id)
if err != nil {
res.FailWithError(err, c)
return
}
var cr BannerCreateRequest
err = c.ShouldBindJSON(&cr)
if err != nil {
res.FailWithError(err, c)
return
}
var model models.BannerModel
err = global.DB.Take(&model, id.ID).Error
if err != nil {
res.FailWithMsg("不存在的id", c)
}
err = global.DB.Model(&model).Updates(map[string]any{
"cover": cr.Cover,
"href": cr.Href,
"show": cr.Show,
}).Error
if err != nil {
res.FailWithError(err, c)
return
}
res.OkWithMsg("banner更新成功", c)
}
models/banner_model.go
Go
package models
// banner表
type BannerModel struct {
Model
Cover string ` json:"cover"` // 图片首页
Href string ` json:"href"` //图片链接
Show bool `json:"show"` //是否展示
Type int8 `json:"type"` //1-banner 2-独家推广
}
24.站内信-消息列表添加好友关系
api/site_msg_api/msg_list.go
Go
package site_msg_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum/message_enum_type"
"blog_server/models/enum/relationships_enum_type"
"blog_server/services/focus_service"
"blog_server/utils/jwts"
"github.com/gin-gonic/gin"
)
type SiteMsgListRequest struct {
common.PageInfo
T int8 `form:"t" binding:"required,oneof=1 2 3"` //1评论回复 2赞和收藏 3系统
}
type SiteMsgListResponse struct {
models.MessageModel
Relationship relationships_enum_type.Relation `json:"relationship"`
}
func (SiteMsgApi) SiteMsgListView(c *gin.Context) {
cr := middlware.GetBind[SiteMsgListRequest](c)
claims := jwts.GetClaims(c)
var typeList []message_enum_type.Type
switch cr.T {
case 1:
typeList = append(typeList, message_enum_type.CommentType, message_enum_type.ApplyType)
case 2:
typeList = append(typeList, message_enum_type.DiggArticleTypeType, message_enum_type.DiggCommentTypeType, message_enum_type.CollectArticleType)
case 3:
typeList = append(typeList, message_enum_type.SystemTypeType)
}
_list, count, _ := common.ListQuery(models.MessageModel{
RevUserID: claims.UserId,
}, common.Options{
PageInfo: cr.PageInfo,
Where: global.DB.Where("type in ?", typeList),
Debug: true,
})
var userIDList []uint
for _, model := range _list {
if model.ActionUserID != 0 {
userIDList = append(userIDList, model.ActionUserID)
}
}
var m = map[uint]relationships_enum_type.Relation{}
if len(userIDList) > 0 {
m = focus_service.CalculatingPatchFriendRelationships(claims.UserId, userIDList)
}
var list = make([]SiteMsgListResponse, 0)
for _, model := range _list {
list = append(list, SiteMsgListResponse{
MessageModel: model,
Relationship: m[model.ActionUserID],
})
}
res.OkWithList(list, count, c)
}
25.批量取消文章收藏优化
api/article_api/article_collect.go
Go
package article_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/services/message_service"
"blog_server/services/redis_service/redis_article"
"blog_server/utils/jwts"
"fmt"
"github.com/gin-gonic/gin"
)
type ArticleCollectRequest struct {
ArticleId uint `json:"article_id" binding:"required"`
CollectID uint `json:"collect_id"`
}
func (ArticleApi) ArticleCollectView(c *gin.Context) {
cr := middlware.GetBind[ArticleCollectRequest](c)
var article models.ArticleModel
err := global.DB.Take(&article, "status = ? and id = ?", enum.ArticleStatusPublishes, cr.ArticleId).Error //查询已发布的
if err != nil {
res.FailWithMsg("文章不存在", c)
return
}
var collectModel models.CollectModel
claims, err := jwts.ParseTokenByGin(c)
if cr.CollectID == 0 {
//默认收藏夹
err = global.DB.Take(&collectModel, "user_id =? and is_default = ?", claims.UserId, 1).Error
if err != nil {
//创建一个默认收藏夹
collectModel.Title = "默认收藏夹"
collectModel.UserID = claims.UserId
collectModel.IsDefault = true
global.DB.Create(&collectModel)
}
cr.CollectID = collectModel.ID
} else {
//判断收藏夹是否存在并且是否是自己创建的
err = global.DB.Take(&collectModel, "user_id =?", claims.UserId).Error
if err != nil {
res.FailWithMsg("收藏夹不存在", c)
return
}
}
//判断是否收藏
var articleCollect models.UserArticleCollectMode
err = global.DB.Where(models.UserArticleCollectMode{
UserID: claims.UserId,
ArticleID: cr.ArticleId,
CollectID: cr.CollectID,
}).Take(&articleCollect).Error
if err != nil {
//没有就收藏
model := models.UserArticleCollectMode{
UserID: claims.UserId,
ArticleID: cr.ArticleId,
CollectID: cr.CollectID,
}
err = global.DB.Create(&model).Error
if err != nil {
res.FailWithMsg("收藏失败", c)
return
}
//对收藏夹进行加1
redis_article.SetCacheCollect(cr.CollectID, true)
//给文章拥有者发消息
message_service.InsertCollectArticleMessage(model)
res.FailWithMsg("收藏成功", c)
//global.DB.Model(&collectModel).Update("article_count", gorm.Expr("article_count + 1"))
return
}
//取消收藏
//err = global.DB.Where(models.UserArticleCollectMode{
// UserID: claims.UserId,
// ArticleID: cr.ArticleId,
// CollectID: cr.CollectID,
//}).Delete(&models.UserArticleCollectMode{}).Error
err = global.DB.Delete(&articleCollect).Error
if err != nil {
res.FailWithMsg("取消收藏失败", c)
return
}
res.FailWithMsg("取消收藏成功", c)
//对收藏夹进行-1
//global.DB.Model(&collectModel).Update("article_count", gorm.Expr("article_count - 1"))
//收藏数同步缓存
redis_article.SetCacheCollect(cr.CollectID, false)
return
}
type ArticleCollectPatchRequest struct {
CollectID uint `json:"collect_id"`
ArticleIDList []uint `json:"article_id_list"`
}
func (ArticleApi) ArticleCollectPatchRemove(c *gin.Context) {
var cr = middlware.GetBind[ArticleCollectPatchRequest](c)
claims := jwts.GetClaims(c)
var userCollectList []models.UserArticleCollectMode
global.DB.Find(&userCollectList, "collect_id = ? and article_id in ? and user_id = ?", cr.CollectID, cr.ArticleIDList, claims.UserId)
if len(userCollectList) > 0 {
global.DB.Debug().Delete(&userCollectList)
for _, u := range cr.ArticleIDList {
redis_article.SetCacheCollect(u, false)
}
}
res.FailWithMsg(fmt.Sprintf("批量删除文章收藏%d个", len(userCollectList)), c)
}
rg.DELETE("article/collect", middlware.AuthMiddleware, middlware.BindMiddleware[article_api.ArticleCollectPatchRequest], app.ArticleCollectPatchRemove) //批量取消文章收藏
26.添加查用户所有的收藏文章
api/article_api/article_list.go
Go
package article_api
import (
"blog_server/common"
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"blog_server/models/enum"
"blog_server/services/redis_service/redis_article"
"blog_server/utils/jwts"
"blog_server/utils/sql"
"fmt"
"github.com/gin-gonic/gin"
)
//可以查某个用户发布的文章,只能查已发布的,不需要登录,支持分类查询
// 可以查某个人用户收藏的文章,前提是这个用户开了对应隐私设置
//用户侧 查自己发布的文章,只能查已发布的,, 需要登录, 支持分类查询
// 也能查自己收藏的文章,不会受到自己的隐私设置
// 支持按照状态查询,已发布,草稿箱,待审核
//管理员侧 查全部,支持按照用户搜索,状态过滤,文章标题模糊匹配,分类过滤
type ArticleListRequest struct {
common.PageInfo
Type int8 `form:"type" binding:"required,oneof=1 2 3"` //1-用户查别人的, 2-查自己的 3-管理员查询
CategoryID *uint `form:"category_id"`
UserID uint `form:"user_id"`
Status enum.ArticleStatusType `form:"status"` //状态 草稿,审核中,已发布
CollectID int `form:"collect_id"` //id =-1为查所有的收藏文章
}
type ArticleListResponse struct {
models.ArticleModel
UserTop bool `json:"user_top"` //是否是用户置顶
AdminTop bool `json:"admin_top"` //是否是管理员置顶
CategoryTitle *string `json:"category_title"` //使用指针,可以使在json序列化中进行判断,""为空值。nill为没有传递
UserNickname string `json:"user_nickname"`
UserAvatar string `json:"user_avatar"`
}
func (ArticleApi) ArticleListView(c *gin.Context) {
var topArticleIdList []uint //用户置顶列表
var orderColumnMap = map[string]bool{
"look_count desc": true,
"digg_count desc": true,
"comment_count desc": true,
"collect_count desc": true,
"look_count": true,
"digg_count": true,
"comment_count": true,
"collect_count": true,
}
cr := middlware.GetBind[ArticleListRequest](c)
switch cr.Type {
case 1:
//查别人的用户id就是必填的
if cr.UserID == 0 {
res.FailWithMsg("用户id必填", c)
return
}
if cr.Page > 2 || cr.Limit > 10 {
res.FailWithMsg("查询更多请登录", c)
return
}
cr.Status = 0
cr.Order = ""
if cr.CollectID != 0 {
if cr.UserID == 0 {
res.FailWithMsg("请传入用户ID", c)
return
}
var userConf models.UserConfModel
err := global.DB.Take(&userConf, "user_id = ?", cr.UserID).Error
if err != nil {
res.FailWithMsg("用户不存在", c)
return
}
if !userConf.OpenCollect {
//不公开我的收藏
//在页面上,我的收藏页面不可见
//收藏列表接口不能访问,不能用这个收藏夹 id 查文章
res.FailWithMsg("用户未开启我的收藏", c)
return
}
}
case 2:
//查自己
claims, err := jwts.ParseTokenByGin(c)
if err != nil {
res.FailWithMsg("请登录", c)
return
}
cr.UserID = claims.UserId
case 3:
//管理员
claims, err := jwts.ParseTokenByGin(c)
if !(err == nil && claims.RoleId == enum.AdminRole) {
res.FailWithMsg("角色错误", c)
return
}
}
//根据收藏夹ID查文章
query := global.DB.Where("")
if cr.CollectID != 0 {
var articleIDList []uint
if cr.CollectID != -1 {
global.DB.Model(models.UserArticleCollectMode{}).Where("collect_id =? ", cr.CollectID).Select("article_id").Scan(&articleIDList)
} else {
//查这个人的所有·的收藏ID
if cr.UserID == 0 {
res.FailWithMsg("查所有的收藏文章,需要用户id", c)
return
}
global.DB.Model(models.UserArticleCollectMode{}).Where("user_id =? ", cr.UserID).Select("article_id").Scan(&articleIDList)
}
query.Where("id in ?", articleIDList)
}
if cr.Order != "" {
_, ok := orderColumnMap[cr.Order]
if !ok {
res.FailWithMsg("不支持的排序方式", c)
return
}
}
//处理用户置顶
var userTopMap = map[uint]bool{}
var adminTopMap = map[uint]bool{}
if cr.UserID != 0 {
var userTopArticleList []models.UserTopArticleModel
global.DB.Preload("UserModel").Order("created_at desc").Find(&userTopArticleList, "user_id = ?", cr.UserID)
for _, i2 := range userTopArticleList {
topArticleIdList = append(topArticleIdList, i2.ArticleID)
if i2.UserModel.Role == enum.AdminRole {
adminTopMap[i2.ArticleID] = true
}
userTopMap[i2.ArticleID] = true
}
}
//判断是否有置顶,有的话话再按照置顶列表进行排序,否则就按照时间倒叙排序
var options = common.Options{
Likes: []string{"title"},
PageInfo: cr.PageInfo,
DefaultOrder: "created_at desc",
Where: query,
PreLoads: []string{"CategoryModel", "UserModel"},
}
if len(topArticleIdList) > 0 {
options.DefaultOrder = fmt.Sprintf("%s,created_at desc", sql.ConvertSliceOrderSql(topArticleIdList)) // [1 2 3] = >(1,2,3)或者 id= 1 desc,id = 2 desc 使用辅助函数实现ConvertSliceOrderSql
}
_list, count, _ := common.ListQuery(models.ArticleModel{
UserID: cr.UserID,
CategoryID: cr.CategoryID,
Status: cr.Status,
}, options)
var list = make([]ArticleListResponse, 0)
//从缓存中获取浏览量和点赞数
collectMap := redis_article.GetAllCacheCollect()
lookMap := redis_article.GetAllCacheLook()
diggMap := redis_article.GetAllCacheDigg()
commentMap := redis_article.GetAllCacheComment() //获取评论
for _, model := range _list {
model.Content = ""
model.DiggCount = model.DiggCount + diggMap[model.ID]
model.LookCount = model.LookCount + lookMap[model.ID]
model.CollectCount = model.CollectCount + collectMap[model.ID]
model.CommentCount = model.CommentCount + commentMap[model.ID] //评论数统计
data := ArticleListResponse{
ArticleModel: model,
UserTop: userTopMap[model.ID],
AdminTop: adminTopMap[model.ID],
UserNickname: model.UserModel.Nickname,
UserAvatar: model.UserModel.Avatar,
}
//如果有关联的分类表,就将分类表中的title传递过来(使用指针,如果是)
if model.CategoryModel != nil {
data.CategoryTitle = &model.CategoryModel.Title
}
list = append(list, data)
}
res.OkWithList(list, count, c)
}
27.用户删除
api/user_api/user_remove.go
Go
package user_api
import (
"blog_server/common/res"
"blog_server/global"
"blog_server/middlware"
"blog_server/models"
"fmt"
"github.com/gin-gonic/gin"
)
func (UserApi) UserRemoveView(c *gin.Context) {
cr := middlware.GetBind[models.IdRemoveRequest](c)
var list []models.UserModel
err := global.DB.Find(&list, "id =? ", cr.IDList).Error
if len(list) > 0 {
err = global.DB.Delete(&list).Error
if err != nil {
res.FailWithMsg("删除用户失败", c)
return
}
}
res.OkWithMsg(fmt.Sprintf("成功删除%d条数据", len(list)), c)
}
models/user_model.go
Go
func (u *UserModel) BeforeDelete(tx *gorm.DB) (err error) {
var list = []any{
ArticleModel{},
ArticleDiggModel{},
CategoryModel{},
CollectModel{},
CommentModel{},
CommentDiggModel{},
LogModel{},
UserArticleCollectMode{},
UserArticleLookHistoryModel{},
UserChatActionModel{},
UserFocusModel{},
UserGlobalNotificationModel{},
UserLoginModel{},
UserTopArticleModel{},
}
for _, model := range list {
count := tx.Delete(&model, "user_id =?", u.ID).RowsAffected
logrus.Infof("删除 %s 成功%d 条", reflect.TypeOf(model).Name(), count)
}
var chatList []ChatModel
tx.Find(&chatList, "send_user_id = ? or rev_user_id =?", u.ID, u.ID).Delete(&chatList)
logrus.Infof("删除关联对话%d 条", len(chatList))
var messageList []MessageModel
tx.Find(&messageList, "rev_user_id =? ", u.ID).Delete(&messageList)
logrus.Infof("删除关联消息%d 条", len(messageList))
return nil
}
r.DELETE("user", middlware.AdminMiddleware, middlware.BindMiddleware[models.IdRemoveRequest], app.UserRemoveView)
=============================
缓存策略