From 153f6dda03889c494dc7bfebba69d4092cb5982f Mon Sep 17 00:00:00 2001 From: mlogclub Date: Mon, 20 Apr 2020 17:04:37 +0800 Subject: [PATCH] ContentType text --- .../controllers/admin/article_controller.go | 211 +++--- server/controllers/render/render.go | 12 +- server/go.sum | 1 + server/model/models.go | 1 + server/model/response.go | 4 +- server/services/article_service.go | 633 +++++++++--------- server/services/collect/spider_api.go | 192 +++--- server/services/project_service.go | 315 +++++---- 8 files changed, 674 insertions(+), 695 deletions(-) diff --git a/server/controllers/admin/article_controller.go b/server/controllers/admin/article_controller.go index f33c414b9..f10b9e748 100644 --- a/server/controllers/admin/article_controller.go +++ b/server/controllers/admin/article_controller.go @@ -1,113 +1,98 @@ -package admin - -import ( - "strconv" - "strings" - - "github.com/PuerkitoBio/goquery" - - "github.com/kataras/iris/v12" - "github.com/mlogclub/simple" - - "bbs-go/controllers/render" - "bbs-go/model" - "bbs-go/services" - "bbs-go/services/cache" -) - -type ArticleController struct { - Ctx iris.Context -} - -func (c *ArticleController) GetBy(id int64) *simple.JsonResult { - t := services.ArticleService.Get(id) - if t == nil { - return simple.JsonErrorMsg("Not found, id=" + strconv.FormatInt(id, 10)) - } - return simple.JsonData(t) -} - -func (c *ArticleController) AnyList() *simple.JsonResult { - list, paging := services.ArticleService.FindPageByParams(simple.NewQueryParams(c.Ctx). - EqByReq("id").EqByReq("user_id").EqByReq("status").LikeByReq("title").PageByReq().Desc("id")) - - var results []map[string]interface{} - for _, article := range list { - builder := simple.NewRspBuilderExcludes(article, "content") - - // 用户 - builder = builder.Put("user", render.BuildUserDefaultIfNull(article.UserId)) - - // 简介 - if article.ContentType == model.ContentTypeMarkdown { - mr := simple.NewMd().Run(article.Content) - if len(article.Summary) == 0 { - builder.Put("summary", mr.SummaryText) - } - } else { - if len(article.Summary) == 0 { - doc, err := goquery.NewDocumentFromReader(strings.NewReader(article.Content)) - if err != nil { - builder.Put("summary", simple.GetSummary(doc.Text(), 256)) - } - } - } - - // 标签 - tagIds := cache.ArticleTagCache.Get(article.Id) - tags := cache.TagCache.GetList(tagIds) - builder.Put("tags", render.BuildTags(tags)) - - results = append(results, builder.Build()) - } - - return simple.JsonData(&simple.PageResult{Results: results, Page: paging}) -} - -func (c *ArticleController) PostCreate() *simple.JsonResult { - return simple.JsonErrorMsg("为实现") -} - -func (c *ArticleController) PostUpdate() *simple.JsonResult { - id := c.Ctx.PostValueInt64Default("id", 0) - if id <= 0 { - return simple.JsonErrorMsg("id is required") - } - t := services.ArticleService.Get(id) - if t == nil { - return simple.JsonErrorMsg("entity not found") - } - - simple.ReadForm(c.Ctx, t) - - // 数据校验 - if len(t.Title) == 0 { - return simple.JsonErrorMsg("标题不能为空") - } - if len(t.Content) == 0 { - return simple.JsonErrorMsg("内容不能为空") - } - if len(t.ContentType) == 0 { - return simple.JsonErrorMsg("请选择内容格式") - } - - t.UpdateTime = simple.NowTimestamp() - err := services.ArticleService.Update(t) - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - - return simple.JsonData(t) -} - -func (c *ArticleController) PostDelete() *simple.JsonResult { - id := c.Ctx.PostValueInt64Default("id", 0) - if id <= 0 { - return simple.JsonErrorMsg("id is required") - } - err := services.ArticleService.Delete(id) - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - return simple.JsonSuccess() -} +package admin + +import ( + "strconv" + + "github.com/kataras/iris/v12" + "github.com/mlogclub/simple" + + "bbs-go/common" + "bbs-go/controllers/render" + "bbs-go/services" + "bbs-go/services/cache" +) + +type ArticleController struct { + Ctx iris.Context +} + +func (c *ArticleController) GetBy(id int64) *simple.JsonResult { + t := services.ArticleService.Get(id) + if t == nil { + return simple.JsonErrorMsg("Not found, id=" + strconv.FormatInt(id, 10)) + } + return simple.JsonData(t) +} + +func (c *ArticleController) AnyList() *simple.JsonResult { + list, paging := services.ArticleService.FindPageByParams(simple.NewQueryParams(c.Ctx). + EqByReq("id").EqByReq("user_id").EqByReq("status").LikeByReq("title").PageByReq().Desc("id")) + + var results []map[string]interface{} + for _, article := range list { + builder := simple.NewRspBuilderExcludes(article, "content") + + // 用户 + builder = builder.Put("user", render.BuildUserDefaultIfNull(article.UserId)) + + // 简介 + builder.Put("summary", common.GetSummary(article.ContentType, article.Content)) + + // 标签 + tagIds := cache.ArticleTagCache.Get(article.Id) + tags := cache.TagCache.GetList(tagIds) + builder.Put("tags", render.BuildTags(tags)) + + results = append(results, builder.Build()) + } + + return simple.JsonData(&simple.PageResult{Results: results, Page: paging}) +} + +func (c *ArticleController) PostCreate() *simple.JsonResult { + return simple.JsonErrorMsg("为实现") +} + +func (c *ArticleController) PostUpdate() *simple.JsonResult { + id := c.Ctx.PostValueInt64Default("id", 0) + if id <= 0 { + return simple.JsonErrorMsg("id is required") + } + t := services.ArticleService.Get(id) + if t == nil { + return simple.JsonErrorMsg("entity not found") + } + + simple.ReadForm(c.Ctx, t) + + // 数据校验 + if len(t.Title) == 0 { + return simple.JsonErrorMsg("标题不能为空") + } + if len(t.Content) == 0 { + return simple.JsonErrorMsg("内容不能为空") + } + if len(t.ContentType) == 0 { + return simple.JsonErrorMsg("请选择内容格式") + } + + t.UpdateTime = simple.NowTimestamp() + err := services.ArticleService.Update(t) + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + + return simple.JsonData(t) +} + +func (c *ArticleController) PostDelete() *simple.JsonResult { + id := c.Ctx.PostValueInt64Default("id", 0) + if id <= 0 { + return simple.JsonErrorMsg("id is required") + } + err := services.ArticleService.Delete(id) + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + return simple.JsonSuccess() +} diff --git a/server/controllers/render/render.go b/server/controllers/render/render.go index addc01ca6..eeb4bc3fe 100644 --- a/server/controllers/render/render.go +++ b/server/controllers/render/render.go @@ -120,7 +120,7 @@ func BuildArticle(article *model.Article) *model.ArticleResponse { if len(rsp.Summary) == 0 { rsp.Summary = mr.SummaryText } - } else { + } else if article.ContentType == model.ContentTypeHtml { rsp.Content = template.HTML(BuildHtmlContent(article.Content)) if len(rsp.Summary) == 0 { rsp.Summary = simple.GetSummary(article.Content, 256) @@ -166,7 +166,7 @@ func BuildSimpleArticle(article *model.Article) *model.ArticleSimpleResponse { mr := simple.NewMd(simple.MdWithTOC()).Run(article.Content) rsp.Summary = mr.SummaryText } - } else { + } else if article.ContentType == model.ContentTypeHtml { if len(rsp.Summary) == 0 { rsp.Summary = simple.GetSummary(simple.GetHtmlText(article.Content), 256) } @@ -408,9 +408,11 @@ func _buildComment(comment *model.Comment, buildQuote bool) *model.CommentRespon if comment.ContentType == model.ContentTypeMarkdown { markdownResult := simple.NewMd().Run(comment.Content) - ret.Content = template.HTML(BuildHtmlContent(markdownResult.ContentHtml)) + ret.Content = BuildHtmlContent(markdownResult.ContentHtml) + } else if comment.ContentType == model.ContentTypeHtml { + ret.Content = BuildHtmlContent(comment.Content) } else { - ret.Content = template.HTML(BuildHtmlContent(comment.Content)) + ret.Content = comment.Content } if buildQuote && comment.QuoteId > 0 { @@ -457,7 +459,7 @@ func BuildFavorite(favorite *model.Favorite) *model.FavoriteResponse { rsp.Title = article.Title if article.ContentType == model.ContentTypeMarkdown { rsp.Content = common.GetMarkdownSummary(article.Content) - } else { + } else if article.ContentType == model.ContentTypeHtml { doc, err := goquery.NewDocumentFromReader(strings.NewReader(article.Content)) if err == nil { text := doc.Text() diff --git a/server/go.sum b/server/go.sum index e618b5708..8a8b48c26 100644 --- a/server/go.sum +++ b/server/go.sum @@ -211,6 +211,7 @@ github.com/mlogclub/simple v1.0.57/go.mod h1:J5p18ObwMAb7pJ8nodcHuZrmU0G3bnoFX2V github.com/mlogclub/simple v1.0.58/go.mod h1:J5p18ObwMAb7pJ8nodcHuZrmU0G3bnoFX2VEzeuj5Os= github.com/mlogclub/simple v1.0.59 h1:XZ3FBJXErh8mbaAx6MMX1dZHAGZCBkOPoZ9u2KF8yP4= github.com/mlogclub/simple v1.0.59/go.mod h1:J5p18ObwMAb7pJ8nodcHuZrmU0G3bnoFX2VEzeuj5Os= +github.com/mlogclub/simple v1.0.60 h1:2w6fBtNxbXVbCKuIgCWCMZY8KV9dbfQakYH/v8ZJrIs= github.com/mlogclub/simple v1.0.60/go.mod h1:J5p18ObwMAb7pJ8nodcHuZrmU0G3bnoFX2VEzeuj5Os= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/server/model/models.go b/server/model/models.go index 4e8f1d56f..aa6adc971 100644 --- a/server/model/models.go +++ b/server/model/models.go @@ -24,6 +24,7 @@ const ( ContentTypeHtml = "html" ContentTypeMarkdown = "markdown" + ContentTypeText = "text" EntityTypeArticle = "article" EntityTypeTopic = "topic" diff --git a/server/model/response.go b/server/model/response.go index f16cf59c6..7891b7a5a 100644 --- a/server/model/response.go +++ b/server/model/response.go @@ -122,10 +122,10 @@ type CommentResponse struct { User *UserInfo `json:"user"` EntityType string `json:"entityType"` EntityId int64 `json:"entityId"` - Content template.HTML `json:"content"` + Content string `json:"content"` QuoteId int64 `json:"quoteId"` Quote *CommentResponse `json:"quote"` - QuoteContent template.HTML `json:"quoteContent"` + QuoteContent string `json:"quoteContent"` Status int `json:"status"` CreateTime int64 `json:"createTime"` } diff --git a/server/services/article_service.go b/server/services/article_service.go index 63a961d28..85dea9e6d 100644 --- a/server/services/article_service.go +++ b/server/services/article_service.go @@ -1,319 +1,314 @@ -package services - -import ( - "errors" - "math" - "path" - "strings" - "time" - - "github.com/emirpasic/gods/sets/hashset" - - "bbs-go/common/baiduseo" - "bbs-go/common/config" - "bbs-go/common/urls" - "bbs-go/repositories" - "bbs-go/services/cache" - - "github.com/gorilla/feeds" - "github.com/jinzhu/gorm" - "github.com/mlogclub/simple" - "github.com/sirupsen/logrus" - - "bbs-go/common" - "bbs-go/model" -) - -type ScanArticleCallback func(articles []model.Article) - -var ArticleService = newArticleService() - -func newArticleService() *articleService { - return &articleService{} -} - -type articleService struct { -} - -func (s *articleService) Get(id int64) *model.Article { - return repositories.ArticleRepository.Get(simple.DB(), id) -} - -func (s *articleService) Take(where ...interface{}) *model.Article { - return repositories.ArticleRepository.Take(simple.DB(), where...) -} - -func (s *articleService) Find(cnd *simple.SqlCnd) []model.Article { - return repositories.ArticleRepository.Find(simple.DB(), cnd) -} - -func (s *articleService) FindOne(cnd *simple.SqlCnd) *model.Article { - return repositories.ArticleRepository.FindOne(simple.DB(), cnd) -} - -func (s *articleService) FindPageByParams(params *simple.QueryParams) (list []model.Article, paging *simple.Paging) { - return repositories.ArticleRepository.FindPageByParams(simple.DB(), params) -} - -func (s *articleService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Article, paging *simple.Paging) { - return repositories.ArticleRepository.FindPageByCnd(simple.DB(), cnd) -} - -func (s *articleService) Update(t *model.Article) error { - err := repositories.ArticleRepository.Update(simple.DB(), t) - return err -} - -func (s *articleService) Updates(id int64, columns map[string]interface{}) error { - err := repositories.ArticleRepository.Updates(simple.DB(), id, columns) - return err -} - -func (s *articleService) UpdateColumn(id int64, name string, value interface{}) error { - err := repositories.ArticleRepository.UpdateColumn(simple.DB(), id, name, value) - return err -} - -func (s *articleService) Delete(id int64) error { - err := repositories.ArticleRepository.UpdateColumn(simple.DB(), id, "status", model.StatusDeleted) - if err == nil { - // 删掉标签文章 - ArticleTagService.DeleteByArticleId(id) - } - return err -} - -// 根据文章编号批量获取文章 -func (s *articleService) GetArticleInIds(articleIds []int64) []model.Article { - if len(articleIds) == 0 { - return nil - } - var articles []model.Article - simple.DB().Where("id in (?)", articleIds).Find(&articles) - return articles -} - -// 获取文章对应的标签 -func (s *articleService) GetArticleTags(articleId int64) []model.Tag { - articleTags := repositories.ArticleTagRepository.Find(simple.DB(), simple.NewSqlCnd().Where("article_id = ?", articleId)) - var tagIds []int64 - for _, articleTag := range articleTags { - tagIds = append(tagIds, articleTag.TagId) - } - return cache.TagCache.GetList(tagIds) -} - -// 文章列表 -func (s *articleService) GetArticles(cursor int64) (articles []model.Article, nextCursor int64) { - cnd := simple.NewSqlCnd().Eq("status", model.StatusOk).Desc("id").Limit(20) - if cursor > 0 { - cnd.Lt("id", cursor) - } - articles = repositories.ArticleRepository.Find(simple.DB(), cnd) - if len(articles) > 0 { - nextCursor = articles[len(articles)-1].Id - } else { - nextCursor = cursor - } - return -} - -// 标签文章列表 -func (s *articleService) GetTagArticles(tagId int64, cursor int64) (articles []model.Article, nextCursor int64) { - cnd := simple.NewSqlCnd().Eq("tag_id", tagId).Eq("status", model.StatusOk).Desc("id").Limit(20) - if cursor > 0 { - cnd.Lt("id", cursor) - } - nextCursor = cursor - articleTags := repositories.ArticleTagRepository.Find(simple.DB(), cnd) - if len(articleTags) > 0 { - var articleIds []int64 - for _, articleTag := range articleTags { - articleIds = append(articleIds, articleTag.ArticleId) - nextCursor = articleTag.Id - } - articles = s.GetArticleInIds(articleIds) - } - return -} - -// 发布文章 -func (s *articleService) Publish(userId int64, title, summary, content, contentType string, tags []string, - sourceUrl string, share bool) (article *model.Article, err error) { - title = strings.TrimSpace(title) - summary = strings.TrimSpace(summary) - content = strings.TrimSpace(content) - - if len(title) == 0 { - return nil, errors.New("标题不能为空") - } - if share { // 如果是分享的内容,必须有Summary和SourceUrl - if len(summary) == 0 { - return nil, errors.New("分享内容摘要不能为空") - } - if len(sourceUrl) == 0 { - return nil, errors.New("分享内容原文链接不能为空") - } - } else { - if len(content) == 0 { - return nil, errors.New("内容不能为空") - } - } - article = &model.Article{ - UserId: userId, - Title: title, - Summary: summary, - Content: content, - ContentType: contentType, - Status: model.StatusOk, - Share: share, - SourceUrl: sourceUrl, - CreateTime: simple.NowTimestamp(), - UpdateTime: simple.NowTimestamp(), - } - - err = simple.Tx(simple.DB(), func(tx *gorm.DB) error { - tagIds := repositories.TagRepository.GetOrCreates(tx, tags) - err := repositories.ArticleRepository.Create(tx, article) - if err != nil { - return err - } - repositories.ArticleTagRepository.AddArticleTags(tx, article.Id, tagIds) - return nil - }) - - if err == nil { - baiduseo.PushUrl(urls.ArticleUrl(article.Id)) - } - return -} - -// 修改文章 -func (s *articleService) Edit(articleId int64, tags []string, title, content string) *simple.CodeError { - if len(title) == 0 { - return simple.NewErrorMsg("请输入标题") - } - if len(content) == 0 { - return simple.NewErrorMsg("请填写文章内容") - } - - err := simple.Tx(simple.DB(), func(tx *gorm.DB) error { - err := repositories.ArticleRepository.Updates(simple.DB(), articleId, map[string]interface{}{ - "title": title, - "content": content, - }) - if err != nil { - return err - } - tagIds := repositories.TagRepository.GetOrCreates(tx, tags) // 创建文章对应标签 - repositories.ArticleTagRepository.DeleteArticleTags(tx, articleId) // 先删掉所有的标签 - repositories.ArticleTagRepository.AddArticleTags(tx, articleId, tagIds) // 然后重新添加标签 - return nil - }) - cache.ArticleTagCache.Invalidate(articleId) - return simple.FromError(err) -} - -// 相关文章 -func (s *articleService) GetRelatedArticles(articleId int64) []model.Article { - tagIds := cache.ArticleTagCache.Get(articleId) - if len(tagIds) == 0 { - return nil - } - var articleTags []model.ArticleTag - simple.DB().Where("tag_id in (?)", tagIds).Limit(30).Find(&articleTags) - - set := hashset.New() - if len(articleTags) > 0 { - for _, articleTag := range articleTags { - set.Add(articleTag.ArticleId) - } - } - - var articleIds []int64 - for i, articleId := range set.Values() { - if i < 10 { - articleIds = append(articleIds, articleId.(int64)) - } - } - - return s.GetArticleInIds(articleIds) -} - -// 最新文章 -func (s *articleService) GetUserNewestArticles(userId int64) []model.Article { - return repositories.ArticleRepository.Find(simple.DB(), simple.NewSqlCnd().Where("user_id = ? and status = ?", - userId, model.StatusOk).Desc("id").Limit(10)) -} - -// 倒序扫描 -func (s *articleService) ScanDesc(dateFrom, dateTo int64, cb ScanArticleCallback) { - var cursor int64 = math.MaxInt64 - for { - list := repositories.ArticleRepository.Find(simple.DB(), simple.NewSqlCnd("id", "status", "create_time", "update_time"). - Lt("id", cursor).Gte("create_time", dateFrom).Lt("create_time", dateTo).Desc("id").Limit(1000)) - if list == nil || len(list) == 0 { - break - } - cursor = list[len(list)-1].Id - cb(list) - } -} - -// rss -func (s *articleService) GenerateRss() { - articles := repositories.ArticleRepository.Find(simple.DB(), - simple.NewSqlCnd().Where("status = ?", model.StatusOk).Desc("id").Limit(1000)) - - var items []*feeds.Item - for _, article := range articles { - articleUrl := urls.ArticleUrl(article.Id) - user := cache.UserCache.Get(article.UserId) - if user == nil { - continue - } - description := "" - if article.ContentType == model.ContentTypeMarkdown { - description = common.GetMarkdownSummary(article.Content) - } else { - description = common.GetHtmlSummary(article.Content) - } - item := &feeds.Item{ - Title: article.Title, - Link: &feeds.Link{Href: articleUrl}, - Description: description, - Author: &feeds.Author{Name: user.Avatar, Email: user.Email.String}, - Created: simple.TimeFromTimestamp(article.CreateTime), - } - items = append(items, item) - } - - siteTitle := cache.SysConfigCache.GetValue(model.SysConfigSiteTitle) - siteDescription := cache.SysConfigCache.GetValue(model.SysConfigSiteDescription) - feed := &feeds.Feed{ - Title: siteTitle, - Link: &feeds.Link{Href: config.Conf.BaseUrl}, - Description: siteDescription, - Author: &feeds.Author{Name: siteTitle}, - Created: time.Now(), - Items: items, - } - atom, err := feed.ToAtom() - if err != nil { - logrus.Error(err) - } else { - _ = simple.WriteString(path.Join(config.Conf.StaticPath, "atom.xml"), atom, false) - } - - rss, err := feed.ToRss() - if err != nil { - logrus.Error(err) - } else { - _ = simple.WriteString(path.Join(config.Conf.StaticPath, "rss.xml"), rss, false) - } -} - -// 浏览数+1 -func (s *articleService) IncrViewCount(articleId int64) { - simple.DB().Exec("update t_article set view_count = view_count + 1 where id = ?", articleId) -} +package services + +import ( + "errors" + "math" + "path" + "strings" + "time" + + "github.com/emirpasic/gods/sets/hashset" + + "bbs-go/common/baiduseo" + "bbs-go/common/config" + "bbs-go/common/urls" + "bbs-go/repositories" + "bbs-go/services/cache" + + "github.com/gorilla/feeds" + "github.com/jinzhu/gorm" + "github.com/mlogclub/simple" + "github.com/sirupsen/logrus" + + "bbs-go/common" + "bbs-go/model" +) + +type ScanArticleCallback func(articles []model.Article) + +var ArticleService = newArticleService() + +func newArticleService() *articleService { + return &articleService{} +} + +type articleService struct { +} + +func (s *articleService) Get(id int64) *model.Article { + return repositories.ArticleRepository.Get(simple.DB(), id) +} + +func (s *articleService) Take(where ...interface{}) *model.Article { + return repositories.ArticleRepository.Take(simple.DB(), where...) +} + +func (s *articleService) Find(cnd *simple.SqlCnd) []model.Article { + return repositories.ArticleRepository.Find(simple.DB(), cnd) +} + +func (s *articleService) FindOne(cnd *simple.SqlCnd) *model.Article { + return repositories.ArticleRepository.FindOne(simple.DB(), cnd) +} + +func (s *articleService) FindPageByParams(params *simple.QueryParams) (list []model.Article, paging *simple.Paging) { + return repositories.ArticleRepository.FindPageByParams(simple.DB(), params) +} + +func (s *articleService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Article, paging *simple.Paging) { + return repositories.ArticleRepository.FindPageByCnd(simple.DB(), cnd) +} + +func (s *articleService) Update(t *model.Article) error { + err := repositories.ArticleRepository.Update(simple.DB(), t) + return err +} + +func (s *articleService) Updates(id int64, columns map[string]interface{}) error { + err := repositories.ArticleRepository.Updates(simple.DB(), id, columns) + return err +} + +func (s *articleService) UpdateColumn(id int64, name string, value interface{}) error { + err := repositories.ArticleRepository.UpdateColumn(simple.DB(), id, name, value) + return err +} + +func (s *articleService) Delete(id int64) error { + err := repositories.ArticleRepository.UpdateColumn(simple.DB(), id, "status", model.StatusDeleted) + if err == nil { + // 删掉标签文章 + ArticleTagService.DeleteByArticleId(id) + } + return err +} + +// 根据文章编号批量获取文章 +func (s *articleService) GetArticleInIds(articleIds []int64) []model.Article { + if len(articleIds) == 0 { + return nil + } + var articles []model.Article + simple.DB().Where("id in (?)", articleIds).Find(&articles) + return articles +} + +// 获取文章对应的标签 +func (s *articleService) GetArticleTags(articleId int64) []model.Tag { + articleTags := repositories.ArticleTagRepository.Find(simple.DB(), simple.NewSqlCnd().Where("article_id = ?", articleId)) + var tagIds []int64 + for _, articleTag := range articleTags { + tagIds = append(tagIds, articleTag.TagId) + } + return cache.TagCache.GetList(tagIds) +} + +// 文章列表 +func (s *articleService) GetArticles(cursor int64) (articles []model.Article, nextCursor int64) { + cnd := simple.NewSqlCnd().Eq("status", model.StatusOk).Desc("id").Limit(20) + if cursor > 0 { + cnd.Lt("id", cursor) + } + articles = repositories.ArticleRepository.Find(simple.DB(), cnd) + if len(articles) > 0 { + nextCursor = articles[len(articles)-1].Id + } else { + nextCursor = cursor + } + return +} + +// 标签文章列表 +func (s *articleService) GetTagArticles(tagId int64, cursor int64) (articles []model.Article, nextCursor int64) { + cnd := simple.NewSqlCnd().Eq("tag_id", tagId).Eq("status", model.StatusOk).Desc("id").Limit(20) + if cursor > 0 { + cnd.Lt("id", cursor) + } + nextCursor = cursor + articleTags := repositories.ArticleTagRepository.Find(simple.DB(), cnd) + if len(articleTags) > 0 { + var articleIds []int64 + for _, articleTag := range articleTags { + articleIds = append(articleIds, articleTag.ArticleId) + nextCursor = articleTag.Id + } + articles = s.GetArticleInIds(articleIds) + } + return +} + +// 发布文章 +func (s *articleService) Publish(userId int64, title, summary, content, contentType string, tags []string, + sourceUrl string, share bool) (article *model.Article, err error) { + title = strings.TrimSpace(title) + summary = strings.TrimSpace(summary) + content = strings.TrimSpace(content) + + if len(title) == 0 { + return nil, errors.New("标题不能为空") + } + if share { // 如果是分享的内容,必须有Summary和SourceUrl + if len(summary) == 0 { + return nil, errors.New("分享内容摘要不能为空") + } + if len(sourceUrl) == 0 { + return nil, errors.New("分享内容原文链接不能为空") + } + } else { + if len(content) == 0 { + return nil, errors.New("内容不能为空") + } + } + article = &model.Article{ + UserId: userId, + Title: title, + Summary: summary, + Content: content, + ContentType: contentType, + Status: model.StatusOk, + Share: share, + SourceUrl: sourceUrl, + CreateTime: simple.NowTimestamp(), + UpdateTime: simple.NowTimestamp(), + } + + err = simple.Tx(simple.DB(), func(tx *gorm.DB) error { + tagIds := repositories.TagRepository.GetOrCreates(tx, tags) + err := repositories.ArticleRepository.Create(tx, article) + if err != nil { + return err + } + repositories.ArticleTagRepository.AddArticleTags(tx, article.Id, tagIds) + return nil + }) + + if err == nil { + baiduseo.PushUrl(urls.ArticleUrl(article.Id)) + } + return +} + +// 修改文章 +func (s *articleService) Edit(articleId int64, tags []string, title, content string) *simple.CodeError { + if len(title) == 0 { + return simple.NewErrorMsg("请输入标题") + } + if len(content) == 0 { + return simple.NewErrorMsg("请填写文章内容") + } + + err := simple.Tx(simple.DB(), func(tx *gorm.DB) error { + err := repositories.ArticleRepository.Updates(simple.DB(), articleId, map[string]interface{}{ + "title": title, + "content": content, + }) + if err != nil { + return err + } + tagIds := repositories.TagRepository.GetOrCreates(tx, tags) // 创建文章对应标签 + repositories.ArticleTagRepository.DeleteArticleTags(tx, articleId) // 先删掉所有的标签 + repositories.ArticleTagRepository.AddArticleTags(tx, articleId, tagIds) // 然后重新添加标签 + return nil + }) + cache.ArticleTagCache.Invalidate(articleId) + return simple.FromError(err) +} + +// 相关文章 +func (s *articleService) GetRelatedArticles(articleId int64) []model.Article { + tagIds := cache.ArticleTagCache.Get(articleId) + if len(tagIds) == 0 { + return nil + } + var articleTags []model.ArticleTag + simple.DB().Where("tag_id in (?)", tagIds).Limit(30).Find(&articleTags) + + set := hashset.New() + if len(articleTags) > 0 { + for _, articleTag := range articleTags { + set.Add(articleTag.ArticleId) + } + } + + var articleIds []int64 + for i, articleId := range set.Values() { + if i < 10 { + articleIds = append(articleIds, articleId.(int64)) + } + } + + return s.GetArticleInIds(articleIds) +} + +// 最新文章 +func (s *articleService) GetUserNewestArticles(userId int64) []model.Article { + return repositories.ArticleRepository.Find(simple.DB(), simple.NewSqlCnd().Where("user_id = ? and status = ?", + userId, model.StatusOk).Desc("id").Limit(10)) +} + +// 倒序扫描 +func (s *articleService) ScanDesc(dateFrom, dateTo int64, cb ScanArticleCallback) { + var cursor int64 = math.MaxInt64 + for { + list := repositories.ArticleRepository.Find(simple.DB(), simple.NewSqlCnd("id", "status", "create_time", "update_time"). + Lt("id", cursor).Gte("create_time", dateFrom).Lt("create_time", dateTo).Desc("id").Limit(1000)) + if list == nil || len(list) == 0 { + break + } + cursor = list[len(list)-1].Id + cb(list) + } +} + +// rss +func (s *articleService) GenerateRss() { + articles := repositories.ArticleRepository.Find(simple.DB(), + simple.NewSqlCnd().Where("status = ?", model.StatusOk).Desc("id").Limit(1000)) + + var items []*feeds.Item + for _, article := range articles { + articleUrl := urls.ArticleUrl(article.Id) + user := cache.UserCache.Get(article.UserId) + if user == nil { + continue + } + description := common.GetSummary(article.ContentType, article.Content) + item := &feeds.Item{ + Title: article.Title, + Link: &feeds.Link{Href: articleUrl}, + Description: description, + Author: &feeds.Author{Name: user.Avatar, Email: user.Email.String}, + Created: simple.TimeFromTimestamp(article.CreateTime), + } + items = append(items, item) + } + + siteTitle := cache.SysConfigCache.GetValue(model.SysConfigSiteTitle) + siteDescription := cache.SysConfigCache.GetValue(model.SysConfigSiteDescription) + feed := &feeds.Feed{ + Title: siteTitle, + Link: &feeds.Link{Href: config.Conf.BaseUrl}, + Description: siteDescription, + Author: &feeds.Author{Name: siteTitle}, + Created: time.Now(), + Items: items, + } + atom, err := feed.ToAtom() + if err != nil { + logrus.Error(err) + } else { + _ = simple.WriteString(path.Join(config.Conf.StaticPath, "atom.xml"), atom, false) + } + + rss, err := feed.ToRss() + if err != nil { + logrus.Error(err) + } else { + _ = simple.WriteString(path.Join(config.Conf.StaticPath, "rss.xml"), rss, false) + } +} + +// 浏览数+1 +func (s *articleService) IncrViewCount(articleId int64) { + simple.DB().Exec("update t_article set view_count = view_count + 1 where id = ?", articleId) +} diff --git a/server/services/collect/spider_api.go b/server/services/collect/spider_api.go index aabba22ca..e1db7f350 100644 --- a/server/services/collect/spider_api.go +++ b/server/services/collect/spider_api.go @@ -1,96 +1,96 @@ -package collect - -import ( - "errors" - - "bbs-go/common" - "bbs-go/common/baiduai" - "bbs-go/model" - "bbs-go/services" -) - -type SpiderApi struct { -} - -func NewSpiderApi() *SpiderApi { - return &SpiderApi{} -} - -func (api *SpiderApi) Publish(article *Article) (articleId int64, err error) { - if article.Summary == "" { - article.Summary = common.GetSummary(article.ContentType, article.Content) - } - - if len(article.Tags) == 0 { - article.Tags = api.AnalyzeTags(article) - } - - t, err := services.ArticleService.Publish(article.UserId, article.Title, article.Summary, article.Content, - article.ContentType, article.Tags, article.SourceUrl, true) - if err == nil { - articleId = t.Id - - if article.PublishTime > 0 { - _ = services.ArticleService.UpdateColumn(articleId, "create_time", article.PublishTime) - } - } - return -} - -func (api *SpiderApi) PublishComment(comment *Comment) (commentId int64, err error) { - if len(comment.Content) == 0 { - err = errors.New("评论内容不能为空") - return - } - - c, err := services.CommentService.Publish(comment.UserId, &model.CreateCommentForm{ - EntityType: comment.EntityType, - EntityId: comment.EntityId, - Content: comment.Content, - ContentType: model.ContentTypeHtml, - }) - if err == nil { - commentId = c.Id - - if comment.PublishTime > 0 { - _ = services.CommentService.UpdateColumn(commentId, "create_time", comment.PublishTime) - } - } - return -} - -func (api *SpiderApi) AnalyzeTags(article *Article) []string { - var analyzeRet *baiduai.AiAnalyzeRet - if article.ContentType == model.ContentTypeMarkdown { - analyzeRet, _ = baiduai.GetAi().AnalyzeMarkdown(article.Title, article.Content) - } else { - analyzeRet, _ = baiduai.GetAi().AnalyzeHtml(article.Title, article.Content) - } - var tags []string - if analyzeRet != nil { - tags = analyzeRet.Tags - if article.Summary == "" { - article.Summary = analyzeRet.Summary - } - } - return tags -} - -type Article struct { - UserId int64 `json:"userId" form:"userId"` // 发布用户编号 - Title string `json:"title" form:"title"` - Summary string `json:"summary" form:"summary"` - Content string `json:"content" form:"content"` - ContentType string `json:"contentType" form:"contentType"` - Tags []string `json:"tags" form:"tags"` - SourceUrl string `json:"sourceUrl" form:"sourceUrl"` - PublishTime int64 `json:"publishTime" form:"publishTime"` -} - -type Comment struct { - UserId int64 `json:"userId" form:"userId"` - Content string `json:"content" form:"content"` - EntityType string `json:"entityType" form:"entityType"` - EntityId int64 `json:"entityId" form:"entityId"` - PublishTime int64 `json:"publishTime" form:"publishTime"` -} +package collect + +import ( + "errors" + + "bbs-go/common" + "bbs-go/common/baiduai" + "bbs-go/model" + "bbs-go/services" +) + +type SpiderApi struct { +} + +func NewSpiderApi() *SpiderApi { + return &SpiderApi{} +} + +func (api *SpiderApi) Publish(article *Article) (articleId int64, err error) { + if article.Summary == "" { + article.Summary = common.GetSummary(article.ContentType, article.Content) + } + + if len(article.Tags) == 0 { + article.Tags = api.AnalyzeTags(article) + } + + t, err := services.ArticleService.Publish(article.UserId, article.Title, article.Summary, article.Content, + article.ContentType, article.Tags, article.SourceUrl, true) + if err == nil { + articleId = t.Id + + if article.PublishTime > 0 { + _ = services.ArticleService.UpdateColumn(articleId, "create_time", article.PublishTime) + } + } + return +} + +func (api *SpiderApi) PublishComment(comment *Comment) (commentId int64, err error) { + if len(comment.Content) == 0 { + err = errors.New("评论内容不能为空") + return + } + + c, err := services.CommentService.Publish(comment.UserId, &model.CreateCommentForm{ + EntityType: comment.EntityType, + EntityId: comment.EntityId, + Content: comment.Content, + ContentType: model.ContentTypeHtml, + }) + if err == nil { + commentId = c.Id + + if comment.PublishTime > 0 { + _ = services.CommentService.UpdateColumn(commentId, "create_time", comment.PublishTime) + } + } + return +} + +func (api *SpiderApi) AnalyzeTags(article *Article) []string { + var analyzeRet *baiduai.AiAnalyzeRet + if article.ContentType == model.ContentTypeMarkdown { + analyzeRet, _ = baiduai.GetAi().AnalyzeMarkdown(article.Title, article.Content) + } else if article.ContentType == model.ContentTypeHtml { + analyzeRet, _ = baiduai.GetAi().AnalyzeHtml(article.Title, article.Content) + } + var tags []string + if analyzeRet != nil { + tags = analyzeRet.Tags + if article.Summary == "" { + article.Summary = analyzeRet.Summary + } + } + return tags +} + +type Article struct { + UserId int64 `json:"userId" form:"userId"` // 发布用户编号 + Title string `json:"title" form:"title"` + Summary string `json:"summary" form:"summary"` + Content string `json:"content" form:"content"` + ContentType string `json:"contentType" form:"contentType"` + Tags []string `json:"tags" form:"tags"` + SourceUrl string `json:"sourceUrl" form:"sourceUrl"` + PublishTime int64 `json:"publishTime" form:"publishTime"` +} + +type Comment struct { + UserId int64 `json:"userId" form:"userId"` + Content string `json:"content" form:"content"` + EntityType string `json:"entityType" form:"entityType"` + EntityId int64 `json:"entityId" form:"entityId"` + PublishTime int64 `json:"publishTime" form:"publishTime"` +} diff --git a/server/services/project_service.go b/server/services/project_service.go index f9ff7962b..34bf9dd0f 100644 --- a/server/services/project_service.go +++ b/server/services/project_service.go @@ -1,160 +1,155 @@ -package services - -import ( - "math" - "path" - "time" - - "github.com/gorilla/feeds" - "github.com/mlogclub/simple" - "github.com/sirupsen/logrus" - - "bbs-go/common" - "bbs-go/common/config" - "bbs-go/common/urls" - "bbs-go/model" - "bbs-go/repositories" - "bbs-go/services/cache" -) - -var ProjectService = newProjectService() - -type ProjectScanCallback func(projects []model.Project) - -func newProjectService() *projectService { - return &projectService{} -} - -type projectService struct { -} - -func (s *projectService) Get(id int64) *model.Project { - return repositories.ProjectRepository.Get(simple.DB(), id) -} - -func (s *projectService) Take(where ...interface{}) *model.Project { - return repositories.ProjectRepository.Take(simple.DB(), where...) -} - -func (s *projectService) Find(cnd *simple.SqlCnd) []model.Project { - return repositories.ProjectRepository.Find(simple.DB(), cnd) -} - -func (s *projectService) FindOne(cnd *simple.SqlCnd) *model.Project { - return repositories.ProjectRepository.FindOne(simple.DB(), cnd) -} - -func (s *projectService) FindPageByParams(params *simple.QueryParams) (list []model.Project, paging *simple.Paging) { - return repositories.ProjectRepository.FindPageByParams(simple.DB(), params) -} - -func (s *projectService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Project, paging *simple.Paging) { - return repositories.ProjectRepository.FindPageByCnd(simple.DB(), cnd) -} - -func (s *projectService) Create(t *model.Project) error { - return repositories.ProjectRepository.Create(simple.DB(), t) -} - -func (s *projectService) Update(t *model.Project) error { - return repositories.ProjectRepository.Update(simple.DB(), t) -} - -func (s *projectService) Updates(id int64, columns map[string]interface{}) error { - return repositories.ProjectRepository.Updates(simple.DB(), id, columns) -} - -func (s *projectService) UpdateColumn(id int64, name string, value interface{}) error { - return repositories.ProjectRepository.UpdateColumn(simple.DB(), id, name, value) -} - -func (s *projectService) Delete(id int64) { - repositories.ProjectRepository.Delete(simple.DB(), id) -} - -// 发布 -func (s *projectService) Publish(userId int64, name, title, logo, url, docUrl, downloadUrl, contentType, - content string) (*model.Project, error) { - project := &model.Project{ - UserId: userId, - Name: name, - Title: title, - Logo: logo, - Url: url, - DocUrl: docUrl, - DownloadUrl: downloadUrl, - ContentType: contentType, - Content: content, - CreateTime: simple.NowTimestamp(), - } - err := repositories.ProjectRepository.Create(simple.DB(), project) - if err != nil { - return nil, err - } - return project, nil -} - -func (s *projectService) ScanDesc(dateFrom, dateTo int64, callback ProjectScanCallback) { - var cursor int64 = math.MaxInt64 - for { - list := repositories.ProjectRepository.Find(simple.DB(), simple.NewSqlCnd().Lt("id", cursor). - Gte("create_time", dateFrom).Lt("create_time", dateTo).Desc("id").Limit(1000)) - if list == nil || len(list) == 0 { - break - } - cursor = list[len(list)-1].Id - callback(list) - } -} - -// rss -func (s *projectService) GenerateRss() { - projects := repositories.ProjectRepository.Find(simple.DB(), - simple.NewSqlCnd().Where("1 = 1").Desc("id").Limit(2000)) - - var items []*feeds.Item - for _, project := range projects { - projectUrl := urls.ProjectUrl(project.Id) - user := cache.UserCache.Get(project.UserId) - if user == nil { - continue - } - description := "" - if project.ContentType == model.ContentTypeMarkdown { - description = common.GetMarkdownSummary(project.Content) - } else { - description = common.GetHtmlSummary(project.Content) - } - item := &feeds.Item{ - Title: project.Name + " - " + project.Title, - Link: &feeds.Link{Href: projectUrl}, - Description: description, - Author: &feeds.Author{Name: user.Avatar, Email: user.Email.String}, - Created: simple.TimeFromTimestamp(project.CreateTime), - } - items = append(items, item) - } - siteTitle := cache.SysConfigCache.GetValue(model.SysConfigSiteTitle) - siteDescription := cache.SysConfigCache.GetValue(model.SysConfigSiteDescription) - feed := &feeds.Feed{ - Title: siteTitle, - Link: &feeds.Link{Href: config.Conf.BaseUrl}, - Description: siteDescription, - Author: &feeds.Author{Name: siteTitle}, - Created: time.Now(), - Items: items, - } - atom, err := feed.ToAtom() - if err != nil { - logrus.Error(err) - } else { - _ = simple.WriteString(path.Join(config.Conf.StaticPath, "project_atom.xml"), atom, false) - } - - rss, err := feed.ToRss() - if err != nil { - logrus.Error(err) - } else { - _ = simple.WriteString(path.Join(config.Conf.StaticPath, "project_rss.xml"), rss, false) - } -} +package services + +import ( + "math" + "path" + "time" + + "github.com/gorilla/feeds" + "github.com/mlogclub/simple" + "github.com/sirupsen/logrus" + + "bbs-go/common" + "bbs-go/common/config" + "bbs-go/common/urls" + "bbs-go/model" + "bbs-go/repositories" + "bbs-go/services/cache" +) + +var ProjectService = newProjectService() + +type ProjectScanCallback func(projects []model.Project) + +func newProjectService() *projectService { + return &projectService{} +} + +type projectService struct { +} + +func (s *projectService) Get(id int64) *model.Project { + return repositories.ProjectRepository.Get(simple.DB(), id) +} + +func (s *projectService) Take(where ...interface{}) *model.Project { + return repositories.ProjectRepository.Take(simple.DB(), where...) +} + +func (s *projectService) Find(cnd *simple.SqlCnd) []model.Project { + return repositories.ProjectRepository.Find(simple.DB(), cnd) +} + +func (s *projectService) FindOne(cnd *simple.SqlCnd) *model.Project { + return repositories.ProjectRepository.FindOne(simple.DB(), cnd) +} + +func (s *projectService) FindPageByParams(params *simple.QueryParams) (list []model.Project, paging *simple.Paging) { + return repositories.ProjectRepository.FindPageByParams(simple.DB(), params) +} + +func (s *projectService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Project, paging *simple.Paging) { + return repositories.ProjectRepository.FindPageByCnd(simple.DB(), cnd) +} + +func (s *projectService) Create(t *model.Project) error { + return repositories.ProjectRepository.Create(simple.DB(), t) +} + +func (s *projectService) Update(t *model.Project) error { + return repositories.ProjectRepository.Update(simple.DB(), t) +} + +func (s *projectService) Updates(id int64, columns map[string]interface{}) error { + return repositories.ProjectRepository.Updates(simple.DB(), id, columns) +} + +func (s *projectService) UpdateColumn(id int64, name string, value interface{}) error { + return repositories.ProjectRepository.UpdateColumn(simple.DB(), id, name, value) +} + +func (s *projectService) Delete(id int64) { + repositories.ProjectRepository.Delete(simple.DB(), id) +} + +// 发布 +func (s *projectService) Publish(userId int64, name, title, logo, url, docUrl, downloadUrl, contentType, + content string) (*model.Project, error) { + project := &model.Project{ + UserId: userId, + Name: name, + Title: title, + Logo: logo, + Url: url, + DocUrl: docUrl, + DownloadUrl: downloadUrl, + ContentType: contentType, + Content: content, + CreateTime: simple.NowTimestamp(), + } + err := repositories.ProjectRepository.Create(simple.DB(), project) + if err != nil { + return nil, err + } + return project, nil +} + +func (s *projectService) ScanDesc(dateFrom, dateTo int64, callback ProjectScanCallback) { + var cursor int64 = math.MaxInt64 + for { + list := repositories.ProjectRepository.Find(simple.DB(), simple.NewSqlCnd().Lt("id", cursor). + Gte("create_time", dateFrom).Lt("create_time", dateTo).Desc("id").Limit(1000)) + if list == nil || len(list) == 0 { + break + } + cursor = list[len(list)-1].Id + callback(list) + } +} + +// rss +func (s *projectService) GenerateRss() { + projects := repositories.ProjectRepository.Find(simple.DB(), + simple.NewSqlCnd().Where("1 = 1").Desc("id").Limit(2000)) + + var items []*feeds.Item + for _, project := range projects { + projectUrl := urls.ProjectUrl(project.Id) + user := cache.UserCache.Get(project.UserId) + if user == nil { + continue + } + description := common.GetSummary(project.ContentType, project.Content) + item := &feeds.Item{ + Title: project.Name + " - " + project.Title, + Link: &feeds.Link{Href: projectUrl}, + Description: description, + Author: &feeds.Author{Name: user.Avatar, Email: user.Email.String}, + Created: simple.TimeFromTimestamp(project.CreateTime), + } + items = append(items, item) + } + siteTitle := cache.SysConfigCache.GetValue(model.SysConfigSiteTitle) + siteDescription := cache.SysConfigCache.GetValue(model.SysConfigSiteDescription) + feed := &feeds.Feed{ + Title: siteTitle, + Link: &feeds.Link{Href: config.Conf.BaseUrl}, + Description: siteDescription, + Author: &feeds.Author{Name: siteTitle}, + Created: time.Now(), + Items: items, + } + atom, err := feed.ToAtom() + if err != nil { + logrus.Error(err) + } else { + _ = simple.WriteString(path.Join(config.Conf.StaticPath, "project_atom.xml"), atom, false) + } + + rss, err := feed.ToRss() + if err != nil { + logrus.Error(err) + } else { + _ = simple.WriteString(path.Join(config.Conf.StaticPath, "project_rss.xml"), rss, false) + } +}