(四)后台-对文章的增删改查操作
文章目录
- (四)后台-对文章的增删改查操作
- 一、RESTFUL API
- 二、路由处理
- 1、/home 后台首页页面
- 2、/upload 文件上传功能
- 三、文章模块
- 1、/list 获取文章列表 GetList
- 2、/ 新增|编辑文章 SaveOrUpdate
- 3、/top 更新文章置顶 UpdateTop
- 4、/:id 文章详情 GetDetail
- 5、/soft-delete 软删除文章 UpdateSoftDelete
- 6、/ 物理删除文章 Delete
- 7、/export 导出文章 Export
- 8、/import 导入文章 Import
一、RESTFUL API
auth.GET("/home", blogInfoAPI.GetHomeInfo) // 后台首页信息auth.POST("/upload", uploadAPI.UploadFile) // 文件上传// 文章模块articles := auth.Group("/article"){articles.GET("/list", articleAPI.GetList) // 文章列表articles.POST("", articleAPI.SaveOrUpdate) // 新增/编辑文章articles.PUT("/top", articleAPI.UpdateTop) // 更新文章置顶articles.GET("/:id", articleAPI.GetDetail) // 文章详情articles.PUT("/soft-delete", articleAPI.UpdateSoftDelete) // 软删除文章articles.DELETE("", articleAPI.Delete) // 物理删除文章articles.POST("/export", articleAPI.Export) // 导出文章articles.POST("/import", articleAPI.Import) // 导入文章}
二、路由处理
1、/home 后台首页页面
func (*BlogInfo) GetHomeInfo(c *gin.Context) {db := GetDB(c)rdb := GetRDB(c)// 获得article数量articleCount, err := model.Count(db, &model.Article{}, "status = ? AND is_delete = ?", 1, 0)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}// 获得user数量userCount, err := model.Count(db, &model.UserInfo{})if err != nil {ReturnError(c, g2.ErrDbOp, err)return}// 获得message数量messageCount, err := model.Count(db, &model.Message{})if err != nil {ReturnError(c, g2.ErrDbOp, err)return}// 获得留言数量viewCount, err := rdb.Get(rctx, g2.VIEW_COUNT).Int()if err != nil && err != redis.Nil {ReturnError(c, g2.ErrRedisOp, err)return}ReturnSuccess(c, BlogHomeVO{ArticleCount: articleCount,UserCount: userCount,MessageCount: messageCount,ViewCount: viewCount,})
}
2、/upload 文件上传功能
1.主功能函数
func (*Upload) UploadFile(c *gin.Context) {_, fileHeader, err := c.Request.FormFile("file")if err != nil {ReturnError(c, g.ErrFileReceive, err)return}oss := upload.NewOSS()filePath, _, err := oss.UploadFile(fileHeader)fmt.Println(filePath)if err != nil {ReturnError(c, g.ErrFileUpload, err)return}ReturnSuccess(c, filePath)
}
2.使用七牛云上传
// 七牛云文件上传
type Qiniu struct{}func (*Qiniu) UploadFile(file *multipart.FileHeader) (filePath, fileName string, err error) {putPolicy := storage.PutPolicy{Scope: g.GetConfig().Qiniu.Bucket,}mac := qbox.NewMac(g.GetConfig().Qiniu.AccessKey, g.GetConfig().Qiniu.SecretKey)upToken := putPolicy.UploadToken(mac)formUploader := storage.NewFormUploader(qiniuConfig())ret := storage.PutRet{}// 获取文件的 Content-TypecontentType := file.Header.Get("Content-Type")// 设置上传额外参数,包括文件名和文件的 Content-TypeputExtra := storage.PutExtra{Params: map[string]string{"x:name": "github logo"},MimeType: contentType,}f, openError := file.Open()if openError != nil {return "", "", errors.New("function file.Open() Filed, err:" + openError.Error())}defer f.Close()// 文件名格式 建议保证唯一性fileKey := fmt.Sprintf("%d%s%s", time.Now().Unix(), utils.MD5(file.Filename), path.Ext(file.Filename))putErr := formUploader.Put(context.Background(), &ret, upToken, fileKey, f, file.Size, &putExtra)if putErr != nil {return "", "", errors.New("function formUploader.Put() Filed, err:" + putErr.Error())}return g.GetConfig().Qiniu.ImgPath + "/" + ret.Key, ret.Key, nil
}func (*Qiniu) DeleteFile(key string) error {mac := qbox.NewMac(g.GetConfig().Qiniu.AccessKey, g.GetConfig().Qiniu.SecretKey)cfg := qiniuConfig()bucketManager := storage.NewBucketManager(mac, cfg)if err := bucketManager.Delete(g.GetConfig().Qiniu.Bucket, key); err != nil {return errors.New("function bucketManager.Delete() Filed, err:" + err.Error())}return nil
}// 七牛云配置信息
func qiniuConfig() *storage.Config {cfg := storage.Config{UseHTTPS: false,UseCdnDomains: false,}switch g.GetConfig().Qiniu.Zone { // 根据配置文件进行初始化空间对应的机房case "ZoneHuadong":cfg.Zone = &storage.ZoneHuadongcase "ZoneHuabei":cfg.Zone = &storage.ZoneHuabeicase "ZoneHuanan":cfg.Zone = &storage.ZoneHuanancase "ZoneBeimei":cfg.Zone = &storage.ZoneBeimeicase "ZoneXinjiapo":cfg.Zone = &storage.ZoneXinjiapo}return &cfg
}
三、文章模块
-
对文章的查询 的结构体
type ArticleQuery struct {PageQuery/*// pageQuery 结构体type PageQuery struct {Page int `form:"page_num"` // 当前页数(从1开始)Size int `form:"page_size"` // 每页条数Keyword string `form:"keyword"` // 搜索关键字}*/Title string `form:"title"`CategoryId int `form:"category_id"`TagId int `form:"tag_id"`Type int `form:"type"`Status int `form:"status"`IsDelete *bool `form:"is_delete"` }
1、/list 获取文章列表 GetList
func (*Article) GetList(c *gin.Context) {var query ArticleQueryif err := c.ShouldBindQuery(&query); err != nil {ReturnError(c, g2.ErrRequest, err)return}// 获取数据库和Redis连接db := GetDB(c)rdb := GetRDB(c)// 查询list, total, err := model.GetArticleList(db, query.Page, query.Size, query.Title, query.IsDelete, query.Status, query.Type, query.CategoryId, query.TagId)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}// 获取点赞数和浏览量likeCountMap := rdb.HGetAll(rctx, g2.ARTICLE_LIKE_COUNT).Val()viewCountZ := rdb.ZRangeWithScores(rctx, g2.ARTICLE_VIEW_COUNT, 0, -1).Val()// 获取浏览量viewCountMap := make(map[int]int)for _, article := range viewCountZ {id, _ := strconv.Atoi(article.Member.(string))viewCountMap[id] = int(article.Score)}// 封装数据data := make([]ArticleVO, 0)for _, article := range list {likeCount, _ := strconv.Atoi(likeCountMap[strconv.Itoa(article.ID)])data = append(data, ArticleVO{Article: article,LikeCount: likeCount,ViewCount: viewCountMap[article.ID],})}ReturnSuccess(c, PageResult[ArticleVO]{Size: query.Size,Page: query.Page,Total: total,List: data,})
}
2、/ 新增|编辑文章 SaveOrUpdate
-
路由功能函数
func (*Article) SaveOrUpdate(c *gin.Context) {var req AddOrEditArticleReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g2.ErrRequest, err)return}// 获取当前用户db := GetDB(c)auth, _ := CurrentUserAuth(c) // 获取当前登录的用户信息// 获取图片if req.Img == "" {req.Img = model.GetConfig(db, g2.CONFIG_ARTICLE_COVER) // 默认图片}// 获取类型if req.Type == 0 {req.Type = 1 // 默认为原创}// 保存文章article := model.Article{Model: model.Model{ID: req.ID},Title: req.Title,Desc: req.Desc,Content: req.Content,Img: req.Img,Type: req.Type,Status: req.Status,OriginalUrl: req.OriginalUrl,IsTop: req.IsTop,UserId: auth.UserInfoId,}err := model.SaveOrUpdateArticle(db, &article, req.CategoryName, req.TagNames)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, article) }
-
数据库底层操作信息
// 新增/编辑文章, 同时根据 分类名称, 标签名称 维护关联表 func SaveOrUpdateArticle(db *gorm.DB, article *Article, categoryName string, tagNames []string) error {return db.Transaction(func(tx *gorm.DB) error {// 分类不存在则创建category := Category{Name: categoryName}result := db.Model(&Category{}).Where("name", categoryName).FirstOrCreate(&category)if result.Error != nil {return result.Error}article.CategoryId = category.ID// 先 添加/更新 文章, 获取到其 IDif article.ID == 0 {result = db.Create(&article)} else {result = db.Model(&article).Where("id", article.ID).Updates(article)}if result.Error != nil {return result.Error}// 清空文章标签关联result = db.Delete(ArticleTag{}, "article_id", article.ID)if result.Error != nil {return result.Error}var articleTags []ArticleTagfor _, tagName := range tagNames {// 标签不存在则创建tag := Tag{Name: tagName}result := db.Model(&Tag{}).Where("name", tagName).FirstOrCreate(&tag)if result.Error != nil {return result.Error}articleTags = append(articleTags, ArticleTag{ArticleId: article.ID,TagId: tag.ID,})}result = db.Create(&articleTags)return result.Error}) }
3、/top 更新文章置顶 UpdateTop
// 修改置顶信息
func (*Article) UpdateTop(c *gin.Context) {var req UpdateArticleTopReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g2.ErrRequest, err)return}err := model.UpdateArticleTop(GetDB(c), req.ID, req.IsTop)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, nil)
}// model 修改数据库置顶信息
func UpdateArticleTop(db *gorm.DB, id int, isTop bool) error {result := db.Model(&Article{Model: Model{ID: id}}).Update("is_top", isTop)return result.Error
}
4、/:id 文章详情 GetDetail
// 获取文章详细信息
func (*Article) GetDetail(c *gin.Context) {id, err := strconv.Atoi(c.Param("id"))if err != nil {ReturnError(c, g2.ErrRequest, err)return}article, err := model.GetArticle(GetDB(c), id)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, article)
}// 数据库根据 id 搜索这个文章信息
func GetArticle(db *gorm.DB, id int) (data *Article, err error) {result := db.Preload("Category").Preload("Tags").Where(Article{Model: Model{ID: id}}).First(&data)return data, result.Error
}
5、/soft-delete 软删除文章 UpdateSoftDelete
func (*Article) UpdateSoftDelete(c *gin.Context) {var req SoftDeleteReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g2.ErrRequest, err)return}rows, err := model.UpdateArticleSoftDelete(GetDB(c), req.Ids, req.IsDelete)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, rows)
}// 数据库软删除信息,也就是说再回收站能够恢复的文件,所以说只需要修改软删除标志即可
// 软删除文章(修改)
func UpdateArticleSoftDelete(db *gorm.DB, ids []int, isDelete bool) (int64, error) {result := db.Model(Article{}).Where("id IN ?", ids).Update("is_delete", isDelete)if result.Error != nil {return 0, result.Error}return result.RowsAffected, nil
}
6、/ 物理删除文章 Delete
func (*Article) Delete(c *gin.Context) {var ids []intif err := c.ShouldBindJSON(&ids); err != nil {ReturnError(c, g2.ErrRequest, err)return}rows, err := model.DeleteArticle(GetDB(c), ids)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, rows)
}// 物理删除文章 把数据库里的信息删掉
func DeleteArticle(db *gorm.DB, ids []int) (int64, error) {// 删除 [文章-标签] 关联result := db.Where("article_id IN ?", ids).Delete(&ArticleTag{})if result.Error != nil {return 0, result.Error}// 删除 [文章]result = db.Where("id IN ?", ids).Delete(&Article{})if result.Error != nil {return 0, result.Error}return result.RowsAffected, nil
}
7、/export 导出文章 Export
8、/import 导入文章 Import
// 导入文章: 题目 + 内容
func (*Article) Import(c *gin.Context) {db := GetDB(c)auth, _ := CurrentUserAuth(c)_, fileHeader, err := c.Request.FormFile("file")if err != nil {ReturnError(c, g2.ErrFileReceive, err)return}fileName := fileHeader.Filenametitle := fileName[:len(fileName)-3]content, err := readFromFileHeader(fileHeader)if err != nil {ReturnError(c, g2.ErrFileReceive, err)return}defaultImg := model.GetConfig(db, g2.CONFIG_ARTICLE_COVER)err = model.ImportArticle(db, auth.ID, title, content, defaultImg)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, nil)
}// md 文件读取
func readFromFileHeader(file *multipart.FileHeader) (string, error) {open, err := file.Open()if err != nil {slog.Error("文件读取, 目标地址错误: ", err)return "", err}defer open.Close()all, err := io.ReadAll(open)if err != nil {slog.Error("文件读取失败: ", err)return "", err}return string(all), nil
}// 导入文章 也就是数据库新建文章信息
func ImportArticle(db *gorm.DB, userAuthId int, title, content, img string) error {article := Article{Title: title,Content: content,Img: img,Status: STATUS_DRAFT,Type: TYPE_ORIGINAL,UserId: userAuthId,}result := db.Create(&article)return result.Error
}