如何制作一个后台管理页面的路由以及功能实现

后台

在这里插入图片描述

文章目录

  • 后台
    • 一、RESFUL API
    • 二、各模块路由处理
      • 1、分类模块
        • 1.1、GET /list 分类列表
        • 1.2、POST / 新增|编辑分类
        • 1.3、DELETE / 删除分类
        • 1.4、GET /option 分类选项列表
      • 2、评论模块
        • 2.1、GET /list 评论列表
        • 2.2、DELETE / 删除评论
        • 2.3、PUT /review 修改评论审核
      • 3、留言模块
        • 3.1、GET /list 留言列表
        • 3.2、DELETE / 删除留言
        • 3.3、PUT /review 审核留言
      • 4、博客设置
        • 4.1、GET /about 获取关于我的info
        • 4.2、PUT /about 编辑关于我的info
      • 5、标签模块
        • 5.1、GET /list 标签列表
        • 5.2、POST / 新增|修改标签
        • 5.3、DELETE / 删除标签
        • 5.4、GET /option 标签选项列表
      • 6、友情链接
        • 6.1、GET /list 友链列表
        • 6.2、POST / 新增|修改
        • 6.3、DELETE / 删除
      • 7、资源模块
        • 7.1、GET /list 资源列表(树形)
        • 7.2、POST / 新增|修改
        • 7.3、DELETE /:id 删除
        • 7.4、PUT /anonymous 修改资源匿名访问
        • 7.5、GET /option 资源选项列表
      • 8、菜单模块
        • 8.1、GET /list 菜单列表
        • 8.2、POST / 新增|编译菜单
        • 8.3、DELETE /:id 删除菜单
        • 8.4、GET /user/list 获取当前用户的菜单
        • 8.5、GET /option 菜单选项列表(树形)
      • 9、角色模块
        • 9.1、GET /list 角色列表(树形)
        • 9.2、POST / 新增|编辑菜单
        • 9.3、DELETE / 删除角色
        • 9.4、GET /option 角色选项列表
      • 10、操作日志模块
        • 10.1、GET /list 操作日志列表
        • 10.2、DELETE / 删除日志操作
      • 11、页面模块
        • 11.1、GET /list 页面列表
        • 11.2、POST / 新增|编辑列表
        • 11.3、DELETE / 删除
      • 12、用户模块
        • 12.1、user.GET("/list", userAPI.GetList) // 用户列表
        • 12.2、user.PUT("", userAPI.Update) // 修改用户信息
        • 12.3、user.PUT("/disable", userAPI.UpdateDisable) // 修改用户禁用状态
        • 12.4、user.PUT("/current/password", userAPI.UpdateCurrentPassword) // 修改管理员密码
        • 12.5、user.GET("/info", userAPI.GetInfo)// 获取当前用户信息
        • 12.6、user.PUT("/current", userAPI.UpdateCurrent) // 修改当前用户信息
        • 12.7、user.GET("/online", userAPI.GetOnlineList) // 获取在线用户
        • 12.8、user.POST("/offline/:id", userAPI.ForceOffline) // 强制用户下线
      • 13、文章模块以及上传功能
        • 13.1、/home 后台首页页面
        • 13.2、/upload 文件上传功能
        • 13.3、/list 获取文章列表 GetList
        • 13.4、/ 新增|编辑文章 SaveOrUpdate
        • 13.4、/top 更新文章置顶 UpdateTop
        • 13.5、/:id 文章详情 GetDetail
        • 13.6、/soft-delete 软删除文章 UpdateSoftDelete
        • 13.7、/ 物理删除文章 Delete
        • 13.8、/export 导出文章 Export
        • 13.9、/import 导入文章 Import

一、RESFUL API

1、分类模块

	// 分类模块category := auth.Group("/category"){category.GET("/list", categoryAPI.GetList)     // 分类列表category.POST("", categoryAPI.SaveOrUpdate)    // 新增/编辑分类category.DELETE("", categoryAPI.Delete)        // 删除分类category.GET("/option", categoryAPI.GetOption) // 分类选项列表}

2、评论模块

	// 评论模块comment := auth.Group("/comment"){comment.GET("/list", commentAPI.GetList)        // 评论列表comment.DELETE("", commentAPI.Delete)           // 删除评论comment.PUT("/review", commentAPI.UpdateReview) // 修改评论审核}

3、留言模块

	// 留言模块message := auth.Group("/message"){message.GET("/list", messageAPI.GetList)        // 留言列表message.DELETE("", messageAPI.Delete)           // 删除留言message.PUT("/review", messageAPI.UpdateReview) // 审核留言}

4、博客设置

	// 博客设置setting := auth.Group("/setting"){setting.GET("/about", blogInfoAPI.GetAbout)    // 获取关于我setting.PUT("/about", blogInfoAPI.UpdateAbout) // 编辑关于我}

5、标签模块

	// 标签模块tag := auth.Group("/tag"){tag.GET("/list", tagAPI.GetList)     // 标签列表tag.POST("", tagAPI.SaveOrUpdate)    // 新增/编辑标签tag.DELETE("", tagAPI.Delete)        // 删除标签tag.GET("/option", tagAPI.GetOption) // 标签选项列表}

6、友情链接

	// 友情链接link := auth.Group("/link"){link.GET("/list", linkAPI.GetList)  // 友链列表link.POST("", linkAPI.SaveOrUpdate) // 新增/编辑友链link.DELETE("", linkAPI.Delete)     // 删除友链}

7、资源模块

	// 资源模块resource := auth.Group("/resource"){resource.GET("/list", resourceAPI.GetTreeList)          // 资源列表(树形)resource.POST("", resourceAPI.SaveOrUpdate)             // 新增/编辑资源resource.DELETE("/:id", resourceAPI.Delete)             // 删除资源resource.PUT("/anonymous", resourceAPI.UpdateAnonymous) // 修改资源匿名访问resource.GET("/option", resourceAPI.GetOption)          // 资源选项列表(树形)}

8、菜单模块

	// 菜单模块menu := auth.Group("/menu"){menu.GET("/list", menuAPI.GetTreeList)      // 菜单列表menu.POST("", menuAPI.SaveOrUpdate)         // 新增/编辑菜单menu.DELETE("/:id", menuAPI.Delete)         // 删除菜单menu.GET("/user/list", menuAPI.GetUserMenu) // 获取当前用户的菜单menu.GET("/option", menuAPI.GetOption)      // 菜单选项列表(树形)}

9、角色模块

	// 角色模块role := auth.Group("/role"){role.GET("/list", roleAPI.GetTreeList) // 角色列表(树形)role.POST("", roleAPI.SaveOrUpdate)    // 新增/编辑菜单role.DELETE("", roleAPI.Delete)        // 删除角色role.GET("/option", roleAPI.GetOption) // 角色选项列表(树形)}

10、操作日志模块

	// 操作日志模块operationLog := auth.Group("/operation/log"){operationLog.GET("/list", operationLogAPI.GetList) // 操作日志列表operationLog.DELETE("", operationLogAPI.Delete)    // 删除操作日志}

11、页面模块

	// 页面模块page := auth.Group("/page"){page.GET("/list", pageAPI.GetList)  // 页面列表page.POST("", pageAPI.SaveOrUpdate) // 新增/编辑页面page.DELETE("", pageAPI.Delete)     // 删除页面}

12、用户模块

	// 用户模块user := auth.Group("/user"){user.GET("/list", userAPI.GetList)          // 用户列表user.PUT("", userAPI.Update)                // 修改用户信息user.PUT("/disable", userAPI.UpdateDisable) // 修改用户禁用状态// user.PUT("/password", userAPI.UpdatePassword)                // 修改普通用户密码user.PUT("/current/password", userAPI.UpdateCurrentPassword) // 修改管理员密码user.GET("/info", userAPI.GetInfo)                           // 获取当前用户信息user.PUT("/current", userAPI.UpdateCurrent)                  // 修改当前用户信息user.GET("/online", userAPI.GetOnlineList)                   // 获取在线用户user.POST("/offline/:id", userAPI.ForceOffline)              // 强制用户下线}

13、文章模块

	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、分类模块

1.1、GET /list 分类列表
// 获取分类列表
func (*Category) GetList(c *gin.Context) {var query PageQueryif err := c.ShouldBindQuery(&query); err != nil {ReturnError(c, g.ErrRequest, err)return}data, total, err := model.GetCategoryList(GetDB(c), query.Page, query.Size, query.Keyword)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, PageResult[model.CategoryVO]{Total: total,List:  data,Size:  query.Size,Page:  query.Page,})
}// gorm 数据库查询
func GetCategoryList(db *gorm.DB, num, size int, keyword string) ([]CategoryVO, int64, error) {var list = make([]CategoryVO, 0)var total int64db = db.Table("category c").Select("c.id", "c.name", "COUNT(a.id) AS article_count", "c.created_at", "c.updated_at").Joins("LEFT JOIN article a ON c.id = a.category_id AND a.is_delete = 0 AND a.status = 1")if keyword != "" {db = db.Where("name LIKE ?", "%"+keyword+"%")}result := db.Group("c.id").Order("c.updated_at DESC").Count(&total).Scopes(Paginate(num, size)).Find(&list)return list, total, result.Error
}
1.2、POST / 新增|编辑分类
// 添加或修改分类
func (*Category) SaveOrUpdate(c *gin.Context) {var req AddOrEditCategoryReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g.ErrRequest, err)return}category, err := model.SaveOrUpdateCategory(GetDB(c), req.ID, req.Name)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, category)
}// 
func SaveOrUpdateCategory(db *gorm.DB, id int, name string) (*Category, error) {category := Category{Model: Model{ID: id},Name:  name,}var result *gorm.DBif id > 0 {result = db.Updates(category)} else {result = db.Create(&category)}return &category, result.Error
}
1.3、DELETE / 删除分类
func (*Category) Delete(c *gin.Context) {var ids []intif err := c.ShouldBindJSON(&ids); err != nil {ReturnError(c, g.ErrRequest, err)return}db := GetDB(c)// 检查分类下是否存在文章count, err := model.Count(db, &model.Article{}, "category_id in ?", ids)if err != nil {ReturnError(c, g.ErrDbOp, err)return}if count > 0 {ReturnError(c, g.ErrCateHasArt, nil)return}rows, err := model.DeleteCategory(db, ids)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, rows)
}func DeleteCategory(db *gorm.DB, ids []int) (int64, error) {result := db.Where("id IN ?", ids).Delete(Category{})if result.Error != nil {return 0, result.Error}return result.RowsAffected, nil
}
1.4、GET /option 分类选项列表
func (*Category) GetOption(c *gin.Context) {list, err := model.GetCategoryOption(GetDB(c))if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, list)
}func GetCategoryOption(db *gorm.DB) ([]OptionVO, error) {var list = make([]OptionVO, 0)result := db.Model(&Category{}).Select("id", "name").Find(&list)return list, result.Error
}

2、评论模块

2.1、GET /list 评论列表
func (*Comment) GetList(c *gin.Context) {var query CommentQueryif err := c.ShouldBindQuery(&query); err != nil {ReturnError(c, g.ErrRequest, err)return}list, total, err := model.GetCommentList(GetDB(c), query.Page, query.Size, query.Type, query.IsReview, query.Nickname)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, PageResult[model.Comment]{Total: total,List:  list,Size:  query.Size,Page:  query.Page,})
}// 获取后台评论列表
func GetCommentList(db *gorm.DB, page, size, typ int, isReview *bool, nickname string) (data []Comment, total int64, err error) {if typ != 0 {db = db.Where("type = ?", typ)}if isReview != nil {db = db.Where("is_review = ?", *isReview)}if nickname != "" {db = db.Where("nickname LIKE ?", "%"+nickname+"%")}result := db.Model(&Comment{}).Count(&total).Preload("User").Preload("User.UserInfo").Preload("ReplyUser").Preload("ReplyUser.UserInfo").Preload("Article").Order("id DESC").Scopes(Paginate(page, size)).Find(&data)return data, total, result.Error
}
2.2、DELETE / 删除评论
func (*Comment) Delete(c *gin.Context) {var ids []intif err := c.ShouldBindJSON(&ids); err != nil {ReturnError(c, g.ErrRequest, err)return}result := GetDB(c).Delete(model.Comment{}, "id in ?", ids)if result.Error != nil {ReturnError(c, g.ErrDbOp, result.Error)return}ReturnSuccess(c, result.RowsAffected)
}
2.3、PUT /review 修改评论审核
func (*Comment) UpdateReview(c *gin.Context) {var req UpdateReviewReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g.ErrRequest, err)return}maps := map[string]any{"is_review": req.IsReview}result := GetDB(c).Model(model.Comment{}).Where("id in ?", req.Ids).Updates(maps)if result.Error != nil {ReturnError(c, g.ErrDbOp, result.Error)return}ReturnSuccess(c, result.RowsAffected)
}

3、留言模块

3.1、GET /list 留言列表
func (*Message) GetList(c *gin.Context) {var query MessageQueryif err := c.ShouldBindQuery(&query); err != nil {ReturnError(c, g.ErrRequest, err)return}data, total, err := model.GetMessageList(GetDB(c), query.Page, query.Size, query.Nickname, query.IsReview)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, PageResult[model.Message]{Total: total,List:  data,Size:  query.Size,Page:  query.Page,})
}func GetMessageList(db *gorm.DB, num, size int, nickname string, isReview *bool) (list []Message, total int64, err error) {db = db.Model(&Message{})if nickname != "" {db = db.Where("nickname LIKE ?", "%"+nickname+"%")}if isReview != nil {db = db.Where("is_review = ?", isReview)}db.Count(&total)result := db.Order("created_at DESC").Scopes(Paginate(num, size)).Find(&list)return list, total, result.Error
}
3.2、DELETE / 删除留言
func (*Message) Delete(c *gin.Context) {var ids []intif err := c.ShouldBindJSON(&ids); err != nil {ReturnError(c, g.ErrRequest, err)return}rows, err := model.DeleteMessages(GetDB(c), ids)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, rows)
}func DeleteMessages(db *gorm.DB, ids []int) (int64, error) {result := db.Where("id in ?", ids).Delete(&Message{})return result.RowsAffected, result.Error
}
3.3、PUT /review 审核留言
func (*Message) UpdateReview(c *gin.Context) {var req UpdateReviewReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g.ErrRequest, err)return}rows, err := model.UpdateMessagesReview(GetDB(c), req.Ids, req.IsReview)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, rows)
}func UpdateMessagesReview(db *gorm.DB, ids []int, isReview bool) (int64, error) {result := db.Model(&Message{}).Where("id in ?", ids).Update("is_review", isReview)return result.RowsAffected, result.Error
}

4、博客设置

4.1、GET /about 获取关于我的info
func (*BlogInfo) GetAbout(c *gin.Context) {ReturnSuccess(c, model.GetConfig(GetDB(c), g2.CONFIG_ABOUT))
}func GetConfig(db *gorm.DB, key string) string {var config Configresult := db.Where("key", key).First(&config)if result.Error != nil {return ""}return config.Value
}
4.2、PUT /about 编辑关于我的info
func (*BlogInfo) UpdateAbout(c *gin.Context) {var req AboutReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g2.ErrRequest, err)return}err := model.CheckConfig(GetDB(c), g2.CONFIG_ABOUT, req.Content)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, req.Content)
}func CheckConfig(db *gorm.DB, key, value string) error {var config Configresult := db.Where("key", key).FirstOrCreate(&config)if result.Error != nil {return result.Error}config.Value = valueresult = db.Save(&config)return result.Error
}

5、标签模块

5.1、GET /list 标签列表
// 根据筛选条件 获取标签列表
func (*Tag) GetList(c *gin.Context) {var query PageQueryif err := c.ShouldBindQuery(&query); err != nil {ReturnError(c, g.ErrRequest, err)return}data, total, err := model.GetTagList(GetDB(c), query.Page, query.Size, query.Keyword)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, PageResult[model.TagVO]{Total: total,List:  data,Size:  query.Size,Page:  query.Page,})
}// 根据参数搜索
func GetTagList(db *gorm.DB, page, size int, keyword string) (list []TagVO, total int64, err error) {db = db.Table("tag t").Joins("LEFT JOIN article_tag at ON t.id = at.tag_id").Select("t.id", "t.name", "COUNT(at.article_id) AS article_count", "t.created_at", "t.updated_at")if keyword != "" {db = db.Where("name LIKE ?", "%"+keyword+"%")}result := db.Group("t.id").Order("t.updated_at DESC").Count(&total).Scopes(Paginate(page, size)).Find(&list)return list, total, result.Error
}
5.2、POST / 新增|修改标签
func (*Tag) SaveOrUpdate(c *gin.Context) {var form AddOrEditTagReqif err := c.ShouldBindJSON(&form); err != nil {ReturnError(c, g.ErrRequest, err)return}tag, err := model.SaveOrUpdateTag(GetDB(c), form.ID, form.Name)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, tag)
}func SaveOrUpdateTag(db *gorm.DB, id int, name string) (*Tag, error) {tag := Tag{Model: Model{ID: id},Name:  name,}var result *gorm.DBif id > 0 {result = db.Updates(tag)} else {result = db.Create(&tag)}return &tag, result.Error
}
5.3、DELETE / 删除标签
// 添加强制删除,有关联数据将数据删除
func (*Tag) Delete(c *gin.Context) {var ids []intif err := c.ShouldBindJSON(&ids); err != nil {ReturnError(c, g.ErrRequest, err)return}db := GetDB(c)// 检查标签下面有没有文章count, err := model.Count(db, &model.ArticleTag{}, "tag_id in ?", ids)if err != nil {ReturnError(c, g.ErrDbOp, err)return}if count > 0 {// ReturnError(c, g.ERROR_TAG_ART_EXIST, nil)ReturnError(c, g.ErrTagHasArt, nil)return}result := db.Delete(model.Tag{}, "id in ?", ids)if result.Error != nil {ReturnError(c, g.ErrDbOp, result.Error)return}ReturnSuccess(c, result.RowsAffected)
}
5.4、GET /option 标签选项列表
func (*Tag) GetOption(c *gin.Context) {list, err := model.GetTagOption(GetDB(c))if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, list)
}func GetTagOption(db *gorm.DB) ([]OptionVO, error) {list := make([]OptionVO, 0)result := db.Model(&Tag{}).Select("id", "name").Find(&list)return list, result.Error
}

6、友情链接

6.1、GET /list 友链列表
func (*Link) GetList(c *gin.Context) {var query PageQueryif err := c.ShouldBindQuery(&query); err != nil {ReturnError(c, g.ErrRequest, err)return}data, total, err := model.GetLinkList(GetDB(c), query.Page, query.Size, query.Keyword)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, PageResult[model.FriendLink]{Total: total,List:  data,Size:  query.Size,Page:  query.Page,})
}func GetLinkList(db *gorm.DB, num, size int, keyword string) (list []FriendLink, total int64, err error) {db = db.Model(&FriendLink{})if keyword != "" {db = db.Where("name LIKE ?", "%"+keyword+"%")db = db.Or("address LIKE ?", "%"+keyword+"%")db = db.Or("intro LIKE ?", "%"+keyword+"%")}db.Count(&total)result := db.Order("created_at DESC").Scopes(Paginate(num, size)).Find(&list)return list, total, result.Error
}
6.2、POST / 新增|修改
func (*Link) SaveOrUpdate(c *gin.Context) {var req AddOrEditLinkReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g.ErrRequest, err)return}link, err := model.SaveOrUpdateLink(GetDB(c), req.ID, req.Name, req.Avatar, req.Address, req.Intro)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, link)
}func SaveOrUpdateLink(db *gorm.DB, id int, name, avatar, address, intro string) (*FriendLink, error) {link := FriendLink{Model:   Model{ID: id},Name:    name,Avatar:  avatar,Address: address,Intro:   intro,}var result *gorm.DBif id > 0 {result = db.Updates(&link)} else {result = db.Create(&link)}return &link, result.Error
}
6.3、DELETE / 删除
func (*Link) Delete(c *gin.Context) {var ids []intif err := c.ShouldBindJSON(&ids); err != nil {ReturnError(c, g.ErrRequest, err)return}result := GetDB(c).Delete(&model.FriendLink{}, "id in ?", ids)if result.Error != nil {ReturnError(c, g.ErrDbOp, result.Error)return}ReturnSuccess(c, result.RowsAffected)
}

7、资源模块

7.1、GET /list 资源列表(树形)
// 获取资源列表(树形)
func (*Resource) GetTreeList(c *gin.Context) {keyword := c.Query("keyword")resourceList, err := model.GetResourceList(GetDB(c), keyword)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, resources2ResourceVos(resourceList))
}func GetResourceList(db *gorm.DB, keyword string) (list []Resource, err error) {if keyword != "" {db = db.Where("name like ?", "%"+keyword+"%")}result := db.Find(&list)return list, result.Error
}
7.2、POST / 新增|修改
// 新增或编辑资源, 关联更新 casbin_rule 中数据
func (*Resource) SaveOrUpdate(c *gin.Context) {var req AddOrEditResourceReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g.ErrRequest, err)return}db := GetDB(c)err := model.SaveOrUpdateResource(db, req.ID, req.ParentId, req.Name, req.Url, req.Method)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, nil)
}func SaveOrUpdateResource(db *gorm.DB, id, pid int, name, url, method string) error {resource := Resource{Model:    Model{ID: id},Name:     name,Url:      url,Method:   method,ParentId: pid,}var result *gorm.DBif id > 0 {result = db.Updates(&resource)} else {result = db.Create(&resource)// TODO: ????// * 解决前端的 BUG: 级联选中某个父节点后, 新增的子节点默认会展示被选中, 实际上未被选中值// * 解决方案: 新增子节点后, 删除该节点对应的父节点与角色的关联关系db.Delete(RoleResource{}, "resource_id", id)}return result.Error
}
7.3、DELETE /:id 删除
// TODO: 考虑删除模块后, 其子资源怎么办? 目前做法是有子资源无法删除
// TODO: 强制删除?
func (*Resource) Delete(c *gin.Context) {resourceId, err := strconv.Atoi(c.Param("id"))if err != nil {ReturnError(c, g.ErrRequest, err)return}db := GetDB(c)// 检查该资源是否被角色使用use, _ := model.CheckResourceInUse(db, resourceId)if use {ReturnError(c, g.ErrResourceUsedByRole, nil)return}// 获取该资源resource, err := model.GetResourceById(db, resourceId)if err != nil {if errors.Is(err, gorm.ErrRecordNotFound) {ReturnError(c, g.ErrResourceNotExist, nil)return}ReturnError(c, g.ErrDbOp, err)return}// 如果作为模块, 检查模块下是否有子资源if resource.ParentId == 0 {hasChild, _ := model.CheckResourceHasChild(db, resourceId)if hasChild {ReturnError(c, g.ErrResourceHasChildren, nil)return}}rows, err := model.DeleteResource(db, resourceId)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, rows)
}// 检查该资源是否被角色使用
func CheckResourceInUse(db *gorm.DB, id int) (bool, error) {var count int64result := db.Model(&RoleResource{}).Where("resource_id = ?", id).Count(&count)return count > 0, result.Error
}// 获取该资源
func GetResourceById(db *gorm.DB, id int) (resource Resource, err error) {result := db.First(&resource, id)return resource, result.Error
}// 删除该资源 
func DeleteResource(db *gorm.DB, id int) (int, error) {result := db.Delete(&Resource{}, id)if result.Error != nil {return 0, result.Error}return int(result.RowsAffected), nil
}
7.4、PUT /anonymous 修改资源匿名访问
// 编辑资源的匿名访问, 关联更新 casbin_rule 中数据
func (*Resource) UpdateAnonymous(c *gin.Context) {var req EditAnonymousReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g.ErrRequest, err)return}err := model.UpdateResourceAnonymous(GetDB(c), req.ID, req.Anonymous)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, nil)
}// mysql 修改
func UpdateResourceAnonymous(db *gorm.DB, id int, anonymous bool) error {result := db.Model(&Resource{}).Where("id = ?", id).Update("anonymous", anonymous)return result.Error
}
7.5、GET /option 资源选项列表
// 获取数据选项(树形)
func (*Resource) GetOption(c *gin.Context) {result := make([]TreeOptionVO, 0)db := GetDB(c)resources, err := model.GetResourceList(db, "")if err != nil {ReturnError(c, g.ErrDbOp, err)return}parentList := getModuleList(resources)childrenMap := getChildrenMap(resources)for _, item := range parentList {var children []TreeOptionVOfor _, re := range childrenMap[item.ID] {children = append(children, TreeOptionVO{ID:    re.ID,Label: re.Name,})}result = append(result, TreeOptionVO{ID:       item.ID,Label:    item.Name,Children: children,})}ReturnSuccess(c, result)
}// 获取一级资源 (parent_id == 0)
func getModuleList(resources []model.Resource) []model.Resource {list := make([]model.Resource, 0)for _, r := range resources {if r.ParentId == 0 {list = append(list, r)}}return list
}// 存储每个节点对应 [子资源列表] 的 map
// key: resourceId
// value: childrenList
func getChildrenMap(resources []model.Resource) map[int][]model.Resource {m := make(map[int][]model.Resource)for _, r := range resources {if r.ParentId != 0 {m[r.ParentId] = append(m[r.ParentId], r)}}return m
}

8、菜单模块

8.1、GET /list 菜单列表
func (*Menu) GetTreeList(c *gin.Context) {keyword := c.Query("keyword")menuList, _, err := model.GetMenuList(GetDB(c), keyword)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, menus2MenuVos(menuList))
}func GetMenuList(db *gorm.DB, keyword string) (list []Menu, total int64, err error) {db = db.Model(&Menu{})if keyword != "" {db = db.Where("name like ?", "%"+keyword+"%")}result := db.Count(&total).Find(&list)return list, total, result.Error
}
8.2、POST / 新增|编译菜单
func (*Menu) SaveOrUpdate(c *gin.Context) {var req model.Menuif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g.ErrRequest, err)return}if err := model.SaveOrUpdateMenu(GetDB(c), &req); err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, nil)
}func SaveOrUpdateMenu(db *gorm.DB, menu *Menu) error {var result *gorm.DBif menu.ID > 0 {result = db.Model(menu).Select("name", "path", "component", "icon", "redirect", "parent_id", "order_num", "catalogue", "hidden", "keep_alive", "external").Updates(menu)} else {result = db.Create(menu)}return result.Error
}
8.3、DELETE /:id 删除菜单
func (*Menu) Delete(c *gin.Context) {menuId, err := strconv.Atoi(c.Param("id"))if err != nil {ReturnError(c, g.ErrRequest, err)return}db := GetDB(c)// 检查要删除的菜单是否被角色使用use, _ := model.CheckMenuInUse(db, menuId)if use {ReturnError(c, g.ErrMenuUsedByRole, nil)return}// 如果是一级菜单, 检查其是否有子菜单menu, err := model.GetMenuById(db, menuId)if err != nil {if errors.Is(err, gorm.ErrRecordNotFound) {ReturnError(c, g.ErrMenuNotExist, nil)return}ReturnError(c, g.ErrDbOp, err)return}// 一级菜单下有子菜单, 不允许删除if menu.ParentId == 0 {has, _ := model.CheckMenuHasChild(db, menuId)if has {ReturnError(c, g.ErrMenuHasChildren, nil)return}}if err = model.DeleteMenu(db, menuId); err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, nil)
}func DeleteMenu(db *gorm.DB, id int) error {result := db.Delete(&Menu{}, id)return result.Error
}
8.4、GET /user/list 获取当前用户的菜单
// 获取当前用户菜单: 生成后台管理界面的菜单
func (*Menu) GetUserMenu(c *gin.Context) {db := GetDB(c)auth, _ := CurrentUserAuth(c)var menus []model.Menuvar err errorif auth.IsSuper {menus, err = model.GetAllMenuList(db)} else {menus, err = model.GetMenuListByUserId(GetDB(c), auth.ID)}if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, menus2MenuVos(menus))
}// 根据 user_id 获取菜单列表
func GetMenuListByUserId(db *gorm.DB, id int) (menus []Menu, err error) {var userAuth UserAuthresult := db.Where(&UserAuth{Model: Model{ID: id}}).Preload("Roles").Preload("Roles.Menus").First(&userAuth)if result.Error != nil {return nil, result.Error}set := make(map[int]Menu)for _, role := range userAuth.Roles {for _, menu := range role.Menus {set[menu.ID] = menu}}for _, menu := range set {menus = append(menus, menu)}return menus, nil
}// 获取所有菜单列表(超级管理员用)
func GetAllMenuList(db *gorm.DB) (menu []Menu, err error) {result := db.Find(&menu)return menu, result.Error
}
8.5、GET /option 菜单选项列表(树形)
func (*Menu) GetOption(c *gin.Context) {menus, _, err := model.GetMenuList(GetDB(c), "")if err != nil {ReturnError(c, g.ErrDbOp, err)return}result := make([]TreeOptionVO, 0)for _, menu := range menus2MenuVos(menus) {option := TreeOptionVO{ID: menu.ID, Label: menu.Name}for _, child := range menu.Children {option.Children = append(option.Children, TreeOptionVO{ID: child.ID, Label: child.Name})}result = append(result, option)}ReturnSuccess(c, result)
}func GetMenuList(db *gorm.DB, keyword string) (list []Menu, total int64, err error) {db = db.Model(&Menu{})if keyword != "" {db = db.Where("name like ?", "%"+keyword+"%")}result := db.Count(&total).Find(&list)return list, total, result.Error
}func DeleteMenu(db *gorm.DB, id int) error {result := db.Delete(&Menu{}, id)return result.Error
}

9、角色模块

9.1、GET /list 角色列表(树形)
func (*Role) GetTreeList(c *gin.Context) {var query PageQueryif err := c.ShouldBindQuery(&query); err != nil {ReturnError(c, g.ErrRequest, err)return}db := GetDB(c)result := make([]model.RoleVO, 0)list, total, err := model.GetRoleList(db, query.Page, query.Size, query.Keyword)if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {ReturnError(c, g.ErrDbOp, err)return}for _, role := range list {role.ResourceIds, _ = model.GetResourceIdsByRoleId(db, role.ID)role.MenuIds, _ = model.GetMenuIdsByRoleId(db, role.ID)result = append(result, role)}ReturnSuccess(c, PageResult[model.RoleVO]{Size:  query.Size,Page:  query.Page,Total: total,List:  result,})
}func GetRoleList(db *gorm.DB, num, size int, keyword string) (list []RoleVO, total int64, err error) {db = db.Model(&Role{})if keyword != "" {db = db.Where("name like ?", "%"+keyword+"%")}db.Count(&total)result := db.Select("id", "name", "label", "created_at", "is_disable").Scopes(Paginate(num, size)).Find(&list)return list, total, result.Error
}
9.2、POST / 新增|编辑菜单
func (*Role) SaveOrUpdate(c *gin.Context) {var req AddOrEditRoleReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g.ErrRequest, err)return}db := GetDB(c)if req.ID == 0 {err := model.SaveRole(db, req.Name, req.Label)if err != nil {ReturnError(c, g.ErrDbOp, err)return}} else {err := model.UpdateRole(db, req.ID, req.Name, req.Label, req.IsDisable, req.ResourceIds, req.MenuIds)if err != nil {ReturnError(c, g.ErrDbOp, err)return}}ReturnSuccess(c, nil)
}func SaveRole(db *gorm.DB, name, label string) error {role := Role{Name:  name,Label: label,}result := db.Create(&role)return result.Error
}
9.3、DELETE / 删除角色
func (*Role) Delete(c *gin.Context) {var ids []intif err := c.ShouldBindJSON(&ids); err != nil {ReturnError(c, g.ErrRequest, err)return}err := model.DeleteRoles(GetDB(c), ids)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, nil)
}// 删除角色: 事务删除 role, role_resource, role_menu
func DeleteRoles(db *gorm.DB, ids []int) error {return db.Transaction(func(tx *gorm.DB) error {result := db.Delete(&Role{}, "id in ?", ids)if result.Error != nil {return result.Error}result = db.Delete(&RoleResource{}, "role_id in ?", ids)if result.Error != nil {return result.Error}result = db.Delete(&RoleMenu{}, "role_id in ?", ids)if result.Error != nil {return result.Error}return nil})
}
9.4、GET /option 角色选项列表
func (*Role) GetOption(c *gin.Context) {list, err := model.GetRoleOption(GetDB(c))if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, list)
}func GetRoleOption(db *gorm.DB) (list []OptionVO, err error) {result := db.Model(&Role{}).Select("id", "name").Find(&list)if result.Error != nil {return nil, result.Error}return list, nil
}

10、操作日志模块

10.1、GET /list 操作日志列表
func (*OperationLog) GetList(c *gin.Context) {var query PageQueryif err := c.ShouldBindQuery(&query); err != nil {ReturnError(c, g.ErrRequest, err)return}list, total, err := model.GetOperationLogList(GetDB(c), query.Page, query.Size, query.Keyword)if err != nil {ReturnError(c, g.ErrDbOp, err)return}ReturnSuccess(c, PageResult[model.OperationLog]{Total: total,List:  list,Size:  query.Size,Page:  query.Page,})
}func GetOperationLogList(db *gorm.DB, num, size int, keyword string) (data []OperationLog, total int64, err error) {db = db.Model(&OperationLog{})if keyword != "" {db = db.Where("opt_module LIKE ?", "%"+keyword+"%").Or("opt_desc LIKE ?", "%"+keyword+"%")}db.Count(&total)result := db.Order("created_at DESC").Scopes(Paginate(num, size)).Find(&data)return data, total, result.Error
}
10.2、DELETE / 删除日志操作
func (*OperationLog) Delete(c *gin.Context) {var ids []intif err := c.ShouldBindJSON(&ids); err != nil {ReturnError(c, g.ErrRequest, err)return}result := GetDB(c).Delete(&model.OperationLog{}, "id in ?", ids)if result.Error != nil {ReturnError(c, g.ErrDbOp, result.Error)return}ReturnSuccess(c, result.RowsAffected)
}

11、页面模块

11.1、GET /list 页面列表
func (*Page) GetList(c *gin.Context) {db := GetDB(c)rdb := GetRDB(c)// get from cachecache, err := getPageCache(rdb)if cache != nil && err == nil {slog.Debug("[handle-page-GetList] get page list from cache")ReturnSuccess(c, cache)return}switch err {case redis.Nil:breakdefault:ReturnError(c, g.ErrRedisOp, err)return}// get from dbdata, _, err := model.GetPageList(db)if err != nil {ReturnError(c, g.ErrDbOp, err)return}// add to cacheif err := addPageCache(GetRDB(c), data); err != nil {ReturnError(c, g.ErrRedisOp, err)return}ReturnSuccess(c, data)
}// 从 Redis 中获取页面列表缓存
// rdb.Get 如果不存在 key, 会返回 redis.Nil 错误
func getPageCache(rdb *redis.Client) (cache []model.Page, err error) {s, err := rdb.Get(rctx, g.PAGE).Result()if err != nil {return nil, err}if err := json.Unmarshal([]byte(s), &cache); err != nil {return nil, err}return cache, nil
}
11.2、POST / 新增|编辑列表
func (*Page) SaveOrUpdate(c *gin.Context) {var req model.Pageif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g.ErrRequest, err)return}db := GetDB(c)rdb := GetRDB(c)page, err := model.SaveOrUpdatePage(db, req.ID, req.Name, req.Label, req.Cover)if err != nil {ReturnError(c, g.ErrDbOp, err)return}// delete cacheif err := removePageCache(rdb); err != nil {ReturnError(c, g.ErrRedisOp, err)return}ReturnSuccess(c, page)
}func SaveOrUpdatePage(db *gorm.DB, id int, name, label, cover string) (*Page, error) {page := Page{Model: Model{ID: id},Name:  name,Label: label,Cover: cover,}var result *gorm.DBif id > 0 {result = db.Updates(&page)} else {result = db.Create(&page)}return &page, result.Error
}// 删除 Redis 中页面列表缓存
func removePageCache(rdb *redis.Client) error {return rdb.Del(rctx, g.PAGE).Err()
}
11.3、DELETE / 删除
func (*Page) Delete(c *gin.Context) {var ids []intif err := c.ShouldBindJSON(&ids); err != nil {ReturnError(c, g.ErrRequest, err)return}result := GetDB(c).Delete(model.Page{}, "id in ?", ids)if result.Error != nil {ReturnError(c, g.ErrDbOp, result.Error)return}// delete cacheif err := removePageCache(GetRDB(c)); err != nil {ReturnError(c, g.ErrRedisOp, err)return}ReturnSuccess(c, result.RowsAffected)
}

12、用户模块

12.1、user.GET(“/list”, userAPI.GetList) // 用户列表
// 获取用户列表
func (*User) GetList(c *gin.Context) {var query UserQueryif err := c.ShouldBindQuery(&query); err != nil {ReturnError(c, g2.ErrRequest, err)return}list, count, err := model.GetUserList(GetDB(c), query.Page, query.Size, query.LoginType, query.Nickname, query.Username)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, PageResult[model.UserAuth]{Size:  query.Size,Page:  query.Page,Total: count,List:  list,})
}// model
func GetUserList(db *gorm.DB, page, size int, loginType int8, nickname, username string) (list []UserAuth, total int64, err error) {if loginType != 0 {db = db.Where("login_type = ?", loginType)}if username != "" {db = db.Where("username LIKE ?", "%"+username+"%")}result := db.Model(&UserAuth{}).Joins("LEFT JOIN user_info ON user_info.id = user_auth.user_info_id").Where("user_info.nickname LIKE ?", "%"+nickname+"%").Preload("UserInfo").Preload("Roles").Count(&total).Scopes(Paginate(page, size)).Find(&list)return list, total, result.Error
}
12.2、user.PUT(“”, userAPI.Update) // 修改用户信息
// 更新用户信息: 昵称 + 角色
func (*User) Update(c *gin.Context) {var req UpdateUserReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g2.ErrRequest, err)return}if err := model.UpdateUserNicknameAndRole(GetDB(c), req.UserAuthId, req.Nickname, req.RoleIds); err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, nil)
}// 数据库修改 gorm
// 更新用户昵称及角色信息
func UpdateUserNicknameAndRole(db *gorm.DB, authId int, nickname string, roleIds []int) error {userAuth, err := GetUserAuthInfoById(db, authId)if err != nil {return err}userInfo := UserInfo{Model:    Model{ID: userAuth.UserInfoId},Nickname: nickname,}result := db.Model(&userInfo).Updates(userInfo)if result.Error != nil {return result.Error}// 至少有一个角色if len(roleIds) == 0 {return nil}// 更新用户角色, 清空原本的 user_role 关系, 添加新的关系result = db.Where(UserAuthRole{UserAuthId: userAuth.UserInfoId}).Delete(UserAuthRole{})if result.Error != nil {return result.Error}var userRoles []UserAuthRolefor _, id := range roleIds {userRoles = append(userRoles, UserAuthRole{RoleId:     id,UserAuthId: userAuth.ID,})}result = db.Create(&userRoles)return result.Error
}
12.3、user.PUT(“/disable”, userAPI.UpdateDisable) // 修改用户禁用状态
// 修改用户禁用状态
func (*User) UpdateDisable(c *gin.Context) {var req UpdateUserDisableReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g2.ErrRequest, err)return}err := model.UpdateUserDisable(GetDB(c), req.UserAuthId, req.IsDisable)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, nil)
}// 修改用户禁用信息
func UpdateUserDisable(db *gorm.DB, id int, isDisable bool) error {userAuth := UserAuth{Model:     Model{ID: id},IsDisable: isDisable,}result := db.Model(&userAuth).Select("is_disable").Updates(&userAuth)return result.Error
}
12.4、user.PUT(“/current/password”, userAPI.UpdateCurrentPassword) // 修改管理员密码
// 修改当前用户密码: 需要输入旧密码进行验证
func (*User) UpdateCurrentPassword(c *gin.Context) {var req UpdateCurrentPasswordReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g2.ErrRequest, err)return}auth, _ := CurrentUserAuth(c)if !utils.BcryptCheck(req.OldPassword, auth.Password) {ReturnError(c, g2.ErrOldPassword, nil)return}hashPassword, _ := utils.BcryptHash(req.NewPassword)err := model.UpdateUserPassword(GetDB(c), auth.ID, hashPassword)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}// TODO: 修改完密码后,强制用户下线ReturnSuccess(c, nil)
}// 修改管理员密码
func UpdateUserPassword(db *gorm.DB, id int, password string) error {userAuth := UserAuth{Model:    Model{ID: id},Password: password,}result := db.Model(&userAuth).Updates(userAuth)return result.Error
}
12.5、user.GET(“/info”, userAPI.GetInfo)// 获取当前用户信息
// 根据 Token 获取用户信息
func (*User) GetInfo(c *gin.Context) {rdb := GetRDB(c)user, err := CurrentUserAuth(c)if err != nil {ReturnError(c, g2.ErrTokenRuntime, err)return}userInfoVO := model.UserInfoVO{UserInfo: *user.UserInfo}userInfoVO.ArticleLikeSet, err = rdb.SMembers(rctx, g2.ARTICLE_USER_LIKE_SET+strconv.Itoa(user.UserInfoId)).Result()if err != nil {ReturnError(c, g2.ErrDbOp, err)return}userInfoVO.CommentLikeSet, err = rdb.SMembers(rctx, g2.COMMENT_USER_LIKE_SET+strconv.Itoa(user.UserInfoId)).Result()if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, userInfoVO)
}
12.6、user.PUT(“/current”, userAPI.UpdateCurrent) // 修改当前用户信息
// 更新当前用户信息, 不需要传 id, 从 Token 中解析出来
func (*User) UpdateCurrent(c *gin.Context) {var req UpdateCurrentUserReqif err := c.ShouldBindJSON(&req); err != nil {ReturnError(c, g2.ErrRequest, err)return}auth, _ := CurrentUserAuth(c)err := model.UpdateUserInfo(GetDB(c), auth.UserInfoId, req.Nickname, req.Avatar, req.Intro, req.Website)if err != nil {ReturnError(c, g2.ErrDbOp, err)return}ReturnSuccess(c, nil)
}// 修改当前用户信息
func UpdateUserInfo(db *gorm.DB, id int, nickname, avatar, intro, website string) error {userInfo := UserInfo{Model:    Model{ID: id},Nickname: nickname,Avatar:   avatar,Intro:    intro,Website:  website,}result := db.Select("nickname", "avatar", "intro", "website").Updates(userInfo)return result.Error
}
12.7、user.GET(“/online”, userAPI.GetOnlineList) // 获取在线用户
// 查询当前在线用户
func (*User) GetOnlineList(c *gin.Context) {keyword := c.Query("keyword")rdb := GetRDB(c)onlineList := make([]model.UserAuth, 0)keys := rdb.Keys(rctx, g2.ONLINE_USER+"*").Val()for _, key := range keys {var auth model.UserAuthval := rdb.Get(rctx, key).Val()json.Unmarshal([]byte(val), &auth)if keyword != "" &&!strings.Contains(auth.Username, keyword) &&!strings.Contains(auth.UserInfo.Nickname, keyword) {continue}onlineList = append(onlineList, auth)}// 根据上次登录时间进行排序sort.Slice(onlineList, func(i, j int) bool {return onlineList[i].LastLoginTime.Unix() > onlineList[j].LastLoginTime.Unix()})ReturnSuccess(c, onlineList)
}
12.8、user.POST(“/offline/:id”, userAPI.ForceOffline) // 强制用户下线
// 强制离线
func (*User) ForceOffline(c *gin.Context) {id := c.Param("id")uid, err := strconv.Atoi(id)if err != nil {ReturnError(c, g2.ErrRequest, err)return}auth, err := CurrentUserAuth(c)if err != nil {ReturnError(c, g2.ErrUserAuth, err)return}// 不能离线自己if auth.ID == uid {ReturnError(c, g2.ErrForceOfflineSelf, nil)return}rdb := GetRDB(c)onlineKey := g2.ONLINE_USER + strconv.Itoa(uid)offlineKey := g2.OFFLINE_USER + strconv.Itoa(uid)rdb.Del(rctx, onlineKey)rdb.Set(rctx, offlineKey, auth, time.Hour)ReturnSuccess(c, "强制离线成功")
}

13、文章模块以及上传功能

13.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,})
}
13.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"`
}
13.3、/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,})
}
13.4、/ 新增|编辑文章 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})
    }
    
13.4、/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
}
13.5、/: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
}
13.6、/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
}
13.7、/ 物理删除文章 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
}
13.8、/export 导出文章 Export
sucss
13.9、/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
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/5103.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux基础IO(下)

目录 1. 缓冲区 1.1 定义 1.2 理解缓冲区 1.2.1 为什么要有缓冲区 1.2.2 缓冲区的工作原理 缓冲区什么时候写入,什么时候刷新? 2. 文件系统 2.1 什么是文件系统? 2.2 为什么要有文件系统? 2.3 认识文件的管理结构 2.…

机器学习:深入解析SVM的核心概念(问题与解答篇)【二、对偶问题】

对偶问题 **问题一:什么叫做凸二次优化问题?而且为什么符合凸二次优化问题?**为什么约束条件也是凸的半空间(Half-Space)凸集(Convex Set)半空间是凸集的例子SVM 约束定义的半空间总结 **问题二…

Flutter创建自定义的软键盘

参考代码: Flutter - Create Custom Keyboard Examples 本文贴出的代码实现了一个输入十六进制数据的键盘: (1)支持长按退格键连续删除字符; (2)可通过退格键删除选中的文字; &…

Spark-机器学习(8)分类学习之随机森林

在之前的文章中,我们学习了分类学习之支持向量机决策树支持向量机,并带来简单案例,学习用法。想了解的朋友可以查看这篇文章。同时,希望我的文章能帮助到你,如果觉得我的文章写的不错,请留下你宝贵的点赞&a…

【论文阅读——基于拍卖的水平联邦学习后付款激励机制设计与声誉和贡献度测量】

1.原文名称 Auction-Based Ex-Post-Payment Incentive Mechanism Design for Horizontal Federated Learning with Reputation and Contribution Measurement 2.本文的贡献 我们提出了一种贡献度测量方法。我们建立了一个声誉系统。声誉易于下降,难以提高。结合声…

第6篇:创建Nios II工程之控制LED<一>

Q:还记得第1篇吗?设计简单的逻辑电路,控制DE2-115开发板上LED的亮与熄灭,一行Verilog HDL的assign赋值语句即可实现。本期开始创建Nios II工程,用C语言代码控制DE2-115开发板上的LED实现流水灯效果。 A:在…

Windows编译OpenCV及扩展模块

OpenCV官网只提供了OpenCV Windows 64位动态库且不包括扩展模块,如果需要32位动态库,或者需要扩展模块的功能,则需要下载源码进行编译。 1. 版本说明与下载地址 OpenCV下载 https://github.com/opencv/opencv/releases/tag/4.9.0 OpenCV扩展模…

企业选择内外网文件摆渡平台的常见三大误区

网络隔离技术现在已经广泛应用于企业安全管理中,企业使用逻辑隔离或物理隔离的方式将网络隔离为内外网进而隔绝外部有害网络攻击,保护内部重要数据资产,但网络隔离后企业仍存在数据交换的需求,此时就需要内外网文件摆渡平台来承担…

人工智能_大模型044_模型微调004_随机梯度下降优化_常见损失计算算法_手写简单神经网络_实现手写体识别---人工智能工作笔记0179

然后对于,梯度下降,为了让训练的速度更好,更快的下降,又做了很多算法,可以看到 这里要知道Transformer中最常用的Adam 和 AdamW这两种算法. 当然,这些算法都是用于优化神经网络中的参数,以最小化损失函数。下面我会尽量以通俗易懂的方式解释它们的原理和适用场景。 1. **L-…

selenium设置元素隐藏和显示

常见元素隐藏情况 在HTML中,由于页面美化和用户交互的需求,元素隐藏的使用非常常见,比如下拉菜单、内容折叠、对话框以及上传文件框等。隐藏常见有以下几种表现形式: hidden:占据空间,无法点击 style"…

Java成员内部类全解析:从创建、使用到优缺点分析

什么是成员内部类? 在Java的开发中,我们有时会遇到一种特殊的类,它并不像平常的类那样独立存在,而是寄生在另一个类的内部,这就是我们今天要讲的成员内部类。 成员内部类,顾名思义,是作为另一…

自然语言处理 (NLP) 和文本分析

自然语言处理 (NLP) 和文本分析:NLP 在很多领域都有着广泛的应用,如智能助手、语言翻译、舆情分析等。热门问题包括情感分析、命名实体识别、文本生成等。 让我们一起来详细举例子的分析讲解一下自然语言处理(NLP)和文本分析的应用…

BiLSTM-KDE的双向长短期记忆神经网络结合核密度估计多变量回归区间预测(Matlab)

BiLSTM-KDE的双向长短期记忆神经网络结合核密度估计多变量回归区间预测(Matlab) 目录 BiLSTM-KDE的双向长短期记忆神经网络结合核密度估计多变量回归区间预测(Matlab)效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.BiLS…

JAVA面试专题-Redis

你在最近的项目中哪些场景使用了Redis 缓存 缓存穿透 缓存穿透:查询一个不存在的数据,mysql查询不到数据也不好直接写入缓存,导致每次请求都查数据库。 解决方案一:缓存空数据,即使查询返回的数据为空,也把…

微信小程序开发核心:样式,组件,布局,矢量图标

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…

MAC 本地搭建Dify环境

Dify 介绍 Dify 是一款开源的大语言模型(LLM) 应用开发平台。它融合了后端即服务(Backend as Service)和 LLMOps 的理念,使开发者可以快速搭建生产级的生成式 AI 应用。即使你是非技术人员,也能参与到 AI 应用的定义和数据运营过…

国内首个图计算平台团体标准发布,创邻科技参与编撰

2024年,由中国通信标准协会批准的团体标准《大数据 图计算平台技术要求与测试方法》(编号:T/CCSA 470—2023)(下称:标准)正式实施。该标准于1月4日在全国团体标准信息平台(https://w…

超越GPT-4,清华发布网页导航智能体AutoWebGLM

随着大语言模型(LLMs)的发展,Agent在网络导航等任务中展现出了前所未有的能力。想象一下,一个基于LLM的Agent能够在你享用早餐时为你总结在线新闻,这样的场景已经不再遥不可及。这种将LLMs融入日常任务的做法&#xff…

AI小白使用Macbook Pro安装llama3与langchain初体验

1. 背景 AI爆火了2年有余,但我仍是一个AI小白,最近零星在学,随手记录点内容供自己复习。 上次在Macbook Pro上安装了Stable Diffusion,体验了本地所心所欲地生成各种心仪的图片,完全没有任何限制的惬意。今天想使用M…

Kafka客户端工具:Offset Explorer 使用指南

Kafka作为一个分布式流处理平台,在大数据处理和实时数据流应用中扮演着至关重要的角色。管理Kafka的topics及其offsets对于维护系统稳定性和数据一致性至关重要。Offset Explorer是一个强大的桌面应用程序,它使得管理和监控Kafka集群变得简单直观。本文将…