yxh před 5 roky
rodič
revize
096b50e913
48 změnil soubory, kde provedl 4636 přidání a 4 odebrání
  1. 3 0
      README.MD
  2. 2 3
      app/service/cache_service/cache_values.go
  3. 1 1
      config/config.toml
  4. 14 0
      library/response/plugin/blog/response.go
  5. 323 0
      plugin/blog/controller/home/index.go
  6. 139 0
      plugin/blog/controller/system/blog_classification.go
  7. 69 0
      plugin/blog/controller/system/blog_comment.go
  8. 103 0
      plugin/blog/controller/system/blog_log.go
  9. 181 0
      plugin/blog/model/blog_classification/blog_classification.go
  10. 65 0
      plugin/blog/model/blog_classification/blog_classification_entity.go
  11. 359 0
      plugin/blog/model/blog_classification/blog_classification_model.go
  12. 214 0
      plugin/blog/model/blog_comment/blog_comment.go
  13. 67 0
      plugin/blog/model/blog_comment/blog_comment_entity.go
  14. 363 0
      plugin/blog/model/blog_comment/blog_comment_model.go
  15. 253 0
      plugin/blog/model/blog_log/blog_log.go
  16. 69 0
      plugin/blog/model/blog_log/blog_log_entity.go
  17. 367 0
      plugin/blog/model/blog_log/blog_log_model.go
  18. 73 0
      plugin/blog/service/blog_service/classification.go
  19. 46 0
      plugin/blog/service/blog_service/comment.go
  20. 45 0
      plugin/blog/service/blog_service/log.go
  21. 1320 0
      public/resource/plugin/blog/css/base.css
  22. binární
      public/resource/plugin/blog/image/001.jpg
  23. binární
      public/resource/plugin/blog/image/002.png
  24. binární
      public/resource/plugin/blog/image/003.jpg
  25. binární
      public/resource/plugin/blog/image/004.jpg
  26. binární
      public/resource/plugin/blog/image/005.jpg
  27. binární
      public/resource/plugin/blog/image/006.jpg
  28. binární
      public/resource/plugin/blog/image/bq1.png
  29. binární
      public/resource/plugin/blog/image/bq2.png
  30. binární
      public/resource/plugin/blog/image/bq3.png
  31. binární
      public/resource/plugin/blog/image/bq4.png
  32. binární
      public/resource/plugin/blog/image/bq5.png
  33. binární
      public/resource/plugin/blog/image/ga.png
  34. binární
      public/resource/plugin/blog/image/gfastLogo.png
  35. binární
      public/resource/plugin/blog/image/icon.png
  36. binární
      public/resource/plugin/blog/image/search.png
  37. binární
      public/resource/plugin/blog/image/weizhi.png
  38. binární
      public/resource/plugin/blog/image/wx.png
  39. binární
      public/resource/plugin/blog/image/yangqq.png
  40. 11 0
      public/resource/plugin/blog/js/nav.js
  41. 26 0
      router/routerPluginBlog.go
  42. 53 0
      template/plugin/blog/aside.html
  43. 52 0
      template/plugin/blog/blogList.html
  44. 193 0
      template/plugin/blog/content.html
  45. 11 0
      template/plugin/blog/footer.html
  46. 52 0
      template/plugin/blog/header.html
  47. 103 0
      template/plugin/blog/homePage.html
  48. 59 0
      template/plugin/blog/link.html

+ 3 - 0
README.MD

@@ -107,3 +107,6 @@ gitee地址:[https://gitee.com/tiger1103/gfast-ui](https://gitee.com/tiger1103
 > <img src="https://gitee.com/tiger1103/gfast/raw/master/public/qqcode.png"/>  
 > <img src="https://gitee.com/tiger1103/gfast/raw/master/public/qqcode.png"/>  
 
 
 > 快来加入群聊【Gfast框架交流群】(群号865697297),发现精彩内容。  
 > 快来加入群聊【Gfast框架交流群】(群号865697297),发现精彩内容。  
+
+### 若框架对您有帮助,您可以捐赠表达一下心意:
+> <img src="https://gitee.com/tiger1103/gfast-ui/raw/master/public/images/jz.jpg"/>

+ 2 - 3
app/service/cache_service/cache_values.go

@@ -6,7 +6,7 @@ const (
 	AdminAuthRole
 	AdminAuthRole
 	AdminCmsMenu
 	AdminCmsMenu
 	AdminConfigDict
 	AdminConfigDict
-	GovProject
+	AdminBlogClassification
 )
 )
 
 
 //缓存TAG标签
 //缓存TAG标签
@@ -15,6 +15,5 @@ const (
 	AdminCmsTag
 	AdminCmsTag
 	AdminSysConfigTag
 	AdminSysConfigTag
 	AdminModelTag
 	AdminModelTag
-	AdminDeptUserTag
-	GovProjectCateTag
+	AdminBlogTag
 )
 )

+ 1 - 1
config/config.toml

@@ -58,7 +58,7 @@
 
 
 #后台相关配置
 #后台相关配置
 [adminInfo]
 [adminInfo]
-    notCheckAuthAdminIds = [1,31]  #无需验证后台权限的用户id
+    notCheckAuthAdminIds = [1,2,31]  #无需验证后台权限的用户id
     pageNum = 10  #后台列表分页长度
     pageNum = 10  #后台列表分页长度
     dataDir =  "./data"  #数据目录
     dataDir =  "./data"  #数据目录
 
 

+ 14 - 0
library/response/plugin/blog/response.go

@@ -0,0 +1,14 @@
+package blog
+
+import (
+	"gfast/library/response"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/os/gview"
+)
+
+func Response(r *ghttp.Request, tpl string, params ...gview.Params) error {
+	view := g.View()
+	view.SetPath("template/plugin/blog")
+	return response.WriteTpl(r, tpl, view, params...)
+}

+ 323 - 0
plugin/blog/controller/home/index.go

@@ -0,0 +1,323 @@
+package home
+
+import (
+	"gfast/app/model/admin/plug_link"
+	"gfast/app/service/admin/plug_link_service"
+	"gfast/app/service/admin/plug_service"
+	"gfast/library/response"
+	"gfast/library/response/plugin/blog"
+	"gfast/plugin/blog/model/blog_classification"
+	"gfast/plugin/blog/model/blog_comment"
+	"gfast/plugin/blog/model/blog_log"
+	"gfast/plugin/blog/service/blog_service"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+type Index struct{}
+
+type Classification struct {
+	*blog_classification.Entity
+	Children []*Classification
+}
+
+// 首页
+func (c *Index) Index(r *ghttp.Request) {
+	// 查询文章列表
+	var req = new(blog_log.SelectPageReq)
+	req.PageSize = 8
+	req.Status = 1
+	_, _, logList, err := blog_service.SelectLogListByPage(req)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询3篇置顶文章列表
+	topList, err := blog_service.FindSizeArticleBySign(3, 1, 1, 0)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询3篇幻灯
+	slideList, err := blog_service.FindSizeArticleBySign(3, 1, 2, 0)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询10篇根据点击数排序的文章用于排行榜
+	hitsList, err := blog_service.FindArticleByHits(10, 1)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询友情链接
+	linkList, err := plug_link_service.ListByTypeId(13, 6, 1)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询赞助分类
+	coffeeArticle, err := plug_service.GetSizeAd(2, 1, 21)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询推荐文章
+	recommendList, err := blog_service.FindSizeArticleBySign(4, 1, 3, 0)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询最新文章
+	newList, err := blog_service.FindSizeArticleBySign(4, 1, 0, 0)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询所有状态为正常的分类
+	classificationList, err := blog_service.FindAllList()
+	if err != nil {
+		g.Log().Error(err)
+	}
+	classifications := getClassificationgList(classificationList, 0)
+	blog.Response(r, "homePage.html", g.Map{
+		"logList":            logList,
+		"topList":            topList,
+		"slideList":          slideList,
+		"hitsList":           hitsList,
+		"linkList":           linkList,
+		"coffeeArticle":      coffeeArticle,
+		"recommendList":      recommendList,
+		"newList":            newList,
+		"classificationList": classifications,
+	})
+}
+
+func getClassificationgList(classificationList []*blog_classification.Entity, pid uint) (cList []*Classification) {
+	cList = make([]*Classification, 0, len(classificationList))
+	for _, c1 := range classificationList {
+		var cl *Classification
+		if c1.ClassificationPid == pid {
+			cl = &Classification{Entity: c1}
+			cl.Children = getClassificationgList(classificationList, c1.ClassificationId)
+			cList = append(cList, cl)
+		}
+	}
+	return
+}
+
+// 分类、搜索
+func (c *Index) BlogList(r *ghttp.Request) {
+	// 根据条件查询当前分类下的文章
+	var req = new(blog_log.SelectPageReq)
+	req.PageNum = r.GetInt64("page")
+	pageSize := 10
+	req.PageSize = int64(pageSize)
+	req.LogTitle = r.GetString("keyboard")
+	req.Status = 1
+	cateId := r.GetInt("cateId")
+	req.CateTypeId = cateId
+	total, _, list, err := blog_service.SelectLogListByPage(req)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 根据分类id查询分类(分类名用于导航)
+	classification, err := blog_service.GetClassificationByID(int64(cateId))
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 以下为右边侧栏所需数据
+	// 查询10篇根据点击数排序的文章用于排行榜
+	hitsList, err := blog_service.FindArticleByHits(10, 1)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询友情链接
+	linkList, err := plug_link_service.ListByTypeId(13, 6, 1)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询赞助分类
+	coffeeArticle, err := plug_service.GetSizeAd(2, 1, 21)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询推荐文章
+	recommendList, err := blog_service.FindSizeArticleBySign(4, 1, 3, 0)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询最新文章
+	newList, err := blog_service.FindSizeArticleBySign(4, 1, 0, 0)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询所有状态为正常的分类
+	classificationList, err := blog_service.FindAllList()
+	if err != nil {
+		g.Log().Error(err)
+	}
+	classifications := getClassificationgList(classificationList, 0)
+	tplData := g.Map{
+		"list":               list,
+		"pageStyle":          r.GetPage(total, pageSize).GetContent(3),
+		"classificationList": classifications,
+		"hitsList":           hitsList,
+		"linkList":           linkList,
+		"coffeeArticle":      coffeeArticle,
+		"recommendList":      recommendList,
+		"newList":            newList,
+		"classification":     classification,
+	}
+	if classification != nil && classification.ClassificationType == 4 {
+		//单页
+		blog.Response(r, "content.html", tplData)
+	} else {
+		blog.Response(r, "blogList.html", tplData)
+	}
+}
+
+// 内容页面(关于博主)
+func (c *Index) Content(r *ghttp.Request) {
+	logId := r.GetInt("logId")
+	// 根据id查询对应博客日志
+	log, err := blog_service.GetLogByID(int64(logId))
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 更新点击数
+	log.LogHits += 1
+	blog_log.Model.Save(log)
+	if log.LogUrl != "" {
+		//跳转连接
+		r.Response.RedirectTo(log.LogUrl)
+		r.Exit()
+	}
+	// 查询4篇相关文章(同一分类下最新的4篇)
+	relevantList, err := blog_service.FindSizeArticle(4, 1, log.LogType)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询10篇根据点击数排序的文章用于排行榜
+	hitsList, err := blog_service.FindArticleByHits(10, 1)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询友情链接
+	linkList, err := plug_link_service.ListByTypeId(13, 6, 1)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询赞助分类
+	coffeeArticle, err := plug_service.GetSizeAd(2, 1, 21)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询推荐文章
+	recommendList, err := blog_service.FindSizeArticleBySign(4, 1, 3, 0)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询最新文章
+	newList, err := blog_service.FindSizeArticleBySign(4, 1, 0, 0)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询所有状态为正常的分类
+	classificationList, err := blog_service.FindAllList()
+	if err != nil {
+		g.Log().Error(err)
+	}
+	classifications := getClassificationgList(classificationList, 0)
+	// 查询当前日志的未停用评论
+	req := new(blog_comment.SelectPageReq)
+	req.Status = "1"
+	req.PageSize = 5
+	req.Flag = 1
+	req.PageNum = r.GetInt64("page")
+	req.CommentLogIds = append(req.CommentLogIds, logId)
+	total, _, commentList, err := blog_service.SelectCommentListByPage(req) // 分页查询一级评论
+	commentList, err = blog_service.GetChildren(commentList)                // 查出二级评论(回复)
+	blog.Response(r, "content.html", g.Map{
+		"classificationList": classifications,
+		"log":                log,
+		"hitsList":           hitsList,
+		"linkList":           linkList,
+		"coffeeArticle":      coffeeArticle,
+		"recommendList":      recommendList,
+		"newList":            newList,
+		"pageStyle":          r.GetPage(total, int(req.PageSize)).GetContent(3),
+		"commentList":        commentList,
+		"relevantList":       relevantList,
+	})
+}
+
+// 新增评论
+func (c *Index) AddComment(r *ghttp.Request) {
+	if r.Method == "POST" {
+		var req *blog_comment.AddReq
+		err := r.Parse(&req)
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		err = blog_service.AddCommentSave(req)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		response.SusJson(true, r, "添加成功")
+	}
+}
+
+// 友情链接申请
+func (c *Index) Link(r *ghttp.Request) {
+	// 查询10篇根据点击数排序的文章用于排行榜
+	hitsList, err := blog_service.FindArticleByHits(10, 1)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询友情链接
+	linkList, err := plug_link_service.ListByTypeId(13, 6, 1)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询赞助分类
+	coffeeArticle, err := plug_service.GetSizeAd(2, 1, 21)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询推荐文章
+	recommendList, err := blog_service.FindSizeArticleBySign(4, 1, 3, 0)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询最新文章
+	newList, err := blog_service.FindSizeArticleBySign(4, 1, 0, 0)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	// 查询所有状态为正常的分类
+	classificationList, err := blog_service.FindAllList()
+	if err != nil {
+		g.Log().Error(err)
+	}
+	classifications := getClassificationgList(classificationList, 0)
+	blog.Response(r, "link.html", g.Map{
+		"classificationList": classifications,
+		"hitsList":           hitsList,
+		"linkList":           linkList,
+		"coffeeArticle":      coffeeArticle,
+		"recommendList":      recommendList,
+		"newList":            newList,
+	})
+}
+
+// 添加申请友情链接
+func (c *Index) AddLink(r *ghttp.Request) {
+	if r.Method == "POST" {
+		var req = new(plug_link.AddReq)
+		err := r.Parse(req)
+		req.LinkOpen = 0
+		req.LinkTarget = "_blank"
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		err = plug_link_service.AddSavePlugLink(req)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		response.SusJson(true, r, "提交成功")
+	}
+}

+ 139 - 0
plugin/blog/controller/system/blog_classification.go

@@ -0,0 +1,139 @@
+package system
+
+import (
+	"gfast/app/service/cache_service"
+	"gfast/library/response"
+	"gfast/plugin/blog/model/blog_classification"
+	"gfast/plugin/blog/service/blog_service"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/util/gconv"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+// 简单博客管理-分类管理
+type BlogClassification struct{}
+
+func (c *BlogClassification) Add(r *ghttp.Request) {
+	if r.Method == "POST" {
+		var req *blog_classification.AddReq
+		// 通过Parse方法解析获取参数
+		err := r.Parse(&req)
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		// 调用service中的添加函数添加广告位
+		err = blog_service.AddClassificationSave(req)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		cache_service.New().RemoveByTag(cache_service.AdminBlogTag)
+		response.SusJson(true, r, "添加成功!")
+	}
+	//获取上级分类(频道)
+	menus, err := blog_service.GetMenuListChannel()
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	res := g.Map{
+		"parentList": menus,
+	}
+	response.SusJson(true, r, "添加栏目", res)
+}
+
+func (c *BlogClassification) Delete(r *ghttp.Request) {
+	// 从页面获取要删除记录的 ID int切片
+	ids := r.GetInts("classificationId")
+	if len(ids) == 0 {
+		response.FailJson(true, r, "ID获取失败,删除失败")
+	}
+	err := blog_service.DeleteClassificationByIds(ids)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	cache_service.New().RemoveByTag(cache_service.AdminBlogTag)
+	response.SusJson(true, r, "删除成功")
+}
+
+func (c *BlogClassification) Edit(r *ghttp.Request) {
+	// 如果是post提交的请求就执行修改操作
+	if r.Method == "POST" {
+		var editReq *blog_classification.EditReq
+		// 通过Parse方法解析获取参数
+		err := r.Parse(&editReq)
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		err = blog_service.EditClassificationSave(editReq)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		cache_service.New().RemoveByTag(cache_service.AdminBlogTag)
+		response.SusJson(true, r, "修改参数成功")
+	}
+	// 不是post提交的请求就到修改页面后查询出要修改的记录
+	id := r.GetInt("classificationId")
+	params, err := blog_service.GetClassificationByID(int64(id))
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	//获取上级分类(频道)
+	menus, err := blog_service.GetMenuListChannel()
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	res := g.Map{
+		"params":     params,
+		"parentList": menus,
+	}
+	response.SusJson(true, r, "ok", res)
+}
+
+func (c *BlogClassification) List(r *ghttp.Request) {
+	// 定义一个结构体存储请求参数
+	var req *blog_classification.SelectPageReq
+	// 获取参数
+	err := r.Parse(&req)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	total, page, list, err := blog_service.SelectClassificationListByPage(req)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	result := g.Map{
+		"currentPage": page,
+		"total":       total,
+		"list":        list,
+	}
+	response.SusJson(true, r, "日志分类列表", result)
+}
+
+func (c *BlogClassification) Sort(r *ghttp.Request) {
+	sorts := r.Get("sorts")
+	s := gconv.Map(sorts)
+	if s == nil {
+		response.FailJson(true, r, "获取记录id、序号失败")
+	}
+	var err error
+	for k, v := range s {
+		_, err = blog_classification.Model.Where("classification_id=?", k).Data("classification_sort", v).Update()
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+	}
+	cache_service.New().RemoveByTag(cache_service.AdminBlogTag)
+	response.SusJson(true, r, "排序成功")
+}
+
+// 查询所有分类名称和对应id
+func (c *BlogClassification) Type(r *ghttp.Request) {
+	res, err := blog_classification.Model.FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("查询所有分类名称失败!")
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "分类名称列表", res)
+}

+ 69 - 0
plugin/blog/controller/system/blog_comment.go

@@ -0,0 +1,69 @@
+package system
+
+import (
+	"gfast/library/response"
+	"gfast/plugin/blog/model/blog_comment"
+	"gfast/plugin/blog/service/blog_service"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+// 简单博客管理-评论管理
+type BlogComment struct{}
+
+func (c *BlogComment) Delete(r *ghttp.Request) {
+	ids := r.GetInts("commentId")
+	if len(ids) == 0 {
+		response.FailJson(true, r, "ID获取失败,删除失败")
+	}
+	err := blog_service.DeleteCommentByIDs(ids)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "删除成功")
+}
+
+func (c *BlogComment) Edit(r *ghttp.Request) {
+	// 如果是post提交的请求就执行修改操作
+	if r.Method == "POST" {
+		var editReq *blog_comment.EditReq
+		// 通过Parse方法解析获取参数
+		err := r.Parse(&editReq)
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		err = blog_service.EditCommentSave(editReq)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		response.SusJson(true, r, "修改参数成功")
+	}
+	// 不是post提交的请求就到修改页面后查询出要修改的记录
+	id := r.GetInt("commentId")
+	params, err := blog_service.GetCommentByID(int64(id))
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "ok", params)
+}
+
+func (c *BlogComment) List(r *ghttp.Request) {
+	// 定义一个结构体存储请求参数
+	var req *blog_comment.SelectPageReq
+	// 获取参数
+	err := r.Parse(&req)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	total, page, list, err := blog_service.SelectCommentListByPage(req)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	result := g.Map{
+		"currentPage": page,
+		"total":       total,
+		"list":        list,
+	}
+	response.SusJson(true, r, "评论列表", result)
+}

+ 103 - 0
plugin/blog/controller/system/blog_log.go

@@ -0,0 +1,103 @@
+package system
+
+import (
+	"gfast/library/response"
+	"gfast/plugin/blog/model/blog_log"
+	"gfast/plugin/blog/service/blog_service"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/util/gconv"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+// 简单博客管理-日志管理
+type BlogLog struct{}
+
+func (c *BlogLog) Add(r *ghttp.Request) {
+	if r.Method == "POST" {
+		var req *blog_log.AddReq
+		// 通过Parse方法解析获取参数
+		err := r.Parse(&req)
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		// 调用service中的添加函数添加广告
+		err = blog_service.AddLogSave(req)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		response.SusJson(true, r, "添加成功!")
+	}
+}
+
+func (c *BlogLog) Delete(r *ghttp.Request) {
+	ids := r.GetInts("logID")
+	if len(ids) == 0 {
+		response.FailJson(true, r, "ID获取失败,删除失败")
+	}
+	err := blog_service.DeleteLogByIDs(ids)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "删除成功")
+}
+
+func (c *BlogLog) Edit(r *ghttp.Request) {
+	// 如果是post提交的请求就执行修改操作
+	if r.Method == "POST" {
+		var editReq *blog_log.EditReq
+		// 通过Parse方法解析获取参数
+		err := r.Parse(&editReq)
+		if err != nil {
+			response.FailJson(true, r, err.(*gvalid.Error).FirstString())
+		}
+		err = blog_service.EditLogSave(editReq)
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+		response.SusJson(true, r, "修改参数成功")
+	}
+	// 不是post提交的请求就到修改页面后查询出要修改的记录
+	id := r.GetInt("logID")
+	params, err := blog_service.GetLogByID(int64(id))
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	response.SusJson(true, r, "ok", params)
+}
+
+func (c *BlogLog) List(r *ghttp.Request) {
+	// 定义一个结构体存储请求参数
+	var req *blog_log.SelectPageReq
+	// 获取参数
+	err := r.Parse(&req)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	total, page, list, err := blog_service.SelectLogListByPage(req)
+	if err != nil {
+		response.FailJson(true, r, err.Error())
+	}
+	result := g.Map{
+		"currentPage": page,
+		"total":       total,
+		"list":        list,
+	}
+	response.SusJson(true, r, "日志列表", result)
+}
+
+func (c *BlogLog) Sort(r *ghttp.Request) {
+	sorts := r.Get("sorts")
+	s := gconv.Map(sorts)
+	if s == nil {
+		response.FailJson(true, r, "获取记录id、序号失败")
+	}
+	var err error
+	for k, v := range s {
+		_, err = blog_log.Model.Where("log_id=?", k).Data("log_sort", v).Update()
+		if err != nil {
+			response.FailJson(true, r, err.Error())
+		}
+	}
+	response.SusJson(true, r, "排序成功")
+}

+ 181 - 0
plugin/blog/model/blog_classification/blog_classification.go

@@ -0,0 +1,181 @@
+package blog_classification
+
+import (
+	"gfast/app/service/cache_service"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+)
+
+// AddReq 用于存储新增记录请求参数
+type AddReq struct {
+	ClassificationName     string `p:"classificationName" v:"required#名称不能为空"`     // 名称
+	ClassificationSort     uint   `p:"classificationSort" v:"required#排序不能为空"`     // 排序
+	ClassificationDescribe string `p:"classificationDescribe"`                     // 分类描述
+	ClassificationPid      uint   `p:"classificationPid"`                          // 父id
+	ClassificationType     uint   `p:"classificationType"`                         // 分类类型1.频道页/2.发布栏目/3.跳转栏目/4.单页栏目
+	ClassificationStatus   uint   `p:"classificationStatus" v:"required#分类状态不能为空"` // 状态
+	ClassificationAddress  string `p:"classificationAddress"`
+	ClassificationContent  string `p:"classificationContent"`
+}
+
+// EditReq 用于存储修改广告位请求参数
+type EditReq struct {
+	ClassificationId int64 `p:"classificationId" v:"required|min:1#主键ID不能为空|主键ID值错误"`
+	AddReq
+}
+
+// SelectPageReq 用于存储分页查询的请求参数
+type SelectPageReq struct {
+	ClassificationName string `p:"classificationName"` // 名称
+	PageNum            int64  `p:"pageNum"`            // 当前页
+	PageSize           int64  `p:"pageSize"`           // 每页显示记录数
+}
+
+// GetClassificationByID 根据ID查询记录
+func GetClassificationByID(id int64) (*Entity, error) {
+	entity, err := Model.FindOne(id)
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("根据ID查询记录出错")
+	}
+	if entity == nil {
+		return nil, gerror.New("根据ID未能查询到记录")
+	}
+	return entity, nil
+}
+
+// 根据名称和ID来判断是否已存在相同名称的分类
+func CheakClassificationNameUnique(classificationName string, classificationId int64) error {
+	var (
+		entity *Entity
+		err    error
+	)
+	if classificationId == 0 {
+		entity, err = Model.FindOne(Columns.ClassificationName, classificationName)
+	} else {
+		entity, err = Model.Where(Columns.ClassificationName, classificationName).And(Columns.ClassificationId+"!=?", classificationId).FindOne()
+	}
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("校验名称唯一性失败")
+	}
+	if entity != nil {
+		return gerror.New("名称已经存在!")
+	}
+	return nil
+}
+
+// AddSave 添加
+func AddSave(req *AddReq) error {
+	var entity Entity
+	entity.ClassificationName = req.ClassificationName
+	entity.ClassificationSort = req.ClassificationSort
+	entity.ClassificationDescribe = req.ClassificationDescribe
+	entity.ClassificationPid = req.ClassificationPid
+	entity.ClassificationType = req.ClassificationType
+	entity.ClassificationStatus = req.ClassificationStatus
+	entity.ClassificationAddress = req.ClassificationAddress
+	entity.ClassificationContent = req.ClassificationContent
+	_, err := Model.Save(entity)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("添加失败")
+	}
+	return nil
+}
+
+// 批量删除
+func DeleteClassificationByIds(ids []int) error {
+	_, err := Model.Where("classification_id in(?)", ids).Delete()
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("删除失败")
+	}
+	return nil
+}
+
+// 根据ID修改信息
+func EditSave(editReq *EditReq) error {
+	// 先根据ID来查询要修改的记录
+	entity, err := GetClassificationByID(editReq.ClassificationId)
+	if err != nil {
+		return err
+	}
+	// 修改实体
+	entity.ClassificationName = editReq.ClassificationName
+	entity.ClassificationSort = editReq.ClassificationSort
+	entity.ClassificationStatus = editReq.ClassificationStatus
+	entity.ClassificationType = editReq.ClassificationType
+	entity.ClassificationPid = editReq.ClassificationPid
+	entity.ClassificationDescribe = editReq.ClassificationDescribe
+	entity.ClassificationAddress = editReq.ClassificationAddress
+	entity.ClassificationContent = editReq.ClassificationContent
+	_, err = Model.Save(entity)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("修改失败")
+	}
+	return nil
+}
+
+// 分页查询,返回值total总记录数,page当前页
+func SelectListByPage(req *SelectPageReq) (total int, page int64, list []*Entity, err error) {
+	model := Model
+	if req != nil {
+		if req.ClassificationName != "" {
+			model = model.Where("classification_name like ?", "%"+req.ClassificationName+"%")
+		}
+	}
+	// 查询总记录数(总行数)
+	total, err = model.Count()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取总记录数失败")
+		return 0, 0, nil, err
+	}
+	if req.PageNum == 0 {
+		req.PageNum = 1
+	}
+	page = req.PageNum
+	if req.PageSize == 0 {
+		req.PageSize = 10
+	}
+	// 分页排序查询
+	list, err = model.Page(int(page), int(req.PageSize)).Order("classification_sort asc,classification_id asc").All()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("分页查询失败")
+		return 0, 0, nil, err
+	}
+	return total, page, list, nil
+}
+
+//获取所有菜单列表
+func GetList() (list []*Entity, err error) {
+	cache := cache_service.New()
+	//从缓存获取数据
+	iList := cache.Get(cache_service.AdminBlogClassification)
+	if iList != nil {
+		list = iList.([]*Entity)
+		return
+	}
+	list, err = Model.Order("classification_sort ASC,classification_id ASC").All()
+	if err != nil {
+		g.Log().Error()
+		err = gerror.New("获取菜单数据失败")
+		return
+	}
+	//缓存数据
+	cache.Set(cache_service.AdminBlogClassification, list, 0, cache_service.AdminBlogTag)
+	return
+}
+
+// 查询所有状态为正常的分类
+func FindAllList() (list []*Entity, err error) {
+	list, err = Model.Where("classification_status = ?", 1).Order("classification_sort asc,classification_id asc").All()
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("查询博客分类列表出错")
+	}
+	return list, nil
+}

+ 65 - 0
plugin/blog/model/blog_classification/blog_classification_entity.go

@@ -0,0 +1,65 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package blog_classification
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table blog_classification.
+type Entity struct {
+	ClassificationId       uint   `orm:"classification_id,primary" json:"classification_id"`       //
+	ClassificationName     string `orm:"classification_name"       json:"classification_name"`     // 分类名称
+	ClassificationPid      uint   `orm:"classification_pid"        json:"classification_pid"`      // 分类父id
+	ClassificationSort     uint   `orm:"classification_sort"       json:"classification_sort"`     // 排序
+	ClassificationType     uint   `orm:"classification_type"       json:"classification_type"`     // 分类类型1.频道页/2.发布栏目/3.跳转栏目/4.单页栏目
+	ClassificationDescribe string `orm:"classification_describe"   json:"classification_describe"` // 分类描述
+	ClassificationStatus   uint   `orm:"classification_status"     json:"classification_status"`   // 分类状态,0停用,1正常
+	ClassificationAddress  string `orm:"classification_address" json:"classification_address"`     // 跳转栏目的跳转地址
+	ClassificationContent  string `orm:"classification_content" json:"classification_content"`     // 单页栏目的内容
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 359 - 0
plugin/blog/model/blog_classification/blog_classification_model.go

@@ -0,0 +1,359 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package blog_classification
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table blog_classification operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of blog_classification.
+	Table = "blog_classification"
+	// Model is the model object of blog_classification.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table blog_classification.
+	Columns = struct {
+		ClassificationId       string //
+		ClassificationName     string // 分类名称
+		ClassificationPid      string // 分类父id
+		ClassificationSort     string // 排序
+		ClassificationType     string // 分类标志(菜单类型)1.频道页/2.发布栏目/3.跳转栏目/4.单页栏目
+		ClassificationDescribe string // 分类描述
+		ClassificationStatus   string // 分类状态,0停用,1正常
+		ClassificationAddress  string // 跳转栏目的跳转地址
+		ClassificationContent  string // 单页栏目的内容
+	}{
+		ClassificationId:       "classification_id",
+		ClassificationName:     "classification_name",
+		ClassificationPid:      "classification_pid",
+		ClassificationSort:     "classification_sort",
+		ClassificationType:     "classification_type",
+		ClassificationDescribe: "classification_describe",
+		ClassificationStatus:   "classification_status",
+		ClassificationAddress:  "classification_address",
+		ClassificationContent:  "classification_content",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 214 - 0
plugin/blog/model/blog_comment/blog_comment.go

@@ -0,0 +1,214 @@
+package blog_comment
+
+import (
+	"gfast/app/model/admin/user"
+	"gfast/plugin/blog/model/blog_log"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+)
+
+// AddReq 用于存储新增请求的请求参数
+type AddReq struct {
+	CommentLogId    uint   `p:"commentLogId" v:"required|min:1#评论所属日志ID不能为空|评论所属日志ID错误"` // 当前评论所属日志id
+	CommentNickname string `p:"commentNickname" v:"required#评论用户昵称不能为空"`                 // 评论用户的用户昵称
+	CommentContent  string `p:"commentContent" v:"required#评论内容不能为空"`                    // 评论内容
+	CommentPid      uint   `p:"commentPid"`                                              // 父评论id
+	ReplyName       string `p:"replyName"`                                               // 当前回复对象的昵称
+	ReplyId         uint   `p:"replyId"`                                                 // 当前回复对象的id
+}
+
+// EditReq 用于存储修改请求参数
+type EditReq struct {
+	CommentId      int64  `p:"commentId" v:"required|min:1#评论ID不能为空|评论ID错误"`
+	CommentStatus  uint   `p:"commentStatus"`                        // 此评论的状态,0隐藏,1发布
+	CommentContent string `p:"commentContent" v:"required#评论内容不能为空"` // 评论内容
+}
+
+// SelectPageReq 用于存储分页查询的请求参数
+type SelectPageReq struct {
+	CommentNickname string `p:"commentNickname"` // 评论用户昵称
+	LogTitle        string `p:"logTitle"`        // 日志标题
+	CommentLogIds   []int  // 存储根据日志标题查出来的日志id
+	PageNum         int64  `p:"pageNum"`  // 当前页
+	PageSize        int64  `p:"pageSize"` // 每页显示记录数
+	Status          string // 状态:用于控制查询相应状态的评论,0停用,1正常
+	// 评论在博客页面回复中分两级一级是没有父评论的,二级是有父评论的,一条一级评论下的二级评论的父id值相同,均为该一级评论的id
+	Flag int // 用于判断查询模式,0:查询不区分评论等级(查询所有),1:只查询一级评论(父id为0的评论)
+}
+
+// 用于存储分页查询的数据
+type ListEntity struct {
+	Entity
+	LogTitle string `orm:"log_title"      json:"log_title" ` // 所属日志标题
+	UserName string `orm:"user_name"      json:"user_name" ` // 所属用户的用户名
+	Children []*Entity
+}
+
+// GetChildren 将分页查出的实体切片中每一个元素的子评论查出来并添加进去
+func GetChildren(listEntity []*ListEntity) ([]*ListEntity, error) {
+	for i, v := range listEntity {
+		children, err := GetChildrenByCommentId(v.CommentId)
+		if err != nil {
+			return nil, err
+		}
+		listEntity[i].Children = children
+	}
+	return listEntity, nil
+}
+
+// GetChildrenByCommentId 根据评论id查询该评论所有已审核的回复
+func GetChildrenByCommentId(id uint) ([]*Entity, error) {
+	list, err := Model.Where("comment_pid = ?", id).And("comment_status = ?", 1).All()
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("根据评论id查询回复评论出错")
+	}
+	return list, nil
+}
+
+// GetCommentByID 根据ID查询记录
+func GetCommentByID(id int64) (*Entity, error) {
+	entity, err := Model.FindOne("comment_id", id)
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("根据ID查询记录出错!")
+	}
+	if entity == nil {
+		return nil, gerror.New("根据ID未能查询到记录")
+	}
+	return entity, nil
+}
+
+// 查询并更新回复数
+func FindSonCommentCount(id int) error {
+	i, err := Model.Where("reply_id = ?", id).Count()
+	if err != nil {
+		g.Log().Debug(err)
+		return gerror.New("查询回复数出错")
+	}
+	entity, err := GetCommentByID(int64(id))
+	if err != nil {
+		return err
+	}
+	entity.CommentNum = uint(i)
+	Model.Save(entity)
+	return nil
+}
+
+// AddSave 添加的方法
+func AddSave(req *AddReq) error {
+	var entity Entity
+	entity.CommentLogId = req.CommentLogId       // 所属日志
+	entity.CommentNickname = req.CommentNickname // 评论用户昵称
+	entity.CommentContent = req.CommentContent   // 评论内容
+	entity.CommentPid = req.CommentPid           // 父评论id,没有父则为0
+	entity.ReplyName = req.ReplyName
+	entity.ReplyId = req.ReplyId
+	entity.CreateTime = uint(gtime.Timestamp()) // 评论时间
+	// 保存实体
+	_, err := entity.Insert()
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("添加记录入库失败!")
+	}
+	return nil
+}
+
+// 批量删除记录
+func DeleteByIDs(ids []int) error {
+	_, err := Model.Delete("comment_id in(?)", ids)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("删除记录失败!")
+	}
+	return nil
+}
+
+// 根据ID修改记录
+func EditSave(req *EditReq) error {
+	// 先根据ID来查询要修改的记录
+	entity, err := GetCommentByID(req.CommentId)
+	if err != nil {
+		return err
+	}
+	// 修改实体
+	entity.CommentContent = req.CommentContent
+	entity.CommentStatus = req.CommentStatus
+	_, err = Model.Save(entity)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("修改记录失败!")
+	}
+	return nil
+}
+
+// 根据日志标题查询对应的日志id切片(因为评论中只存有日志id没有日志标题)
+func FindUserAndLogIds(req *SelectPageReq) (*SelectPageReq, error) {
+	if req.LogTitle != "" {
+		logList, err := blog_log.Model.Where("log_title like ?", "%"+req.LogTitle+"%").Fields("log_id").All()
+		if err != nil {
+			g.Log().Error(err)
+			return req, gerror.New("根据日志标题查询日志id时出错")
+		}
+		for _, v := range logList {
+			req.CommentLogIds = append(req.CommentLogIds, v.LogId)
+		}
+	}
+	return req, nil
+}
+
+// 分页查询,返回值total总记录数,page当前页
+func SelectListByPage(req *SelectPageReq) (total int, page int64, list []*ListEntity, err error) {
+	model := g.DB().Table(Table + " comment")
+	if req != nil {
+		if req.Flag == 1 {
+			model.Where("comment_pid = ?", 0)
+		}
+		if req.Status == "0" {
+			model.Where("comment.comment_status = ?", 0)
+		}
+		if req.Status == "1" {
+			model.Where("comment.comment_status = ?", 1)
+		}
+		if req.CommentNickname != "" {
+			model.Where("comment.comment_nickname like ?", "%"+req.CommentNickname+"%")
+		}
+		if req.CommentLogIds != nil {
+			model.Where("comment.comment_log_id in(?)", req.CommentLogIds)
+		}
+	}
+	model = model.LeftJoin(blog_log.Table+" log", "log.log_id=comment.comment_log_id")
+	model = model.LeftJoin(user.Table+" u", "u.id=comment.comment_user_id")
+	// 查询广告位总记录数(总行数)
+	total, err = model.Count()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取总记录数失败")
+		return 0, 0, nil, err
+	}
+	if req.PageNum == 0 {
+		req.PageNum = 1
+	}
+	page = req.PageNum
+	if req.PageSize == 0 {
+		req.PageSize = 10
+	}
+	// 分页排序查询
+	var res gdb.Result
+	res, err = model.Fields("comment.*,log.log_title,u.user_name").Page(int(page), int(req.PageSize)).Order("comment.create_time desc,comment.comment_id asc").All()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("分页查询失败")
+		return 0, 0, nil, err
+	}
+
+	err = res.Structs(&list)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("分页查询失败")
+		return 0, 0, nil, err
+	}
+	return total, page, list, nil
+}

+ 67 - 0
plugin/blog/model/blog_comment/blog_comment_entity.go

@@ -0,0 +1,67 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package blog_comment
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table blog_comment.
+type Entity struct {
+	CommentId       uint   `orm:"comment_id,primary" json:"comment_id"`      //
+	CommentUserId   uint   `orm:"comment_user_id"    json:"comment_user_id"` // 评论用户的用户id
+	CommentContent  string `orm:"comment_content"    json:"comment_content"` // 评论内容
+	CommentPid      uint   `orm:"comment_pid"        json:"comment_pid"`     // 当前评论所回复的父评论的id
+	CommentNum      uint   `orm:"comment_num"        json:"comment_num"`     // 当前评论的回复数(下一级子评论数)
+	CommentStatus   uint   `orm:"comment_status"     json:"comment_status"`  // 此评论的状态,0隐藏,1发布
+	CommentLogId    uint   `orm:"comment_log_id"     json:"comment_log_id"`  // 当前评论所属日志id
+	CreateTime      uint   `orm:"create_time"        json:"create_time"`     // 评论创建时间
+	CommentNickname string `orm:"comment_nickname" json:"comment_nickname"`  // 评论用户昵称
+	ReplyName       string `orm:"reply_name" json:"reply_name"`              // 当前所回复的对象昵称
+	ReplyId         uint   `orm:"reply_id" json:"reply_id"`                  // 当前回复对象的id
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 363 - 0
plugin/blog/model/blog_comment/blog_comment_model.go

@@ -0,0 +1,363 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package blog_comment
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table blog_comment operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of blog_comment.
+	Table = "blog_comment"
+	// Model is the model object of blog_comment.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table blog_comment.
+	Columns = struct {
+		CommentId       string //
+		CommentUserId   string // 评论用户的用户id
+		CommentContent  string // 评论内容
+		CommentPid      string // 当前评论所回复的父评论的id
+		CommentNum      string // 当前评论的回复数(下一级子评论数)
+		CommentStatus   string // 此评论的状态,0隐藏,1发布
+		CommentLogId    string // 当前评论所属日志id
+		CreateTime      string // 评论创建时间
+		CommentNickname string // 评论用户昵称
+		ReplyName       string // 当前所回复的对象昵称
+		ReplyId         string // 当前回复对象的id
+	}{
+		CommentId:       "comment_id",
+		CommentUserId:   "comment_user_id",
+		CommentContent:  "comment_content",
+		CommentPid:      "comment_pid",
+		CommentNum:      "comment_num",
+		CommentStatus:   "comment_status",
+		CommentLogId:    "comment_log_id",
+		CreateTime:      "create_time",
+		CommentNickname: "comment_nickname",
+		ReplyName:       "reply_name",
+		ReplyId:         "reply_id",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 253 - 0
plugin/blog/model/blog_log/blog_log.go

@@ -0,0 +1,253 @@
+package blog_log
+
+import (
+	"gfast/plugin/blog/model/blog_classification"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+)
+
+// AddReq 用于存储新增请求的请求参数
+type AddReq struct {
+	LogType      int    `p:"logType" v:"required#名称不能为空"`    // 所属分类
+	LogSign      int    `p:"logSign"`                        // 0.一般 1.置顶,2.幻灯,3.推荐
+	LogTitle     string `p:"logTitle" v:"required#标题不能为空"`   // 日志标题
+	LogAuthor    string `p:"logAuthor"`                      // 作者名
+	LogUrl       string `p:"logUrl"`                         // 跳转地址
+	LogThumbnail string `p:"logThumbnail"`                   // 缩略图
+	LogStatus    uint   `p:"logStatus"`                      // 状态:1发布,0未发布
+	LogSort      int    `P:"logSort"`                        // 排序
+	LogContent   string `p:"logContent" v:"required#内容不能为空"` // 内容
+}
+
+// EditReq 用于存储修改请求参数
+type EditReq struct {
+	LogId int64 `p:"logId" v:"required|min:1#日志ID不能为空|日志ID错误"`
+	AddReq
+}
+
+// SelectPageReq 用于存储分页查询的请求参数
+type SelectPageReq struct {
+	LogTitle   string `p:"logTitle"` // 日志标题
+	PageNum    int64  `p:"pageNum"`  // 当前页
+	PageSize   int64  `p:"pageSize"` // 每页显示记录数
+	CateTypeId int    `p:"cateId"`   // 分类类型id
+	Status     int    // 状态
+}
+
+// 用于存储联合查询的数据
+type ListEntity struct {
+	Entity
+	ClassificationName string `orm:"classification_name"      json:"classification_name" ` // 所属分类名
+}
+
+// GetLogByID 根据ID查询记录
+func GetLogByID(id int64) (entity *ListEntity, err error) {
+	entity = new(ListEntity)
+	model := g.DB().Table(Table + " log")
+	model = model.Where("log_id = ?", id)
+	model = model.LeftJoin(blog_classification.Table+" cf", "cf.classification_id = log.log_type")
+	var res gdb.Record
+	res, err = model.Fields("log.*,cf.classification_name").FindOne()
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("根据ID查询记录出错!")
+	}
+	err = res.Struct(entity)
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("根据ID查询转换时出错")
+	}
+	if entity == nil {
+		return nil, gerror.New("根据ID未能查询到记录")
+	}
+	return entity, nil
+}
+
+// AddSave 添加的方法
+func AddSave(req *AddReq) error {
+	var entity Entity
+	entity.LogType = req.LogType
+	entity.LogSign = req.LogSign
+	entity.LogTitle = req.LogTitle
+	entity.LogAuthor = req.LogAuthor
+	entity.LogUrl = req.LogUrl
+	entity.LogThumbnail = req.LogThumbnail
+	entity.CreatTime = uint(gtime.Timestamp())
+	entity.LogStatus = req.LogStatus
+	entity.LogSort = req.LogSort
+	entity.LogContent = req.LogContent
+	// 保存实体
+	_, err := entity.Insert()
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("添加记录入库失败!")
+	}
+	return nil
+}
+
+// 批量删除记录
+func DeleteByIDs(ids []int) error {
+	_, err := Model.Delete("log_id in(?)", ids)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("删除记录失败!")
+	}
+	return nil
+}
+
+// 根据ID修改记录
+func EditSave(req *EditReq) error {
+	// 先根据ID来查询要修改的记录
+	entity, err := GetLogByID(req.LogId)
+	if err != nil {
+		return err
+	}
+	// 修改实体
+	entity.LogType = req.LogType
+	entity.LogSign = req.LogSign
+	entity.LogTitle = req.LogTitle
+	entity.LogAuthor = req.LogAuthor
+	entity.LogUrl = req.LogUrl
+	entity.LogThumbnail = req.LogThumbnail
+	entity.LogStatus = req.LogStatus
+	entity.LogSort = req.LogSort
+	entity.LogContent = req.LogContent
+	_, err = Model.Filter().Save(entity)
+	if err != nil {
+		g.Log().Error(err)
+		return gerror.New("修改记录失败!")
+	}
+	return nil
+}
+
+// 分页查询,返回值total总记录数,page当前页
+func SelectListByPage(req *SelectPageReq) (total int, page int64, list []*ListEntity, err error) {
+	model := g.DB().Table(Table + " log")
+	if req != nil {
+		if req.LogTitle != "" {
+			model.Where("log.log_title like ?", "%"+req.LogTitle+"%")
+		}
+		if req.Status == 1 {
+			model.Where("log.log_status = 1")
+		}
+		if req.CateTypeId != 0 {
+			model.Where("log_type = ?", req.CateTypeId)
+		}
+	}
+	model = model.LeftJoin(blog_classification.Table+" cf", "cf.classification_id=log.log_type")
+	// 查询广告位总记录数(总行数)
+	total, err = model.Count()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取总记录数失败")
+		return 0, 0, nil, err
+	}
+	if req.PageNum == 0 {
+		req.PageNum = 1
+	}
+	page = req.PageNum
+	if req.PageSize == 0 {
+		req.PageSize = 10
+	}
+	// 分页排序查询
+	var res gdb.Result
+	res, err = model.Fields("log.*,cf.classification_name").
+		Page(int(page), int(req.PageSize)).Order("log.log_sort asc,log.log_id desc").All()
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("分页查询失败")
+		return 0, 0, nil, err
+	}
+	err = res.Structs(&list)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("分页查询失败")
+		return 0, 0, nil, err
+	}
+	return total, page, list, nil
+}
+
+// 按时间倒序查询size篇标志为sign分类id为typeId,状态为status的文章,标志值0.一般,1.置顶,2.幻灯,3.推荐,typeId等于0时不区分分类
+func FindSizeArticleBySign(size int, status int, sign int, typeId int) (list []*ListEntity, err error) {
+	model := g.DB().Table(Table + " log")
+	if status == 1 {
+		model = model.Where("log.log_status = ?", 1)
+	}
+	if status == 0 {
+		model = model.Where("log.log_status = ?", 0)
+	}
+	if sign == 0 {
+		model = model.Where("log.log_sign = 0")
+	}
+	if sign == 1 {
+		model = model.Where("log.log_sign = 1")
+	}
+	if sign == 2 {
+		model = model.Where("log.log_sign = 2")
+	}
+	if sign == 3 {
+		model = model.Where("log.log_sign = 3")
+	}
+	if typeId != 0 {
+		model = model.Where("log_type = ?", typeId)
+	}
+	model = model.LeftJoin(blog_classification.Table+" cf", "cf.classification_id=log.log_type")
+	// 分页排序查询
+	var res gdb.Result
+	res, err = model.Fields("log.*,cf.classification_name").
+		Order("log.log_sort asc,log.creat_time desc").Limit(size).All()
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("根据标志查询出错")
+	}
+	err = res.Structs(&list)
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("根据标志查询出错")
+	}
+	return
+}
+
+// 按时间倒序查询size篇分类id为typeId,状态为status的文章,typeId等于0时不区分分类
+func FindSizeArticle(size int, status int, typeId int) (list []*ListEntity, err error) {
+	model := g.DB().Table(Table + " log")
+	if status == 1 {
+		model = model.Where("log.log_status = ?", 1)
+	}
+	if status == 0 {
+		model = model.Where("log.log_status = ?", 0)
+	}
+	if typeId != 0 {
+		model = model.Where("log_type = ?", typeId)
+	}
+	model = model.LeftJoin(blog_classification.Table+" cf", "cf.classification_id=log.log_type")
+	// 分页排序查询
+	var res gdb.Result
+	res, err = model.Fields("log.*,cf.classification_name").Order("log.log_sort asc,log.creat_time desc").Limit(size).All()
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("根据标志查询出错")
+	}
+	err = res.Structs(&list)
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("根据标志查询出错")
+	}
+	return
+}
+
+// 查询size篇文章并根据点击数排序
+func FindArticleByHits(size int, status int) (list []*Entity, err error) {
+	model := Model
+	if status == 1 {
+		model.Where("log_status = 1")
+	}
+	list, err = model.Order("log_hits desc").FindAll()
+	if err != nil {
+		g.Log().Error(err)
+		return nil, gerror.New("根据点击数排序查询失败")
+	}
+	return
+}

+ 69 - 0
plugin/blog/model/blog_log/blog_log_entity.go

@@ -0,0 +1,69 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package blog_log
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+)
+
+// Entity is the golang structure for table blog_log.
+type Entity struct {
+	LogId        int    `orm:"log_id,primary" json:"log_id"`
+	LogType      int    `orm:"log_type"       json:"log_type"`      // 所属分类
+	LogSign      int    `orm:"log_sign"       json:"log_sign"`      // 0.一般 1.置顶,2.幻灯,3.推荐
+	LogTitle     string `orm:"log_title"      json:"log_title"`     // 日志标题
+	LogAuthor    string `orm:"log_author" json:"log_author"`        // 用户名
+	LogUrl       string `orm:"log_url"        json:"log_url"`       // 跳转地址
+	LogThumbnail string `orm:"log_thumbnail"  json:"log_thumbnail"` // 缩略图
+	LogHits      uint64 `orm:"log_hits"       json:"log_hits"`      // 查看数点击数
+	LogComments  int64  `orm:"log_comments"   json:"log_comments"`  // 评论数
+	CreatTime    uint   `orm:"creat_time"     json:"creat_time"`    // 创建时间
+	LogStatus    uint   `orm:"log_status"     json:"log_status"`    // 状态:1发布,0未发布
+	LogSort      int    `orm:"log_sort"       json:"log_sort"`      // 排序
+	LogContent   string `orm:"log_content" json:"log_content"`      // 内容
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (r *Entity) OmitEmpty() *arModel {
+	return Model.Data(r).OmitEmpty()
+}
+
+// Inserts does "INSERT...INTO..." statement for inserting current object into table.
+func (r *Entity) Insert() (result sql.Result, err error) {
+	return Model.Data(r).Insert()
+}
+
+// InsertIgnore does "INSERT IGNORE INTO ..." statement for inserting current object into table.
+func (r *Entity) InsertIgnore() (result sql.Result, err error) {
+	return Model.Data(r).InsertIgnore()
+}
+
+// Replace does "REPLACE...INTO..." statement for inserting current object into table.
+// If there's already another same record in the table (it checks using primary key or unique index),
+// it deletes it and insert this one.
+func (r *Entity) Replace() (result sql.Result, err error) {
+	return Model.Data(r).Replace()
+}
+
+// Save does "INSERT...INTO..." statement for inserting/updating current object into table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Save() (result sql.Result, err error) {
+	return Model.Data(r).Save()
+}
+
+// Update does "UPDATE...WHERE..." statement for updating current object from table.
+// It updates the record if there's already another same record in the table
+// (it checks using primary key or unique index).
+func (r *Entity) Update() (result sql.Result, err error) {
+	return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update()
+}
+
+// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table.
+func (r *Entity) Delete() (result sql.Result, err error) {
+	return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete()
+}

+ 367 - 0
plugin/blog/model/blog_log/blog_log_model.go

@@ -0,0 +1,367 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. You may not really want to edit it.
+// ==========================================================================
+
+package blog_log
+
+import (
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"time"
+)
+
+// arModel is a active record design model for table blog_log operations.
+type arModel struct {
+	gmvc.M
+}
+
+var (
+	// Table is the table name of blog_log.
+	Table = "blog_log"
+	// Model is the model object of blog_log.
+	Model = &arModel{g.DB("default").Table(Table).Safe()}
+	// Columns defines and stores column names for table blog_log.
+	Columns = struct {
+		LogId        string //
+		LogType      string // 分类, 1va
+		LogSign      string // 0.一般 1.置顶,2.幻灯,3.推荐
+		LogTitle     string // 日志标题
+		LogAuthor    string // 作者名
+		LogUrl       string // 跳转地址
+		LogThumbnail string // 缩略图
+		LogHits      string // 查看数点击数
+		LogComments  string // 评论数
+		CreatTime    string // 创建时间
+		LogStatus    string // 状态:1发布,0未发布
+		LogSort      string // 排序
+		LogContent   string // 内容
+	}{
+		LogId:        "log_id",
+		LogType:      "log_type",
+		LogSign:      "log_sign",
+		LogTitle:     "log_title",
+		LogAuthor:    "log_author",
+		LogUrl:       "log_url",
+		LogThumbnail: "log_thumbnail",
+		LogHits:      "log_hits",
+		LogComments:  "log_comments",
+		CreatTime:    "creat_time",
+		LogStatus:    "log_status",
+		LogSort:      "log_sort",
+		LogContent:   "log_content",
+	}
+)
+
+// FindOne is a convenience method for Model.FindOne.
+// See Model.FindOne.
+func FindOne(where ...interface{}) (*Entity, error) {
+	return Model.FindOne(where...)
+}
+
+// FindAll is a convenience method for Model.FindAll.
+// See Model.FindAll.
+func FindAll(where ...interface{}) ([]*Entity, error) {
+	return Model.FindAll(where...)
+}
+
+// FindValue is a convenience method for Model.FindValue.
+// See Model.FindValue.
+func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) {
+	return Model.FindValue(fieldsAndWhere...)
+}
+
+// FindArray is a convenience method for Model.FindArray.
+// See Model.FindArray.
+func FindArray(fieldsAndWhere ...interface{}) ([]gdb.Value, error) {
+	return Model.FindArray(fieldsAndWhere...)
+}
+
+// FindCount is a convenience method for Model.FindCount.
+// See Model.FindCount.
+func FindCount(where ...interface{}) (int, error) {
+	return Model.FindCount(where...)
+}
+
+// Insert is a convenience method for Model.Insert.
+func Insert(data ...interface{}) (result sql.Result, err error) {
+	return Model.Insert(data...)
+}
+
+// InsertIgnore is a convenience method for Model.InsertIgnore.
+func InsertIgnore(data ...interface{}) (result sql.Result, err error) {
+	return Model.InsertIgnore(data...)
+}
+
+// Replace is a convenience method for Model.Replace.
+func Replace(data ...interface{}) (result sql.Result, err error) {
+	return Model.Replace(data...)
+}
+
+// Save is a convenience method for Model.Save.
+func Save(data ...interface{}) (result sql.Result, err error) {
+	return Model.Save(data...)
+}
+
+// Update is a convenience method for Model.Update.
+func Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
+	return Model.Update(dataAndWhere...)
+}
+
+// Delete is a convenience method for Model.Delete.
+func Delete(where ...interface{}) (result sql.Result, err error) {
+	return Model.Delete(where...)
+}
+
+// As sets an alias name for current table.
+func (m *arModel) As(as string) *arModel {
+	return &arModel{m.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (m *arModel) TX(tx *gdb.TX) *arModel {
+	return &arModel{m.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (m *arModel) Master() *arModel {
+	return &arModel{m.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (m *arModel) Slave() *arModel {
+	return &arModel{m.M.Slave()}
+}
+
+// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) LeftJoin(table ...string) *arModel {
+	return &arModel{m.M.LeftJoin(table...)}
+}
+
+// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) RightJoin(table ...string) *arModel {
+	return &arModel{m.M.RightJoin(table...)}
+}
+
+// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+// The parameter <table> can be joined table and its joined condition,
+// and also with its alias name, like:
+// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+func (m *arModel) InnerJoin(table ...string) *arModel {
+	return &arModel{m.M.InnerJoin(table...)}
+}
+
+// Fields sets the operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) Fields(fields string) *arModel {
+	return &arModel{m.M.Fields(fields)}
+}
+
+// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+func (m *arModel) FieldsEx(fields string) *arModel {
+	return &arModel{m.M.FieldsEx(fields)}
+}
+
+// Option sets the extra operation option for the model.
+func (m *arModel) Option(option int) *arModel {
+	return &arModel{m.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (m *arModel) OmitEmpty() *arModel {
+	return &arModel{m.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (m *arModel) Filter() *arModel {
+	return &arModel{m.M.Filter()}
+}
+
+// Where sets the condition statement for the model. The parameter <where> can be type of
+// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+// multiple conditions will be joined into where statement using "AND".
+// Eg:
+// Where("uid=10000")
+// Where("uid", 10000)
+// Where("money>? AND name like ?", 99999, "vip_%")
+// Where("uid", 1).Where("name", "john")
+// Where("status IN (?)", g.Slice{1,2,3})
+// Where("age IN(?,?)", 18, 50)
+// Where(User{ Id : 1, UserName : "john"})
+func (m *arModel) Where(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Where(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (m *arModel) And(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (m *arModel) Or(where interface{}, args ...interface{}) *arModel {
+	return &arModel{m.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (m *arModel) Group(groupBy string) *arModel {
+	return &arModel{m.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (m *arModel) Order(orderBy ...string) *arModel {
+	return &arModel{m.M.Order(orderBy...)}
+}
+
+// Limit sets the "LIMIT" statement for the model.
+// The parameter <limit> can be either one or two number, if passed two number is passed,
+// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+// statement.
+func (m *arModel) Limit(limit ...int) *arModel {
+	return &arModel{m.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (m *arModel) Offset(offset int) *arModel {
+	return &arModel{m.M.Offset(offset)}
+}
+
+// Page sets the paging number for the model.
+// The parameter <page> is started from 1 for paging.
+// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+func (m *arModel) Page(page, limit int) *arModel {
+	return &arModel{m.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (m *arModel) Batch(batch int) *arModel {
+	return &arModel{m.M.Batch(batch)}
+}
+
+// Cache sets the cache feature for the model. It caches the result of the sql, which means
+// if there's another same sql request, it just reads and returns the result from cache, it
+// but not committed and executed into the database.
+//
+// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+// If the parameter <duration> = 0, which means it never expires.
+// If the parameter <duration> > 0, which means it expires after <duration>.
+//
+// The optional parameter <name> is used to bind a name to the cache, which means you can later
+// control the cache like changing the <duration> or clearing the cache with specified <name>.
+//
+// Note that, the cache feature is disabled if the model is operating on a transaction.
+func (m *arModel) Cache(duration time.Duration, name ...string) *arModel {
+	return &arModel{m.M.Cache(duration, name...)}
+}
+
+// Data sets the operation data for the model.
+// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+// Eg:
+// Data("uid=10000")
+// Data("uid", 10000)
+// Data(g.Map{"uid": 10000, "name":"john"})
+// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+func (m *arModel) Data(data ...interface{}) *arModel {
+	return &arModel{m.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) All(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// One retrieves one record from table and returns the result as *Entity.
+// It returns nil if there's no record retrieved with the given conditions from table.
+//
+// The optional parameter <where> is the same as the parameter of Model.Where function,
+// see Model.Where.
+func (m *arModel) One(where ...interface{}) (*Entity, error) {
+	one, err := m.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
+// Also see Model.WherePri and Model.One.
+func (m *arModel) FindOne(where ...interface{}) (*Entity, error) {
+	one, err := m.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *Entity
+	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entity, nil
+}
+
+// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
+// Also see Model.WherePri and Model.All.
+func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) {
+	all, err := m.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*Entity
+	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+		return nil, err
+	}
+	return entities, nil
+}
+
+// Chunk iterates the table with given size and callback function.
+func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) {
+	m.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*Entity
+		err = result.Structs(&entities)
+		if err == sql.ErrNoRows {
+			return false
+		}
+		return callback(entities, err)
+	})
+}
+
+// LockUpdate sets the lock for update for current operation.
+func (m *arModel) LockUpdate() *arModel {
+	return &arModel{m.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (m *arModel) LockShared() *arModel {
+	return &arModel{m.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (m *arModel) Unscoped() *arModel {
+	return &arModel{m.M.Unscoped()}
+}

+ 73 - 0
plugin/blog/service/blog_service/classification.go

@@ -0,0 +1,73 @@
+package blog_service
+
+import (
+	"gfast/plugin/blog/model/blog_classification"
+)
+
+//获取频道列表
+func GetMenuListChannel() (list []*blog_classification.Entity, err error) {
+	//获取频道列表
+	listAll, err := GetMenuList()
+	if err != nil {
+		return
+	}
+	list = make([]*blog_classification.Entity, 0, len(listAll))
+	for _, v := range listAll {
+		list = append(list, v)
+	}
+	return
+}
+
+//获取所有菜单列表
+func GetMenuList() (list []*blog_classification.Entity, err error) {
+	return blog_classification.GetList()
+}
+
+// 添加
+func AddClassificationSave(req *blog_classification.AddReq) (err error) {
+	// 判断名称是否已存在
+	err = blog_classification.CheakClassificationNameUnique(req.ClassificationName, 0)
+	if err != nil {
+		return err
+	}
+	// 不存在则调用AddSave()函数添加
+	err = blog_classification.AddSave(req)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 批量删除
+func DeleteClassificationByIds(ids []int) error {
+	return blog_classification.DeleteClassificationByIds(ids)
+}
+
+// 修改
+func EditClassificationSave(editReq *blog_classification.EditReq) error {
+	// 判断修改后的名称的唯一性
+	err := blog_classification.CheakClassificationNameUnique(editReq.ClassificationName, editReq.ClassificationId)
+	if err != nil {
+		return err
+	}
+	err = blog_classification.EditSave(editReq)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 根据ID查询
+func GetClassificationByID(id int64) (*blog_classification.Entity, error) {
+	return blog_classification.GetClassificationByID(id)
+}
+
+// 分页查询
+func SelectClassificationListByPage(req *blog_classification.SelectPageReq) (total int, page int64, list []*blog_classification.Entity, err error) {
+	return blog_classification.SelectListByPage(req)
+}
+
+// 查询所有状态为正常的分类
+func FindAllList() (list []*blog_classification.Entity, err error) {
+	return blog_classification.FindAllList()
+}

+ 46 - 0
plugin/blog/service/blog_service/comment.go

@@ -0,0 +1,46 @@
+package blog_service
+
+import (
+	"gfast/plugin/blog/model/blog_comment"
+)
+
+// 添加
+func AddCommentSave(req *blog_comment.AddReq) error {
+	err := blog_comment.AddSave(req)
+	if err != nil {
+		return err
+	}
+	if req.CommentPid != 0 {
+		err = blog_comment.FindSonCommentCount(int(req.ReplyId)) // 每次添加评论都要更新所回复评论的回复数
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// 删除
+func DeleteCommentByIDs(Ids []int) error {
+	return blog_comment.DeleteByIDs(Ids)
+}
+
+//修改
+func EditCommentSave(editReq *blog_comment.EditReq) error {
+	return blog_comment.EditSave(editReq)
+}
+
+// 根据ID查询
+func GetCommentByID(id int64) (*blog_comment.Entity, error) {
+	return blog_comment.GetCommentByID(id)
+}
+
+// 分页查询
+func SelectCommentListByPage(req *blog_comment.SelectPageReq) (total int, page int64, list []*blog_comment.ListEntity, err error) {
+	blog_comment.FindUserAndLogIds(req)
+	return blog_comment.SelectListByPage(req)
+}
+
+// GetChildren 将分页查出的实体切片中每一个元素的子评论查出来并添加进去
+func GetChildren(listEntity []*blog_comment.ListEntity) ([]*blog_comment.ListEntity, error) {
+	return blog_comment.GetChildren(listEntity)
+}

+ 45 - 0
plugin/blog/service/blog_service/log.go

@@ -0,0 +1,45 @@
+package blog_service
+
+import (
+	"gfast/plugin/blog/model/blog_log"
+)
+
+// 添加
+func AddLogSave(req *blog_log.AddReq) error {
+	return blog_log.AddSave(req)
+}
+
+// 删除
+func DeleteLogByIDs(Ids []int) error {
+	return blog_log.DeleteByIDs(Ids)
+}
+
+//修改
+func EditLogSave(editReq *blog_log.EditReq) error {
+	return blog_log.EditSave(editReq)
+}
+
+// 根据ID查询
+func GetLogByID(id int64) (*blog_log.ListEntity, error) {
+	return blog_log.GetLogByID(id)
+}
+
+// 分页查询日志
+func SelectLogListByPage(req *blog_log.SelectPageReq) (total int, page int64, list []*blog_log.ListEntity, err error) {
+	return blog_log.SelectListByPage(req)
+}
+
+// 按时间倒序查询size篇标志为sign分类id为typeId,状态为status的文章,标志值0.一般,1.置顶,2.幻灯,3.推荐,typeId等于0时不区分分类
+func FindSizeArticleBySign(size int, status int, sign int, typeId int) ([]*blog_log.ListEntity, error) {
+	return blog_log.FindSizeArticleBySign(size, status, sign, typeId)
+}
+
+// 按时间倒序查询size篇分类id为typeId,状态为status的文章,typeId等于0时不区分分类
+func FindSizeArticle(size int, status int, typeId int) (list []*blog_log.ListEntity, err error) {
+	return blog_log.FindSizeArticle(size, status, typeId)
+}
+
+// 查询size篇文章并根据点击数排序
+func FindArticleByHits(size int, status int) ([]*blog_log.Entity, error) {
+	return blog_log.FindArticleByHits(size, status)
+}

+ 1320 - 0
public/resource/plugin/blog/css/base.css

@@ -0,0 +1,1320 @@
+@charset "utf-8";
+/* CSS Document */
+* {
+    margin: 0;
+    padding: 0
+}
+
+img {
+    border: 0;
+    display: block
+}
+/*去除图片默认的1px边框;将图片标签变成块级元素,可清除默认的3px距离*/
+
+ul li {
+    list-style: none;
+}
+/*去除ul li标签前面小点*/
+
+a {
+    color: #222;
+    text-decoration: none;
+}
+/* 所有a 标签的 颜色为#222; 去除默认的下划线*/
+
+a:hover {
+    color: #e01109;
+}
+
+h1 {
+    font-size: 28px
+}
+
+h2 {
+    font-size: 18px
+}
+
+h3, h4, h5, h6 {
+    font-size: 16px
+}
+
+body {
+    font: 15px "Microsoft YaHei", Arial, Helvetica, sans-serif;
+    color: #222;
+    background: #f5f5f5
+}
+
+/*设置 文字大小、字体、颜色、背景*/
+header, main {
+    width: 1200px;
+    margin: auto
+}
+
+article {
+    width: 860px;
+    float: left;
+}
+
+aside {
+    width: 320px;
+    float: right;
+}
+
+footer {
+    width: 100%;
+    padding-bottom: 20px;
+    text-align: center;
+    clear: both
+}
+
+.nav {
+    /*float: left;*/
+    display: table;
+    width: 100%;
+    height: 105px;
+    margin: 20px 0;
+    box-shadow: rgba(0, 0, 0, .1) 3px 5px 5px;
+}
+
+.nav a img {
+    width: 90px;
+    height: 90px;
+    float: left;
+    /*background: #fff;*/
+    /*display: block;*/
+    /*margin-right: 20px;*/
+    /*border-radius: 100%;*/
+}
+
+.nav-list1 {
+    float: right;
+}
+
+.submenu {
+    float: left;
+    margin: 0px 20px 0px 0px;
+    font-size: 20px;
+    width: 86px;
+    height: 37px;
+    text-align: center;
+}
+
+.logo-name {
+    margin-top: 20px;
+}
+
+.describe {
+    margin-top: 10px;
+    float: left;
+}
+
+.submenu:hover .nav-submenu {
+    display: block;
+}
+
+.nav-submenu {
+    width: 125px;
+    display: none;
+    border: 1px solid #c7c3c3;
+    text-align: left;
+    margin: 10px 0 0 0;
+    background: #f2f2f4;
+    font-size: 17px;
+    position: relative;
+    z-index: 10;
+    box-shadow: rgba(0, 0, 0, 0.2) 3px 5px 5px;
+}
+
+.nav-submenu div {
+    background: #f2f2f4;
+    border-left: 1px solid #c7c3c3;
+    border-top: 1px solid #c7c3c3;
+    left: 38px;
+    transform: rotate(45deg);
+    top: -6px;
+    width: 8px;
+    height: 8px;
+    position: absolute;
+}
+
+.nav-submenu li {
+    margin: 7px 0px;
+    padding-left: 15px;
+}
+
+.nav-submenu li:hover {
+   background: #dedfe1;
+}
+
+.nav-submenu li a:hover {
+    color: black;
+}
+
+/*blogs*/
+.blogs {
+}
+
+.blogs li {
+    overflow: hidden;
+    margin-bottom: 20px;
+    background: #fff;
+    padding: 20px;
+    position: relative;
+}
+
+.top_blog .blogs li::after {
+    position: absolute;
+    content: "";
+    right: 10px;
+    top: 40px;
+    border-style: solid;
+    border-width: 0 13px 13px 13px;
+}
+
+.top_blog .blogs li::after {
+    border-color: #e01109 #e01109 transparent #e01109;
+}
+
+.top_blog .blogs li::before {
+    background: #e01109;
+    position: absolute;
+    content: "";
+    right: 10px;
+    top: 0;
+    width: 26px;
+    height: 30px;
+    text-align: center;
+    font-size: 16px;
+    font-weight: bold;
+    color: #fff;
+    padding: 10px 0 0 0;
+}
+
+.top_blog .blogs li:nth-child(1)::before {
+    content: "1"
+}
+
+.top_blog .blogs li:nth-child(2)::before {
+    content: "2"
+}
+
+.top_blog .blogs li:nth-child(3)::before {
+    content: "3"
+}
+
+.blogs li i {
+    display: block;
+    width: 160px;
+    height: 110px;
+    overflow: hidden;
+    float: left;
+    margin-right: 20px;
+}
+
+.blogs li i img {
+    width: 100%;
+    min-height: 100%;
+    transition: .5s;
+}
+
+.blogs h2 {
+    margin: 0 0 14px 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.blog_smalltext {
+    overflow: hidden;
+    -webkit-box-orient: vertical;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    font-size: 14px;
+    color: #666;
+    margin-bottom: 14px;
+}
+
+.blog_info {
+    color: #888;
+    font-size: 13px;
+}
+
+.blog_info span {
+    margin-right: 15px;
+    position: relative;
+    padding-left: 20px;
+    line-height: 20px;
+}
+
+.blog_info span a {
+    color: #518f97
+}
+
+.blog_info span a:hover {
+    color: #e01109;
+}
+
+.blog_info span:nth-child(n):before {
+    position: absolute;
+    content: "";
+    width: 20px;
+    height: 20px;
+    left: 0;
+    top: 0;
+}
+
+.blog_info span:nth-child(1) {
+    background: url(../image/icon.png) no-repeat 0 0;
+    background-size: 18px
+}
+
+.blog_info span:nth-child(2) {
+    background: url(../image/icon.png) no-repeat 0 -18px;
+    background-size: 18px
+}
+
+.blog_info span:nth-child(3) {
+    background: url(../image/icon.png) no-repeat 0 -36px;
+    background-size: 18px
+}
+
+.blog_info span:nth-child(4) {
+    background: url(../image/icon.png) no-repeat 0 -54px;
+    background-size: 18px
+}
+
+.blog_info span:nth-child(5) {
+    background: url(../image/icon.png) no-repeat 0 -72px;
+    background-size: 18px
+}
+
+.blogs li:hover {
+    box-shadow: rgba(0, 0, 0, .1) 3px 5px 5px;
+}
+
+.blogs li:hover h2 {
+    color: #F00
+}
+
+.blogs li:hover img {
+    transform: scale(1.1)
+}
+
+/*aside*/
+aside div {
+    background: #fff;
+    margin-bottom: 20px
+}
+
+.search {
+    background: #ed4040;
+    position: relative;
+    border: #ed4040 2px solid;
+    border-radius: 5px;
+    overflow: hidden;
+}
+
+.search input.input_submit {
+    border: 0;
+    color: #fff;
+    outline: none;
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 25%;
+    display: block;
+    font-size: 15px;
+    height: 36px;
+    line-height: 36px;
+    text-indent: 1em;
+    cursor: pointer;
+    background: url(../image/search.png) no-repeat left 10px center;
+    background-size: 21px;
+}
+
+.search input.input_text {
+    border: 0;
+    line-height: 36px;
+    height: 36px;
+    font-size: 14px;
+    width: 75%;
+    outline: none;
+    text-indent: 1em;
+}
+
+h2.aside_title {
+    padding: 20px;
+}
+
+.paihang {
+}
+
+.paihang ol {
+    padding: 0 20px 20px 40px;
+}
+
+.paihang ol li {
+    margin-bottom: 20px;
+    padding-left: 10px;
+}
+
+.coffee {
+    padding: 0px 20px 20px 20px;
+    width: 280px;
+}
+
+.recommend {
+    padding: 0px 20px 20px 20px;
+    height: 150px;
+    width: 280px;
+}
+
+.links span {
+    float: right;
+    font-size: 16px;
+    font-weight: normal
+}
+
+.links span a {
+    color: #666;
+}
+
+.links span a:hover {
+    color: #f00
+}
+
+.links ul {
+    padding: 0 20px 20px 20px;
+    overflow: hidden
+}
+
+.links li {
+    width: 50%;
+    float: left;
+    display: inline-block;
+    line-height: 30px;
+    text-align: center
+}
+
+.links li a {
+    margin: 5px;
+    border: #666 1px solid;
+    display: block;
+    border-radius: 3px;
+    color: #666
+}
+
+.links li a:hover {
+    border: #222 1px solid;
+    color: #000
+}
+
+/*footer*/
+footer img {
+    display: inline-block;
+    vertical-align: middle;
+    margin-right: 5px;
+}
+
+footer p {
+    line-height: 30px;
+    font-size: 14px;
+    color: #666
+}
+
+footer p span {
+    margin: 0 10px
+}
+
+footer a {
+    color: #666
+}
+
+/*weizhi*/
+.weizhi {
+    margin: 0 0 20px 0;
+    font-size: 14px;
+    color: #949494;
+    background: url(../image/weizhi.png) no-repeat left center;
+    background-size: 20px;
+    padding-left: 30px
+}
+
+.weizhi a {
+    color: #949494;
+}
+
+.weizhi a:hover {
+    color: #000
+}
+
+/*suiyan*/
+.suiyan_list {
+}
+
+.suiyan_list ul {
+    overflow: hidden;
+}
+
+.suiyan_list ul li {
+    width: 25%;
+    float: left;
+}
+
+.suiyan_list ul li a {
+    display: block;
+    padding: 20px;
+    margin: 30px auto;
+    background: #fff;
+    box-shadow: rgba(0, 0, 0, .1) 3px 5px 5px;
+    width: 200px;
+    height: 200px;
+    transform: rotate(-10deg);
+    position: relative;
+    color: #333
+}
+
+.suiyan_list ul li:nth-child(even) a {
+    -webkit-transform: rotate(10deg);
+    top: 10px;
+    background: rgb(255, 255, 205);;
+}
+
+.suiyan_list ul li:nth-child(3n) a {
+    -webkit-transform: rotate(-5deg);
+    top: -10px;
+    background: rgb(254, 201, 227);
+}
+
+.suiyan_list ul li:nth-child(5n) a {
+    -webkit-transform: rotate(8deg);
+    top: -10px;
+    background: rgb(210, 251, 253);
+}
+
+.suiyan_list ul li a:hover {
+    transform: scale(1.1);
+    transition: .2s;
+    box-shadow: rgba(0, 0, 0, .5) 3px 5px 5px;
+}
+
+.suiyan_time {
+    font-size: 14px;
+    color: #999;
+    margin-bottom: 10px;
+}
+
+.suiyan_text {
+    line-height: 30px;
+}
+
+.suiyan_list span {
+    position: absolute;
+    width: 50px;
+    height: 50px;
+    bottom: 5px;
+    right: 5px;
+}
+
+/*tuijian*/
+.tuijian {
+}
+
+.tuijian ul {
+    padding: 0 20px 20px 20px;
+}
+
+.tuijian ul li {
+    overflow: hidden;
+    margin-bottom: 20px;
+}
+
+.tuijian ul li a {
+}
+
+.tuijian i {
+    display: block;
+    width: 90px;
+    height: 60px;
+    overflow: hidden;
+    margin-right: 20px;
+    float: left;
+}
+
+.tuijian p {
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 2;
+    overflow: hidden;
+    margin-top: 8px
+}
+
+.tuijian img {
+    width: 100%;
+    min-height: 100%;
+}
+
+/*love*/
+.love {
+}
+
+.love ul {
+    padding: 0 20px 20px 20px;
+    overflow: hidden
+}
+
+.love ul li {
+    overflow: hidden;
+    width: 50%;
+    float: left;
+}
+
+.love ul li a {
+    margin: 0 3px 10px;
+    display: block;
+    overflow: hidden;
+}
+
+.love i {
+    display: block;
+    width: 100%;
+    height: 90px;
+    overflow: hidden;
+}
+
+.love p {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    font-size: 14px;
+}
+
+.love img {
+    width: 100%;
+    min-height: 100%;
+}
+
+/*tagsclous*/
+.tagsclous h2 {
+    padding-bottom: 0 !important
+}
+
+.tagsclous ul {
+    padding: 20px;
+    overflow: hidden;
+    position: relative;
+}
+
+.tagsclous ul:before {
+    animation: linescroll 5s;
+    animation-iteration-count: infinite;
+    content: "";
+    width: 20px;
+    height: 20px;
+    background: red;
+    position: absolute;
+    z-index: 9
+}
+
+.tagsclous a {
+    display: inline-block;
+    background: #ffffff;
+    width: 33.3333%;
+    font-size: 14px;
+    float: left;
+    line-height: 40px;
+    text-align: center;
+    position: relative;
+}
+
+.tagsclous a:nth-child(3n-1):before {
+    width: 1px;
+    height: 100%;
+    content: "";
+    position: absolute;
+    background: #eae5e5;
+    left: 0;
+    top: 0;
+}
+
+.tagsclous a:nth-child(3n-1):after {
+    width: 1px;
+    height: 100%;
+    content: "";
+    position: absolute;
+    background: #eae5e5;
+    right: 0;
+    top: 0;
+}
+
+.tagsclous a:nth-child(6n+1) {
+    background: #f5f5f5;
+}
+
+.tagsclous a:nth-child(6n+2) {
+    background: #f5f5f5;
+}
+
+.tagsclous a:nth-child(6n+3) {
+    background: #f5f5f5;
+}
+
+.tagsclous a:hover {
+    background: #e01109;
+    color: #fff
+}
+
+@keyframes linescroll {
+    0% {
+        left: 0px;
+        top: 0px;
+    }
+    25% {
+        left: 300px;
+        top: 0px;
+    }
+    50% {
+        left: 300px;
+        top: 220px;
+    }
+    75% {
+        left: 0px;
+        top: 220px;
+    }
+    100% {
+        left: 0px;
+        top: 0px;
+    }
+}
+
+/*xiangce*/
+.xiangce_list ul {
+    width: 25%;
+    float: left;
+    overflow: hidden;
+}
+
+.xiangce_list li {
+    margin: 10px
+}
+
+.xiangce_list li a {
+    margin: 0 0 30px 0;
+    padding: 12px;
+    background: white;
+    border-radius: 3px;
+    box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
+    display: block;
+}
+
+.xiangce_list li img {
+    width: 100%;
+    margin: 0 auto 10px
+}
+
+.xiangce_list li p {
+    color: #666;
+    text-align: center
+}
+
+.xiangce_list li a:hover {
+    box-shadow: 0px 2px 3px 1px rgba(0, 0, 0, 0.1);
+    transform: translateY(-5px);
+    transition: all .2s;
+}
+
+.time_list {
+    background: #fff;
+    padding: 20px;
+}
+
+.time_list li {
+    line-height: 36px;
+}
+
+.time_list li a {
+    display: block;
+    position: relative;
+}
+
+.time_list li span {
+    margin: 0 40px 0 0;
+    float: left;
+    color: #999;
+}
+
+.time_list ul {
+    position: relative;
+}
+
+.time_list ul:before {
+    position: absolute;
+    content: "";
+    width: 2px;
+    height: 100%;
+    background: #ccc;
+    left: 102px;
+    top: 0;
+}
+
+.time_list li a:before {
+    position: absolute;
+    content: "";
+    width: 12px;
+    height: 12px;
+    background: #fff;
+    border: #ccc 2px solid;
+    border-radius: 100%;
+    left: 95px;
+    top: 10px;
+}
+
+.time_list li a:hover:before {
+    background: #f00;
+}
+
+.about {
+    background: #fff;
+    padding: 20px;
+    font-size: 16px;
+    line-height: 24px;
+}
+
+.about p {
+    margin: 10px 0;
+}
+
+.about h2 {
+    line-height: 40px;
+    background: #f2f2f2;
+    border-left: #000 4px solid;
+    padding-left: 10px;
+    margin: 10px 0;
+}
+
+.about img {
+    width: 80% !important;
+    height: auto !important;
+    margin: auto;
+}
+
+.aside_right {
+    padding: 20px;
+}
+
+.aside_right h2 {
+    margin: 0 0 10px 0;
+}
+
+.aside_right ul {
+    overflow: hidden;
+    padding: 0 0 20px 0;
+}
+
+.aside_right ul li {
+    line-height: 30px;
+    background: #f2f2f2;
+    margin-bottom: 10px;
+    text-indent: 1em;
+    border-radius: 10px;
+}
+
+.aside_right ol {
+    line-height: 30px;
+    margin-left: 30px;
+}
+
+.container {
+    background: #fff;
+    padding: 20px;
+}
+
+.container h1 {
+    font-size: 24px;
+    margin-bottom: 20px;
+}
+
+.content {
+    margin-top: 15px;
+    font-size: 16px;
+    line-height: 24px;
+}
+
+.content p {
+    margin: 20px 0;
+}
+
+.content img {
+    margin: auto;
+    max-width: 100% !important;
+    height: auto !important;
+}
+
+.otherlink {
+    margin: 20px 0;
+    line-height: 26px;
+}
+
+.otherlink h2, .pinglun_box h2 {
+    margin-bottom: 10px;
+    position: relative;
+}
+
+.otherlink h2:before, .pinglun_box h2:before {
+    position: absolute;
+    content: "";
+    width: 4px;
+    height: 100%;
+    background: #f00;
+    left: -20px;
+}
+
+
+.relevant_article {
+    width: 820px;
+    height: 145px;
+}
+
+.relevant_article div {
+    float: left;
+    margin-right: 5px;
+    width: 200px;
+    height: 135px;
+}
+
+.otherlink img {
+    width: 200px;
+    height: 135px;
+}
+
+@media only screen and (max-width: 1200px) {
+    header, main {
+        width: 90%;
+    }
+
+    article {
+        width: 68%;
+    }
+
+    aside {
+        width: 30%;
+    }
+}
+
+@media only screen and (max-width: 768px) {
+    article {
+        width: 100%;
+    }
+
+    .touxiang {
+        width: 60%;
+    }
+
+    aside {
+        display: none;
+    }
+
+    nav ul li a {
+        padding: 0 15px;
+    }
+
+    img.weixin {
+        z-index: 999;
+    }
+
+    .suiyan_list ul {
+        padding: 30px;
+    }
+
+    .suiyan_list ul li {
+        width: 33.33333%;
+    }
+
+    .suiyan_list ul li a {
+        padding: 10px;
+        width: 180px;
+    }
+
+    .suiyan_text {
+        font-size: 14px;
+    }
+
+    .suiyan_list ul li a:hover {
+        z-index: 9;
+    }
+
+    .xiangce_list li p {
+        font-size: 14px;
+    }
+
+    .xiangce_list li a {
+        padding: 5px;
+    }
+
+    .xiangce_list li {
+        margin: 5px;
+    }
+}
+
+@media only screen and (max-width: 480px) {
+    header, main {
+        width: 96%;
+    }
+
+    .touxiang {
+        width: 100%;
+        margin: 5px 0;
+    }
+
+    .touxiang i {
+        width: 60px;
+        height: 60px;
+        margin-right: 10px;
+    }
+
+    .touxiang i img {
+        width: 100%;
+    }
+
+    .touxiang h2 {
+        margin: 5px 0;
+    }
+
+    .guanzhu {
+        position: fixed;
+        z-index: 9;
+        bottom: 0;
+        top: inherit;
+        width: 100%;
+    }
+
+    .guanzhu:hover .weixin {
+        bottom: 30px;
+        left: 33%;
+        box-shadow: #b8b1b1 2px 2px 10px;
+    }
+
+    footer {
+        padding-bottom: 50px;
+        margin-top: 20px
+    }
+
+    nav {
+        overflow-x: scroll;
+        height: 40px;
+        line-height: 40px;
+    }
+
+    nav ul {
+        width: max-content;
+        overflow-x: scroll;
+    }
+
+    .blogs li {
+        padding: 10px;
+    }
+
+    .blogs h2 {
+        font-size: 16px;
+        margin: 0 0 5px 0;
+    }
+
+    .blogs li i {
+        width: 90px;
+        height: 60px;
+        margin-right: 10px;
+    }
+
+    .top_blog .blogs li::after {
+        display: none;
+    }
+
+    .top_blog .blogs li::before {
+        display: none;
+    }
+
+    .blog_info span:last-child {
+        display: none;
+    }
+
+    .suiyan_list ul li {
+        width: 50%;
+    }
+
+    .xiangce_list ul {
+        width: 100%
+    }
+
+    .xiangce_list li a {
+        padding: 12px;
+    }
+
+    .time_list li span {
+        width: 100%;
+    }
+
+    .time_list ul:before {
+        left: 0;
+    }
+
+    .time_list li a:before {
+        left: -8px;
+    }
+
+    .time_list li a {
+        padding-left: 20px;
+    }
+
+    .time_list li span {
+        padding-left: 20px;
+    }
+
+    .about img {
+        width: 100% !important;
+    }
+
+    .container h1 {
+        font-size: 20px;
+    }
+
+    .info-pre-next p, .otherlink li {
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+    }
+
+    footer p span:last-child {
+        display: block;
+    }
+
+    main {
+        overflow: hidden;
+    }
+}
+
+.swiper-container {
+    width: 860px;
+    height: 350px;
+    margin-bottom: 20px;
+    float: left;
+}
+
+.swiper-wrapper img {
+    width: 860px;
+    height: 350px;
+}
+
+.swiper-container {
+    --swiper-theme-color: #e5e4e4; /* 设置Swiper风格 */
+    --swiper-navigation-color: #8B8EAD; /* 单独设置按钮颜色 */
+    --swiper-navigation-size: 30px; /* 设置按钮大小 */
+}
+
+/*留言*/
+.guest_tit {
+    height: 70px;
+    line-height: 50px;
+}
+
+.guest_tit input {
+    border-radius: 5px;
+    height: 40px;
+    border: 1px solid #dddddd;
+    font-size: 17px;
+    font-family: 微软雅黑;
+    width: 200px;
+    float: left;
+    padding: 0 10px;
+}
+
+.guest_tit label {
+    width: 100px;
+    line-height: 40px;
+    display: block;
+    float: left;
+    font-size: 18px;
+}
+
+.guest_txt {
+    height: 187px;
+}
+
+.guest_txt textarea {
+    border-radius: 5px;
+    width: 80%;
+    font-size: 17px;
+    font-family: 微软雅黑;
+    height: 150px;
+    padding: 5px 10px;
+    border: 1px solid #dddddd;
+}
+
+.guest_s {
+    line-height: 50px;
+    width: 450px;
+}
+
+.btn {
+    width: 20%;
+    border-radius: 5px;
+    background-color: #e01109;
+    display: inline-block;
+    color: #fff;
+    border: none;
+    line-height: 47px;
+    font-size: 20px;
+    cursor: pointer;
+    padding: 0 10px;
+    margin-right: 15px;
+}
+
+/* 分页样式 */
+.paging {
+    margin: 15px;
+    text-align: center;
+    font-size: 15px;
+}
+
+.paging a, .paging span {
+    margin: 0 1px;
+    border: solid 1px #ccc;
+    padding: 5px;
+}
+
+.GPageSpan {
+    color: red;
+}
+
+/* 没有评论的样式 */
+.no_comment {
+    height: 100px;
+    color: #7d8293;
+    padding-top: 6px;
+}
+
+/* 评论样式 */
+.comment {
+    margin-top: 10px;
+    border-bottom: 1px solid #dee2e6;
+}
+
+.comment_nickname {
+    float: left;
+    font-size: 15px;
+    font-weight: 700;
+    line-height: 18px;
+    padding-bottom: 4px;
+}
+
+.comment_create_time {
+    color: #99a2aa;
+    line-height: 18px;
+    font-size: 13px;
+}
+
+.comment_content {
+    padding-left: 5px;
+    margin-top: 10px;
+    color: #666;
+}
+
+.comment_reply {
+    color: #99a2aa;
+    font-size: 14px;
+    margin: 7px 0px;
+}
+
+.comment_reply span {
+    padding: 5px;
+    border-radius: 4px;
+    display: inline-block;
+}
+
+.comment_reply span:hover {
+    color: #508af5;
+    background: #dcdee3;
+    border-radius: 4px;
+    cursor: pointer;
+    display: inline-block;
+}
+
+/* 友情链接 */
+.link_content {
+    width: 860px;
+    float: left;
+}
+
+.link_content h2 {
+    margin: 0 auto;
+    width: 100px;
+}
+
+.link_content div {
+    margin: 30px auto;
+    width: 470px;
+}
+
+.link_content div input {
+    height: 35px;
+    width: 438px;
+    padding: 2px 15px;
+    font-size: 16px;
+    border-radius: 4px;
+    border: 1px solid #c8d0d0;
+}
+
+.link_content div textarea {
+    width: 438px;
+    padding: 10px 15px;
+    font-size: 16px;
+    font-family: 微软雅黑;
+    border-radius: 4px;
+    border: 1px solid #c8d0d0;
+}
+
+::-webkit-input-placeholder {
+    color: #d1d3da;
+}
+
+.comment_button {
+    border: none;
+    background: #e01109;
+    color: #fff;
+    border-radius: 5px;
+    width: 100%;
+    cursor: pointer;
+    line-height: 50px;
+    font-size: 20px;
+}
+
+.reply_box {
+    margin-left: 25px;
+}
+
+.reply_id {
+    display: none;
+}
+
+.reply_item {
+    display: none;
+}
+
+.view_more {
+    color: #99a2aa;
+    font-size: 14px;
+    margin-left: 29px;
+}
+
+.view_more span {
+    padding: 5px;
+    border-radius: 4px;
+    display: inline-block;
+}
+
+.view_more span:hover {
+    color: #508af5;
+    background: #dcdee3;
+    border-radius: 4px;
+    cursor: pointer;
+    display: inline-block;
+}

binární
public/resource/plugin/blog/image/001.jpg


binární
public/resource/plugin/blog/image/002.png


binární
public/resource/plugin/blog/image/003.jpg


binární
public/resource/plugin/blog/image/004.jpg


binární
public/resource/plugin/blog/image/005.jpg


binární
public/resource/plugin/blog/image/006.jpg


binární
public/resource/plugin/blog/image/bq1.png


binární
public/resource/plugin/blog/image/bq2.png


binární
public/resource/plugin/blog/image/bq3.png


binární
public/resource/plugin/blog/image/bq4.png


binární
public/resource/plugin/blog/image/bq5.png


binární
public/resource/plugin/blog/image/ga.png


binární
public/resource/plugin/blog/image/gfastLogo.png


binární
public/resource/plugin/blog/image/icon.png


binární
public/resource/plugin/blog/image/search.png


binární
public/resource/plugin/blog/image/weizhi.png


binární
public/resource/plugin/blog/image/wx.png


binární
public/resource/plugin/blog/image/yangqq.png


+ 11 - 0
public/resource/plugin/blog/js/nav.js

@@ -0,0 +1,11 @@
+// JavaScript Document
+
+$(document).ready(function(){
+ var myNav = $("nav a"),i;            
+  for(i=0;i<myNav.length;i++){        
+    var links =myNav.eq(i).attr("href"),myURL =document.URL;    
+     if(myURL.indexOf(links) != -1) {       
+       myNav.eq(i).parent().addClass("current");        
+     }
+  }
+});

+ 26 - 0
router/routerPluginBlog.go

@@ -0,0 +1,26 @@
+package router
+
+import (
+	"gfast/middleWare"
+	blogHome "gfast/plugin/blog/controller/home"
+	blogSystem "gfast/plugin/blog/controller/system"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+)
+
+func init() {
+	s := g.Server()
+	group := s.Group("/")
+	s.Group("/plugin", func(group *ghttp.RouterGroup) {
+		group.ALL("/blog", new(blogHome.Index))
+	})
+	group.Group("/system/plugin", func(group *ghttp.RouterGroup) {
+		group.Middleware(middleWare.Auth) //后台权限验证
+		// 简单博客管理
+		group.Group("/blog", func(group *ghttp.RouterGroup) {
+			group.ALL("/log", new(blogSystem.BlogLog))
+			group.ALL("/classification", new(blogSystem.BlogClassification))
+			group.ALL("/comment", new(blogSystem.BlogComment))
+		})
+	})
+}

+ 53 - 0
template/plugin/blog/aside.html

@@ -0,0 +1,53 @@
+${define "aside"}
+<aside>
+    <div class="search">
+        <form action="${.domain}plugin/blog/blogList" method="post" name="searchform" id="searchform">
+            <input name="keyboard" id="keyboard" class="input_text" value="请输入关键字词"
+                   style="color: rgb(153, 153, 153);"
+                   onfocus="if(value=='请输入关键字词'){this.style.color='#000';value=''}"
+                   onblur="if(value==''){this.style.color='#999';value='请输入关键字词'}" type="text">
+            <input name="show" value="title" type="hidden">
+            <input name="tempid" value="1" type="hidden">
+            <input name="tbname" value="news" type="hidden">
+            <input name="Submit" class="input_submit" value="搜索" type="submit">
+        </form>
+    </div>
+
+    <div class="paihang">
+        <h2 class="aside_title">点击排行</h2>
+        <ol start="1">
+            ${range $index, $article := .hitsList}
+            <li><a href="${$.domain}plugin/blog/content?logId=${$article.LogId}" target="_blank">${$article.LogTitle}</a></li>
+            ${end}
+        </ol>
+    </div>
+    <div class="paihang">
+        <h2 class="aside_title">推荐文章</h2>
+        ${range $index,$article := .recommendList}
+        <a href="${$.domain}plugin/blog/content?logId=${$article.LogId}" target="_blank"><img class="recommend" src="${$.domain}${$article.LogThumbnail}" alt="推荐文章"></a>
+        ${end}
+    </div>
+    <div class="paihang">
+        <h2 class="aside_title">最新文章</h2>
+        <ol start="1">
+            ${range $index, $article := .newList}
+            <li><a href="${$.domain}plugin/blog/content?logId=${$article.LogId}" target="_blank">${$article.LogTitle}</a></li>
+            ${end}
+        </ol>
+    </div>
+    <div class="links">
+        <h2 class="aside_title"><span><a href="${.domain}plugin/blog/link" target="_blank">申请</a></span>友情链接</h2>
+        <ul>
+            ${range $index,$link := .linkList}
+            <li><a href="${$link.LinkUrl}" target="${$link.LinkTarget}">${$link.LinkName}</a></li>
+            ${end}
+        </ul>
+    </div>
+    <div class="paihang">
+        <h2 class="aside_title">请喝咖啡</h2>
+        ${range $index,$article := .coffeeArticle}
+        <img class="coffee" src="${$.domain}${$article.AdPic}" alt="请喝咖啡">
+        ${end}
+    </div>
+</aside>
+${end}

+ 52 - 0
template/plugin/blog/blogList.html

@@ -0,0 +1,52 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>gfast博客</title>
+    <link href="${.domain}plugin/blog/css/base.css" rel="stylesheet">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <script src="${.domain}js/jquery.js"></script>
+    <script src="${.domain}plugin/blog/js/nav.js"></script>
+</head>
+<body>
+${template "header" .}
+<!--header end-->
+<main>
+    ${if ne .classification nil}
+    <p class="weizhi">您现在的位置是:<a href="${.domain}plugin/blog/index">网站首页</a> > ${.classification.ClassificationName}</p>
+    ${else}
+    <p class="weizhi">您现在的位置是:<a href="${.domain}plugin/blog/index">网站首页</a> > 搜索</p>
+    ${end}
+    <article>
+        <!--new_blog 最新文章 begin-->
+        <div class="new_blog">
+            <ul class="blogs">
+                ${range $index,$article := .list}
+                <li>
+                    <a href="${$.domain}plugin/blog/content?logId=${$article.LogId}" target="_blank">
+                        <i><img src="${$.domain}${$article.LogThumbnail}" alt="标题图片"></i>
+                        <h2>${$article.LogTitle}</h2>
+                    </a>
+                    <div class="blog_smalltext">${subStr $article.LogContent 23}</div>
+                    <p class="blog_info">
+                        <span>${timeFormatYear $article.CreatTime}</span>
+                        <span>${$article.LogAuthor}</span>
+                        <span>${$article.ClassificationName}</span>
+                        <span>${$article.LogHits}</span>
+                    </p>
+                </li>
+                ${end}
+            </ul>
+        </div>
+        <!--new_blog 最新文章 end-->
+
+    </article>
+    <!--article end-->
+
+    ${template "aside" .}
+    <!--aside end-->
+</main>
+${template "footer" .}
+<!--footer end-->
+</body>
+</html>

+ 193 - 0
template/plugin/blog/content.html

@@ -0,0 +1,193 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>gfast博客</title>
+    <link href="${.domain}plugin/blog/css/base.css" rel="stylesheet">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <script src="${.domain}js/jquery/jquery.js"></script>
+    <script src="${.domain}plugin/blog/js/nav.js"></script>
+    <script type="text/javascript">
+        $(function () {
+            // 提交评论后在页面弹框显示返回的添加信息
+            $('#addComment').submit(function () {
+                $.post($(this).attr('action'), $(this).serializeArray(), function (data) {
+                    alert(data.msg);
+                    $('.com_content').val(""); // 把评论中的内容置为空
+                    // window.location.reload(); // 添加后刷新一下页面
+                });
+                return false;
+            });
+        });
+
+        $(document).ready(function () {
+            // 重置按钮功能
+            $('.reset_button').click(function () {
+                $('.hidden_commentPid').attr('value', '0');
+                $('.hidden_replyName').attr('value', "");
+                $('.com_content').attr('placeholder', "请输入评论");
+            });
+
+            // 点击回复
+            $('.reply').each(function (index) {
+                $(this).click(function () {
+                    const ph1 = $('.comment_nickname');
+                    const r = $('.reply_id');
+                    $('.com_content').attr('placeholder', "@"+$(ph1[index]).text());
+                    // 将.hidden_commentPid所属标签的value值改为所回复评论的id
+                    // 用回复span的id把父评论的id值传过来
+                    $('.hidden_commentPid').attr('value', $(this).attr('id'));
+                    // 将所回复对象的id放入隐藏的input中
+                    $('.hidden_replyId').attr("value", $(r[index]).text());
+                    // 将所回复的对象的昵称放入隐藏的input中提交,$(ph1[index]).text()从评论的div中获取所回复对象的昵称
+                    $('.hidden_replyName').attr('value', $(ph1[index]).text());
+                    $('.com_content').focus();
+                });
+            });
+
+            // 点击查看更多
+            $('.click_view').each(function (index) {
+                $(this).click(function () {
+                    const rb = $('.reply_box');
+                    $(rb[index]).find('.reply_item').removeClass("reply_item");
+                    const vm = $('.view_more');
+                    $(vm[index]).addClass("reply_item");
+                });
+            });
+        })
+    </script>
+</head>
+<body>
+${template "header" .}
+<main>
+    ${if and (ne .classification nil) (eq .classification.ClassificationType 4)}
+    <p class="weizhi">您现在的位置是:<a href="${.domain}plugin/blog/index">网站首页</a> > <a href="${.domain}plugin/blog/blogList?cateId=${.classification.ClassificationId}">${.classification.ClassificationName}</a> </p>
+    <article>
+        <div class="container">
+            <h1>${.classification.ClassificationName}</h1>
+            <div class="content">
+                ${.classification.ClassificationContent}
+            </div>
+        </div>
+    </article>
+    ${else}
+    <p class="weizhi">您现在的位置是:<a href="${.domain}plugin/blog/index">网站首页</a> > <a href="${.domain}plugin/blog/blogList?cateId=${.log.LogType}">${.log.ClassificationName}</a> > ${.log.LogTitle}</p>
+    <article>
+        <div class="container">
+            <h1>${.log.LogTitle}</h1>
+            <p class="blog_info">
+                <span>${timeFormatYear .log.CreatTime}</span>
+                <span>${.log.LogAuthor}</span>
+                <span><a href="${.domain}plugin/blog/blogList?cateId=${.log.LogType}" target="_blank">${.log.ClassificationName}</a></span>
+                <span>${.log.LogHits}</span>
+            </p>
+            <div class="content">
+                ${.log.LogContent}
+            </div>
+            <div class="otherlink">
+                <h2>相关文章</h2>
+                <div class="relevant_article">
+                    ${range $index,$article := .relevantList}
+                    ${if ne $.log.LogId $article.LogId}
+                    <div>
+                        <a href="${$.domain}plugin/blog/content?logId=${$article.LogId}"><img src="${$.domain}${$article.LogThumbnail}"></a>
+                    </div>
+                    ${end}
+                    ${end}
+                </div>
+            </div>
+            <div class="pinglun_box">
+                <div>
+                    <h2>文章评论</h2>
+                    ${range $index,$comment := .commentList}
+                    <div class="comment">
+                        <div class="comment_nickname">${$comment.CommentNickname}</div>
+                        <div class="comment_create_time">
+                            (${timeFormatYear $comment.CreateTime})
+                        </div>
+                        <div class="comment_content">
+                            ${$comment.CommentContent}
+                        </div>
+                        <div class="reply_id">${$comment.CommentId}</div>
+                        <div class="comment_reply"><span class="reply" id="${$comment.CommentId}">回复</span></div>
+                        <!-- 二级评论 -->
+                        <div class="reply_box">
+                            ${range $index2, $item := $comment.Children}
+                            <!-- 小于或者等于3条回复则显示,大于3条的隐藏 -->
+                            ${if lt $index2 3}
+                            <div>
+                                <div class="comment_nickname">${$item.CommentNickname}</div>
+                                <div class="comment_create_time">
+                                    (${timeFormatYear $item.CreateTime})
+                                </div>
+                                <div class="comment_content">
+                                    @${$item.ReplyName}:${$item.CommentContent}
+                                </div>
+                                <div class="reply_id">${$item.CommentId}</div>
+                                <div class="comment_reply"><span class="reply" id="${$comment.CommentId}">回复</span></div>
+                            </div>
+                            ${else}
+                            <div class="reply_item">
+                                <div class="comment_nickname">${$item.CommentNickname}</div>
+                                <div class="comment_create_time">
+                                    (${timeFormatYear $item.CreateTime})
+                                </div>
+                                <div class="comment_content">
+                                    @${$item.ReplyName}:${$item.CommentContent}
+                                </div>
+                                <div class="reply_id">${$item.CommentId}</div>
+                                <div class="comment_reply"><span class="reply" id="${$comment.CommentId}">回复</span></div>
+                            </div>
+                            ${end}
+                            ${end}
+                        </div>
+                        ${if gt $comment.CommentNum 3}
+                        <div class="view_more">
+                            共<b>${$comment.CommentNum}</b>条回复,<span class="click_view">点击查看</span>
+                        </div>
+                        ${else}
+                        <div class="view_more reply_item">
+                            共<b>${$comment.CommentNum}</b>条回复,<span class="click_view">点击查看</span>
+                        </div>
+                        ${end}
+                    </div>
+                    ${end}
+                    ${if ne .commentList nil}
+                    <div class="paging">
+                        ${$.pageStyle}
+                    </div>
+                    ${else}
+                    <div class="no_comment">还没有人发表评论</div>
+                    ${end}
+                </div>
+                <div>
+                    <h2>发表评论</h2>
+                    <form id="addComment" action="${.domain}plugin/blog/addComment" method="post">
+                        <input type="hidden" name="commentLogId" value="${.log.LogId}">
+                        <input type="hidden" class="hidden_commentPid" name="commentPid" value="0">
+                        <input type="hidden" class="hidden_replyName" name="replyName" value="">
+                        <input type="hidden" class="hidden_replyId" name="replyId" value="">
+                        <div class="guest_tit">
+                            <input type="text" name="commentNickname" placeholder="请输入昵称"><label>&ensp;昵称</label>
+                        </div>
+                        <div class="guest_txt">
+                            <textarea  class="com_content" name="commentContent" placeholder="请输入评论"></textarea>
+                        </div>
+                        <div class="guest_s">
+                            <button class="btn" type="submit">提交</button>
+                            <button class="btn reset_button" type="reset">重置</button>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </article>
+    ${end}
+    <!--article end-->
+
+    ${template "aside" .}
+    <!--aside end-->
+</main>
+${template "footer" .}
+</body>
+</html>

+ 11 - 0
template/plugin/blog/footer.html

@@ -0,0 +1,11 @@
+${define "footer"}
+<footer>
+    <p> © 2019-2020blog.com 版权所有:<a href="/" target="_blank">gfast个人博客</a></p>
+    <p>
+        <span><a href="/" target="_blank">XML网站地图</a></span>
+        <span>备案号:<a href="/">XXX XXXX</a></span>
+        <span><img src="${.domain}plugin/blog/image/ga.png">公安备案号 XXXXXXX</span>
+    </p>
+</footer>
+<!--footer end-->
+${end}

+ 52 - 0
template/plugin/blog/header.html

@@ -0,0 +1,52 @@
+${define "header"}
+<header>
+    <div class="nav">
+        <a href="${.domain}plugin/blog/index">
+            <img src="${.domain}plugin/blog/image/gfastLogo.png" alt="gfastLogo">
+        </a>
+        <h2 class="logo-name">gfast</h2>
+        <p class="describe">一款极速开发框架</p>
+        <div class="nav-list1">
+            <ul>
+                ${range $index,$column := .classificationList}
+                ${if lt $index 5}
+                <!-- 只显示前五条 -->
+                ${if ne (len $column.Children) 0}
+                <!-- 有子栏目的情况 级别2 -->
+                <li class="submenu">
+                    <a style="cursor: pointer">${$column.ClassificationName}</a>
+                    <ul class="nav-submenu">
+                        <div></div>
+                        ${range $sonIndex,$sonColumn := $column.Children}
+                        <!-- 如果子栏目ClassificationType=3是跳转栏目,则解析另一种a标签 -->
+                            ${if eq $sonColumn.ClassificationType 3}
+                            <li>
+                                <a href="${$.domain}${$sonColumn.ClassificationAddress}?cateId=${$sonColumn.ClassificationId}">${$sonColumn.ClassificationName}</a>
+                            </li>
+                            ${else}
+                            <li>
+                                <a href="${$.domain}plugin/blog/blogList?cateId=${$sonColumn.ClassificationId}">${$sonColumn.ClassificationName}</a>
+                            </li>
+                            ${end}
+                        ${end}
+                    </ul>
+                </li>
+                ${else}
+                <!-- 没有子栏目的情况 级别2 -->
+                ${if eq $column.ClassificationType 3}
+                <li class="submenu">
+                    <a href="${$.domain}${$column.ClassificationAddress}?cateId=${$column.ClassificationId}">${$column.ClassificationName}</a>
+                </li>
+                ${else}
+                <li class="submenu">
+                    <a href="${$.domain}plugin/blog/blogList?cateId=${$column.ClassificationId}">${$column.ClassificationName}</a>
+                </li>
+                ${end}
+                ${end}
+                ${end}
+                ${end}
+            </ul>
+        </div>
+    </div>
+</header>
+${end}

+ 103 - 0
template/plugin/blog/homePage.html

@@ -0,0 +1,103 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>gfast博客</title>
+    <link href="${.domain}plugin/blog/css/base.css" rel="stylesheet">
+    <link href="${.domain}css/swiper-6.1.1/swiper-bundle.min.css" rel="stylesheet">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <script src="${.domain}js/jquery/jquery.js"></script>
+    <script src="${.domain}plugin/blog/js/nav.js"></script>
+    <script src="${.domain}js/swiper-6.1.1/swiper-bundle.min.js"></script>
+</head>
+<body>
+${template "header" .}
+<!--header end-->
+<main>
+    <article>
+        <!-- 图片滑动 -->
+        <div class="swiper-container">
+            <div class="swiper-wrapper">
+                ${range $index,$article := .slideList}
+                <div class="swiper-slide"><a href="${$.domain}plugin/blog/content?logId=${$article.LogId}" target="_blank"><img src="${$.domain}${$article.LogThumbnail}"></a></div>
+                ${end}
+            </div>
+            <!-- 分页器 -->
+            <div class="swiper-pagination"></div>
+            <!-- 导航按钮 -->
+            <div class="swiper-button-prev"></div>
+            <div class="swiper-button-next"></div>
+        </div>
+        <!--top_blog 置顶文章 begin-->
+        <div class="top_blog">
+            <ul class="blogs">
+                ${range $index,$article := .topList}
+                <li>
+                    <a href="${$.domain}plugin/blog/content?logId=${$article.LogId}" target="_blank">
+                        <i><img src="${$.domain}${$article.LogThumbnail}" alt="标题图片"></i>
+                        <h2>${$article.LogTitle}</h2>
+                    </a>
+                    <div class="blog_smalltext">${subStr $article.LogContent 23}</div>
+                    <p class="blog_info">
+                        <span>${timeFormatYear $article.CreatTime}</span>
+                        <span>${$article.LogAuthor}</span>
+                        <span>${$article.ClassificationName}</span>
+                        <span>${$article.LogHits}</span>
+                    </p>
+                </li>
+                ${end}
+            </ul>
+        </div>
+        <!--top_blog 置顶文章 end-->
+
+        <!--new_blog 最新文章 begin-->
+        <div class="new_blog">
+            <ul class="blogs">
+                ${range $index,$article := .logList}
+                    ${if ne $article.LogSign 1}
+                    <li>
+                        <a href="${$.domain}plugin/blog/content?logId=${$article.LogId}" target="_blank">
+                            <i><img src="${$.domain}${$article.LogThumbnail}" alt="标题图片"></i>
+                            <h2>${$article.LogTitle}</h2>
+                        </a>
+                        <div class="blog_smalltext">${subStr $article.LogContent 23}</div>
+                        <p class="blog_info">
+                            <span>${timeFormatYear $article.CreatTime}</span>
+                            <span>${$article.LogAuthor}</span>
+                            <span>${$article.ClassificationName}</span>
+                            <span>${$article.LogHits}</span>
+                        </p>
+                    </li>
+                    ${end}
+                ${end}
+            </ul>
+        </div>
+        <!--new_blog 最新文章 end-->
+
+    </article>
+    <!--article end-->
+
+    ${template "aside" .}
+    <!--aside end-->
+</main>
+${template "footer" .}
+
+<script>
+    var mySwiper = new Swiper('.swiper-container', {
+        loop: true, // 循环模式选项
+        autoplay: {
+            delay: 3500, // 3.5秒切换一次
+        }, // 自动切换
+        // 如果需要分页器
+        pagination: {
+            el: '.swiper-pagination',
+        },
+        // 如果需要前进后退按钮
+        navigation: {
+            nextEl: '.swiper-button-next',
+            prevEl: '.swiper-button-prev',
+        },
+    })
+</script>
+</body>
+</html>

+ 59 - 0
template/plugin/blog/link.html

@@ -0,0 +1,59 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>gfast博客</title>
+    <link href="${.domain}plugin/blog/css/base.css" rel="stylesheet">
+    <link href="${.domain}css/swiper-6.1.1/swiper-bundle.min.css" rel="stylesheet">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <script src="${.domain}js/jquery/jquery.js"></script>
+    <script src="${.domain}plugin/blog/js/nav.js"></script>
+    <script src="${.domain}js/swiper-6.1.1/swiper-bundle.min.js"></script>
+    <script type="text/javascript">
+        $(function () {
+            $('#addLink').submit(function () {
+                $.post($(this).attr('action'), $(this).serializeArray(), function (data) {
+                    alert(data.msg);
+                    window.location.reload(); // 添加后刷新一下页面
+                });
+                return false;
+            });
+        });
+    </script>
+</head>
+<body>
+${template "header" .}
+<!--header end-->
+<main>
+    <div class="link_content">
+        <h2>申请信息</h2>
+        <form id="addLink" action="${.domain}plugin/blog/addLink" method="post">
+            <div>
+               <input type="text" name="linkUsername" required placeholder="请输入联系人">
+            </div>
+            <div>
+                <input type="number" name="linkQQ" required placeholder="请输入QQ">
+            </div>
+            <div>
+               <input type="email" name="linkEmail" required placeholder="请输入邮箱地址">
+            </div>
+            <div>
+               <input type="text" name="linkName" required placeholder="请输入申请链接名称">
+            </div>
+            <div>
+                <input type="url" name="linkUrl" required placeholder="请输入申请链接URL">
+            </div>
+            <div>
+                <textarea rows="6" name="linkRemark" placeholder="请输入备注"></textarea>
+            </div>
+            <div>
+                <button class="comment_button" type="submit">提交申请</button>
+            </div>
+        </form>
+    </div>
+    ${template "aside" .}
+    <!--aside end-->
+</main>
+${template "footer" .}
+</body>
+</html>