rss 不输出树洞、水区和垃圾场内容

By 张怀义 at 2020-04-02

https://github.com/Terminus2049/2049bbs/issues/7

修改代码路径 https://github.com/Terminus2049/2049bbs/blob/master/controller/feed.go

package controller



import (

	"net/http"

	"strconv"

	"text/template"



	"github.com/terminus2049/2049bbs/model"

	"goji.io/pat"

)



func (h *BaseHandler) FeedHandler(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")



	scf := h.App.Cf.Site



	var feed = `<?xml version="1.0" encoding="utf-8"?>

<feed xmlns="http://www.w3.org/2005/Atom">

	<title>` + scf.Name + `</title>

	<link rel="self" type="application/atom+xml" href="` + scf.MainDomain + `/feed"/>

	<link rel="hub" href="https://pubsubhubbub.appspot.com"/>

	<updated>{ {.Update} }</updated>

	<id>` + scf.MainDomain + `/feed</id>

	<author>

		<name>` + scf.Name + `</name>

	</author>

	{ {range $_, $item := .Items} }

	<entry>

		<title>{ {$item.Title} }</title>

		<id>` + scf.MainDomain + `/t/{ {$item.Id} }</id>

		<link rel="alternate" type="text/html" href="` + scf.MainDomain + `/t/{ {$item.Id} }" />

		<published>{ {$item.AddTimeFmt} }</published>

		<updated>{ {$item.EditTimeFmt} }</updated>

		<content type="text/plain">

		  { {$item.Cname} } - { {$item.Name} } - { {$item.Des} }

		</content>

    </entry>

	{ {end} }

</feed>

`



	db := h.App.Db



	items := model.ArticleFeedList(db, 20, h.App.Cf.Site.TimeZone)



	var upDate string

	if len(items) > 0 {

		upDate = items[0].AddTimeFmt

	}



	t := template.Must(template.New("feed").Parse(feed))

	t.Execute(w, struct {

		Update string

		Items  []model.ArticleFeedListItem

	}{

		Update: upDate,

		Items:  items,

	})

}



func (h *BaseHandler) FeedCategoryHandler(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")



	scf := h.App.Cf.Site



	var feed = `<?xml version="1.0" encoding="utf-8"?>

<feed xmlns="http://www.w3.org/2005/Atom">

	<title>` + scf.Name + `</title>

	<link rel="self" type="application/atom+xml" href="` + scf.MainDomain + `/feed"/>

	<link rel="hub" href="https://pubsubhubbub.appspot.com"/>

	<updated>{ {.Update} }</updated>

	<id>` + scf.MainDomain + `/feed</id>

	<author>

		<name>` + scf.Name + `</name>

	</author>

	{ {range $_, $item := .Items} }

	<entry>

		<title>{ {$item.Title} }</title>

		<id>` + scf.MainDomain + `/t/{ {$item.Id} }</id>

		<link rel="alternate" type="text/html" href="` + scf.MainDomain + `/t/{ {$item.Id} }" />

		<published>{ {$item.AddTimeFmt} }</published>

		<updated>{ {$item.EditTimeFmt} }</updated>

		<content type="text/plain">

		  { {$item.Cname} } - { {$item.Name} } - { {$item.Des} }

		</content>

    </entry>

	{ {end} }

</feed>

`



	cid := pat.Param(r, "cid")

	_, err := strconv.Atoi(cid)

	if err != nil {

		w.Write([]byte(`{"retcode":400,"retmsg":"cid type err"}`))

		return

	}

			if cid == 19 || cid == 20 {

		w.Write([]byte(`{"retcode":400,"retmsg":"cid is illeage"}`))

		return

					}

	db := h.App.Db



	items := model.ArticleFeedCategoryList(db, cid, 20, h.App.Cf.Site.TimeZone)



	var upDate string

	if len(items) > 0 {

		upDate = items[0].AddTimeFmt

	}



	t := template.Must(template.New("feed").Parse(feed))

	t.Execute(w, struct {

		Update string

		Items  []model.ArticleFeedListItem

	}{

		Update: upDate,

		Items:  items,

	})

}

水区, rss, 垃圾场, 树洞, 输出


直接在 ArticleFeedList 里处理会更好。可以看下提交记录

https://github.com/Terminus2049/2049bbs/commits/master

我会把issue完善下,免得你做重复工作

翻车了 at 2020-04-02
1

@翻车了 #1 我发不了帖子了!!!

我觉得,feed那段代码,实在不敢恭维!

feed下,走的是 ArticleFeedList

feed节点下,走的是 ArticleFeedCategoryList

我也想在ArticleFeedList里写,但是我觉得很多代码完全是重复而且冗余

如果我来改,我是从route层就已经改掉了,后面的代码会精简很多

张怀义 at 2020-04-02
2

@张怀义 #2 你为啥发不了帖子?

的确很冗余,当时只是为了快速解决问题嘛,在原有全部帖子输出的基础上再搞一个。但现在木已成舟,说啥都晚了。

小二 at 2020-04-02
3

树洞、水区和垃圾场at无效

https://github.com/Terminus2049/2049bbs/issues/8

修改代码 https://github.com/Terminus2049/2049bbs/blob/master/controller/article.go

package controller

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"html/template"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/ego008/youdb"
	"github.com/rs/xid"
	"github.com/terminus2049/2049bbs/model"
	"github.com/terminus2049/2049bbs/util"
	"goji.io/pat"
)

func (h *BaseHandler) ArticleAdd(w http.ResponseWriter, r *http.Request) {
	cid := pat.Param(r, "cid")
	_, err := strconv.Atoi(cid)
	if err != nil {
		w.Write([]byte(`{"retcode":400,"retmsg":"cid type err"}`))
		return
	}

	currentUser, _ := h.CurrentUser(w, r)
	if currentUser.Id == 0 {
		w.Write([]byte(`{"retcode":401,"retmsg":"authored err"}`))
		return
	}
	if currentUser.Flag < 5 {
		var msg string
		if currentUser.Flag == 1 {
			msg = "注册验证中,等待管理员通过"
		} else {
			msg = "您已被禁用"
		}
		w.Write([]byte(`{"retcode":401,"retmsg":"` + msg + `"}`))
		return
	}

	db := h.App.Db

	cobj, err := model.CategoryGetById(db, cid)
	if err != nil {
		w.Write([]byte(`{"retcode":404,"retmsg":"` + err.Error() + `"}`))
		return
	}

	type pageData struct {
		PageData
		Cobj      model.Category
		MainNodes []model.CategoryMini
	}

	tpl := h.CurrentTpl(r)
	evn := &pageData{}
	evn.SiteCf = h.App.Cf.Site
	evn.Title = "发表文章"
	evn.IsMobile = tpl == "mobile"
	evn.CurrentUser = currentUser
	evn.ShowSideAd = true
	evn.PageName = "article_add"

	evn.Cobj = cobj
	evn.MainNodes = model.CategoryGetMain(db, cobj)

	h.SetCookie(w, "token", xid.New().String(), 1)
	h.Render(w, tpl, evn, "layout.html", "articlecreate.html")
}

func (h *BaseHandler) ArticleAddPost(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	token := h.GetCookie(r, "token")
	if len(token) == 0 {
		w.Write([]byte(`{"retcode":400,"retmsg":"token cookie missed"}`))
		return
	}

	currentUser, _ := h.CurrentUser(w, r)
	if currentUser.Id == 0 {
		w.Write([]byte(`{"retcode":401,"retmsg":"authored require"}`))
		return
	}
	if currentUser.Flag < 5 {
		w.Write([]byte(`{"retcode":403,"retmsg":"user flag err"}`))
		return
	}

	type recForm struct {
		Act     string `json:"act"`
		Cid     uint64 `json:"cid"`
		Title   string `json:"title"`
		Content string `json:"content"`
	}

	decoder := json.NewDecoder(r.Body)
	var rec recForm
	err := decoder.Decode(&rec)
	if err != nil {
		w.Write([]byte(`{"retcode":400,"retmsg":"json Decode err:` + err.Error() + `"}`))
		return
	}
	defer r.Body.Close()

	rec.Title = strings.TrimSpace(rec.Title)
	rec.Content = strings.TrimSpace(rec.Content)

	db := h.App.Db
	if rec.Act == "preview" {
		tmp := struct {
			normalRsp
			Html string `json:"html"`
		}{
			normalRsp{200, ""},
			util.ContentFmt(db, rec.Content),
		}
		json.NewEncoder(w).Encode(tmp)
		return
	}

	// check title
	hash := md5.Sum([]byte(rec.Title))
	titleMd5 := hex.EncodeToString(hash[:])
	if db.Hget("title_md5", []byte(titleMd5)).State == "ok" {
		w.Write([]byte(`{"retcode":403,"retmsg":"title has existed"}`))
		return
	}

	now := uint64(time.Now().UTC().Unix())
	scf := h.App.Cf.Site

	if currentUser.Flag < 99 && currentUser.LastPostTime > 0 {
		if (now - currentUser.LastPostTime) < uint64(scf.PostInterval) {
			w.Write([]byte(`{"retcode":403,"retmsg":"PostInterval limited"}`))
			return
		}
	}

	if rec.Cid == 0 || len(rec.Title) == 0 {
		w.Write([]byte(`{"retcode":400,"retmsg":"missed args"}`))
		return
	}
	if len(rec.Title) > scf.TitleMaxLen {
		w.Write([]byte(`{"retcode":403,"retmsg":"TitleMaxLen limited"}`))
		return
	}
	if len(rec.Content) > scf.ContentMaxLen {
		w.Write([]byte(`{"retcode":403,"retmsg":"ContentMaxLen limited"}`))
		return
	}

	newAid, _ := db.HnextSequence("article")
	aobj := model.Article{
		Id:       newAid,
		Uid:      currentUser.Id,
		Cid:      rec.Cid,
		Title:    rec.Title,
		Content:  rec.Content,
		AddTime:  now,
		EditTime: now,
		ClientIp: "",
	}

	jb, _ := json.Marshal(aobj)
	aidB := youdb.I2b(newAid)
	db.Hset("article", aidB, jb)
	// 总文章列表
	ignorenodes := scf.NotHomeNodeIds
	if len(ignorenodes) > 0 {
		for _, node := range strings.Split(ignorenodes, ",") {
			node, err := strconv.Atoi(node)
			if err == nil && aobj.Cid != uint64(node) {
				db.Zset("article_timeline", aidB, aobj.EditTime)
			}
		}
	}

	// 分类文章列表
	db.Zset("category_article_timeline:"+strconv.FormatUint(aobj.Cid, 10), aidB, aobj.EditTime)
	// 用户文章列表
	db.Hset("user_article_timeline:"+strconv.FormatUint(aobj.Uid, 10), youdb.I2b(aobj.Id), []byte(""))
	// 分类下文章数
	db.Zincr("category_article_num", youdb.I2b(aobj.Cid), 1)

	currentUser.LastPostTime = now
	currentUser.Articles++

	jb, _ = json.Marshal(currentUser)
	db.Hset("user", youdb.I2b(aobj.Uid), jb)

	// title md5
	db.Hset("title_md5", []byte(titleMd5), aidB)

	// send task work
	// get tag from title
	if scf.AutoGetTag {
		db.Hset("task_to_get_tag", aidB, []byte(rec.Title))
	}

	// @ somebody in content
if rec.Cid != 19 && rec.Cid != 20 {
	sbs := util.GetMention(rec.Content,
		[]string{currentUser.Name, strconv.FormatUint(currentUser.Id, 10)})

	aid := strconv.FormatUint(newAid, 10)
	for _, sb := range sbs {
		var sbObj model.User
		sbu, err := strconv.ParseUint(sb, 10, 64)
		if err != nil {
			// @ user name
			sbObj, err = model.UserGetByName(db, strings.ToLower(sb))
		} else {
			// @ user id
			sbObj, err = model.UserGetById(db, sbu)
		}

		if err == nil {
			if len(sbObj.Notice) > 0 {
				aidList := util.SliceUniqStr(strings.Split(aid+","+sbObj.Notice, ","))
				if len(aidList) > 100 {
					aidList = aidList[:100]
				}
				sbObj.Notice = strings.Join(aidList, ",")
				sbObj.NoticeNum = len(aidList)
			} else {
				sbObj.Notice = aid
				sbObj.NoticeNum = 1
			}
			jb, _ := json.Marshal(sbObj)
			db.Hset("user", youdb.I2b(sbObj.Id), jb)
		}
}
	}

	h.DelCookie(w, "token")

	tmp := struct {
		normalRsp
		Aid uint64 `json:"aid"`
	}{
		normalRsp{200, "ok"},
		aobj.Id,
	}
	json.NewEncoder(w).Encode(tmp)
}

func (h *BaseHandler) ArticleHomeList(w http.ResponseWriter, r *http.Request) {
	btn, key, score := r.FormValue("btn"), r.FormValue("key"), r.FormValue("score")
	if len(key) > 0 {
		_, err := strconv.ParseUint(key, 10, 64)
		if err != nil {
			w.Write([]byte(`{"retcode":400,"retmsg":"key type err"}`))
			return
		}
	}
	if len(score) > 0 {
		_, err := strconv.ParseUint(score, 10, 64)
		if err != nil {
			w.Write([]byte(`{"retcode":400,"retmsg":"score type err"}`))
			return
		}
	}

	cmd := "zrscan"
	if btn == "prev" {
		cmd = "zscan"
	}

	db := h.App.Db
	scf := h.App.Cf.Site
	pageInfo := model.ArticleList(db, cmd, "article_timeline", key, score, scf.HomeShowNum*2, scf.TimeZone, scf.NotHomeNodeIds)
	currentUser, _ := h.CurrentUser(w, r)

	// 首页第二栏节点
	if currentUser.Theme != "2" {
		cid := scf.HomeNode
		cobj, err := model.CategoryGetById(db, cid)
		if err == nil {
			cobj.Articles = db.Zget("category_article_num", youdb.I2b(cobj.Id)).Uint64()
			pageInfo2 := model.ArticleList(db, cmd, "category_article_timeline:"+cid, key, score, scf.HomeShowNum, scf.TimeZone, "")
			pageInfo.Items = append(pageInfo.Items, pageInfo2.Items...)
		}
	}

	type siteInfo struct {
		Days     int
		UserNum  uint64
		NodeNum  uint64
		TagNum   uint64
		PostNum  uint64
		ReplyNum uint64
	}

	type pageData struct {
		PageData
		SiteInfo     siteInfo
		PageInfo     model.ArticlePageInfo
		Links        []model.Link
		Announcement string
		Proverb      template.HTML
	}

	si := siteInfo{}
	rs := db.Hget("count", []byte("site_create_time"))
	var siteCreateTime uint64
	if rs.State == "ok" {
		siteCreateTime = rs.Data[0].Uint64()
	} else {
		rs2 := db.Hscan("user", []byte(""), 1)
		if rs2.State == "ok" {
			user := model.User{}
			json.Unmarshal(rs2.Data[1], &user)
			siteCreateTime = user.RegTime
		} else {
			siteCreateTime = uint64(time.Now().UTC().Unix())
		}
		db.Hset("count", []byte("site_create_time"), youdb.I2b(siteCreateTime))
	}
	then := time.Unix(int64(siteCreateTime), 0)
	diff := time.Now().UTC().Sub(then)
	si.Days = int(diff.Hours()/24) + 1
	si.UserNum = db.Hsequence("user")
	si.NodeNum = db.Hsequence("category")
	si.TagNum = db.Hsequence("tag")
	si.PostNum = db.Hsequence("article")
	si.ReplyNum = db.Hget("count", []byte("comment_num")).Uint64()

	// fix
	if si.NodeNum == 0 {
		newCid, err2 := db.HnextSequence("category")
		if err2 == nil {
			cobj := model.Category{
				Id:    newCid,
				Name:  "默认分类",
				About: "默认第一个分类",
			}
			jb, _ := json.Marshal(cobj)
			db.Hset("category", youdb.I2b(cobj.Id), jb)
			si.NodeNum = 1
		}
		// link
		model.LinkSet(db, model.Link{
			Name:  "youBBS",
			Url:   "https://www.youbbs.org",
			Score: 100,
		})
	}

	tpl := h.CurrentTpl(r)
	evn := &pageData{}
	evn.SiteCf = scf
	evn.Title = scf.Name
	evn.Keywords = evn.Title
	evn.Description = scf.Desc
	evn.IsMobile = tpl == "mobile"
	evn.CurrentUser = currentUser
	evn.ShowSideAd = true
	evn.PageName = "home"
	evn.HotNodes = model.CategoryHot(db, scf.CategoryShowNum, scf.MustLoginNodeIds)

	if currentUser.IgnoreNode != "" {
		for _, node := range strings.Split(currentUser.IgnoreNode, ",") {
			node, err := strconv.ParseUint(node, 10, 64)

			if err != nil {
				w.Write([]byte(`{"retcode":400,"retmsg":"忽略节点id应为整数,请在设置中检查。"}`))
				return
			}

			for i := 0; i < len(pageInfo.Items); i++ {
				if pageInfo.Items[i].Cid == node {
					pageInfo.Items = append(pageInfo.Items[:i], pageInfo.Items[i+1:]...)
					i--
				}
			}
		}
	}

	if currentUser.IgnoreUser != "" {
		for _, uid := range strings.Split(currentUser.IgnoreUser, ",") {
			uid, err := strconv.ParseUint(uid, 10, 64)

			if err != nil {
				w.Write([]byte(`{"retcode":400,"retmsg":"忽略用户id应为整数,请在设置中检查。"}`))
				return
			}

			for i := 0; i < len(pageInfo.Items); i++ {
				if pageInfo.Items[i].Uid == uid {
					pageInfo.Items = append(pageInfo.Items[:i], pageInfo.Items[i+1:]...)
					i--
				}
			}
		}
	}

	// 类似 solidot 格言
	if !evn.IsMobile {
		proverbs := model.CommentList(db, "hscan", "article_comment:"+scf.ProverbId, "", 500, scf.TimeZone, false)
		// 剔除折叠的回复
		for i := 0; i < len(proverbs.Items); i++ {
			if proverbs.Items[i].Fold {
				proverbs.Items = append(proverbs.Items[:i], proverbs.Items[i+1:]...)
				i--
			}
		}

		rand.Seed(time.Now().Unix())
		if len(proverbs.Items) > 0 {
			evn.Proverb = proverbs.Items[rand.Intn(len(proverbs.Items))].ContentFmt
		}
	}

	evn.SiteInfo = si
	evn.PageInfo = pageInfo
	evn.Links = model.LinkList(db, false)

	// 公告板功能
	uobj, err := model.UserGetById(db, 1)
	if err != nil {
		evn.Announcement = "公告板,可修改1号用户的个人简介进行修改。"
	} else {
		evn.Announcement = uobj.About
	}

	if currentUser.Theme == "2" && !evn.IsMobile {
		h.Render(w, tpl, evn, "layout.html", "index2.html")
	} else {
		h.Render(w, tpl, evn, "layout.html", "index.html")
	}
}

func (h *BaseHandler) ArticleDetail(w http.ResponseWriter, r *http.Request) {
	btn, key, score := r.FormValue("btn"), r.FormValue("key"), r.FormValue("score")
	if len(key) > 0 {
		_, err := strconv.ParseUint(key, 10, 64)
		if err != nil {
			w.Write([]byte(`{"retcode":400,"retmsg":"key type err"}`))
			return
		}
	}
	if len(score) > 0 {
		_, err := strconv.ParseUint(score, 10, 64)
		if err != nil {
			w.Write([]byte(`{"retcode":400,"retmsg":"score type err"}`))
			return
		}
	}

	aid := pat.Param(r, "aid")
	_, err := strconv.Atoi(aid)
	if err != nil {
		w.Write([]byte(`{"retcode":400,"retmsg":"aid type err"}`))
		return
	}

	cmd := "hscan"
	if btn == "prev" {
		cmd = "hrscan"
	}

	db := h.App.Db
	scf := h.App.Cf.Site
	aobj, err := model.ArticleGetById(db, aid)
	if err != nil {
		w.Write([]byte(err.Error()))
		return
	}
	currentUser, _ := h.CurrentUser(w, r)

	if len(currentUser.Notice) > 0 && len(currentUser.Notice) >= len(aid) {
		if len(aid) == len(currentUser.Notice) && aid == currentUser.Notice {
			currentUser.Notice = ""
			currentUser.NoticeNum = 0
			jb, _ := json.Marshal(currentUser)
			db.Hset("user", youdb.I2b(currentUser.Id), jb)
		} else {
			subStr := "," + aid + ","
			newNotice := "," + currentUser.Notice + ","
			if strings.Index(newNotice, subStr) >= 0 {
				currentUser.Notice = strings.Trim(strings.Replace(newNotice, subStr, "", 1), ",")
				currentUser.NoticeNum = len(strings.Split(currentUser.Notice, ","))
				jb, _ := json.Marshal(currentUser)
				db.Hset("user", youdb.I2b(currentUser.Id), jb)
			}
		}
	}

	if aobj.Hidden && currentUser.Flag < 1 {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte(`{"retcode":404,"retmsg":"仅登录用户可见"}`))
		return
	}
	cobj, err := model.CategoryGetById(db, strconv.FormatUint(aobj.Cid, 10))
	if err != nil {
		w.Write([]byte(err.Error()))
		return
	}

	if cobj.Hidden && currentUser.Flag < 1 {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte(`{"retcode":404,"retmsg":"仅登录用户可见"}`))
		return
	}

	if cobj.Id == 20 && currentUser.Flag < 99 {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte(`{"retcode":404,"retmsg":"请勿在垃圾场逗留"}`))
		return
	}

	// Authorized
	if scf.Authorized && currentUser.Flag < 5 {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte(`{"retcode":401,"retmsg":"Unauthorized"}`))
		return
	}

	cobj.Articles = db.Zget("category_article_num", youdb.I2b(cobj.Id)).Uint64()
	pageInfo := model.CommentList(db, cmd, "article_comment:"+aid, key, scf.CommentListNum, scf.TimeZone, currentUser.IgnoreLimitedUsers)

	if currentUser.IgnoreUser != "" {
		for _, uid := range strings.Split(currentUser.IgnoreUser, ",") {
			uid, err := strconv.ParseUint(uid, 10, 64)

			if err != nil {
				w.Write([]byte(`{"retcode":400,"retmsg":"type err"}`))
				return
			}

			for i := 0; i < len(pageInfo.Items); i++ {
				if pageInfo.Items[i].Uid == uid {
					pageInfo.Items = append(pageInfo.Items[:i], pageInfo.Items[i+1:]...)
					i--
				}
			}
		}
	}

	type articleForDetail struct {
		model.Article
		ContentFmt  template.HTML
		TagStr      template.HTML
		Name        string
		Avatar      string
		Views       uint64
		AddTimeFmt  string
		EditTimeFmt string
	}

	type pageData struct {
		PageData
		Aobj     articleForDetail
		Author   model.User
		Cobj     model.Category
		Relative model.ArticleRelative
		PageInfo model.CommentPageInfo
		Views    uint64
	}

	tpl := h.CurrentTpl(r)
	evn := &pageData{}
	evn.SiteCf = scf
	evn.Title = aobj.Title + " - " + cobj.Name + " - " + scf.Name
	evn.Keywords = aobj.Tags
	evn.Description = cobj.Name + " - " + aobj.Title + " - " + aobj.Tags
	evn.IsMobile = tpl == "mobile"

	evn.CurrentUser = currentUser
	evn.ShowSideAd = true
	evn.PageName = "article_detail"
	evn.HotNodes = model.CategoryHot(db, scf.CategoryShowNum, scf.MustLoginNodeIds)

	author, _ := model.UserGetById(db, aobj.Uid)
	viewsNum, _ := db.Hincr("article_views", youdb.I2b(aobj.Id), 1)
	evn.Aobj = articleForDetail{
		Article:     aobj,
		ContentFmt:  template.HTML(util.ContentFmt(db, aobj.Content)),
		Name:        author.Name,
		Avatar:      author.Avatar,
		Views:       viewsNum,
		AddTimeFmt:  util.TimeFmt(aobj.AddTime, "2006-01-02", scf.TimeZone),
		EditTimeFmt: util.TimeFmt(aobj.EditTime, "2006-01-02", scf.TimeZone),
	}

	if len(aobj.Tags) > 0 {
		var tags []string
		for _, v := range strings.Split(aobj.Tags, ",") {
			tags = append(tags, `<a href="/tag/`+v+`">`+v+`</a>`)
		}
		evn.Aobj.TagStr = template.HTML(strings.Join(tags, ", "))
	}

	evn.Cobj = cobj
	evn.Relative = model.ArticleGetRelative(db, aobj.Id, aobj.Tags)
	evn.PageInfo = pageInfo

	token := h.GetCookie(r, "token")
	if len(token) == 0 {
		token := xid.New().String()
		h.SetCookie(w, "token", token, 1)
	}

	h.Render(w, tpl, evn, "layout.html", "article.html")
}

func (h *BaseHandler) ArticleDetailPost(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	token := h.GetCookie(r, "token")
	if len(token) == 0 {
		w.Write([]byte(`{"retcode":400,"retmsg":"token cookie missed"}`))
		return
	}

	aid := pat.Param(r, "aid")
	_, err := strconv.Atoi(aid)
	if err != nil {
		w.Write([]byte(`{"retcode":400,"retmsg":"aid type err:` + err.Error() + `"}`))
		return
	}

	type recForm struct {
		Act     string `json:"act"`
		Link    string `json:"link"`
		Content string `json:"content"`
	}

	type response struct {
		normalRsp
		Content string        `json:"content"`
		Html    template.HTML `json:"html"`
	}

	decoder := json.NewDecoder(r.Body)
	var rec recForm
	err = decoder.Decode(&rec)
	if err != nil {
		w.Write([]byte(`{"retcode":400,"retmsg":"json Decode err:` + err.Error() + `"}`))
		return
	}
	defer r.Body.Close()

	db := h.App.Db
	rsp := response{}

	if rec.Act == "link_click" {
		rsp.Retcode = 200
		if len(rec.Link) > 0 {
			hash := md5.Sum([]byte(rec.Link))
			urlMd5 := hex.EncodeToString(hash[:])
			bn := "article_detail_token"
			clickKey := []byte(token + ":click:" + urlMd5)
			if db.Zget(bn, clickKey).State == "ok" {
				w.Write([]byte(`{"retcode":403,"retmsg":"err"}`))
				return
			}
			db.Zset(bn, clickKey, uint64(time.Now().UTC().Unix()))
			db.Hincr("url_md5_click", []byte(urlMd5), 1)

			w.Write([]byte(`{"retcode":200,"retmsg":"ok"}`))
			return
		}
	} else if rec.Act == "comment_preview" {
		rsp.Retcode = 200
		rsp.Html = template.HTML(util.ContentFmt(db, rec.Content))
	} else if rec.Act == "comment_submit" {
		timeStamp := uint64(time.Now().UTC().Unix())
		currentUser, _ := h.CurrentUser(w, r)
		if currentUser.Flag < 5 {
			w.Write([]byte(`{"retcode":403,"retmsg":"forbidden"}`))
			return
		}
		if (timeStamp - currentUser.LastReplyTime) < uint64(h.App.Cf.Site.CommentInterval) {
			w.Write([]byte(`{"retcode":403,"retmsg":"out off comment interval"}`))
			return
		}
		aobj, err := model.ArticleGetById(db, aid)
		if err != nil {
			w.Write([]byte(`{"retcode":404,"retmsg":"not found"}`))
			return
		}
		if aobj.CloseComment {
			w.Write([]byte(`{"retcode":403,"retmsg":"comment forbidden"}`))
			return
		}
		commentId, _ := db.HnextSequence("article_comment:" + aid)

		scf := h.App.Cf.Site

		if currentUser.Flag == 99 && (strings.Contains(rec.Content, "警告一次") ||
			strings.Contains(rec.Content, "管理员回复")) {
			currentUser, err = model.UserGetById(db, uint64(scf.AdminBotId))
			if err != nil {
				w.Write([]byte(`{"retcode":400,"retmsg":"json Decode err:` + err.Error() + `"}`))
				return
			}
		}

		if currentUser.Flag >= 96 && strings.Contains(rec.Content, "匿名者说") {
			currentUser, err = model.UserGetById(db, uint64(scf.AnonymousBotId))
			if err != nil {
				w.Write([]byte(`{"retcode":400,"retmsg":"json Decode err:` + err.Error() + `"}`))
				return
			}
		}

		obj := model.Comment{
			Id:       commentId,
			Aid:      aobj.Id,
			Uid:      currentUser.Id,
			Content:  rec.Content,
			AddTime:  timeStamp,
			ClientIp: "",
		}
		jb, _ := json.Marshal(obj)

		db.Hset("article_comment:"+aid, youdb.I2b(obj.Id), jb) // 文章评论bucket
		db.Hincr("count", []byte("comment_num"), 1)            // 评论总数
		// 用户回复文章列表
		db.Zset("user_article_reply:"+strconv.FormatUint(obj.Uid, 10), youdb.I2b(obj.Aid), obj.AddTime)

		// 更新文章列表时间

		aobj.Comments = commentId
		aobj.RUid = currentUser.Id
		aobj.EditTime = timeStamp
		jb2, _ := json.Marshal(aobj)
		db.Hset("article", youdb.I2b(aobj.Id), jb2)

		currentUser.LastReplyTime = timeStamp
		currentUser.Replies += 1
		jb3, _ := json.Marshal(currentUser)
		db.Hset("user", youdb.I2b(currentUser.Id), jb3)

		// 不顶帖用户组
		if currentUser.Flag != 6 {
			// 总文章列表
			db.Zset("article_timeline", youdb.I2b(aobj.Id), timeStamp)
			// 分类文章列表
			db.Zset("category_article_timeline:"+strconv.FormatUint(aobj.Cid, 10), youdb.I2b(aobj.Id), timeStamp)
		}

		// @ somebody in comment & topic author
if aobj.Cid != 19 && aobj.Cid != 20 {
		sbs := util.GetMention("@"+strconv.FormatUint(aobj.Uid, 10)+" "+rec.Content,
			[]string{currentUser.Name, strconv.FormatUint(currentUser.Id, 10)})
		for _, sb := range sbs {
			var sbObj model.User
			sbu, err := strconv.ParseUint(sb, 10, 64)
			if err != nil {
				// @ user name
				sbObj, err = model.UserGetByName(db, strings.ToLower(sb))
			} else {
				// @ user id
				sbObj, err = model.UserGetById(db, sbu)
			}

			if err == nil {
				if len(sbObj.Notice) > 0 {
					aidList := util.SliceUniqStr(strings.Split(aid+","+sbObj.Notice, ","))
					if len(aidList) > 100 {
						aidList = aidList[:100]
					}
					sbObj.Notice = strings.Join(aidList, ",")
					sbObj.NoticeNum = len(aidList)
				} else {
					sbObj.Notice = aid
					sbObj.NoticeNum = 1
				}
				jb, _ := json.Marshal(sbObj)
				db.Hset("user", youdb.I2b(sbObj.Id), jb)
			}
		}
}

		rsp.Retcode = 200
	}

	json.NewEncoder(w).Encode(rsp)
}

func (h *BaseHandler) ContentPreviewPost(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	token := h.GetCookie(r, "token")
	if len(token) == 0 {
		w.Write([]byte(`{"retcode":400,"retmsg":"token cookie missed"}`))
		return
	}

	currentUser, _ := h.CurrentUser(w, r)
	if currentUser.Flag < 5 {
		w.Write([]byte(`{"retcode":403,"retmsg":"forbidden"}`))
		return
	}

	type recForm struct {
		Act     string `json:"act"`
		Link    string `json:"link"`
		Content string `json:"content"`
	}

	type response struct {
		normalRsp
		Content string        `json:"content"`
		Html    template.HTML `json:"html"`
	}

	decoder := json.NewDecoder(r.Body)
	var rec recForm
	err := decoder.Decode(&rec)
	if err != nil {
		w.Write([]byte(`{"retcode":400,"retmsg":"json Decode err:` + err.Error() + `"}`))
		return
	}
	defer r.Body.Close()

	db := h.App.Db
	rsp := response{}

	if rec.Act == "preview" && len(rec.Content) > 0 {
		rsp.Retcode = 200
		rsp.Html = template.HTML(util.ContentFmt(db, rec.Content))
	}
	json.NewEncoder(w).Encode(rsp)
}
张怀义 at 2020-04-02
4

@小二 #3 不知道,点了“发表”,什么反应都没有。。

算了,有空把那些代码都改了吧。我是手机,没法git clone完整的项目,所以我都是怎么改简单,怎么来的

张怀义 at 2020-04-02
5

@张怀义 #5 我也发现这个问题了,打开控制台,是能看到反馈的,一般是标题过长等原因,但是不知道为啥没有弹出来。这个是js的问题,i hate js。

小二 at 2020-04-02
6