diff --git a/docs/sql/update.20200418.sql b/docs/sql/update.20200418.sql new file mode 100644 index 0000000000000000000000000000000000000000..6ae93ebfb17bdec550cd14aa837ace7b502ab588 --- /dev/null +++ b/docs/sql/update.20200418.sql @@ -0,0 +1,13 @@ +rename table t_topic_like to t_user_like; + +alter table t_user_like + add column entity_type varchar(32) not null after user_id, + change column topic_id entity_id bigint not null after entity_type, + drop index idx_topic_like_user_id, + drop index idx_topic_like_topic_id; + +update t_user_like set entity_type = 'topic'; + +alter table t_topic + drop column type, + drop column image_list; diff --git a/server/app/iris.go b/server/app/iris.go index ed677846d910164de3a77884a003e131d5af2e73..7ee5f5214d1ca098a9141f6b13e06eed12d525b3 100644 --- a/server/app/iris.go +++ b/server/app/iris.go @@ -1,139 +1,140 @@ -package app - -import ( - "net/http" - "os" - "os/signal" - "strings" - "syscall" - - "github.com/go-resty/resty/v2" - - "github.com/iris-contrib/middleware/cors" - "github.com/kataras/iris/v12" - "github.com/kataras/iris/v12/middleware/logger" - "github.com/kataras/iris/v12/middleware/recover" - "github.com/kataras/iris/v12/mvc" - "github.com/mlogclub/simple" - "github.com/sirupsen/logrus" - - "bbs-go/common/config" - "bbs-go/controllers/api" - - "bbs-go/controllers/admin" - "bbs-go/middleware" -) - -func InitIris() { - app := iris.New() - app.Logger().SetLevel("warn") - app.Use(recover.New()) - app.Use(logger.New()) - app.Use(cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts. - AllowCredentials: true, - MaxAge: 600, - AllowedMethods: []string{iris.MethodGet, iris.MethodPost, iris.MethodOptions, iris.MethodHead, iris.MethodDelete, iris.MethodPut}, - AllowedHeaders: []string{"*"}, - })) - app.AllowMethods(iris.MethodOptions) - - app.OnAnyErrorCode(func(ctx iris.Context) { - path := ctx.Path() - var err error - if strings.Contains(path, "/api/admin/") { - _, err = ctx.JSON(simple.JsonErrorCode(ctx.GetStatusCode(), "Http error")) - } - if err != nil { - logrus.Error(err) - } - }) - - app.Any("/", func(i iris.Context) { - _, _ = i.HTML("

Powered by bbs-go

") - }) - - // api - mvc.Configure(app.Party("/api"), func(m *mvc.Application) { - m.Party("/topic").Handle(new(api.TopicController)) - m.Party("/article").Handle(new(api.ArticleController)) - m.Party("/project").Handle(new(api.ProjectController)) - m.Party("/login").Handle(new(api.LoginController)) - m.Party("/user").Handle(new(api.UserController)) - m.Party("/tag").Handle(new(api.TagController)) - m.Party("/comment").Handle(new(api.CommentController)) - m.Party("/favorite").Handle(new(api.FavoriteController)) - m.Party("/config").Handle(new(api.ConfigController)) - m.Party("/upload").Handle(new(api.UploadController)) - m.Party("/link").Handle(new(api.LinkController)) - m.Party("/captcha").Handle(new(api.CaptchaController)) - m.Party("/spider").Handle(new(api.SpiderController)) - }) - - // admin - mvc.Configure(app.Party("/api/admin"), func(m *mvc.Application) { - m.Router.Use(middleware.AdminAuth) - m.Party("/common").Handle(new(admin.CommonController)) - m.Party("/user").Handle(new(admin.UserController)) - m.Party("/third-account").Handle(new(admin.ThirdAccountController)) - m.Party("/tag").Handle(new(admin.TagController)) - m.Party("/article").Handle(new(admin.ArticleController)) - m.Party("/comment").Handle(new(admin.CommentController)) - m.Party("/favorite").Handle(new(admin.FavoriteController)) - m.Party("/article-tag").Handle(new(admin.ArticleTagController)) - m.Party("/topic").Handle(new(admin.TopicController)) - m.Party("/topic-node").Handle(new(admin.TopicNodeController)) - m.Party("/sys-config").Handle(new(admin.SysConfigController)) - m.Party("/link").Handle(new(admin.LinkController)) - m.Party("/user-score").Handle(new(admin.UserScoreController)) - m.Party("/user-score-log").Handle(new(admin.UserScoreLogController)) - }) - - app.Get("/api/img/proxy", func(i iris.Context) { - url := i.FormValue("url") - resp, err := resty.New().R().Get(url) - i.Header("Content-Type", "image/jpg") - if err == nil { - _, _ = i.Write(resp.Body()) - } else { - logrus.Error(err) - } - }) - - server := &http.Server{Addr: ":" + config.Conf.Port} - handleSignal(server) - err := app.Run(iris.Server(server), iris.WithConfiguration(iris.Configuration{ - DisableStartupLog: false, - DisableInterruptHandler: false, - DisablePathCorrection: false, - EnablePathEscape: false, - FireMethodNotAllowed: false, - DisableBodyConsumptionOnUnmarshal: false, - DisableAutoFireStatusCode: false, - EnableOptimizations: true, - TimeFormat: "2006-01-02 15:04:05", - Charset: "UTF-8", - })) - if err != nil { - logrus.Error(err) - os.Exit(-1) - } -} - -func handleSignal(server *http.Server) { - c := make(chan os.Signal) - signal.Notify(c, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM) - - go func() { - s := <-c - logrus.Infof("got signal [%s], exiting now", s) - if err := server.Close(); nil != err { - logrus.Errorf("server close failed: " + err.Error()) - } - - simple.CloseDB() - - logrus.Infof("Exited") - os.Exit(0) - }() -} +package app + +import ( + "net/http" + "os" + "os/signal" + "strings" + "syscall" + + "github.com/go-resty/resty/v2" + + "github.com/iris-contrib/middleware/cors" + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/middleware/logger" + "github.com/kataras/iris/v12/middleware/recover" + "github.com/kataras/iris/v12/mvc" + "github.com/mlogclub/simple" + "github.com/sirupsen/logrus" + + "bbs-go/common/config" + "bbs-go/controllers/api" + + "bbs-go/controllers/admin" + "bbs-go/middleware" +) + +func InitIris() { + app := iris.New() + app.Logger().SetLevel("warn") + app.Use(recover.New()) + app.Use(logger.New()) + app.Use(cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts. + AllowCredentials: true, + MaxAge: 600, + AllowedMethods: []string{iris.MethodGet, iris.MethodPost, iris.MethodOptions, iris.MethodHead, iris.MethodDelete, iris.MethodPut}, + AllowedHeaders: []string{"*"}, + })) + app.AllowMethods(iris.MethodOptions) + + app.OnAnyErrorCode(func(ctx iris.Context) { + path := ctx.Path() + var err error + if strings.Contains(path, "/api/admin/") { + _, err = ctx.JSON(simple.JsonErrorCode(ctx.GetStatusCode(), "Http error")) + } + if err != nil { + logrus.Error(err) + } + }) + + app.Any("/", func(i iris.Context) { + _, _ = i.HTML("

Powered by bbs-go

") + }) + + // api + mvc.Configure(app.Party("/api"), func(m *mvc.Application) { + m.Party("/topic").Handle(new(api.TopicController)) + m.Party("/tweet").Handle(new(api.TweetController)) + m.Party("/article").Handle(new(api.ArticleController)) + m.Party("/project").Handle(new(api.ProjectController)) + m.Party("/login").Handle(new(api.LoginController)) + m.Party("/user").Handle(new(api.UserController)) + m.Party("/tag").Handle(new(api.TagController)) + m.Party("/comment").Handle(new(api.CommentController)) + m.Party("/favorite").Handle(new(api.FavoriteController)) + m.Party("/config").Handle(new(api.ConfigController)) + m.Party("/upload").Handle(new(api.UploadController)) + m.Party("/link").Handle(new(api.LinkController)) + m.Party("/captcha").Handle(new(api.CaptchaController)) + m.Party("/spider").Handle(new(api.SpiderController)) + }) + + // admin + mvc.Configure(app.Party("/api/admin"), func(m *mvc.Application) { + m.Router.Use(middleware.AdminAuth) + m.Party("/common").Handle(new(admin.CommonController)) + m.Party("/user").Handle(new(admin.UserController)) + m.Party("/third-account").Handle(new(admin.ThirdAccountController)) + m.Party("/tag").Handle(new(admin.TagController)) + m.Party("/article").Handle(new(admin.ArticleController)) + m.Party("/comment").Handle(new(admin.CommentController)) + m.Party("/favorite").Handle(new(admin.FavoriteController)) + m.Party("/article-tag").Handle(new(admin.ArticleTagController)) + m.Party("/topic").Handle(new(admin.TopicController)) + m.Party("/topic-node").Handle(new(admin.TopicNodeController)) + m.Party("/sys-config").Handle(new(admin.SysConfigController)) + m.Party("/link").Handle(new(admin.LinkController)) + m.Party("/user-score").Handle(new(admin.UserScoreController)) + m.Party("/user-score-log").Handle(new(admin.UserScoreLogController)) + }) + + app.Get("/api/img/proxy", func(i iris.Context) { + url := i.FormValue("url") + resp, err := resty.New().R().Get(url) + i.Header("Content-Type", "image/jpg") + if err == nil { + _, _ = i.Write(resp.Body()) + } else { + logrus.Error(err) + } + }) + + server := &http.Server{Addr: ":" + config.Conf.Port} + handleSignal(server) + err := app.Run(iris.Server(server), iris.WithConfiguration(iris.Configuration{ + DisableStartupLog: false, + DisableInterruptHandler: false, + DisablePathCorrection: false, + EnablePathEscape: false, + FireMethodNotAllowed: false, + DisableBodyConsumptionOnUnmarshal: false, + DisableAutoFireStatusCode: false, + EnableOptimizations: true, + TimeFormat: "2006-01-02 15:04:05", + Charset: "UTF-8", + })) + if err != nil { + logrus.Error(err) + os.Exit(-1) + } +} + +func handleSignal(server *http.Server) { + c := make(chan os.Signal) + signal.Notify(c, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM) + + go func() { + s := <-c + logrus.Infof("got signal [%s], exiting now", s) + if err := server.Close(); nil != err { + logrus.Errorf("server close failed: " + err.Error()) + } + + simple.CloseDB() + + logrus.Infof("Exited") + os.Exit(0) + }() +} diff --git a/server/codegen/codegen.go b/server/codegen/codegen.go index 3b37b6c66d22c8181669edd86b644346bc311022..ac02f1ca9fba74616147d00fbfb4c9d8546e60dd 100644 --- a/server/codegen/codegen.go +++ b/server/codegen/codegen.go @@ -1,12 +1,11 @@ -package main - -import ( - "github.com/mlogclub/simple" - - "bbs-go/model" -) - -func main() { - simple.Generate("./", "bbs-go", simple.GetGenerateStruct(&model.UserScore{})) - simple.Generate("./", "bbs-go", simple.GetGenerateStruct(&model.UserScoreLog{})) -} +package main + +import ( + "github.com/mlogclub/simple" + + "bbs-go/model" +) + +func main() { + simple.Generate("./", "bbs-go", simple.GetGenerateStruct(&model.Tweet{})) +} diff --git a/server/controllers/admin/topic_like_controller.go b/server/controllers/admin/tweet_controller.go similarity index 58% rename from server/controllers/admin/topic_like_controller.go rename to server/controllers/admin/tweet_controller.go index edafba97e3937e2d5e90a65f4409328c3305af8a..39d97928fdfde8316ecc234df5a168b05e09466b 100644 --- a/server/controllers/admin/topic_like_controller.go +++ b/server/controllers/admin/tweet_controller.go @@ -1,64 +1,64 @@ -package admin - -import ( - "strconv" - - "github.com/kataras/iris/v12" - "github.com/mlogclub/simple" - - "bbs-go/model" - "bbs-go/services" -) - -type TopicLikeController struct { - Ctx iris.Context -} - -func (c *TopicLikeController) GetBy(id int64) *simple.JsonResult { - t := services.TopicLikeService.Get(id) - if t == nil { - return simple.JsonErrorMsg("Not found, id=" + strconv.FormatInt(id, 10)) - } - return simple.JsonData(t) -} - -func (c *TopicLikeController) AnyList() *simple.JsonResult { - list, paging := services.TopicLikeService.FindPageByParams(simple.NewQueryParams(c.Ctx).PageByReq().Desc("id")) - return simple.JsonData(&simple.PageResult{Results: list, Page: paging}) -} - -func (c *TopicLikeController) PostCreate() *simple.JsonResult { - t := &model.TopicLike{} - err := simple.ReadForm(c.Ctx, t) - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - - err = services.TopicLikeService.Create(t) - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - return simple.JsonData(t) -} - -func (c *TopicLikeController) PostUpdate() *simple.JsonResult { - id, err := simple.FormValueInt64(c.Ctx, "id") - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - t := services.TopicLikeService.Get(id) - if t == nil { - return simple.JsonErrorMsg("entity not found") - } - - err = simple.ReadForm(c.Ctx, t) - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - - err = services.TopicLikeService.Update(t) - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - return simple.JsonData(t) -} +package admin + +import ( + "strconv" + + "github.com/kataras/iris/v12" + "github.com/mlogclub/simple" + + "bbs-go/model" + "bbs-go/services" +) + +type TweetController struct { + Ctx iris.Context +} + +func (c *TweetController) GetBy(id int64) *simple.JsonResult { + t := services.TweetService.Get(id) + if t == nil { + return simple.JsonErrorMsg("Not found, id=" + strconv.FormatInt(id, 10)) + } + return simple.JsonData(t) +} + +func (c *TweetController) AnyList() *simple.JsonResult { + list, paging := services.TweetService.FindPageByParams(simple.NewQueryParams(c.Ctx).PageByReq().Desc("id")) + return simple.JsonData(&simple.PageResult{Results: list, Page: paging}) +} + +func (c *TweetController) PostCreate() *simple.JsonResult { + t := &model.Tweet{} + err := simple.ReadForm(c.Ctx, t) + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + + err = services.TweetService.Create(t) + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + return simple.JsonData(t) +} + +func (c *TweetController) PostUpdate() *simple.JsonResult { + id, err := simple.FormValueInt64(c.Ctx, "id") + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + t := services.TweetService.Get(id) + if t == nil { + return simple.JsonErrorMsg("entity not found") + } + + err = simple.ReadForm(c.Ctx, t) + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + + err = services.TweetService.Update(t) + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + return simple.JsonData(t) +} diff --git a/server/controllers/api/topic_controller.go b/server/controllers/api/topic_controller.go index cb4112334756355f54fff9c663b28eb0432fa343..f553ad3e883df13b65ebbbeeaf24ee1815065e80 100644 --- a/server/controllers/api/topic_controller.go +++ b/server/controllers/api/topic_controller.go @@ -1,285 +1,280 @@ -package api - -import ( - "math/rand" - "strings" - - "github.com/kataras/iris/v12" - "github.com/mlogclub/simple" - - "bbs-go/controllers/render" - "bbs-go/model" - "bbs-go/services" - "bbs-go/services/cache" -) - -type TopicController struct { - Ctx iris.Context -} - -// 节点 -func (c *TopicController) GetNodes() *simple.JsonResult { - nodes := services.TopicNodeService.GetNodes() - return simple.JsonData(render.BuildNodes(nodes)) -} - -// 节点信息 -func (c *TopicController) GetNode() *simple.JsonResult { - nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0) - node := services.TopicNodeService.Get(nodeId) - return simple.JsonData(render.BuildNode(node)) -} - -// 发表帖子 -func (c *TopicController) PostCreate() *simple.JsonResult { - user := services.UserTokenService.GetCurrent(c.Ctx) - if user == nil { - return simple.JsonError(simple.ErrorNotLogin) - } - nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0) - topicType := simple.FormValueIntDefault(c.Ctx, "type", model.TopicTypeNormal) - title := strings.TrimSpace(simple.FormValue(c.Ctx, "title")) - content := strings.TrimSpace(simple.FormValue(c.Ctx, "content")) - imageList := simple.FormValue(c.Ctx, "imageList") - tags := simple.FormValueStringArray(c.Ctx, "tags") - topic, err := services.TopicService.Publish(topicType, user.Id, nodeId, tags, title, content, imageList) - if err != nil { - return simple.JsonError(err) - } - return simple.JsonData(render.BuildSimpleTopic(topic)) -} - -// 编辑时获取详情 -func (c *TopicController) GetEditBy(topicId int64) *simple.JsonResult { - user := services.UserTokenService.GetCurrent(c.Ctx) - if user == nil { - return simple.JsonError(simple.ErrorNotLogin) - } - - topic := services.TopicService.Get(topicId) - if topic == nil || topic.Status != model.StatusOk { - return simple.JsonErrorMsg("话题不存在或已被删除") - } - if topic.UserId != user.Id { - return simple.JsonErrorMsg("无权限") - } - - tags := services.TopicService.GetTopicTags(topicId) - var tagNames []string - if len(tags) > 0 { - for _, tag := range tags { - tagNames = append(tagNames, tag.Name) - } - } - - return simple.NewEmptyRspBuilder(). - Put("topicId", topic.Id). - Put("nodeId", topic.NodeId). - Put("title", topic.Title). - Put("content", topic.Content). - Put("tags", tagNames). - JsonResult() -} - -// 编辑帖子 -func (c *TopicController) PostEditBy(topicId int64) *simple.JsonResult { - user := services.UserTokenService.GetCurrent(c.Ctx) - if user == nil { - return simple.JsonError(simple.ErrorNotLogin) - } - - topic := services.TopicService.Get(topicId) - if topic == nil || topic.Status != model.StatusOk { - return simple.JsonErrorMsg("话题不存在或已被删除") - } - if topic.UserId != user.Id { - return simple.JsonErrorMsg("无权限") - } - if topic.Type == model.TopicTypeTwitter { - return simple.JsonErrorMsg("推文类型话题不允许修改") - } - - nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0) - title := strings.TrimSpace(simple.FormValue(c.Ctx, "title")) - content := strings.TrimSpace(simple.FormValue(c.Ctx, "content")) - tags := simple.FormValueStringArray(c.Ctx, "tags") - - err := services.TopicService.Edit(topicId, nodeId, tags, title, content) - if err != nil { - return simple.JsonError(err) - } - return simple.JsonData(render.BuildSimpleTopic(topic)) -} - -// 删除帖子 -func (c *TopicController) PostDeleteBy(topicId int64) *simple.JsonResult { - user := services.UserTokenService.GetCurrent(c.Ctx) - if user == nil { - return simple.JsonError(simple.ErrorNotLogin) - } - topic := services.TopicService.Get(topicId) - if topic == nil || topic.Status != model.StatusOk { - return simple.JsonSuccess() - } - if topic.UserId != user.Id { - return simple.JsonErrorMsg("无权限") - } - err := services.TopicService.Delete(topicId) - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - return simple.JsonSuccess() -} - -// 帖子详情 -func (c *TopicController) GetBy(topicId int64) *simple.JsonResult { - topic := services.TopicService.Get(topicId) - if topic == nil || topic.Status != model.StatusOk { - return simple.JsonErrorMsg("主题不存在") - } - services.TopicService.IncrViewCount(topicId) // 增加浏览量 - return simple.JsonData(render.BuildTopic(topic)) -} - -// 点赞 -func (c *TopicController) PostLikeBy(topicId int64) *simple.JsonResult { - user := services.UserTokenService.GetCurrent(c.Ctx) - if user == nil { - return simple.JsonError(simple.ErrorNotLogin) - } - err := services.TopicLikeService.Like(user.Id, topicId) - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - return simple.JsonSuccess() -} - -// 点赞用户 -func (c *TopicController) GetRecentlikesBy(topicId int64) *simple.JsonResult { - topicLikes := services.TopicLikeService.Recent(topicId, 10) - var users []model.UserInfo - for _, topicLike := range topicLikes { - userInfo := render.BuildUserById(topicLike.UserId) - if userInfo != nil { - users = append(users, *userInfo) - } - } - return simple.JsonData(users) -} - -// 最新帖子 -func (c *TopicController) GetRecent() *simple.JsonResult { - topics := services.TopicService.Find(simple.NewSqlCnd().Where("status = ?", model.StatusOk).Desc("id").Limit(10)) - return simple.JsonData(render.BuildSimpleTopics(topics)) -} - -// 用户最近的帖子 -func (c *TopicController) GetUserRecent() *simple.JsonResult { - userId, err := simple.FormValueInt64(c.Ctx, "userId") - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - topics := services.TopicService.Find(simple.NewSqlCnd().Where("user_id = ? and status = ?", - userId, model.StatusOk).Desc("id").Limit(10)) - return simple.JsonData(render.BuildSimpleTopics(topics)) -} - -// 用户帖子列表 -func (c *TopicController) GetUserTopics() *simple.JsonResult { - userId, err := simple.FormValueInt64(c.Ctx, "userId") - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - page := simple.FormValueIntDefault(c.Ctx, "page", 1) - - topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd(). - Eq("user_id", userId). - Eq("status", model.StatusOk). - Page(page, 20).Desc("id")) - - return simple.JsonPageData(render.BuildSimpleTopics(topics), paging) -} - -// 帖子列表 -func (c *TopicController) GetTopics() *simple.JsonResult { - page := simple.FormValueIntDefault(c.Ctx, "page", 1) - - topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd(). - Eq("status", model.StatusOk). - Page(page, 20).Desc("last_comment_time")) - - return simple.JsonPageData(render.BuildSimpleTopics(topics), paging) -} - -// 节点帖子列表 -func (c *TopicController) GetNodeTopics() *simple.JsonResult { - page := simple.FormValueIntDefault(c.Ctx, "page", 1) - nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0) - topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd(). - Eq("node_id", nodeId). - Eq("status", model.StatusOk). - Page(page, 20).Desc("last_comment_time")) - - return simple.JsonPageData(render.BuildSimpleTopics(topics), paging) -} - -// 标签帖子列表 -func (c *TopicController) GetTagTopics() *simple.JsonResult { - page := simple.FormValueIntDefault(c.Ctx, "page", 1) - tagId, err := simple.FormValueInt64(c.Ctx, "tagId") - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - topics, paging := services.TopicService.GetTagTopics(tagId, page) - return simple.JsonPageData(render.BuildSimpleTopics(topics), paging) -} - -// 推荐帖子 -func (c *TopicController) GetRecommendTopics() *simple.JsonResult { - page := simple.FormValueIntDefault(c.Ctx, "page", 1) - topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd(). - Eq("recommend", true). - Eq("status", model.StatusOk). - Page(page, 20).Desc("last_comment_time")) - - return simple.JsonPageData(render.BuildSimpleTopics(topics), paging) -} - -// 收藏 -func (c *TopicController) GetFavoriteBy(topicId int64) *simple.JsonResult { - user := services.UserTokenService.GetCurrent(c.Ctx) - if user == nil { - return simple.JsonError(simple.ErrorNotLogin) - } - err := services.FavoriteService.AddTopicFavorite(user.Id, topicId) - if err != nil { - return simple.JsonErrorMsg(err.Error()) - } - return simple.JsonSuccess() -} - -// 推荐 -func (c *TopicController) GetRecommend() *simple.JsonResult { - topics := cache.TopicCache.GetRecommendTopics() - if topics == nil || len(topics) == 0 { - return simple.JsonSuccess() - } else { - dest := make([]model.Topic, len(topics)) - perm := rand.Perm(len(topics)) - for i, v := range perm { - dest[v] = topics[i] - } - end := 10 - if end > len(topics) { - end = len(topics) - } - ret := dest[0:end] - return simple.JsonData(render.BuildSimpleTopics(ret)) - } -} - -// 最新话题 -func (c *TopicController) GetNewest() *simple.JsonResult { - topics := services.TopicService.Find(simple.NewSqlCnd().Eq("status", model.StatusOk).Desc("id").Limit(6)) - return simple.JsonData(render.BuildSimpleTopics(topics)) -} +package api + +import ( + "math/rand" + "strings" + + "github.com/kataras/iris/v12" + "github.com/mlogclub/simple" + + "bbs-go/controllers/render" + "bbs-go/model" + "bbs-go/services" + "bbs-go/services/cache" +) + +type TopicController struct { + Ctx iris.Context +} + +// 节点 +func (c *TopicController) GetNodes() *simple.JsonResult { + nodes := services.TopicNodeService.GetNodes() + return simple.JsonData(render.BuildNodes(nodes)) +} + +// 节点信息 +func (c *TopicController) GetNode() *simple.JsonResult { + nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0) + node := services.TopicNodeService.Get(nodeId) + return simple.JsonData(render.BuildNode(node)) +} + +// 发表帖子 +func (c *TopicController) PostCreate() *simple.JsonResult { + user := services.UserTokenService.GetCurrent(c.Ctx) + if user == nil { + return simple.JsonError(simple.ErrorNotLogin) + } + nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0) + title := strings.TrimSpace(simple.FormValue(c.Ctx, "title")) + content := strings.TrimSpace(simple.FormValue(c.Ctx, "content")) + tags := simple.FormValueStringArray(c.Ctx, "tags") + topic, err := services.TopicService.Publish(user.Id, nodeId, tags, title, content) + if err != nil { + return simple.JsonError(err) + } + return simple.JsonData(render.BuildSimpleTopic(topic)) +} + +// 编辑时获取详情 +func (c *TopicController) GetEditBy(topicId int64) *simple.JsonResult { + user := services.UserTokenService.GetCurrent(c.Ctx) + if user == nil { + return simple.JsonError(simple.ErrorNotLogin) + } + + topic := services.TopicService.Get(topicId) + if topic == nil || topic.Status != model.StatusOk { + return simple.JsonErrorMsg("话题不存在或已被删除") + } + if topic.UserId != user.Id { + return simple.JsonErrorMsg("无权限") + } + + tags := services.TopicService.GetTopicTags(topicId) + var tagNames []string + if len(tags) > 0 { + for _, tag := range tags { + tagNames = append(tagNames, tag.Name) + } + } + + return simple.NewEmptyRspBuilder(). + Put("topicId", topic.Id). + Put("nodeId", topic.NodeId). + Put("title", topic.Title). + Put("content", topic.Content). + Put("tags", tagNames). + JsonResult() +} + +// 编辑帖子 +func (c *TopicController) PostEditBy(topicId int64) *simple.JsonResult { + user := services.UserTokenService.GetCurrent(c.Ctx) + if user == nil { + return simple.JsonError(simple.ErrorNotLogin) + } + + topic := services.TopicService.Get(topicId) + if topic == nil || topic.Status != model.StatusOk { + return simple.JsonErrorMsg("话题不存在或已被删除") + } + if topic.UserId != user.Id { + return simple.JsonErrorMsg("无权限") + } + + nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0) + title := strings.TrimSpace(simple.FormValue(c.Ctx, "title")) + content := strings.TrimSpace(simple.FormValue(c.Ctx, "content")) + tags := simple.FormValueStringArray(c.Ctx, "tags") + + err := services.TopicService.Edit(topicId, nodeId, tags, title, content) + if err != nil { + return simple.JsonError(err) + } + return simple.JsonData(render.BuildSimpleTopic(topic)) +} + +// 删除帖子 +func (c *TopicController) PostDeleteBy(topicId int64) *simple.JsonResult { + user := services.UserTokenService.GetCurrent(c.Ctx) + if user == nil { + return simple.JsonError(simple.ErrorNotLogin) + } + topic := services.TopicService.Get(topicId) + if topic == nil || topic.Status != model.StatusOk { + return simple.JsonSuccess() + } + if topic.UserId != user.Id { + return simple.JsonErrorMsg("无权限") + } + err := services.TopicService.Delete(topicId) + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + return simple.JsonSuccess() +} + +// 帖子详情 +func (c *TopicController) GetBy(topicId int64) *simple.JsonResult { + topic := services.TopicService.Get(topicId) + if topic == nil || topic.Status != model.StatusOk { + return simple.JsonErrorMsg("主题不存在") + } + services.TopicService.IncrViewCount(topicId) // 增加浏览量 + return simple.JsonData(render.BuildTopic(topic)) +} + +// 点赞 +func (c *TopicController) PostLikeBy(topicId int64) *simple.JsonResult { + user := services.UserTokenService.GetCurrent(c.Ctx) + if user == nil { + return simple.JsonError(simple.ErrorNotLogin) + } + err := services.UserLikeService.TopicLike(user.Id, topicId) + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + return simple.JsonSuccess() +} + +// 点赞用户 +func (c *TopicController) GetRecentlikesBy(topicId int64) *simple.JsonResult { + likes := services.UserLikeService.Recent(model.EntityTypeTopic, topicId, 10) + var users []model.UserInfo + for _, like := range likes { + userInfo := render.BuildUserById(like.UserId) + if userInfo != nil { + users = append(users, *userInfo) + } + } + return simple.JsonData(users) +} + +// 最新帖子 +func (c *TopicController) GetRecent() *simple.JsonResult { + topics := services.TopicService.Find(simple.NewSqlCnd().Where("status = ?", model.StatusOk).Desc("id").Limit(10)) + return simple.JsonData(render.BuildSimpleTopics(topics)) +} + +// 用户最近的帖子 +func (c *TopicController) GetUserRecent() *simple.JsonResult { + userId, err := simple.FormValueInt64(c.Ctx, "userId") + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + topics := services.TopicService.Find(simple.NewSqlCnd().Where("user_id = ? and status = ?", + userId, model.StatusOk).Desc("id").Limit(10)) + return simple.JsonData(render.BuildSimpleTopics(topics)) +} + +// 用户帖子列表 +func (c *TopicController) GetUserTopics() *simple.JsonResult { + userId, err := simple.FormValueInt64(c.Ctx, "userId") + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + page := simple.FormValueIntDefault(c.Ctx, "page", 1) + + topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd(). + Eq("user_id", userId). + Eq("status", model.StatusOk). + Page(page, 20).Desc("id")) + + return simple.JsonPageData(render.BuildSimpleTopics(topics), paging) +} + +// 帖子列表 +func (c *TopicController) GetTopics() *simple.JsonResult { + page := simple.FormValueIntDefault(c.Ctx, "page", 1) + + topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd(). + Eq("status", model.StatusOk). + Page(page, 20).Desc("last_comment_time")) + + return simple.JsonPageData(render.BuildSimpleTopics(topics), paging) +} + +// 节点帖子列表 +func (c *TopicController) GetNodeTopics() *simple.JsonResult { + page := simple.FormValueIntDefault(c.Ctx, "page", 1) + nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0) + topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd(). + Eq("node_id", nodeId). + Eq("status", model.StatusOk). + Page(page, 20).Desc("last_comment_time")) + + return simple.JsonPageData(render.BuildSimpleTopics(topics), paging) +} + +// 标签帖子列表 +func (c *TopicController) GetTagTopics() *simple.JsonResult { + page := simple.FormValueIntDefault(c.Ctx, "page", 1) + tagId, err := simple.FormValueInt64(c.Ctx, "tagId") + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + topics, paging := services.TopicService.GetTagTopics(tagId, page) + return simple.JsonPageData(render.BuildSimpleTopics(topics), paging) +} + +// 推荐帖子 +func (c *TopicController) GetRecommendTopics() *simple.JsonResult { + page := simple.FormValueIntDefault(c.Ctx, "page", 1) + topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd(). + Eq("recommend", true). + Eq("status", model.StatusOk). + Page(page, 20).Desc("last_comment_time")) + + return simple.JsonPageData(render.BuildSimpleTopics(topics), paging) +} + +// 收藏 +func (c *TopicController) GetFavoriteBy(topicId int64) *simple.JsonResult { + user := services.UserTokenService.GetCurrent(c.Ctx) + if user == nil { + return simple.JsonError(simple.ErrorNotLogin) + } + err := services.FavoriteService.AddTopicFavorite(user.Id, topicId) + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + return simple.JsonSuccess() +} + +// 推荐 +func (c *TopicController) GetRecommend() *simple.JsonResult { + topics := cache.TopicCache.GetRecommendTopics() + if topics == nil || len(topics) == 0 { + return simple.JsonSuccess() + } else { + dest := make([]model.Topic, len(topics)) + perm := rand.Perm(len(topics)) + for i, v := range perm { + dest[v] = topics[i] + } + end := 10 + if end > len(topics) { + end = len(topics) + } + ret := dest[0:end] + return simple.JsonData(render.BuildSimpleTopics(ret)) + } +} + +// 最新话题 +func (c *TopicController) GetNewest() *simple.JsonResult { + topics := services.TopicService.Find(simple.NewSqlCnd().Eq("status", model.StatusOk).Desc("id").Limit(6)) + return simple.JsonData(render.BuildSimpleTopics(topics)) +} diff --git a/server/controllers/api/tweet_controller.go b/server/controllers/api/tweet_controller.go new file mode 100644 index 0000000000000000000000000000000000000000..acbdd668351d3f4223c4f08d3c91d70d47a2800e --- /dev/null +++ b/server/controllers/api/tweet_controller.go @@ -0,0 +1,53 @@ +package api + +import ( + "strconv" + "strings" + + "github.com/kataras/iris/v12" + "github.com/mlogclub/simple" + + "bbs-go/controllers/render" + "bbs-go/services" +) + +type TweetController struct { + Ctx iris.Context +} + +func (c *TweetController) PostCreate() *simple.JsonResult { + user := services.UserTokenService.GetCurrent(c.Ctx) + if user == nil { + return simple.JsonError(simple.ErrorNotLogin) + } + content := strings.TrimSpace(simple.FormValue(c.Ctx, "content")) + imageList := simple.FormValue(c.Ctx, "imageList") + tweets, err := services.TweetService.Publish(user.Id, content, imageList) + if err != nil { + return simple.JsonErrorMsg(err.Error()) + } + return simple.JsonData(render.BuildTweet(tweets)) +} + +func (c *TweetController) GetList() *simple.JsonResult { + cursor := simple.FormValueInt64Default(c.Ctx, "cursor", 0) + tweets, cursor := services.TweetService.GetTweets(cursor) + return simple.JsonCursorData(render.BuildTweets(tweets), strconv.FormatInt(cursor, 10)) +} + +func (c *TweetController) GetBy(tweetId int64) *simple.JsonResult { + tweet := services.TweetService.Get(tweetId) + return simple.JsonData(render.BuildTweet(tweet)) +} + +func (c *TweetController) PostLikeBy(tweetId int64) *simple.JsonResult { + user := services.UserTokenService.GetCurrent(c.Ctx) + if user == nil { + return simple.JsonError(simple.ErrorNotLogin) + } + err := services.UserLikeService.TweetLike(user.Id, tweetId) + 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 52592c77d0dbff57f12e95ee9de72126136a75a5..c7e672b992ce0aebd95174e5a059979cabcb464b 100644 --- a/server/controllers/render/render.go +++ b/server/controllers/render/render.go @@ -1,562 +1,580 @@ -package render - -import ( - "html/template" - "strconv" - "strings" - - "github.com/sirupsen/logrus" - "github.com/tidwall/gjson" - - "github.com/PuerkitoBio/goquery" - "github.com/mlogclub/simple" - - "bbs-go/common" - "bbs-go/common/avatar" - "bbs-go/common/urls" - "bbs-go/model" - "bbs-go/services" - "bbs-go/services/cache" -) - -func BuildUserDefaultIfNull(id int64) *model.UserInfo { - user := cache.UserCache.Get(id) - if user == nil { - user = &model.User{} - user.Id = id - user.Username = simple.SqlNullString(strconv.FormatInt(id, 10)) - user.Avatar = avatar.DefaultAvatar - user.CreateTime = simple.NowTimestamp() - } - return BuildUser(user) -} - -func BuildUserById(id int64) *model.UserInfo { - user := cache.UserCache.Get(id) - return BuildUser(user) -} - -func BuildUser(user *model.User) *model.UserInfo { - if user == nil { - return nil - } - a := user.Avatar - if len(a) == 0 { - a = avatar.DefaultAvatar - } - roles := strings.Split(user.Roles, ",") - ret := &model.UserInfo{ - Id: user.Id, - Username: user.Username.String, - Nickname: user.Nickname, - Avatar: a, - Email: user.Email.String, - Type: user.Type, - Roles: roles, - HomePage: user.HomePage, - Description: user.Description, - TopicCount: user.TopicCount, - CommentCount: user.CommentCount, - PasswordSet: len(user.Password) > 0, - Status: user.Status, - CreateTime: user.CreateTime, - } - if user.Status == model.StatusDeleted { - ret.Username = "blacklist" - ret.Nickname = "黑名单用户" - ret.Avatar = avatar.DefaultAvatar - ret.Email = "" - ret.HomePage = "" - ret.Description = "" - } else { - ret.Score = cache.UserCache.GetScore(user.Id) - } - return ret -} - -func BuildUsers(users []model.User) []model.UserInfo { - if len(users) == 0 { - return nil - } - var responses []model.UserInfo - for _, user := range users { - item := BuildUser(&user) - if item != nil { - responses = append(responses, *item) - } - } - return responses -} - -func BuildArticle(article *model.Article) *model.ArticleResponse { - if article == nil { - return nil - } - - rsp := &model.ArticleResponse{} - rsp.ArticleId = article.Id - rsp.Title = article.Title - rsp.Summary = article.Summary - rsp.Share = article.Share - rsp.SourceUrl = article.SourceUrl - rsp.ViewCount = article.ViewCount - rsp.CreateTime = article.CreateTime - - rsp.User = BuildUserDefaultIfNull(article.UserId) - - tagIds := cache.ArticleTagCache.Get(article.Id) - tags := cache.TagCache.GetList(tagIds) - rsp.Tags = BuildTags(tags) - - if article.ContentType == model.ContentTypeMarkdown { - mr := simple.NewMd(simple.MdWithTOC()).Run(article.Content) - rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml)) - rsp.Toc = template.HTML(mr.TocHtml) - if len(rsp.Summary) == 0 { - rsp.Summary = mr.SummaryText - } - } else { - rsp.Content = template.HTML(BuildHtmlContent(article.Content)) - if len(rsp.Summary) == 0 { - rsp.Summary = simple.GetSummary(article.Content, 256) - } - } - - return rsp -} - -func BuildArticles(articles []model.Article) []model.ArticleResponse { - if articles == nil || len(articles) == 0 { - return nil - } - var responses []model.ArticleResponse - for _, article := range articles { - responses = append(responses, *BuildArticle(&article)) - } - return responses -} - -func BuildSimpleArticle(article *model.Article) *model.ArticleSimpleResponse { - if article == nil { - return nil - } - - rsp := &model.ArticleSimpleResponse{} - rsp.ArticleId = article.Id - rsp.Title = article.Title - rsp.Summary = article.Summary - rsp.Share = article.Share - rsp.SourceUrl = article.SourceUrl - rsp.ViewCount = article.ViewCount - rsp.CreateTime = article.CreateTime - - rsp.User = BuildUserDefaultIfNull(article.UserId) - - tagIds := cache.ArticleTagCache.Get(article.Id) - tags := cache.TagCache.GetList(tagIds) - rsp.Tags = BuildTags(tags) - - if article.ContentType == model.ContentTypeMarkdown { - if len(rsp.Summary) == 0 { - mr := simple.NewMd(simple.MdWithTOC()).Run(article.Content) - rsp.Summary = mr.SummaryText - } - } else { - if len(rsp.Summary) == 0 { - rsp.Summary = simple.GetSummary(simple.GetHtmlText(article.Content), 256) - } - } - - return rsp -} - -func BuildSimpleArticles(articles []model.Article) []model.ArticleSimpleResponse { - if articles == nil || len(articles) == 0 { - return nil - } - var responses []model.ArticleSimpleResponse - for _, article := range articles { - responses = append(responses, *BuildSimpleArticle(&article)) - } - return responses -} - -func BuildNode(node *model.TopicNode) *model.NodeResponse { - if node == nil { - return nil - } - return &model.NodeResponse{ - NodeId: node.Id, - Name: node.Name, - Description: node.Description, - } -} - -func BuildNodes(nodes []model.TopicNode) []model.NodeResponse { - if len(nodes) == 0 { - return nil - } - var ret []model.NodeResponse - for _, node := range nodes { - ret = append(ret, *BuildNode(&node)) - } - return ret -} - -func BuildTopic(topic *model.Topic) *model.TopicResponse { - if topic == nil { - return nil - } - - rsp := &model.TopicResponse{} - - rsp.TopicId = topic.Id - rsp.Type = topic.Type - rsp.Title = topic.Title - rsp.User = BuildUserDefaultIfNull(topic.UserId) - rsp.LastCommentTime = topic.LastCommentTime - rsp.CreateTime = topic.CreateTime - rsp.ViewCount = topic.ViewCount - rsp.CommentCount = topic.CommentCount - rsp.LikeCount = topic.LikeCount - - if topic.NodeId > 0 { - node := services.TopicNodeService.Get(topic.NodeId) - rsp.Node = BuildNode(node) - } - - tags := services.TopicService.GetTopicTags(topic.Id) - rsp.Tags = BuildTags(tags) - - mr := simple.NewMd(simple.MdWithTOC()).Run(topic.Content) - rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml)) - rsp.Toc = template.HTML(mr.TocHtml) - - if len(topic.ImageList) > 0 { - if err := simple.ParseJson(topic.ImageList, &rsp.ImageList); err != nil { - logrus.Error(err) - } - } - - return rsp -} - -func BuildSimpleTopic(topic *model.Topic) *model.TopicSimpleResponse { - if topic == nil { - return nil - } - - rsp := &model.TopicSimpleResponse{} - - rsp.TopicId = topic.Id - rsp.Type = topic.Type - rsp.Title = topic.Title - rsp.User = BuildUserDefaultIfNull(topic.UserId) - rsp.LastCommentTime = topic.LastCommentTime - rsp.CreateTime = topic.CreateTime - rsp.ViewCount = topic.ViewCount - rsp.CommentCount = topic.CommentCount - rsp.LikeCount = topic.LikeCount - - if len(topic.ImageList) > 0 { - if err := simple.ParseJson(topic.ImageList, &rsp.ImageList); err != nil { - logrus.Error(err) - } - } - - if topic.NodeId > 0 { - node := services.TopicNodeService.Get(topic.NodeId) - rsp.Node = BuildNode(node) - } - - tags := services.TopicService.GetTopicTags(topic.Id) - rsp.Tags = BuildTags(tags) - return rsp -} - -func BuildSimpleTopics(topics []model.Topic) []model.TopicSimpleResponse { - if topics == nil || len(topics) == 0 { - return nil - } - var responses []model.TopicSimpleResponse - for _, topic := range topics { - responses = append(responses, *BuildSimpleTopic(&topic)) - } - return responses -} - -func BuildProject(project *model.Project) *model.ProjectResponse { - if project == nil { - return nil - } - rsp := &model.ProjectResponse{} - rsp.ProjectId = project.Id - rsp.User = BuildUserDefaultIfNull(project.UserId) - rsp.Name = project.Name - rsp.Title = project.Title - rsp.Logo = project.Logo - rsp.Url = project.Url - rsp.Url = project.Url - rsp.DocUrl = project.DocUrl - rsp.CreateTime = project.CreateTime - - if project.ContentType == model.ContentTypeHtml { - rsp.Content = template.HTML(BuildHtmlContent(project.Content)) - rsp.Summary = simple.GetSummary(simple.GetHtmlText(project.Content), 256) - } else { - mr := simple.NewMd().Run(project.Content) - rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml)) - rsp.Summary = mr.SummaryText - } - - return rsp -} - -func BuildSimpleProjects(projects []model.Project) []model.ProjectSimpleResponse { - if projects == nil || len(projects) == 0 { - return nil - } - var responses []model.ProjectSimpleResponse - for _, project := range projects { - responses = append(responses, *BuildSimpleProject(&project)) - } - return responses -} - -func BuildSimpleProject(project *model.Project) *model.ProjectSimpleResponse { - if project == nil { - return nil - } - rsp := &model.ProjectSimpleResponse{} - rsp.ProjectId = project.Id - rsp.User = BuildUserDefaultIfNull(project.UserId) - rsp.Name = project.Name - rsp.Title = project.Title - rsp.Logo = project.Logo - rsp.Url = project.Url - rsp.DocUrl = project.DocUrl - rsp.DownloadUrl = project.DownloadUrl - rsp.CreateTime = project.CreateTime - - if project.ContentType == model.ContentTypeHtml { - rsp.Summary = simple.GetSummary(simple.GetHtmlText(project.Content), 256) - } else { - rsp.Summary = common.GetMarkdownSummary(project.Content) - } - - return rsp -} - -func BuildComments(comments []model.Comment) []model.CommentResponse { - var ret []model.CommentResponse - for _, comment := range comments { - ret = append(ret, *BuildComment(comment)) - } - return ret -} - -func BuildComment(comment model.Comment) *model.CommentResponse { - return _buildComment(&comment, true) -} - -func _buildComment(comment *model.Comment, buildQuote bool) *model.CommentResponse { - if comment == nil { - return nil - } - - ret := &model.CommentResponse{ - CommentId: comment.Id, - User: BuildUserDefaultIfNull(comment.UserId), - EntityType: comment.EntityType, - EntityId: comment.EntityId, - QuoteId: comment.QuoteId, - Status: comment.Status, - CreateTime: comment.CreateTime, - } - - if comment.ContentType == model.ContentTypeMarkdown { - markdownResult := simple.NewMd().Run(comment.Content) - ret.Content = template.HTML(BuildHtmlContent(markdownResult.ContentHtml)) - } else { - ret.Content = template.HTML(BuildHtmlContent(comment.Content)) - } - - if buildQuote && comment.QuoteId > 0 { - quote := _buildComment(services.CommentService.Get(comment.QuoteId), false) - if quote != nil { - ret.Quote = quote - ret.QuoteContent = template.HTML(quote.User.Nickname+":") + quote.Content - } - } - return ret -} - -func BuildTag(tag *model.Tag) *model.TagResponse { - if tag == nil { - return nil - } - return &model.TagResponse{TagId: tag.Id, TagName: tag.Name} -} - -func BuildTags(tags []model.Tag) *[]model.TagResponse { - if len(tags) == 0 { - return nil - } - var responses []model.TagResponse - for _, tag := range tags { - responses = append(responses, *BuildTag(&tag)) - } - return &responses -} - -func BuildFavorite(favorite *model.Favorite) *model.FavoriteResponse { - rsp := &model.FavoriteResponse{} - rsp.FavoriteId = favorite.Id - rsp.EntityType = favorite.EntityType - rsp.CreateTime = favorite.CreateTime - - if favorite.EntityType == model.EntityTypeArticle { - article := services.ArticleService.Get(favorite.EntityId) - if article == nil || article.Status != model.StatusOk { - rsp.Deleted = true - } else { - rsp.Url = urls.ArticleUrl(article.Id) - rsp.User = BuildUserById(article.UserId) - rsp.Title = article.Title - if article.ContentType == model.ContentTypeMarkdown { - rsp.Content = common.GetMarkdownSummary(article.Content) - } else { - doc, err := goquery.NewDocumentFromReader(strings.NewReader(article.Content)) - if err == nil { - text := doc.Text() - rsp.Content = simple.GetSummary(text, 256) - } - } - } - } else { - topic := services.TopicService.Get(favorite.EntityId) - if topic == nil || topic.Status != model.StatusOk { - rsp.Deleted = true - } else { - rsp.Url = urls.TopicUrl(topic.Id) - rsp.User = BuildUserById(topic.UserId) - rsp.Title = topic.Title - rsp.Content = common.GetMarkdownSummary(topic.Content) - } - } - return rsp -} - -func BuildFavorites(favorites []model.Favorite) []model.FavoriteResponse { - if favorites == nil || len(favorites) == 0 { - return nil - } - var responses []model.FavoriteResponse - for _, favorite := range favorites { - responses = append(responses, *BuildFavorite(&favorite)) - } - return responses -} - -func BuildMessage(message *model.Message) *model.MessageResponse { - if message == nil { - return nil - } - - detailUrl := "" - if message.Type == model.MsgTypeComment { - entityType := gjson.Get(message.ExtraData, "entityType") - entityId := gjson.Get(message.ExtraData, "entityId") - if entityType.String() == model.EntityTypeArticle { - detailUrl = urls.ArticleUrl(entityId.Int()) - } else if entityType.String() == model.EntityTypeTopic { - detailUrl = urls.TopicUrl(entityId.Int()) - } - } - from := BuildUserDefaultIfNull(message.FromId) - if message.FromId <= 0 { - from.Nickname = "系统通知" - from.Avatar = avatar.DefaultAvatar - } - return &model.MessageResponse{ - MessageId: message.Id, - From: from, - UserId: message.UserId, - Content: message.Content, - QuoteContent: message.QuoteContent, - Type: message.Type, - DetailUrl: detailUrl, - ExtraData: message.ExtraData, - Status: message.Status, - CreateTime: message.CreateTime, - } -} - -func BuildMessages(messages []model.Message) []model.MessageResponse { - if len(messages) == 0 { - return nil - } - var responses []model.MessageResponse - for _, message := range messages { - responses = append(responses, *BuildMessage(&message)) - } - return responses -} - -func BuildHtmlContent(htmlContent string) string { - doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent)) - if err != nil { - return htmlContent - } - - doc.Find("a").Each(func(i int, selection *goquery.Selection) { - href := selection.AttrOr("href", "") - - if len(href) == 0 { - return - } - - // 不是内部链接 - if !urls.IsInternalUrl(href) { - selection.SetAttr("target", "_blank") - selection.SetAttr("rel", "external nofollow") // 标记站外链接,搜索引擎爬虫不传递权重值 - - config := services.SysConfigService.GetConfig() - if config.UrlRedirect { // 开启非内部链接跳转 - newHref := simple.ParseUrl(urls.AbsUrl("/redirect")).AddQuery("url", href).BuildStr() - selection.SetAttr("href", newHref) - } - } - - // 如果是锚链接 - if urls.IsAnchor(href) { - selection.ReplaceWithHtml(selection.Text()) - } - - // 如果a标签没有title,那么设置title - title := selection.AttrOr("title", "") - if len(title) == 0 { - selection.SetAttr("title", selection.Text()) - } - }) - - // 处理图片 - doc.Find("img").Each(func(i int, selection *goquery.Selection) { - src := selection.AttrOr("src", "") - // 处理第三方图片 - if strings.Contains(src, "qpic.cn") { - src = simple.ParseUrl("/api/img/proxy").AddQuery("url", src).BuildStr() - // selection.SetAttr("src", src) - } - - // 处理lazyload - selection.SetAttr("data-src", src) - selection.RemoveAttr("src") - }) - - html, err := doc.Find("body").Html() - if err != nil { - return htmlContent - } - return html -} +package render + +import ( + "html/template" + "strconv" + "strings" + + "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" + + "github.com/PuerkitoBio/goquery" + "github.com/mlogclub/simple" + + "bbs-go/common" + "bbs-go/common/avatar" + "bbs-go/common/urls" + "bbs-go/model" + "bbs-go/services" + "bbs-go/services/cache" +) + +func BuildUserDefaultIfNull(id int64) *model.UserInfo { + user := cache.UserCache.Get(id) + if user == nil { + user = &model.User{} + user.Id = id + user.Username = simple.SqlNullString(strconv.FormatInt(id, 10)) + user.Avatar = avatar.DefaultAvatar + user.CreateTime = simple.NowTimestamp() + } + return BuildUser(user) +} + +func BuildUserById(id int64) *model.UserInfo { + user := cache.UserCache.Get(id) + return BuildUser(user) +} + +func BuildUser(user *model.User) *model.UserInfo { + if user == nil { + return nil + } + a := user.Avatar + if len(a) == 0 { + a = avatar.DefaultAvatar + } + roles := strings.Split(user.Roles, ",") + ret := &model.UserInfo{ + Id: user.Id, + Username: user.Username.String, + Nickname: user.Nickname, + Avatar: a, + Email: user.Email.String, + Type: user.Type, + Roles: roles, + HomePage: user.HomePage, + Description: user.Description, + TopicCount: user.TopicCount, + CommentCount: user.CommentCount, + PasswordSet: len(user.Password) > 0, + Status: user.Status, + CreateTime: user.CreateTime, + } + if len(ret.Description) == 0 { + ret.Description = "这家伙很懒,什么都没留下" + } + if user.Status == model.StatusDeleted { + ret.Username = "blacklist" + ret.Nickname = "黑名单用户" + ret.Avatar = avatar.DefaultAvatar + ret.Email = "" + ret.HomePage = "" + ret.Description = "" + } else { + ret.Score = cache.UserCache.GetScore(user.Id) + } + return ret +} + +func BuildUsers(users []model.User) []model.UserInfo { + if len(users) == 0 { + return nil + } + var responses []model.UserInfo + for _, user := range users { + item := BuildUser(&user) + if item != nil { + responses = append(responses, *item) + } + } + return responses +} + +func BuildArticle(article *model.Article) *model.ArticleResponse { + if article == nil { + return nil + } + + rsp := &model.ArticleResponse{} + rsp.ArticleId = article.Id + rsp.Title = article.Title + rsp.Summary = article.Summary + rsp.Share = article.Share + rsp.SourceUrl = article.SourceUrl + rsp.ViewCount = article.ViewCount + rsp.CreateTime = article.CreateTime + + rsp.User = BuildUserDefaultIfNull(article.UserId) + + tagIds := cache.ArticleTagCache.Get(article.Id) + tags := cache.TagCache.GetList(tagIds) + rsp.Tags = BuildTags(tags) + + if article.ContentType == model.ContentTypeMarkdown { + mr := simple.NewMd(simple.MdWithTOC()).Run(article.Content) + rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml)) + rsp.Toc = template.HTML(mr.TocHtml) + if len(rsp.Summary) == 0 { + rsp.Summary = mr.SummaryText + } + } else { + rsp.Content = template.HTML(BuildHtmlContent(article.Content)) + if len(rsp.Summary) == 0 { + rsp.Summary = simple.GetSummary(article.Content, 256) + } + } + + return rsp +} + +func BuildArticles(articles []model.Article) []model.ArticleResponse { + if articles == nil || len(articles) == 0 { + return nil + } + var responses []model.ArticleResponse + for _, article := range articles { + responses = append(responses, *BuildArticle(&article)) + } + return responses +} + +func BuildSimpleArticle(article *model.Article) *model.ArticleSimpleResponse { + if article == nil { + return nil + } + + rsp := &model.ArticleSimpleResponse{} + rsp.ArticleId = article.Id + rsp.Title = article.Title + rsp.Summary = article.Summary + rsp.Share = article.Share + rsp.SourceUrl = article.SourceUrl + rsp.ViewCount = article.ViewCount + rsp.CreateTime = article.CreateTime + + rsp.User = BuildUserDefaultIfNull(article.UserId) + + tagIds := cache.ArticleTagCache.Get(article.Id) + tags := cache.TagCache.GetList(tagIds) + rsp.Tags = BuildTags(tags) + + if article.ContentType == model.ContentTypeMarkdown { + if len(rsp.Summary) == 0 { + mr := simple.NewMd(simple.MdWithTOC()).Run(article.Content) + rsp.Summary = mr.SummaryText + } + } else { + if len(rsp.Summary) == 0 { + rsp.Summary = simple.GetSummary(simple.GetHtmlText(article.Content), 256) + } + } + + return rsp +} + +func BuildSimpleArticles(articles []model.Article) []model.ArticleSimpleResponse { + if articles == nil || len(articles) == 0 { + return nil + } + var responses []model.ArticleSimpleResponse + for _, article := range articles { + responses = append(responses, *BuildSimpleArticle(&article)) + } + return responses +} + +func BuildNode(node *model.TopicNode) *model.NodeResponse { + if node == nil { + return nil + } + return &model.NodeResponse{ + NodeId: node.Id, + Name: node.Name, + Description: node.Description, + } +} + +func BuildNodes(nodes []model.TopicNode) []model.NodeResponse { + if len(nodes) == 0 { + return nil + } + var ret []model.NodeResponse + for _, node := range nodes { + ret = append(ret, *BuildNode(&node)) + } + return ret +} + +func BuildTopic(topic *model.Topic) *model.TopicResponse { + if topic == nil { + return nil + } + + rsp := &model.TopicResponse{} + + rsp.TopicId = topic.Id + rsp.Title = topic.Title + rsp.User = BuildUserDefaultIfNull(topic.UserId) + rsp.LastCommentTime = topic.LastCommentTime + rsp.CreateTime = topic.CreateTime + rsp.ViewCount = topic.ViewCount + rsp.CommentCount = topic.CommentCount + rsp.LikeCount = topic.LikeCount + + if topic.NodeId > 0 { + node := services.TopicNodeService.Get(topic.NodeId) + rsp.Node = BuildNode(node) + } + + tags := services.TopicService.GetTopicTags(topic.Id) + rsp.Tags = BuildTags(tags) + + mr := simple.NewMd(simple.MdWithTOC()).Run(topic.Content) + rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml)) + rsp.Toc = template.HTML(mr.TocHtml) + + return rsp +} + +func BuildSimpleTopic(topic *model.Topic) *model.TopicSimpleResponse { + if topic == nil { + return nil + } + + rsp := &model.TopicSimpleResponse{} + + rsp.TopicId = topic.Id + rsp.Title = topic.Title + rsp.User = BuildUserDefaultIfNull(topic.UserId) + rsp.LastCommentTime = topic.LastCommentTime + rsp.CreateTime = topic.CreateTime + rsp.ViewCount = topic.ViewCount + rsp.CommentCount = topic.CommentCount + rsp.LikeCount = topic.LikeCount + + if topic.NodeId > 0 { + node := services.TopicNodeService.Get(topic.NodeId) + rsp.Node = BuildNode(node) + } + + tags := services.TopicService.GetTopicTags(topic.Id) + rsp.Tags = BuildTags(tags) + return rsp +} + +func BuildSimpleTopics(topics []model.Topic) []model.TopicSimpleResponse { + if topics == nil || len(topics) == 0 { + return nil + } + var responses []model.TopicSimpleResponse + for _, topic := range topics { + responses = append(responses, *BuildSimpleTopic(&topic)) + } + return responses +} + +func BuildTweet(tweet *model.Tweet) *model.TweetResponse { + if tweet == nil { + return nil + } + + rsp := &model.TweetResponse{ + TweetId: tweet.Id, + User: BuildUserDefaultIfNull(tweet.UserId), + Content: tweet.Content, + CommentCount: tweet.CommentCount, + LikeCount: tweet.LikeCount, + CreateTime: tweet.CreateTime, + } + if len(tweet.ImageList) > 0 { + if err := simple.ParseJson(tweet.ImageList, &rsp.ImageList); err != nil { + logrus.Error(err) + } + } + return rsp +} + +func BuildTweets(tweets []model.Tweet) []model.TweetResponse { + var ret []model.TweetResponse + for _, tweet := range tweets { + ret = append(ret, *BuildTweet(&tweet)) + } + return ret +} + +func BuildProject(project *model.Project) *model.ProjectResponse { + if project == nil { + return nil + } + rsp := &model.ProjectResponse{} + rsp.ProjectId = project.Id + rsp.User = BuildUserDefaultIfNull(project.UserId) + rsp.Name = project.Name + rsp.Title = project.Title + rsp.Logo = project.Logo + rsp.Url = project.Url + rsp.Url = project.Url + rsp.DocUrl = project.DocUrl + rsp.CreateTime = project.CreateTime + + if project.ContentType == model.ContentTypeHtml { + rsp.Content = template.HTML(BuildHtmlContent(project.Content)) + rsp.Summary = simple.GetSummary(simple.GetHtmlText(project.Content), 256) + } else { + mr := simple.NewMd().Run(project.Content) + rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml)) + rsp.Summary = mr.SummaryText + } + + return rsp +} + +func BuildSimpleProjects(projects []model.Project) []model.ProjectSimpleResponse { + if projects == nil || len(projects) == 0 { + return nil + } + var responses []model.ProjectSimpleResponse + for _, project := range projects { + responses = append(responses, *BuildSimpleProject(&project)) + } + return responses +} + +func BuildSimpleProject(project *model.Project) *model.ProjectSimpleResponse { + if project == nil { + return nil + } + rsp := &model.ProjectSimpleResponse{} + rsp.ProjectId = project.Id + rsp.User = BuildUserDefaultIfNull(project.UserId) + rsp.Name = project.Name + rsp.Title = project.Title + rsp.Logo = project.Logo + rsp.Url = project.Url + rsp.DocUrl = project.DocUrl + rsp.DownloadUrl = project.DownloadUrl + rsp.CreateTime = project.CreateTime + + if project.ContentType == model.ContentTypeHtml { + rsp.Summary = simple.GetSummary(simple.GetHtmlText(project.Content), 256) + } else { + rsp.Summary = common.GetMarkdownSummary(project.Content) + } + + return rsp +} + +func BuildComments(comments []model.Comment) []model.CommentResponse { + var ret []model.CommentResponse + for _, comment := range comments { + ret = append(ret, *BuildComment(comment)) + } + return ret +} + +func BuildComment(comment model.Comment) *model.CommentResponse { + return _buildComment(&comment, true) +} + +func _buildComment(comment *model.Comment, buildQuote bool) *model.CommentResponse { + if comment == nil { + return nil + } + + ret := &model.CommentResponse{ + CommentId: comment.Id, + User: BuildUserDefaultIfNull(comment.UserId), + EntityType: comment.EntityType, + EntityId: comment.EntityId, + QuoteId: comment.QuoteId, + Status: comment.Status, + CreateTime: comment.CreateTime, + } + + if comment.ContentType == model.ContentTypeMarkdown { + markdownResult := simple.NewMd().Run(comment.Content) + ret.Content = template.HTML(BuildHtmlContent(markdownResult.ContentHtml)) + } else { + ret.Content = template.HTML(BuildHtmlContent(comment.Content)) + } + + if buildQuote && comment.QuoteId > 0 { + quote := _buildComment(services.CommentService.Get(comment.QuoteId), false) + if quote != nil { + ret.Quote = quote + ret.QuoteContent = template.HTML(quote.User.Nickname+":") + quote.Content + } + } + return ret +} + +func BuildTag(tag *model.Tag) *model.TagResponse { + if tag == nil { + return nil + } + return &model.TagResponse{TagId: tag.Id, TagName: tag.Name} +} + +func BuildTags(tags []model.Tag) *[]model.TagResponse { + if len(tags) == 0 { + return nil + } + var responses []model.TagResponse + for _, tag := range tags { + responses = append(responses, *BuildTag(&tag)) + } + return &responses +} + +func BuildFavorite(favorite *model.Favorite) *model.FavoriteResponse { + rsp := &model.FavoriteResponse{} + rsp.FavoriteId = favorite.Id + rsp.EntityType = favorite.EntityType + rsp.CreateTime = favorite.CreateTime + + if favorite.EntityType == model.EntityTypeArticle { + article := services.ArticleService.Get(favorite.EntityId) + if article == nil || article.Status != model.StatusOk { + rsp.Deleted = true + } else { + rsp.Url = urls.ArticleUrl(article.Id) + rsp.User = BuildUserById(article.UserId) + rsp.Title = article.Title + if article.ContentType == model.ContentTypeMarkdown { + rsp.Content = common.GetMarkdownSummary(article.Content) + } else { + doc, err := goquery.NewDocumentFromReader(strings.NewReader(article.Content)) + if err == nil { + text := doc.Text() + rsp.Content = simple.GetSummary(text, 256) + } + } + } + } else { + topic := services.TopicService.Get(favorite.EntityId) + if topic == nil || topic.Status != model.StatusOk { + rsp.Deleted = true + } else { + rsp.Url = urls.TopicUrl(topic.Id) + rsp.User = BuildUserById(topic.UserId) + rsp.Title = topic.Title + rsp.Content = common.GetMarkdownSummary(topic.Content) + } + } + return rsp +} + +func BuildFavorites(favorites []model.Favorite) []model.FavoriteResponse { + if favorites == nil || len(favorites) == 0 { + return nil + } + var responses []model.FavoriteResponse + for _, favorite := range favorites { + responses = append(responses, *BuildFavorite(&favorite)) + } + return responses +} + +func BuildMessage(message *model.Message) *model.MessageResponse { + if message == nil { + return nil + } + + detailUrl := "" + if message.Type == model.MsgTypeComment { + entityType := gjson.Get(message.ExtraData, "entityType") + entityId := gjson.Get(message.ExtraData, "entityId") + if entityType.String() == model.EntityTypeArticle { + detailUrl = urls.ArticleUrl(entityId.Int()) + } else if entityType.String() == model.EntityTypeTopic { + detailUrl = urls.TopicUrl(entityId.Int()) + } + } + from := BuildUserDefaultIfNull(message.FromId) + if message.FromId <= 0 { + from.Nickname = "系统通知" + from.Avatar = avatar.DefaultAvatar + } + return &model.MessageResponse{ + MessageId: message.Id, + From: from, + UserId: message.UserId, + Content: message.Content, + QuoteContent: message.QuoteContent, + Type: message.Type, + DetailUrl: detailUrl, + ExtraData: message.ExtraData, + Status: message.Status, + CreateTime: message.CreateTime, + } +} + +func BuildMessages(messages []model.Message) []model.MessageResponse { + if len(messages) == 0 { + return nil + } + var responses []model.MessageResponse + for _, message := range messages { + responses = append(responses, *BuildMessage(&message)) + } + return responses +} + +func BuildHtmlContent(htmlContent string) string { + doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent)) + if err != nil { + return htmlContent + } + + doc.Find("a").Each(func(i int, selection *goquery.Selection) { + href := selection.AttrOr("href", "") + + if len(href) == 0 { + return + } + + // 不是内部链接 + if !urls.IsInternalUrl(href) { + selection.SetAttr("target", "_blank") + selection.SetAttr("rel", "external nofollow") // 标记站外链接,搜索引擎爬虫不传递权重值 + + config := services.SysConfigService.GetConfig() + if config.UrlRedirect { // 开启非内部链接跳转 + newHref := simple.ParseUrl(urls.AbsUrl("/redirect")).AddQuery("url", href).BuildStr() + selection.SetAttr("href", newHref) + } + } + + // 如果是锚链接 + if urls.IsAnchor(href) { + selection.ReplaceWithHtml(selection.Text()) + } + + // 如果a标签没有title,那么设置title + title := selection.AttrOr("title", "") + if len(title) == 0 { + selection.SetAttr("title", selection.Text()) + } + }) + + // 处理图片 + doc.Find("img").Each(func(i int, selection *goquery.Selection) { + src := selection.AttrOr("src", "") + // 处理第三方图片 + if strings.Contains(src, "qpic.cn") { + src = simple.ParseUrl("/api/img/proxy").AddQuery("url", src).BuildStr() + // selection.SetAttr("src", src) + } + + // 处理lazyload + selection.SetAttr("data-src", src) + selection.RemoveAttr("src") + }) + + html, err := doc.Find("body").Html() + if err != nil { + return htmlContent + } + return html +} diff --git a/server/model/models.go b/server/model/models.go index 51dca7f290d72ffa1a2400d204d7e062352770ca..4e8f1d56f4a15e748686e74d974d343bfe64464b 100644 --- a/server/model/models.go +++ b/server/model/models.go @@ -5,9 +5,9 @@ import ( ) var Models = []interface{}{ - &User{}, &UserToken{}, &Tag{}, &Article{}, &ArticleTag{}, &Comment{}, &Favorite{}, - &Topic{}, &TopicNode{}, &TopicTag{}, &TopicLike{}, &Message{}, &SysConfig{}, &Project{}, &Link{}, - &ThirdAccount{}, &Sitemap{}, &UserScore{}, &UserScoreLog{}, + &User{}, &UserToken{}, &Tag{}, &Article{}, &ArticleTag{}, &Comment{}, &Favorite{}, &Topic{}, &TopicNode{}, + &TopicTag{}, &UserLike{}, &Tweet{}, &Message{}, &SysConfig{}, &Project{}, &Link{}, &ThirdAccount{}, &Sitemap{}, + &UserScore{}, &UserScoreLog{}, } type Model struct { @@ -28,6 +28,7 @@ const ( EntityTypeArticle = "article" EntityTypeTopic = "topic" EntityTypeComment = "comment" + EntityTypeTweet = "tweet" MsgStatusUnread = 0 // 消息未读 MsgStatusReaded = 1 // 消息已读 @@ -39,9 +40,6 @@ const ( ScoreTypeIncr = 0 // 积分+ ScoreTypeDecr = 1 // 积分- - - TopicTypeNormal = 0 // 普通帖子 - TopicTypeTwitter = 1 // 推文 ) type User struct { @@ -153,12 +151,10 @@ type TopicNode struct { // 话题节点 type Topic struct { Model - Type int `gorm:"not null;index:idx_topic_type" json:"type" form:"type"` // 类型 NodeId int64 `gorm:"not null;index:idx_node_id;" json:"nodeId" form:"nodeId"` // 节点编号 UserId int64 `gorm:"not null;index:idx_topic_user_id;" json:"userId" form:"userId"` // 用户 Title string `gorm:"size:128" json:"title" form:"title"` // 标题 Content string `gorm:"type:longtext" json:"content" form:"content"` // 内容 - ImageList string `gorm:"type:longtext" json:"imageList" form:"imageList"` // 图片 Recommend bool `gorm:"not null;index:idx_recommend" json:"recommend" form:"recommend"` // 是否推荐 ViewCount int64 `gorm:"not null" json:"viewCount" form:"viewCount"` // 查看数量 CommentCount int64 `gorm:"not null" json:"commentCount" form:"commentCount"` // 跟帖数量 @@ -179,12 +175,13 @@ type TopicTag struct { CreateTime int64 `json:"createTime" form:"createTime"` // 创建时间 } -// 话题点赞 -type TopicLike struct { +// 用户点赞 +type UserLike struct { Model - UserId int64 `gorm:"not null;index:idx_topic_like_user_id;" json:"userId" form:"userId"` // 用户 - TopicId int64 `gorm:"not null;index:idx_topic_like_topic_id;" json:"topicId" form:"topicId"` // 主题编号 - CreateTime int64 `json:"createTime" form:"createTime"` // 创建时间 + UserId int64 `gorm:"not null;unique_index:idx_user_like_unique;" json:"userId" form:"userId"` // 用户 + EntityType string `gorm:"not null;size:32;unique_index:idx_user_like_unique;index:idx_user_like_entity;" json:"entityType" form:"entityType"` // 实体类型 + EntityId int64 `gorm:"not null;unique_index:idx_user_like_unique;index:idx_user_like_entity;" json:"topicId" form:"topicId"` // 实体编号 + CreateTime int64 `json:"createTime" form:"createTime"` // 创建时间 } // 动态 diff --git a/server/model/response.go b/server/model/response.go index 4c4f06cfae033ba8561debd6dd4e006fdc0e4d9c..a2c2d5f8098ff2d49de257315aebfc0d24488d37 100644 --- a/server/model/response.go +++ b/server/model/response.go @@ -1,147 +1,156 @@ -package model - -import ( - "html/template" -) - -type UserInfo struct { - Id int64 `json:"id"` - Username string `json:"username"` - Email string `json:"email"` - Nickname string `json:"nickname"` - Avatar string `json:"avatar"` - Type int `json:"type"` - Roles []string `json:"roles"` - HomePage string `json:"homePage"` - Description string `json:"description"` - Score int `json:"score"` // 积分 - TopicCount int `json:"topicCount"` // 话题数量 - CommentCount int `json:"commentCount"` // 跟帖数量 - PasswordSet bool `json:"passwordSet"` // 密码已设置 - Status int `json:"status"` - CreateTime int64 `json:"createTime"` -} - -func (info *UserInfo) HasRole(role string) bool { - if len(info.Roles) == 0 { - return false - } - for _, r := range info.Roles { - if r == role { - return true - } - } - return false -} - -type TagResponse struct { - TagId int64 `json:"tagId"` - TagName string `json:"tagName"` -} - -type ArticleSimpleResponse struct { - ArticleId int64 `json:"articleId"` - User *UserInfo `json:"user"` - Tags *[]TagResponse `json:"tags"` - Title string `json:"title"` - Summary string `json:"summary"` - Share bool `json:"share"` - SourceUrl string `json:"sourceUrl"` - ViewCount int64 `json:"viewCount"` - CreateTime int64 `json:"createTime"` -} - -type ArticleResponse struct { - ArticleSimpleResponse - Content template.HTML `json:"content"` - Toc template.HTML `json:"toc"` -} - -type NodeResponse struct { - NodeId int64 `json:"nodeId"` - Name string `json:"name"` - Description string `json:"description"` -} - -// 帖子列表返回实体 -type TopicSimpleResponse struct { - TopicId int64 `json:"topicId"` - Type int `json:"type"` - User *UserInfo `json:"user"` - Node *NodeResponse `json:"node"` - Tags *[]TagResponse `json:"tags"` - Title string `json:"title"` - ImageList *[]string `json:"imageList"` - LastCommentTime int64 `json:"lastCommentTime"` - ViewCount int64 `json:"viewCount"` - CommentCount int64 `json:"commentCount"` - LikeCount int64 `json:"likeCount"` - Liked bool `json:"liked"` - CreateTime int64 `json:"createTime"` -} - -// 帖子详情返回实体 -type TopicResponse struct { - TopicSimpleResponse - Content template.HTML `json:"content"` - Toc template.HTML `json:"toc"` -} - -// 项目简单返回 -type ProjectSimpleResponse struct { - ProjectId int64 `json:"projectId"` - User *UserInfo `json:"user"` - Name string `json:"name"` - Title string `json:"title"` - Logo string `json:"logo"` - Url string `json:"url"` - DocUrl string `json:"docUrl"` - DownloadUrl string `json:"downloadUrl"` - Summary string `json:"summary"` - CreateTime int64 `json:"createTime"` -} - -// 项目详情 -type ProjectResponse struct { - ProjectSimpleResponse - Content template.HTML `json:"content"` -} - -type CommentResponse struct { - CommentId int64 `json:"commentId"` - User *UserInfo `json:"user"` - EntityType string `json:"entityType"` - EntityId int64 `json:"entityId"` - Content template.HTML `json:"content"` - QuoteId int64 `json:"quoteId"` - Quote *CommentResponse `json:"quote"` - QuoteContent template.HTML `json:"quoteContent"` - Status int `json:"status"` - CreateTime int64 `json:"createTime"` -} - -type FavoriteResponse struct { - FavoriteId int64 `json:"favoriteId"` - EntityType string `json:"entityType"` - EntityId int64 `json:"entityId"` - Deleted bool `json:"deleted"` - Title string `json:"title"` - Content string `json:"content"` - User *UserInfo `json:"user"` - Url string `json:"url"` - CreateTime int64 `json:"createTime"` -} - -// 消息 -type MessageResponse struct { - MessageId int64 `json:"messageId"` - From *UserInfo `json:"from"` // 消息发送人 - UserId int64 `json:"userId"` // 消息接收人编号 - Content string `json:"content"` // 消息内容 - QuoteContent string `json:"quoteContent"` - Type int `json:"type"` - DetailUrl string `json:"detailUrl"` // 消息详情url - ExtraData string `json:"extraData"` - Status int `json:"status"` - CreateTime int64 `json:"createTime"` -} +package model + +import ( + "html/template" +) + +type UserInfo struct { + Id int64 `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + Type int `json:"type"` + Roles []string `json:"roles"` + HomePage string `json:"homePage"` + Description string `json:"description"` + Score int `json:"score"` // 积分 + TopicCount int `json:"topicCount"` // 话题数量 + CommentCount int `json:"commentCount"` // 跟帖数量 + PasswordSet bool `json:"passwordSet"` // 密码已设置 + Status int `json:"status"` + CreateTime int64 `json:"createTime"` +} + +func (info *UserInfo) HasRole(role string) bool { + if len(info.Roles) == 0 { + return false + } + for _, r := range info.Roles { + if r == role { + return true + } + } + return false +} + +type TagResponse struct { + TagId int64 `json:"tagId"` + TagName string `json:"tagName"` +} + +type ArticleSimpleResponse struct { + ArticleId int64 `json:"articleId"` + User *UserInfo `json:"user"` + Tags *[]TagResponse `json:"tags"` + Title string `json:"title"` + Summary string `json:"summary"` + Share bool `json:"share"` + SourceUrl string `json:"sourceUrl"` + ViewCount int64 `json:"viewCount"` + CreateTime int64 `json:"createTime"` +} + +type ArticleResponse struct { + ArticleSimpleResponse + Content template.HTML `json:"content"` + Toc template.HTML `json:"toc"` +} + +type NodeResponse struct { + NodeId int64 `json:"nodeId"` + Name string `json:"name"` + Description string `json:"description"` +} + +// 帖子列表返回实体 +type TopicSimpleResponse struct { + TopicId int64 `json:"topicId"` + User *UserInfo `json:"user"` + Node *NodeResponse `json:"node"` + Tags *[]TagResponse `json:"tags"` + Title string `json:"title"` + LastCommentTime int64 `json:"lastCommentTime"` + ViewCount int64 `json:"viewCount"` + CommentCount int64 `json:"commentCount"` + LikeCount int64 `json:"likeCount"` + Liked bool `json:"liked"` + CreateTime int64 `json:"createTime"` +} + +// 帖子详情返回实体 +type TopicResponse struct { + TopicSimpleResponse + Content template.HTML `json:"content"` + Toc template.HTML `json:"toc"` +} + +// 帖子列表返回实体 +type TweetResponse struct { + TweetId int64 `json:"tweetId"` + User *UserInfo `json:"user"` + Content string `json:"content"` + ImageList *[]string `json:"imageList"` + CommentCount int64 `json:"commentCount"` + LikeCount int64 `json:"likeCount"` + CreateTime int64 `json:"createTime"` +} + +// 项目简单返回 +type ProjectSimpleResponse struct { + ProjectId int64 `json:"projectId"` + User *UserInfo `json:"user"` + Name string `json:"name"` + Title string `json:"title"` + Logo string `json:"logo"` + Url string `json:"url"` + DocUrl string `json:"docUrl"` + DownloadUrl string `json:"downloadUrl"` + Summary string `json:"summary"` + CreateTime int64 `json:"createTime"` +} + +// 项目详情 +type ProjectResponse struct { + ProjectSimpleResponse + Content template.HTML `json:"content"` +} + +type CommentResponse struct { + CommentId int64 `json:"commentId"` + User *UserInfo `json:"user"` + EntityType string `json:"entityType"` + EntityId int64 `json:"entityId"` + Content template.HTML `json:"content"` + QuoteId int64 `json:"quoteId"` + Quote *CommentResponse `json:"quote"` + QuoteContent template.HTML `json:"quoteContent"` + Status int `json:"status"` + CreateTime int64 `json:"createTime"` +} + +type FavoriteResponse struct { + FavoriteId int64 `json:"favoriteId"` + EntityType string `json:"entityType"` + EntityId int64 `json:"entityId"` + Deleted bool `json:"deleted"` + Title string `json:"title"` + Content string `json:"content"` + User *UserInfo `json:"user"` + Url string `json:"url"` + CreateTime int64 `json:"createTime"` +} + +// 消息 +type MessageResponse struct { + MessageId int64 `json:"messageId"` + From *UserInfo `json:"from"` // 消息发送人 + UserId int64 `json:"userId"` // 消息接收人编号 + Content string `json:"content"` // 消息内容 + QuoteContent string `json:"quoteContent"` + Type int `json:"type"` + DetailUrl string `json:"detailUrl"` // 消息详情url + ExtraData string `json:"extraData"` + Status int `json:"status"` + CreateTime int64 `json:"createTime"` +} diff --git a/server/repositories/topic_like_repository.go b/server/repositories/topic_like_repository.go deleted file mode 100644 index 9c8e29e47fd13df52308259e6f3295c61de9c22d..0000000000000000000000000000000000000000 --- a/server/repositories/topic_like_repository.go +++ /dev/null @@ -1,86 +0,0 @@ -package repositories - -import ( - "github.com/jinzhu/gorm" - "github.com/mlogclub/simple" - - "bbs-go/model" -) - -var TopicLikeRepository = newTopicLikeRepository() - -func newTopicLikeRepository() *topicLikeRepository { - return &topicLikeRepository{} -} - -type topicLikeRepository struct { -} - -func (r *topicLikeRepository) Get(db *gorm.DB, id int64) *model.TopicLike { - ret := &model.TopicLike{} - if err := db.First(ret, "id = ?", id).Error; err != nil { - return nil - } - return ret -} - -func (r *topicLikeRepository) Take(db *gorm.DB, where ...interface{}) *model.TopicLike { - ret := &model.TopicLike{} - if err := db.Take(ret, where...).Error; err != nil { - return nil - } - return ret -} - -func (r *topicLikeRepository) Find(db *gorm.DB, cnd *simple.SqlCnd) (list []model.TopicLike) { - cnd.Find(db, &list) - return -} - -func (r *topicLikeRepository) FindOne(db *gorm.DB, cnd *simple.SqlCnd) *model.TopicLike { - ret := &model.TopicLike{} - if err := cnd.FindOne(db, &ret); err != nil { - return nil - } - return ret -} - -func (r *topicLikeRepository) FindPageByParams(db *gorm.DB, params *simple.QueryParams) (list []model.TopicLike, paging *simple.Paging) { - return r.FindPageByCnd(db, ¶ms.SqlCnd) -} - -func (r *topicLikeRepository) FindPageByCnd(db *gorm.DB, cnd *simple.SqlCnd) (list []model.TopicLike, paging *simple.Paging) { - cnd.Find(db, &list) - count := cnd.Count(db, &model.TopicLike{}) - - paging = &simple.Paging{ - Page: cnd.Paging.Page, - Limit: cnd.Paging.Limit, - Total: count, - } - return -} - -func (r *topicLikeRepository) Create(db *gorm.DB, t *model.TopicLike) (err error) { - err = db.Create(t).Error - return -} - -func (r *topicLikeRepository) Update(db *gorm.DB, t *model.TopicLike) (err error) { - err = db.Save(t).Error - return -} - -func (r *topicLikeRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) { - err = db.Model(&model.TopicLike{}).Where("id = ?", id).Updates(columns).Error - return -} - -func (r *topicLikeRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) { - err = db.Model(&model.TopicLike{}).Where("id = ?", id).UpdateColumn(name, value).Error - return -} - -func (r *topicLikeRepository) Delete(db *gorm.DB, id int64) { - db.Delete(&model.TopicLike{}, "id = ?", id) -} diff --git a/server/repositories/tweet_repository.go b/server/repositories/tweet_repository.go new file mode 100644 index 0000000000000000000000000000000000000000..a581eff942aa19b71a828a1acdf64a102b07c732 --- /dev/null +++ b/server/repositories/tweet_repository.go @@ -0,0 +1,90 @@ +package repositories + +import ( + "bbs-go/model" + "github.com/mlogclub/simple" + "github.com/jinzhu/gorm" +) + +var TweetRepository = newTweetRepository() + +func newTweetRepository() *tweetRepository { + return &tweetRepository{} +} + +type tweetRepository struct { +} + +func (r *tweetRepository) Get(db *gorm.DB, id int64) *model.Tweet { + ret := &model.Tweet{} + if err := db.First(ret, "id = ?", id).Error; err != nil { + return nil + } + return ret +} + +func (r *tweetRepository) Take(db *gorm.DB, where ...interface{}) *model.Tweet { + ret := &model.Tweet{} + if err := db.Take(ret, where...).Error; err != nil { + return nil + } + return ret +} + +func (r *tweetRepository) Find(db *gorm.DB, cnd *simple.SqlCnd) (list []model.Tweet) { + cnd.Find(db, &list) + return +} + +func (r *tweetRepository) FindOne(db *gorm.DB, cnd *simple.SqlCnd) *model.Tweet { + ret := &model.Tweet{} + if err := cnd.FindOne(db, &ret); err != nil { + return nil + } + return ret +} + +func (r *tweetRepository) FindPageByParams(db *gorm.DB, params *simple.QueryParams) (list []model.Tweet, paging *simple.Paging) { + return r.FindPageByCnd(db, ¶ms.SqlCnd) +} + +func (r *tweetRepository) FindPageByCnd(db *gorm.DB, cnd *simple.SqlCnd) (list []model.Tweet, paging *simple.Paging) { + cnd.Find(db, &list) + count := cnd.Count(db, &model.Tweet{}) + + paging = &simple.Paging{ + Page: cnd.Paging.Page, + Limit: cnd.Paging.Limit, + Total: count, + } + return +} + +func (r *tweetRepository) Count(db *gorm.DB, cnd *simple.SqlCnd) int { + return cnd.Count(db, &model.Tweet{}) +} + +func (r *tweetRepository) Create(db *gorm.DB, t *model.Tweet) (err error) { + err = db.Create(t).Error + return +} + +func (r *tweetRepository) Update(db *gorm.DB, t *model.Tweet) (err error) { + err = db.Save(t).Error + return +} + +func (r *tweetRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) { + err = db.Model(&model.Tweet{}).Where("id = ?", id).Updates(columns).Error + return +} + +func (r *tweetRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) { + err = db.Model(&model.Tweet{}).Where("id = ?", id).UpdateColumn(name, value).Error + return +} + +func (r *tweetRepository) Delete(db *gorm.DB, id int64) { + db.Delete(&model.Tweet{}, "id = ?", id) +} + diff --git a/server/repositories/user_like_repository.go b/server/repositories/user_like_repository.go new file mode 100644 index 0000000000000000000000000000000000000000..1dffcb0a1381fed1a7ae357856a218eadd91174c --- /dev/null +++ b/server/repositories/user_like_repository.go @@ -0,0 +1,86 @@ +package repositories + +import ( + "github.com/jinzhu/gorm" + "github.com/mlogclub/simple" + + "bbs-go/model" +) + +var UserLikeRepository = newUserLikeRepository() + +func newUserLikeRepository() *userLikeRepository { + return &userLikeRepository{} +} + +type userLikeRepository struct { +} + +func (r *userLikeRepository) Get(db *gorm.DB, id int64) *model.UserLike { + ret := &model.UserLike{} + if err := db.First(ret, "id = ?", id).Error; err != nil { + return nil + } + return ret +} + +func (r *userLikeRepository) Take(db *gorm.DB, where ...interface{}) *model.UserLike { + ret := &model.UserLike{} + if err := db.Take(ret, where...).Error; err != nil { + return nil + } + return ret +} + +func (r *userLikeRepository) Find(db *gorm.DB, cnd *simple.SqlCnd) (list []model.UserLike) { + cnd.Find(db, &list) + return +} + +func (r *userLikeRepository) FindOne(db *gorm.DB, cnd *simple.SqlCnd) *model.UserLike { + ret := &model.UserLike{} + if err := cnd.FindOne(db, &ret); err != nil { + return nil + } + return ret +} + +func (r *userLikeRepository) FindPageByParams(db *gorm.DB, params *simple.QueryParams) (list []model.UserLike, paging *simple.Paging) { + return r.FindPageByCnd(db, ¶ms.SqlCnd) +} + +func (r *userLikeRepository) FindPageByCnd(db *gorm.DB, cnd *simple.SqlCnd) (list []model.UserLike, paging *simple.Paging) { + cnd.Find(db, &list) + count := cnd.Count(db, &model.UserLike{}) + + paging = &simple.Paging{ + Page: cnd.Paging.Page, + Limit: cnd.Paging.Limit, + Total: count, + } + return +} + +func (r *userLikeRepository) Create(db *gorm.DB, t *model.UserLike) (err error) { + err = db.Create(t).Error + return +} + +func (r *userLikeRepository) Update(db *gorm.DB, t *model.UserLike) (err error) { + err = db.Save(t).Error + return +} + +func (r *userLikeRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) { + err = db.Model(&model.UserLike{}).Where("id = ?", id).Updates(columns).Error + return +} + +func (r *userLikeRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) { + err = db.Model(&model.UserLike{}).Where("id = ?", id).UpdateColumn(name, value).Error + return +} + +func (r *userLikeRepository) Delete(db *gorm.DB, id int64) { + db.Delete(&model.UserLike{}, "id = ?", id) +} diff --git a/server/services/sys_config_service.go b/server/services/sys_config_service.go index f6c63f829acde8ba4456617da4de9564b85c009c..290d6aa5a52c23d53c8e934e497c7bc712d793aa 100644 --- a/server/services/sys_config_service.go +++ b/server/services/sys_config_service.go @@ -1,163 +1,171 @@ -package services - -import ( - "errors" - "strconv" - "strings" - - "github.com/jinzhu/gorm" - "github.com/mlogclub/simple" - "github.com/sirupsen/logrus" - "github.com/tidwall/gjson" - - "bbs-go/model" - "bbs-go/repositories" - "bbs-go/services/cache" -) - -var SysConfigService = newSysConfigService() - -func newSysConfigService() *sysConfigService { - return &sysConfigService{} -} - -type sysConfigService struct { -} - -func (s *sysConfigService) Get(id int64) *model.SysConfig { - return repositories.SysConfigRepository.Get(simple.DB(), id) -} - -func (s *sysConfigService) Take(where ...interface{}) *model.SysConfig { - return repositories.SysConfigRepository.Take(simple.DB(), where...) -} - -func (s *sysConfigService) Find(cnd *simple.SqlCnd) []model.SysConfig { - return repositories.SysConfigRepository.Find(simple.DB(), cnd) -} - -func (s *sysConfigService) FindOne(cnd *simple.SqlCnd) *model.SysConfig { - return repositories.SysConfigRepository.FindOne(simple.DB(), cnd) -} - -func (s *sysConfigService) FindPageByParams(params *simple.QueryParams) (list []model.SysConfig, paging *simple.Paging) { - return repositories.SysConfigRepository.FindPageByParams(simple.DB(), params) -} - -func (s *sysConfigService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.SysConfig, paging *simple.Paging) { - return repositories.SysConfigRepository.FindPageByCnd(simple.DB(), cnd) -} - -func (s *sysConfigService) GetAll() []model.SysConfig { - return repositories.SysConfigRepository.Find(simple.DB(), simple.NewSqlCnd().Asc("id")) -} - -func (s *sysConfigService) SetAll(configStr string) error { - json := gjson.Parse(configStr) - configs, ok := json.Value().(map[string]interface{}) - if !ok { - return errors.New("配置数据格式错误") - } - return simple.Tx(simple.DB(), func(tx *gorm.DB) error { - for k, _ := range configs { - v := json.Get(k).String() - if err := s.setSingle(tx, k, v, "", ""); err != nil { - return err - } - } - return nil - }) -} - -// 设置配置,如果配置不存在,那么创建 -func (s *sysConfigService) Set(key, value, name, description string) error { - return simple.Tx(simple.DB(), func(tx *gorm.DB) error { - if err := s.setSingle(tx, key, value, name, description); err != nil { - return err - } - return nil - }) -} - -func (s *sysConfigService) setSingle(db *gorm.DB, key, value, name, description string) error { - if len(key) == 0 { - return errors.New("sys config key is null") - } - sysConfig := repositories.SysConfigRepository.GetByKey(db, key) - if sysConfig == nil { - sysConfig = &model.SysConfig{ - CreateTime: simple.NowTimestamp(), - } - } - sysConfig.Key = key - sysConfig.Value = value - sysConfig.UpdateTime = simple.NowTimestamp() - - if len(name) > 0 { - sysConfig.Name = name - } - if len(description) > 0 { - sysConfig.Description = description - } - - var err error - if sysConfig.Id > 0 { - err = repositories.SysConfigRepository.Update(db, sysConfig) - } else { - err = repositories.SysConfigRepository.Create(db, sysConfig) - } - if err != nil { - return err - } else { - cache.SysConfigCache.Invalidate(key) - return nil - } -} - -func (s *sysConfigService) GetConfig() *model.ConfigData { - var ( - siteTitle = cache.SysConfigCache.GetValue(model.SysConfigSiteTitle) - siteDescription = cache.SysConfigCache.GetValue(model.SysConfigSiteDescription) - siteKeywords = cache.SysConfigCache.GetValue(model.SysConfigSiteKeywords) - siteNavs = cache.SysConfigCache.GetValue(model.SysConfigSiteNavs) - siteNotification = cache.SysConfigCache.GetValue(model.SysConfigSiteNotification) - recommendTags = cache.SysConfigCache.GetValue(model.SysConfigRecommendTags) - urlRedirect = cache.SysConfigCache.GetValue(model.SysConfigUrlRedirect) - scoreConfigStr = cache.SysConfigCache.GetValue(model.SysConfigScoreConfig) - defaultNodeIdStr = cache.SysConfigCache.GetValue(model.SysConfigDefaultNodeId) - ) - - var siteKeywordsArr []string - if err := simple.ParseJson(siteKeywords, &siteKeywordsArr); err != nil { - logrus.Warn("站点关键词数据错误", err) - } - - var siteNavsArr []model.SiteNav - if err := simple.ParseJson(siteNavs, &siteNavsArr); err != nil { - logrus.Warn("站点导航数据错误", err) - } - - var recommendTagsArr []string - if err := simple.ParseJson(recommendTags, &recommendTagsArr); err != nil { - logrus.Warn("推荐标签数据错误", err) - } - - var scoreConfig model.ScoreConfig - if err := simple.ParseJson(scoreConfigStr, &scoreConfig); err != nil { - logrus.Warn("积分配置错误", err) - } - - var defaultNodeId, _ = strconv.ParseInt(defaultNodeIdStr, 10, 64) - - return &model.ConfigData{ - SiteTitle: siteTitle, - SiteDescription: siteDescription, - SiteKeywords: siteKeywordsArr, - SiteNavs: siteNavsArr, - SiteNotification: siteNotification, - RecommendTags: recommendTagsArr, - UrlRedirect: strings.ToLower(urlRedirect) == "true", - ScoreConfig: scoreConfig, - DefaultNodeId: defaultNodeId, - } -} +package services + +import ( + "errors" + "strconv" + "strings" + + "github.com/jinzhu/gorm" + "github.com/mlogclub/simple" + "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" + + "bbs-go/model" + "bbs-go/repositories" + "bbs-go/services/cache" +) + +var SysConfigService = newSysConfigService() + +func newSysConfigService() *sysConfigService { + return &sysConfigService{} +} + +type sysConfigService struct { +} + +func (s *sysConfigService) Get(id int64) *model.SysConfig { + return repositories.SysConfigRepository.Get(simple.DB(), id) +} + +func (s *sysConfigService) Take(where ...interface{}) *model.SysConfig { + return repositories.SysConfigRepository.Take(simple.DB(), where...) +} + +func (s *sysConfigService) Find(cnd *simple.SqlCnd) []model.SysConfig { + return repositories.SysConfigRepository.Find(simple.DB(), cnd) +} + +func (s *sysConfigService) FindOne(cnd *simple.SqlCnd) *model.SysConfig { + return repositories.SysConfigRepository.FindOne(simple.DB(), cnd) +} + +func (s *sysConfigService) FindPageByParams(params *simple.QueryParams) (list []model.SysConfig, paging *simple.Paging) { + return repositories.SysConfigRepository.FindPageByParams(simple.DB(), params) +} + +func (s *sysConfigService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.SysConfig, paging *simple.Paging) { + return repositories.SysConfigRepository.FindPageByCnd(simple.DB(), cnd) +} + +func (s *sysConfigService) GetAll() []model.SysConfig { + return repositories.SysConfigRepository.Find(simple.DB(), simple.NewSqlCnd().Asc("id")) +} + +func (s *sysConfigService) SetAll(configStr string) error { + json := gjson.Parse(configStr) + configs, ok := json.Value().(map[string]interface{}) + if !ok { + return errors.New("配置数据格式错误") + } + return simple.Tx(simple.DB(), func(tx *gorm.DB) error { + for k, _ := range configs { + v := json.Get(k).String() + if err := s.setSingle(tx, k, v, "", ""); err != nil { + return err + } + } + return nil + }) +} + +// 设置配置,如果配置不存在,那么创建 +func (s *sysConfigService) Set(key, value, name, description string) error { + return simple.Tx(simple.DB(), func(tx *gorm.DB) error { + if err := s.setSingle(tx, key, value, name, description); err != nil { + return err + } + return nil + }) +} + +func (s *sysConfigService) setSingle(db *gorm.DB, key, value, name, description string) error { + if len(key) == 0 { + return errors.New("sys config key is null") + } + sysConfig := repositories.SysConfigRepository.GetByKey(db, key) + if sysConfig == nil { + sysConfig = &model.SysConfig{ + CreateTime: simple.NowTimestamp(), + } + } + sysConfig.Key = key + sysConfig.Value = value + sysConfig.UpdateTime = simple.NowTimestamp() + + if len(name) > 0 { + sysConfig.Name = name + } + if len(description) > 0 { + sysConfig.Description = description + } + + var err error + if sysConfig.Id > 0 { + err = repositories.SysConfigRepository.Update(db, sysConfig) + } else { + err = repositories.SysConfigRepository.Create(db, sysConfig) + } + if err != nil { + return err + } else { + cache.SysConfigCache.Invalidate(key) + return nil + } +} + +func (s *sysConfigService) GetConfig() *model.ConfigData { + var ( + siteTitle = cache.SysConfigCache.GetValue(model.SysConfigSiteTitle) + siteDescription = cache.SysConfigCache.GetValue(model.SysConfigSiteDescription) + siteKeywords = cache.SysConfigCache.GetValue(model.SysConfigSiteKeywords) + siteNavs = cache.SysConfigCache.GetValue(model.SysConfigSiteNavs) + siteNotification = cache.SysConfigCache.GetValue(model.SysConfigSiteNotification) + recommendTags = cache.SysConfigCache.GetValue(model.SysConfigRecommendTags) + urlRedirect = cache.SysConfigCache.GetValue(model.SysConfigUrlRedirect) + scoreConfigStr = cache.SysConfigCache.GetValue(model.SysConfigScoreConfig) + defaultNodeIdStr = cache.SysConfigCache.GetValue(model.SysConfigDefaultNodeId) + ) + + var siteKeywordsArr []string + if len(siteKeywords) > 0 { + if err := simple.ParseJson(siteKeywords, &siteKeywordsArr); err != nil { + logrus.Warn("站点关键词数据错误", err) + } + } + + var siteNavsArr []model.SiteNav + if len(siteNavs)> 0 { + if err := simple.ParseJson(siteNavs, &siteNavsArr); err != nil { + logrus.Warn("站点导航数据错误", err) + } + } + + var recommendTagsArr []string + if len(recommendTags)> 0 { + if err := simple.ParseJson(recommendTags, &recommendTagsArr); err != nil { + logrus.Warn("推荐标签数据错误", err) + } + } + + var scoreConfig model.ScoreConfig + if len(scoreConfigStr)>0 { + if err := simple.ParseJson(scoreConfigStr, &scoreConfig); err != nil { + logrus.Warn("积分配置错误", err) + } + } + + var defaultNodeId, _ = strconv.ParseInt(defaultNodeIdStr, 10, 64) + + return &model.ConfigData{ + SiteTitle: siteTitle, + SiteDescription: siteDescription, + SiteKeywords: siteKeywordsArr, + SiteNavs: siteNavsArr, + SiteNotification: siteNotification, + RecommendTags: recommendTagsArr, + UrlRedirect: strings.ToLower(urlRedirect) == "true", + ScoreConfig: scoreConfig, + DefaultNodeId: defaultNodeId, + } +} diff --git a/server/services/topic_like_service.go b/server/services/topic_like_service.go deleted file mode 100644 index 21b8514a9e01274ed1a8fec62615b3f7e88e66f9..0000000000000000000000000000000000000000 --- a/server/services/topic_like_service.go +++ /dev/null @@ -1,104 +0,0 @@ -package services - -import ( - "errors" - - "github.com/jinzhu/gorm" - "github.com/mlogclub/simple" - - "bbs-go/model" - "bbs-go/repositories" -) - -var TopicLikeService = newTopicLikeService() - -func newTopicLikeService() *topicLikeService { - return &topicLikeService{} -} - -type topicLikeService struct { -} - -func (s *topicLikeService) Get(id int64) *model.TopicLike { - return repositories.TopicLikeRepository.Get(simple.DB(), id) -} - -func (s *topicLikeService) Take(where ...interface{}) *model.TopicLike { - return repositories.TopicLikeRepository.Take(simple.DB(), where...) -} - -func (s *topicLikeService) Find(cnd *simple.SqlCnd) []model.TopicLike { - return repositories.TopicLikeRepository.Find(simple.DB(), cnd) -} - -func (s *topicLikeService) FindOne(cnd *simple.SqlCnd) *model.TopicLike { - return repositories.TopicLikeRepository.FindOne(simple.DB(), cnd) -} - -func (s *topicLikeService) FindPageByParams(params *simple.QueryParams) (list []model.TopicLike, paging *simple.Paging) { - return repositories.TopicLikeRepository.FindPageByParams(simple.DB(), params) -} - -func (s *topicLikeService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.TopicLike, paging *simple.Paging) { - return repositories.TopicLikeRepository.FindPageByCnd(simple.DB(), cnd) -} - -func (s *topicLikeService) Create(t *model.TopicLike) error { - return repositories.TopicLikeRepository.Create(simple.DB(), t) -} - -func (s *topicLikeService) Update(t *model.TopicLike) error { - return repositories.TopicLikeRepository.Update(simple.DB(), t) -} - -func (s *topicLikeService) Updates(id int64, columns map[string]interface{}) error { - return repositories.TopicLikeRepository.Updates(simple.DB(), id, columns) -} - -func (s *topicLikeService) UpdateColumn(id int64, name string, value interface{}) error { - return repositories.TopicLikeRepository.UpdateColumn(simple.DB(), id, name, value) -} - -func (s *topicLikeService) Delete(id int64) { - repositories.TopicLikeRepository.Delete(simple.DB(), id) -} - -// 统计数量 -func (s *topicLikeService) Count(topicId int64) int64 { - var count int64 = 0 - simple.DB().Model(&model.TopicLike{}).Where("topic_id = ?", topicId).Count(&count) - return count -} - -// 最近点赞 -func (s *topicLikeService) Recent(topicId int64, count int) []model.TopicLike { - return s.Find(simple.NewSqlCnd().Eq("topic_id", topicId).Desc("id").Limit(count)) -} - -func (s *topicLikeService) Like(userId int64, topicId int64) error { - topic := repositories.TopicRepository.Get(simple.DB(), topicId) - if topic == nil || topic.Status != model.StatusOk { - return errors.New("话题不存在") - } - - // 判断是否已经点赞了 - topicLike := repositories.TopicLikeRepository.Take(simple.DB(), "user_id = ? and topic_id = ?", userId, topicId) - if topicLike != nil { - return errors.New("已点赞") - } - - return simple.Tx(simple.DB(), func(tx *gorm.DB) error { - // 点赞 - err := repositories.TopicLikeRepository.Create(tx, &model.TopicLike{ - UserId: userId, - TopicId: topicId, - CreateTime: simple.NowTimestamp(), - }) - if err != nil { - return err - } - - // 更新帖子点赞数 - return simple.DB().Exec("update t_topic set like_count = like_count + 1 where id = ?", topicId).Error - }) -} diff --git a/server/services/topic_service.go b/server/services/topic_service.go index 9465c5b0b6fe2fc7284740eae92b47b1ced396c8..cbba6a746697064a261d638dffebe20a7479b4df 100644 --- a/server/services/topic_service.go +++ b/server/services/topic_service.go @@ -1,315 +1,313 @@ -package services - -import ( - "math" - "path" - "time" - - "github.com/gorilla/feeds" - "github.com/jinzhu/gorm" - "github.com/mlogclub/simple" - "github.com/sirupsen/logrus" - - "bbs-go/common" - "bbs-go/common/baiduseo" - "bbs-go/common/config" - "bbs-go/common/urls" - "bbs-go/model" - "bbs-go/repositories" - "bbs-go/services/cache" -) - -type ScanTopicCallback func(topics []model.Topic) - -var TopicService = newTopicService() - -func newTopicService() *topicService { - return &topicService{} -} - -type topicService struct{} - -func (s *topicService) Get(id int64) *model.Topic { - return repositories.TopicRepository.Get(simple.DB(), id) -} - -func (s *topicService) Take(where ...interface{}) *model.Topic { - return repositories.TopicRepository.Take(simple.DB(), where...) -} - -func (s *topicService) Find(cnd *simple.SqlCnd) []model.Topic { - return repositories.TopicRepository.Find(simple.DB(), cnd) -} - -func (s *topicService) FindOne(cnd *simple.SqlCnd) *model.Topic { - return repositories.TopicRepository.FindOne(simple.DB(), cnd) -} - -func (s *topicService) FindPageByParams(params *simple.QueryParams) (list []model.Topic, paging *simple.Paging) { - return repositories.TopicRepository.FindPageByParams(simple.DB(), params) -} - -func (s *topicService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Topic, paging *simple.Paging) { - return repositories.TopicRepository.FindPageByCnd(simple.DB(), cnd) -} - -func (s *topicService) Count(cnd *simple.SqlCnd) int { - return repositories.TopicRepository.Count(simple.DB(), cnd) -} - -func (s *topicService) Create(t *model.Topic) error { - return repositories.TopicRepository.Create(simple.DB(), t) -} - -func (s *topicService) Update(t *model.Topic) error { - return repositories.TopicRepository.Update(simple.DB(), t) -} - -func (s *topicService) Updates(id int64, columns map[string]interface{}) error { - return repositories.TopicRepository.Updates(simple.DB(), id, columns) -} - -func (s *topicService) UpdateColumn(id int64, name string, value interface{}) error { - return repositories.TopicRepository.UpdateColumn(simple.DB(), id, name, value) -} - -// 删除 -func (s *topicService) Delete(id int64) error { - err := repositories.TopicRepository.UpdateColumn(simple.DB(), id, "status", model.StatusDeleted) - if err == nil { - // 删掉标签文章 - TopicTagService.DeleteByTopicId(id) - } - return err -} - -// 取消删除 -func (s *topicService) Undelete(id int64) error { - err := repositories.TopicRepository.UpdateColumn(simple.DB(), id, "status", model.StatusOk) - if err == nil { - // 删掉标签文章 - TopicTagService.UndeleteByTopicId(id) - } - return err -} - -// 发表 -func (s *topicService) Publish(topicType int, userId, nodeId int64, tags []string, title, content, imageList string) (*model.Topic, *simple.CodeError) { - if len(title) == 0 { - return nil, simple.NewErrorMsg("标题不能为空") - } - - if simple.RuneLen(title) > 128 { - return nil, simple.NewErrorMsg("标题长度不能超过128") - } - - if nodeId <= 0 { - nodeId = SysConfigService.GetConfig().DefaultNodeId - if nodeId <= 0 { - return nil, simple.NewErrorMsg("请配置默认节点") - } - } - node := repositories.TopicNodeRepository.Get(simple.DB(), nodeId) - if node == nil || node.Status != model.StatusOk { - return nil, simple.NewErrorMsg("节点不存在") - } - - now := simple.NowTimestamp() - topic := &model.Topic{ - Type: topicType, - UserId: userId, - NodeId: nodeId, - Title: title, - Content: content, - ImageList: imageList, - Status: model.StatusOk, - LastCommentTime: now, - CreateTime: now, - } - - err := simple.Tx(simple.DB(), func(tx *gorm.DB) error { - tagIds := repositories.TagRepository.GetOrCreates(tx, tags) - err := repositories.TopicRepository.Create(tx, topic) - if err != nil { - return err - } - - repositories.TopicTagRepository.AddTopicTags(tx, topic.Id, tagIds) - return nil - }) - if err == nil { - // 用户话题计数 - UserService.IncrTopicCount(userId) - // 获得积分 - UserScoreService.IncrementPostTopicScore(topic) - // 百度链接推送 - baiduseo.PushUrl(urls.TopicUrl(topic.Id)) - } - return topic, simple.FromError(err) -} - -// 更新 -func (s *topicService) Edit(topicId, nodeId int64, tags []string, title, content string) *simple.CodeError { - if len(title) == 0 { - return simple.NewErrorMsg("标题不能为空") - } - - if simple.RuneLen(title) > 128 { - return simple.NewErrorMsg("标题长度不能超过128") - } - - node := repositories.TopicNodeRepository.Get(simple.DB(), nodeId) - if node == nil || node.Status != model.StatusOk { - return simple.NewErrorMsg("节点不存在") - } - - err := simple.Tx(simple.DB(), func(tx *gorm.DB) error { - err := repositories.TopicRepository.Updates(simple.DB(), topicId, map[string]interface{}{ - "node_id": nodeId, - "title": title, - "content": content, - }) - if err != nil { - return err - } - - tagIds := repositories.TagRepository.GetOrCreates(tx, tags) // 创建帖子对应标签 - repositories.TopicTagRepository.DeleteTopicTags(tx, topicId) // 先删掉所有的标签 - repositories.TopicTagRepository.AddTopicTags(tx, topicId, tagIds) // 然后重新添加标签 - return nil - }) - return simple.FromError(err) -} - -// 推荐 -func (s *topicService) SetRecommend(topicId int64, recommend bool) error { - return s.UpdateColumn(topicId, "recommend", recommend) -} - -// 话题的标签 -func (s *topicService) GetTopicTags(topicId int64) []model.Tag { - topicTags := repositories.TopicTagRepository.Find(simple.DB(), simple.NewSqlCnd().Where("topic_id = ?", topicId)) - - var tagIds []int64 - for _, topicTag := range topicTags { - tagIds = append(tagIds, topicTag.TagId) - } - return cache.TagCache.GetList(tagIds) -} - -// 指定标签下话题列表 -func (s *topicService) GetTagTopics(tagId int64, page int) (topics []model.Topic, paging *simple.Paging) { - topicTags, paging := repositories.TopicTagRepository.FindPageByCnd(simple.DB(), simple.NewSqlCnd(). - Eq("tag_id", tagId). - Eq("status", model.StatusOk). - Page(page, 20).Desc("last_comment_time")) - if len(topicTags) > 0 { - var topicIds []int64 - for _, topicTag := range topicTags { - topicIds = append(topicIds, topicTag.TopicId) - } - - topicsMap := s.GetTopicInIds(topicIds) - if topicsMap != nil { - for _, topicTag := range topicTags { - if topic, found := topicsMap[topicTag.TopicId]; found { - topics = append(topics, topic) - } - } - } - } - return -} - -// GetTopicInIds 根据编号批量获取主题 -func (s *topicService) GetTopicInIds(topicIds []int64) map[int64]model.Topic { - if len(topicIds) == 0 { - return nil - } - var topics []model.Topic - simple.DB().Where("id in (?)", topicIds).Find(&topics) - - topicsMap := make(map[int64]model.Topic, len(topics)) - for _, topic := range topics { - topicsMap[topic.Id] = topic - } - return topicsMap -} - -// 浏览数+1 -func (s *topicService) IncrViewCount(topicId int64) { - simple.DB().Exec("update t_topic set view_count = view_count + 1 where id = ?", topicId) -} - -// 当帖子被评论的时候,更新最后回复时间、回复数量+1 -func (s *topicService) OnComment(topicId, lastCommentTime int64) { - simple.Tx(simple.DB(), func(tx *gorm.DB) error { - if err := tx.Exec("update t_topic set last_comment_time = ?, comment_count = comment_count + 1 where id = ?", lastCommentTime, topicId).Error; err != nil { - return err - } - if err := tx.Exec("update t_topic_tag set last_comment_time = ? where topic_id = ?", lastCommentTime, topicId).Error; err != nil { - return err - } - return nil - }) -} - -// rss -func (s *topicService) GenerateRss() { - topics := repositories.TopicRepository.Find(simple.DB(), - simple.NewSqlCnd().Where("status = ?", model.StatusOk).Desc("id").Limit(1000)) - - var items []*feeds.Item - for _, topic := range topics { - topicUrl := urls.TopicUrl(topic.Id) - user := cache.UserCache.Get(topic.UserId) - if user == nil { - continue - } - item := &feeds.Item{ - Title: topic.Title, - Link: &feeds.Link{Href: topicUrl}, - Description: common.GetMarkdownSummary(topic.Content), - Author: &feeds.Author{Name: user.Avatar, Email: user.Email.String}, - Created: simple.TimeFromTimestamp(topic.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, "topic_atom.xml"), atom, false) - } - - rss, err := feed.ToRss() - if err != nil { - logrus.Error(err) - } else { - _ = simple.WriteString(path.Join(config.Conf.StaticPath, "topic_rss.xml"), rss, false) - } -} - -// 倒序扫描 -func (s *topicService) ScanDesc(dateFrom, dateTo int64, cb ScanTopicCallback) { - var cursor int64 = math.MaxInt64 - for { - list := repositories.TopicRepository.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 - cb(list) - } -} +package services + +import ( + "math" + "path" + "time" + + "github.com/gorilla/feeds" + "github.com/jinzhu/gorm" + "github.com/mlogclub/simple" + "github.com/sirupsen/logrus" + + "bbs-go/common" + "bbs-go/common/baiduseo" + "bbs-go/common/config" + "bbs-go/common/urls" + "bbs-go/model" + "bbs-go/repositories" + "bbs-go/services/cache" +) + +type ScanTopicCallback func(topics []model.Topic) + +var TopicService = newTopicService() + +func newTopicService() *topicService { + return &topicService{} +} + +type topicService struct{} + +func (s *topicService) Get(id int64) *model.Topic { + return repositories.TopicRepository.Get(simple.DB(), id) +} + +func (s *topicService) Take(where ...interface{}) *model.Topic { + return repositories.TopicRepository.Take(simple.DB(), where...) +} + +func (s *topicService) Find(cnd *simple.SqlCnd) []model.Topic { + return repositories.TopicRepository.Find(simple.DB(), cnd) +} + +func (s *topicService) FindOne(cnd *simple.SqlCnd) *model.Topic { + return repositories.TopicRepository.FindOne(simple.DB(), cnd) +} + +func (s *topicService) FindPageByParams(params *simple.QueryParams) (list []model.Topic, paging *simple.Paging) { + return repositories.TopicRepository.FindPageByParams(simple.DB(), params) +} + +func (s *topicService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Topic, paging *simple.Paging) { + return repositories.TopicRepository.FindPageByCnd(simple.DB(), cnd) +} + +func (s *topicService) Count(cnd *simple.SqlCnd) int { + return repositories.TopicRepository.Count(simple.DB(), cnd) +} + +func (s *topicService) Create(t *model.Topic) error { + return repositories.TopicRepository.Create(simple.DB(), t) +} + +func (s *topicService) Update(t *model.Topic) error { + return repositories.TopicRepository.Update(simple.DB(), t) +} + +func (s *topicService) Updates(id int64, columns map[string]interface{}) error { + return repositories.TopicRepository.Updates(simple.DB(), id, columns) +} + +func (s *topicService) UpdateColumn(id int64, name string, value interface{}) error { + return repositories.TopicRepository.UpdateColumn(simple.DB(), id, name, value) +} + +// 删除 +func (s *topicService) Delete(id int64) error { + err := repositories.TopicRepository.UpdateColumn(simple.DB(), id, "status", model.StatusDeleted) + if err == nil { + // 删掉标签文章 + TopicTagService.DeleteByTopicId(id) + } + return err +} + +// 取消删除 +func (s *topicService) Undelete(id int64) error { + err := repositories.TopicRepository.UpdateColumn(simple.DB(), id, "status", model.StatusOk) + if err == nil { + // 删掉标签文章 + TopicTagService.UndeleteByTopicId(id) + } + return err +} + +// 发表 +func (s *topicService) Publish(userId, nodeId int64, tags []string, title, content string) (*model.Topic, *simple.CodeError) { + if len(title) == 0 { + return nil, simple.NewErrorMsg("标题不能为空") + } + + if simple.RuneLen(title) > 128 { + return nil, simple.NewErrorMsg("标题长度不能超过128") + } + + if nodeId <= 0 { + nodeId = SysConfigService.GetConfig().DefaultNodeId + if nodeId <= 0 { + return nil, simple.NewErrorMsg("请配置默认节点") + } + } + node := repositories.TopicNodeRepository.Get(simple.DB(), nodeId) + if node == nil || node.Status != model.StatusOk { + return nil, simple.NewErrorMsg("节点不存在") + } + + now := simple.NowTimestamp() + topic := &model.Topic{ + UserId: userId, + NodeId: nodeId, + Title: title, + Content: content, + Status: model.StatusOk, + LastCommentTime: now, + CreateTime: now, + } + + err := simple.Tx(simple.DB(), func(tx *gorm.DB) error { + tagIds := repositories.TagRepository.GetOrCreates(tx, tags) + err := repositories.TopicRepository.Create(tx, topic) + if err != nil { + return err + } + + repositories.TopicTagRepository.AddTopicTags(tx, topic.Id, tagIds) + return nil + }) + if err == nil { + // 用户话题计数 + UserService.IncrTopicCount(userId) + // 获得积分 + UserScoreService.IncrementPostTopicScore(topic) + // 百度链接推送 + baiduseo.PushUrl(urls.TopicUrl(topic.Id)) + } + return topic, simple.FromError(err) +} + +// 更新 +func (s *topicService) Edit(topicId, nodeId int64, tags []string, title, content string) *simple.CodeError { + if len(title) == 0 { + return simple.NewErrorMsg("标题不能为空") + } + + if simple.RuneLen(title) > 128 { + return simple.NewErrorMsg("标题长度不能超过128") + } + + node := repositories.TopicNodeRepository.Get(simple.DB(), nodeId) + if node == nil || node.Status != model.StatusOk { + return simple.NewErrorMsg("节点不存在") + } + + err := simple.Tx(simple.DB(), func(tx *gorm.DB) error { + err := repositories.TopicRepository.Updates(simple.DB(), topicId, map[string]interface{}{ + "node_id": nodeId, + "title": title, + "content": content, + }) + if err != nil { + return err + } + + tagIds := repositories.TagRepository.GetOrCreates(tx, tags) // 创建帖子对应标签 + repositories.TopicTagRepository.DeleteTopicTags(tx, topicId) // 先删掉所有的标签 + repositories.TopicTagRepository.AddTopicTags(tx, topicId, tagIds) // 然后重新添加标签 + return nil + }) + return simple.FromError(err) +} + +// 推荐 +func (s *topicService) SetRecommend(topicId int64, recommend bool) error { + return s.UpdateColumn(topicId, "recommend", recommend) +} + +// 话题的标签 +func (s *topicService) GetTopicTags(topicId int64) []model.Tag { + topicTags := repositories.TopicTagRepository.Find(simple.DB(), simple.NewSqlCnd().Where("topic_id = ?", topicId)) + + var tagIds []int64 + for _, topicTag := range topicTags { + tagIds = append(tagIds, topicTag.TagId) + } + return cache.TagCache.GetList(tagIds) +} + +// 指定标签下话题列表 +func (s *topicService) GetTagTopics(tagId int64, page int) (topics []model.Topic, paging *simple.Paging) { + topicTags, paging := repositories.TopicTagRepository.FindPageByCnd(simple.DB(), simple.NewSqlCnd(). + Eq("tag_id", tagId). + Eq("status", model.StatusOk). + Page(page, 20).Desc("last_comment_time")) + if len(topicTags) > 0 { + var topicIds []int64 + for _, topicTag := range topicTags { + topicIds = append(topicIds, topicTag.TopicId) + } + + topicsMap := s.GetTopicInIds(topicIds) + if topicsMap != nil { + for _, topicTag := range topicTags { + if topic, found := topicsMap[topicTag.TopicId]; found { + topics = append(topics, topic) + } + } + } + } + return +} + +// GetTopicInIds 根据编号批量获取主题 +func (s *topicService) GetTopicInIds(topicIds []int64) map[int64]model.Topic { + if len(topicIds) == 0 { + return nil + } + var topics []model.Topic + simple.DB().Where("id in (?)", topicIds).Find(&topics) + + topicsMap := make(map[int64]model.Topic, len(topics)) + for _, topic := range topics { + topicsMap[topic.Id] = topic + } + return topicsMap +} + +// 浏览数+1 +func (s *topicService) IncrViewCount(topicId int64) { + simple.DB().Exec("update t_topic set view_count = view_count + 1 where id = ?", topicId) +} + +// 当帖子被评论的时候,更新最后回复时间、回复数量+1 +func (s *topicService) OnComment(topicId, lastCommentTime int64) { + simple.Tx(simple.DB(), func(tx *gorm.DB) error { + if err := tx.Exec("update t_topic set last_comment_time = ?, comment_count = comment_count + 1 where id = ?", lastCommentTime, topicId).Error; err != nil { + return err + } + if err := tx.Exec("update t_topic_tag set last_comment_time = ? where topic_id = ?", lastCommentTime, topicId).Error; err != nil { + return err + } + return nil + }) +} + +// rss +func (s *topicService) GenerateRss() { + topics := repositories.TopicRepository.Find(simple.DB(), + simple.NewSqlCnd().Where("status = ?", model.StatusOk).Desc("id").Limit(1000)) + + var items []*feeds.Item + for _, topic := range topics { + topicUrl := urls.TopicUrl(topic.Id) + user := cache.UserCache.Get(topic.UserId) + if user == nil { + continue + } + item := &feeds.Item{ + Title: topic.Title, + Link: &feeds.Link{Href: topicUrl}, + Description: common.GetMarkdownSummary(topic.Content), + Author: &feeds.Author{Name: user.Avatar, Email: user.Email.String}, + Created: simple.TimeFromTimestamp(topic.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, "topic_atom.xml"), atom, false) + } + + rss, err := feed.ToRss() + if err != nil { + logrus.Error(err) + } else { + _ = simple.WriteString(path.Join(config.Conf.StaticPath, "topic_rss.xml"), rss, false) + } +} + +// 倒序扫描 +func (s *topicService) ScanDesc(dateFrom, dateTo int64, cb ScanTopicCallback) { + var cursor int64 = math.MaxInt64 + for { + list := repositories.TopicRepository.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 + cb(list) + } +} diff --git a/server/services/tweet_service.go b/server/services/tweet_service.go new file mode 100644 index 0000000000000000000000000000000000000000..b38d0416b2772191f7ccd3aba284439902ae9ca4 --- /dev/null +++ b/server/services/tweet_service.go @@ -0,0 +1,93 @@ +package services + +import ( + "github.com/mlogclub/simple" + + "bbs-go/model" + "bbs-go/repositories" +) + +var TweetService = newTweetService() + +func newTweetService() *tweetService { + return &tweetService{} +} + +type tweetService struct { +} + +func (s *tweetService) Get(id int64) *model.Tweet { + return repositories.TweetRepository.Get(simple.DB(), id) +} + +func (s *tweetService) Take(where ...interface{}) *model.Tweet { + return repositories.TweetRepository.Take(simple.DB(), where...) +} + +func (s *tweetService) Find(cnd *simple.SqlCnd) []model.Tweet { + return repositories.TweetRepository.Find(simple.DB(), cnd) +} + +func (s *tweetService) FindOne(cnd *simple.SqlCnd) *model.Tweet { + return repositories.TweetRepository.FindOne(simple.DB(), cnd) +} + +func (s *tweetService) FindPageByParams(params *simple.QueryParams) (list []model.Tweet, paging *simple.Paging) { + return repositories.TweetRepository.FindPageByParams(simple.DB(), params) +} + +func (s *tweetService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Tweet, paging *simple.Paging) { + return repositories.TweetRepository.FindPageByCnd(simple.DB(), cnd) +} + +func (s *tweetService) Count(cnd *simple.SqlCnd) int { + return repositories.TweetRepository.Count(simple.DB(), cnd) +} + +func (s *tweetService) GetTweets(cursor int64) (tweets []model.Tweet, nextCursor int64) { + cnd := simple.NewSqlCnd().Eq("status", model.StatusOk).Desc("id").Limit(50) + if cursor > 0 { + cnd.Lt("id", cursor) + } + tweets = repositories.TweetRepository.Find(simple.DB(), cnd) + if len(tweets) > 0 { + nextCursor = tweets[len(tweets)-1].Id + } else { + nextCursor = cursor + } + return +} + +func (s *tweetService) Publish(userId int64, content, imageList string) (*model.Tweet, error) { + tweet := &model.Tweet{ + UserId: userId, + Content: content, + ImageList: imageList, + Status: model.StatusOk, + CreateTime: simple.NowTimestamp(), + } + if err := repositories.TweetRepository.Create(simple.DB(), tweet); err != nil { + return nil, err + } + return tweet, nil +} + +func (s *tweetService) Create(t *model.Tweet) error { + return repositories.TweetRepository.Create(simple.DB(), t) +} + +func (s *tweetService) Update(t *model.Tweet) error { + return repositories.TweetRepository.Update(simple.DB(), t) +} + +func (s *tweetService) Updates(id int64, columns map[string]interface{}) error { + return repositories.TweetRepository.Updates(simple.DB(), id, columns) +} + +func (s *tweetService) UpdateColumn(id int64, name string, value interface{}) error { + return repositories.TweetRepository.UpdateColumn(simple.DB(), id, name, value) +} + +func (s *tweetService) Delete(id int64) { + repositories.TweetRepository.Delete(simple.DB(), id) +} diff --git a/server/services/user_like_service.go b/server/services/user_like_service.go new file mode 100644 index 0000000000000000000000000000000000000000..4865706c88ed3de7c824e087af8da511bc4cbe77 --- /dev/null +++ b/server/services/user_like_service.go @@ -0,0 +1,127 @@ +package services + +import ( + "errors" + + "github.com/jinzhu/gorm" + "github.com/mlogclub/simple" + + "bbs-go/model" + "bbs-go/repositories" +) + +var UserLikeService = newUserLikeService() + +func newUserLikeService() *userLikeService { + return &userLikeService{} +} + +type userLikeService struct { +} + +func (s *userLikeService) Get(id int64) *model.UserLike { + return repositories.UserLikeRepository.Get(simple.DB(), id) +} + +func (s *userLikeService) Take(where ...interface{}) *model.UserLike { + return repositories.UserLikeRepository.Take(simple.DB(), where...) +} + +func (s *userLikeService) Find(cnd *simple.SqlCnd) []model.UserLike { + return repositories.UserLikeRepository.Find(simple.DB(), cnd) +} + +func (s *userLikeService) FindOne(cnd *simple.SqlCnd) *model.UserLike { + return repositories.UserLikeRepository.FindOne(simple.DB(), cnd) +} + +func (s *userLikeService) FindPageByParams(params *simple.QueryParams) (list []model.UserLike, paging *simple.Paging) { + return repositories.UserLikeRepository.FindPageByParams(simple.DB(), params) +} + +func (s *userLikeService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.UserLike, paging *simple.Paging) { + return repositories.UserLikeRepository.FindPageByCnd(simple.DB(), cnd) +} + +func (s *userLikeService) Create(t *model.UserLike) error { + return repositories.UserLikeRepository.Create(simple.DB(), t) +} + +func (s *userLikeService) Update(t *model.UserLike) error { + return repositories.UserLikeRepository.Update(simple.DB(), t) +} + +func (s *userLikeService) Updates(id int64, columns map[string]interface{}) error { + return repositories.UserLikeRepository.Updates(simple.DB(), id, columns) +} + +func (s *userLikeService) UpdateColumn(id int64, name string, value interface{}) error { + return repositories.UserLikeRepository.UpdateColumn(simple.DB(), id, name, value) +} + +func (s *userLikeService) Delete(id int64) { + repositories.UserLikeRepository.Delete(simple.DB(), id) +} + +// 统计数量 +func (s *userLikeService) Count(entityType string, entityId int64) int64 { + var count int64 = 0 + simple.DB().Model(&model.UserLike{}).Where("entity_type = ?", entityType).Where("entity_id = ?", entityId).Count(&count) + return count +} + +// 最近点赞 +func (s *userLikeService) Recent(entityType string, entityId int64, count int) []model.UserLike { + return s.Find(simple.NewSqlCnd().Eq("entity_type", entityType).Eq("entity_id", entityId).Desc("id").Limit(count)) +} + +// Exists 是否点赞 +func (s *userLikeService) Exists(userId int64, entityType string, entityId int64) bool { + return repositories.UserLikeRepository.FindOne(simple.DB(), simple.NewSqlCnd().Eq("user_id", userId). + Eq("entity_type", entityType).Eq("entity_id", entityId)) != nil +} + +// 话题点赞 +func (s *userLikeService) TopicLike(userId int64, topicId int64) error { + topic := repositories.TopicRepository.Get(simple.DB(), topicId) + if topic == nil || topic.Status != model.StatusOk { + return errors.New("话题不存在") + } + + return simple.Tx(simple.DB(), func(tx *gorm.DB) error { + if err := s.like(tx, userId, model.EntityTypeTopic, topicId); err != nil { + return err + } + // 更新点赞数 + return tx.Exec("update t_topic set like_count = like_count + 1 where id = ?", topicId).Error + }) +} + +// 动态点赞 +func (s *userLikeService) TweetLike(userId int64, tweetId int64) error { + tweet := repositories.TweetRepository.Get(simple.DB(), tweetId) + if tweet == nil || tweet.Status != model.StatusOk { + return errors.New("动态不存在") + } + return simple.Tx(simple.DB(), func(tx *gorm.DB) error { + if err := s.like(tx, userId, model.EntityTypeTweet, tweetId); err != nil { + return err + } + // 更新点赞数 + return tx.Exec("update t_tweet set like_count = like_count + 1 where id = ?", tweetId).Error + }) +} + +func (s *userLikeService) like(db *gorm.DB, userId int64, entityType string, entityId int64) error { + // 判断是否已经点赞了 + if s.Exists(userId, entityType, entityId) { + return errors.New("已点赞") + } + // 点赞 + return repositories.UserLikeRepository.Create(db, &model.UserLike{ + UserId: userId, + EntityType: entityType, + EntityId: entityId, + CreateTime: simple.NowTimestamp(), + }) +} diff --git a/site/assets/styles/common.scss b/site/assets/styles/common.scss index c3ae666f7fcb0d1c29fe7814fb6a3e856000a344..367678c59cbb46bb4199b2a97783029404551d48 100644 --- a/site/assets/styles/common.scss +++ b/site/assets/styles/common.scss @@ -15,6 +15,12 @@ body { padding: 1rem 1rem; } +.ellipsis { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + @media screen and (max-width: 768px) { .main { padding: 1rem 0.5rem 0 !important; diff --git a/site/assets/styles/main.scss b/site/assets/styles/main.scss index f12d927108db2e1ef77e389c61507c1efa9d1ec7..aa52dc9298e93d3099d7b0534659bc9613555dde 100644 --- a/site/assets/styles/main.scss +++ b/site/assets/styles/main.scss @@ -1,5 +1,6 @@ -@import "./common"; -@import "./article"; -@import "./topic"; -@import "./link"; -@import "./ad"; +@import "./common"; +@import "./article"; +@import "./topic"; +@import "./tweets"; +@import "./link"; +@import "./ad"; diff --git a/site/assets/styles/topic.scss b/site/assets/styles/topic.scss index 039d59c06bd72c4d1d1631b449faf2e7806f608d..42f7a61c0fd2cd3cba008579c138ca8b9794c56d 100644 --- a/site/assets/styles/topic.scss +++ b/site/assets/styles/topic.scss @@ -1,302 +1,243 @@ -.topic-wrap { - .topic-header { - display: flex; - - @media screen and (max-width: 1024px) { - .topic-header-right { - display: none; - } - } - - .topic-header-left { - width: 50px; - height: 50px; - } - - .topic-header-center { - width: 100%; - margin-left: 10px; - - .topic-title, - .topic-title a { - color: #555; - font-size: 14px; - font-weight: bold; - overflow: hidden; - } - - .topic-meta { - position: relative; - font-size: 12px; - line-height: 24px; - color: #70727c; - margin-top: 5px; - - span.meta-item { - font-size: 12px; - - &:not(:last-child) { - margin-right: 8px; - } - - &.act { - // float: right; - margin-right: 5px; - - a { - font-size: 12px; - // color: #3273dc; - color: #7a7a7a; - font-weight: 700; - - &:hover { - text-decoration: underline; - } - - i { - font-size: 12px; - color: #000; - } - } - } - - .tag { - height: auto !important; - - &:not(:last-child) { - margin-right: 3px; - } - } - - .node { - font-weight: bold; - color: #3273dcde; - // color: #e7672e; - } - } - - a { - color: #778087; - } - } - } - - .topic-header-right { - min-width: 100px; - max-width: 100px; - text-align: right; - padding-right: 8px; - - .like { - font-size: 12px; - - .like-btn { - transition: all 0.5s; - cursor: pointer; - - background-color: rgba(126, 107, 1, 0.08); - color: #e7672e; - // padding: 0; - // display: inline-block; - // height: 20px; - // width: 20px; - // line-height: 20px; - // border-radius: 10px; - // text-align: center; - padding: 4px; - border-radius: 50%; - - &:hover, - &.liked { - color: #fff; - background-color: #e7672e; - } - } - - .like-count { - font-weight: bold; - color: #e7672e; - - &::before { - content: ' x '; - } - } - } - - .count { - font-size: 12px; - color: #fff; - - background: #aab0c6; - padding: 2px 10px; - border-radius: 6px; - font-weight: 700; - } - } - } - - .topic-content { - font-size: 15px; - padding-top: 0 !important; - } - - .topic-actions { - display: flex; - height: 42px; - width: max-content; - padding: 5px 10px 5px 5px; - - background: #fff; - border: 1px solid #dae0e4; - border-radius: 30px; - vertical-align: middle; - - .avatar { - margin-left: 5px; - border: solid 1px #e8ecee; - } - - .split { - display: inline-block; - margin: auto 7px auto 10px; - height: 16px; - width: 1px; - opacity: 0.4; - background: #dae0e4; - vertical-align: middle; - } - - .action { - margin-left: 5px; - height: 30px; - width: 30px; - line-height: 30px; - text-align: center; - color: #e7672e; - background-color: rgba(126, 107, 1, 0.08); - border-radius: 50%; - transition: all 0.5s; - cursor: pointer; - - i { - font-size: 16px; - } - - &:hover, - &.active { - color: #fff; - background-color: #e7672e; - } - } - } -} - -.topic-list { - margin: 0 0 10px 0 !important; - - li.topic-item { - padding: 8px 0 8px 8px; - position: relative; - overflow: hidden; - transition: background 0.5s; - - //&:hover { - // background: #f3f6f9; - //} - - &:not(:last-child) { - border-bottom: 1px solid #f2f2f2; - } - } - - .topic-header { - .topic-header-center { - // 控制内容只显示一行,多余的折叠,暂时不需要先注释掉 - //.topic-title, - //.topic-title a { - // word-break: break-all; - // -webkit-line-clamp: 1; - // text-overflow: ellipsis; - // -webkit-box-orient: vertical; - // display: -webkit-box; - //} - } - } - - .topic-images { - margin-top: 8px; - margin-left: 60px; - - $topic-image-size: 120px; // 侧边栏之间的缝隙 - li { - cursor: pointer; - border: 1px dashed #ddd; - text-align: center; - - display: inline-block; - vertical-align: middle; - width: $topic-image-size; - height: $topic-image-size; - line-height: $topic-image-size; - margin: 0 8px 8px 0; - background-color: #e8e8e8; - background-size: 32px 32px; - background-position: 50%; - background-repeat: no-repeat; - overflow: hidden; - position: relative; - - .topic-image-item { - //min-width: $topic-image-width; - //min-height: $topic-image-width; - //width: $topic-image-width; - //height: $topic-image-width; - //background-repeat: no-repeat; - //background-size: cover; - //background-position: center; - - //display: inline-block; - //max-width: 100%; - //height: auto; - //vertical-align: middle; - //transition: all .5s ease-out .1s; - - display: block; - width: $topic-image-size; - height: $topic-image-size; - overflow: hidden; - transform-style: preserve-3d; - - & > img { - width: 100%; - height: 100%; - object-fit: cover; - transition: all .5s ease-out .1s; - - &:hover { - transform: matrix(1.04,0,0,1.04,0,0); - backface-visibility: hidden; - } - } - } - } - } -} - -.topic-detail { - margin-bottom: 20px; - - .content { - padding-top: 10px; - font-size: 15px; - color: #000; - white-space: normal; - word-break: break-all; - word-wrap: break-word; - } - - .topic-header { - padding: 10px; - border-bottom: 1px solid #eee; - } -} +.topic-wrap { + .topic-header { + display: flex; + + @media screen and (max-width: 1024px) { + .topic-header-right { + display: none; + } + } + + .topic-header-left { + width: 50px; + height: 50px; + } + + .topic-header-center { + width: 100%; + margin-left: 10px; + + .topic-title, + .topic-title a { + color: #555; + font-size: 14px; + font-weight: bold; + overflow: hidden; + } + + .topic-meta { + position: relative; + font-size: 12px; + line-height: 24px; + color: #70727c; + margin-top: 5px; + + span.meta-item { + font-size: 12px; + + &:not(:last-child) { + margin-right: 8px; + } + + &.act { + // float: right; + margin-right: 5px; + + a { + font-size: 12px; + // color: #3273dc; + color: #7a7a7a; + font-weight: 700; + + &:hover { + text-decoration: underline; + } + + i { + font-size: 12px; + color: #000; + } + } + } + + .tag { + height: auto !important; + + &:not(:last-child) { + margin-right: 3px; + } + } + + .node { + font-weight: bold; + color: #3273dcde; + // color: #e7672e; + } + } + + a { + color: #778087; + } + } + } + + .topic-header-right { + min-width: 100px; + max-width: 100px; + text-align: right; + padding-right: 8px; + + .like { + font-size: 12px; + + .like-btn { + transition: all 0.5s; + cursor: pointer; + + background-color: rgba(126, 107, 1, 0.08); + color: #e7672e; + // padding: 0; + // display: inline-block; + // height: 20px; + // width: 20px; + // line-height: 20px; + // border-radius: 10px; + // text-align: center; + padding: 4px; + border-radius: 50%; + + &:hover, + &.liked { + color: #fff; + background-color: #e7672e; + } + } + + .like-count { + font-weight: bold; + color: #e7672e; + + &::before { + content: ' x '; + } + } + } + + .count { + font-size: 12px; + color: #fff; + + background: #aab0c6; + padding: 2px 10px; + border-radius: 6px; + font-weight: 700; + } + } + } + + .topic-content { + font-size: 15px; + padding-top: 0 !important; + } + + .topic-actions { + display: flex; + height: 42px; + width: max-content; + padding: 5px 10px 5px 5px; + + background: #fff; + border: 1px solid #dae0e4; + border-radius: 30px; + vertical-align: middle; + + .avatar { + margin-left: 5px; + border: solid 1px #e8ecee; + } + + .split { + display: inline-block; + margin: auto 7px auto 10px; + height: 16px; + width: 1px; + opacity: 0.4; + background: #dae0e4; + vertical-align: middle; + } + + .action { + margin-left: 5px; + height: 30px; + width: 30px; + line-height: 30px; + text-align: center; + color: #e7672e; + background-color: rgba(126, 107, 1, 0.08); + border-radius: 50%; + transition: all 0.5s; + cursor: pointer; + + i { + font-size: 16px; + } + + &:hover, + &.active { + color: #fff; + background-color: #e7672e; + } + } + } +} + +.topic-list { + margin: 0 0 10px 0 !important; + + li.topic-item { + padding: 8px 0 8px 8px; + position: relative; + overflow: hidden; + transition: background 0.5s; + + //&:hover { + // background: #f3f6f9; + //} + + &:not(:last-child) { + border-bottom: 1px solid #f2f2f2; + } + } + + .topic-header { + .topic-header-center { + // 控制内容只显示一行,多余的折叠,暂时不需要先注释掉 + //.topic-title, + //.topic-title a { + // word-break: break-all; + // -webkit-line-clamp: 1; + // text-overflow: ellipsis; + // -webkit-box-orient: vertical; + // display: -webkit-box; + //} + } + } +} + +.topic-detail { + margin-bottom: 20px; + + .content { + padding-top: 10px; + font-size: 15px; + color: #000; + white-space: normal; + word-break: break-all; + word-wrap: break-word; + } + + .topic-header { + padding: 10px; + border-bottom: 1px solid #eee; + } +} diff --git a/site/assets/styles/tweets.scss b/site/assets/styles/tweets.scss new file mode 100644 index 0000000000000000000000000000000000000000..5e38c1581193c5e4d8ff7b349f4612121f2c2438 --- /dev/null +++ b/site/assets/styles/tweets.scss @@ -0,0 +1,160 @@ +.tweets { + +} + +.tweet { + margin-bottom: 8px; + background: #fff; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + + border-radius: 0.2rem; + + .pin-header-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 2rem 0 20px; + + .account-group { + align-items: center; + display: flex; + + .pin-header-content { + margin-left: 10px; + + .nickname { + font-size: 16px; + font-weight: 500; + color: #2e3135; + } + + .meta-box { + display: flex; + align-items: center; + margin: 0.3rem 0 0; + font-size: 13px; + color: #8a9aa9; + cursor: default; + + .position { + max-width: 24rem; + } + .dot { + margin: 0 0.5em; + } + .time { + color: #8a9aa9; + } + } + } + } + } + + .pin-content-row { + position: relative; + margin: 5px 4rem 5px 75px; + + .content-box { + font-size: 15px; + line-height: 1.6; + white-space: pre-wrap; + color: #17181a; + } + } + + .pin-image-row { + margin: 5px 4rem 5px 75px; + + li { + cursor: pointer; + border: 1px dashed #ddd; + text-align: center; + + // 图片尺寸 + $image-size: 120px; + + display: inline-block; + vertical-align: middle; + width: $image-size; + height: $image-size; + line-height: $image-size; + margin: 0 8px 8px 0; + background-color: #e8e8e8; + background-size: 32px 32px; + background-position: 50%; + background-repeat: no-repeat; + overflow: hidden; + position: relative; + + .image-item { + display: block; + width: $image-size; + height: $image-size; + overflow: hidden; + transform-style: preserve-3d; + + & > img { + width: 100%; + height: 100%; + object-fit: cover; + transition: all 0.5s ease-out 0.1s; + + &:hover { + transform: matrix(1.04, 0, 0, 1.04, 0, 0); + backface-visibility: hidden; + } + } + } + } + } + + .pin-action-row { + .action-box { + display: flex; + position: relative; + margin-top: 1.333rem; + height: 34px; + border-top: 1px solid #ebebeb; + + .action { + flex: 1 1 33.333%; + display: flex; + align-items: center; + justify-content: center; + position: relative; + height: 100%; + cursor: pointer; + user-select: none; + + &:not(:last-child):after { + content: ''; + position: absolute; + top: 50%; + right: 0; + margin-top: -1rem; + width: 1px; + height: 2rem; + background-color: #ebebeb; + } + + .action-title-box { + display: flex; + align-items: center; + justify-content: center; + color: #8a93a0; + + .iconfont { + color: #000; + } + + .action-title { + margin-left: 0.3em; + font-size: 13px; + font-weight: 500; + color: #8a93a0; + } + } + } + } + } +} diff --git a/site/components/Comment.vue b/site/components/Comment.vue index 790a83f648781f2b4183b6c6920c6cd7c9e60b7d..fe8b3b97fd0b9d9ddca1fa725622f85de3242d0d 100644 --- a/site/components/Comment.vue +++ b/site/components/Comment.vue @@ -1,7 +1,7 @@