加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
topic.go 14.00 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
/*
主题
*/
package gopher
import (
"fmt"
"html/template"
"net/http"
"sort"
"time"
"github.com/gorilla/mux"
"github.com/jimmykuu/gt-go-sdk"
"github.com/jimmykuu/wtforms"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
// 用于测试
var testParam func() = func() {}
type City struct {
Name string `bson:"_id"`
MemberCount int `bson:"count"`
}
type ByCount []City
func (a ByCount) Len() int { return len(a) }
func (a ByCount) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByCount) Less(i, j int) bool { return a[i].MemberCount > a[j].MemberCount }
func topicsHandler(handler *Handler, conditions bson.M, sortBy string, url string, subActive string) {
page, err := getPage(handler.Request)
if err != nil {
message(handler, "页码错误", "页码错误", "error")
return
}
var nodes []Node
c := handler.DB.C(NODES)
c.Find(bson.M{"topiccount": bson.M{"$gt": 0}}).Sort("-topiccount").All(&nodes)
var status Status
c = handler.DB.C(STATUS)
c.Find(nil).One(&status)
c = handler.DB.C(CONTENTS)
var topTopics []Topic
if page == 1 {
c.Find(bson.M{"is_top": true}).Sort(sortBy).All(&topTopics)
var objectIds []bson.ObjectId
for _, topic := range topTopics {
objectIds = append(objectIds, topic.Id_)
}
if len(topTopics) > 0 {
conditions["_id"] = bson.M{"$not": bson.M{"$in": objectIds}}
}
}
pagination := NewPagination(c.Find(conditions).Sort(sortBy), url, PerPage)
var topics []Topic
query, err := pagination.Page(page)
if err != nil {
message(handler, "页码错误", "页码错误", "error")
return
}
query.(*mgo.Query).All(&topics)
var linkExchanges []LinkExchange
c = handler.DB.C(LINK_EXCHANGES)
c.Find(bson.M{"is_on_home": true}).All(&linkExchanges)
topics = append(topTopics, topics...)
c = handler.DB.C(USERS)
var cities []City
c.Pipe([]bson.M{bson.M{
"$group": bson.M{
"_id": "$location",
"count": bson.M{"$sum": 1},
},
}}).All(&cities)
sort.Sort(ByCount(cities))
var hotCities []City
count := 0
for _, city := range cities {
if city.Name != "" {
hotCities = append(hotCities, city)
count += 1
}
if count == 10 {
break
}
}
handler.renderTemplate("index.html", BASE, map[string]interface{}{
"nodes": nodes,
"cities": hotCities,
"status": status,
"topics": topics,
"linkExchanges": linkExchanges,
"pagination": pagination,
"page": page,
"active": "topic",
"subActive": subActive,
})
}
// URL: /
// 网站首页,列出按回帖时间倒序排列的第一页
func indexHandler(handler *Handler) {
topicsHandler(handler, bson.M{"content.type": TypeTopic}, "-latestrepliedat", "/", "latestReply")
}
// URL: /topics/latest
// 最新发布的主题,按照发布时间倒序排列
func latestTopicsHandler(handler *Handler) {
topicsHandler(handler, bson.M{"content.type": TypeTopic}, "-content.createdat", "/topics/latest", "latestCreate")
}
// URL: /topics/no_reply
// 无人回复的主题,按照发布时间倒序排列
func noReplyTopicsHandler(handler *Handler) {
topicsHandler(handler, bson.M{"content.type": TypeTopic, "content.commentcount": 0}, "-content.createdat", "/topics/no_reply", "noReply")
}
// URL: /topic/new
// 新建主题
func newTopicHandler(handler *Handler) {
user, _ := currentUser(handler)
if user.IsBlocked {
handler.Redirect("/user/blocked")
return
}
nodeID := mux.Vars(handler.Request)["node"]
var nodes []Node
c := handler.DB.C(NODES)
c.Find(nil).All(&nodes)
var choices = []wtforms.Choice{wtforms.Choice{}} // 第一个选项为空
for _, node := range nodes {
choices = append(choices, wtforms.Choice{Value: node.Id_.Hex(), Label: node.Name})
}
geeTest := geetest.NewGeeTest(Config.GtCaptchaId, Config.GtPrivateKey)
form := wtforms.NewForm(
wtforms.NewSelectField("node", "节点", choices, nodeID, &wtforms.Required{}),
wtforms.NewTextArea("title", "标题", "", &wtforms.Required{}),
wtforms.NewTextArea("editormd-markdown-doc", "内容", ""),
wtforms.NewTextArea("editormd-html-code", "HTML", ""),
wtforms.NewTextField("geetest_challenge", "challenge", ""),
wtforms.NewTextField("geetest_validate", "validate", ""),
wtforms.NewTextField("geetest_seccode", "seccode", ""),
)
if handler.Request.Method == "POST" {
if form.Validate(handler.Request) {
if !geeTest.Validate(form.Value("geetest_challenge"), form.Value("geetest_validate"), form.Value("geetest_seccode")) {
handler.renderTemplate("topic/form.html", BASE, map[string]interface{}{
"form": form,
"title": "新建",
"gtUrl": geeTest.EmbedURL(),
"captchaErr": true,
})
return
}
now := time.Now()
c = handler.DB.C(CONTENTS)
// 查找最新的一篇帖子,限制发帖间隔
var latestTopic Topic
err := c.Find(bson.M{"content.createdby": user.Id_}).Sort("-content.createdat").Limit(1).One(&latestTopic)
if err == nil {
fmt.Println("lastTopic", latestTopic.Content.CreatedAt)
if !latestTopic.Content.CreatedAt.Add(time.Minute * 30).Before(now) {
// 半小时内只能发一帖
message(handler, "发表主题过于频繁", "发表主题过于频繁,不能发布该主题", "error")
return
}
}
id_ := bson.NewObjectId()
nodeID := bson.ObjectIdHex(form.Value("node"))
err = c.Insert(&Topic{
Content: Content{
Id_: id_,
Type: TypeTopic,
Title: form.Value("title"),
Markdown: form.Value("editormd-markdown-doc"),
Html: template.HTML(form.Value("editormd-html-code")),
CreatedBy: user.Id_,
CreatedAt: now,
},
Id_: id_,
NodeId: nodeID,
LatestRepliedAt: now,
})
if err != nil {
fmt.Println("newTopicHandler:", err.Error())
return
}
// 增加Node.TopicCount
c = handler.DB.C(NODES)
c.Update(bson.M{"_id": nodeID}, bson.M{"$inc": bson.M{"topiccount": 1}})
c = handler.DB.C(STATUS)
c.Update(nil, bson.M{"$inc": bson.M{"topiccount": 1}})
http.Redirect(handler.ResponseWriter, handler.Request, "/t/"+id_.Hex(), http.StatusFound)
return
}
}
handler.renderTemplate("topic/form.html", BASE, map[string]interface{}{
"form": form,
"title": "新建",
"action": "/topic/new",
"active": "topic",
"gtUrl": geeTest.EmbedURL(),
})
}
// URL: /t/{topicId}/edit
// 编辑主题
func editTopicHandler(handler *Handler) {
user, _ := currentUser(handler)
topicId := bson.ObjectIdHex(mux.Vars(handler.Request)["topicId"])
c := handler.DB.C(CONTENTS)
var topic Topic
err := c.Find(bson.M{"_id": topicId, "content.type": TypeTopic}).One(&topic)
if err != nil {
message(handler, "没有该主题", "没有该主题,不能编辑", "error")
return
}
if !topic.CanEdit(user.Username, handler.DB) {
message(handler, "没有该权限", "对不起,你没有权限编辑该主题", "error")
return
}
var nodes []Node
c = handler.DB.C(NODES)
c.Find(nil).All(&nodes)
var choices = []wtforms.Choice{wtforms.Choice{}} // 第一个选项为空
for _, node := range nodes {
choices = append(choices, wtforms.Choice{Value: node.Id_.Hex(), Label: node.Name})
}
geeTest := geetest.NewGeeTest(Config.GtCaptchaId, Config.GtPrivateKey)
form := wtforms.NewForm(
wtforms.NewSelectField("node", "节点", choices, topic.NodeId.Hex(), &wtforms.Required{}),
wtforms.NewTextArea("title", "标题", topic.Title, &wtforms.Required{}),
wtforms.NewTextArea("editormd-markdown-doc", "内容", topic.Markdown),
wtforms.NewTextArea("editormd-html-code", "html", ""),
wtforms.NewTextField("geetest_challenge", "challenge", ""),
wtforms.NewTextField("geetest_validate", "validate", ""),
wtforms.NewTextField("geetest_seccode", "seccode", ""),
)
if handler.Request.Method == "POST" {
if form.Validate(handler.Request) {
if !geeTest.Validate(form.Value("geetest_challenge"), form.Value("geetest_validate"), form.Value("geetest_seccode")) {
handler.renderTemplate("topic/form.html", BASE, map[string]interface{}{
"form": form,
"gtUrl": geeTest.EmbedURL(),
"captchaErr": true,
})
return
}
nodeId := bson.ObjectIdHex(form.Value("node"))
c = handler.DB.C(CONTENTS)
c.Update(bson.M{"_id": topic.Id_}, bson.M{"$set": bson.M{
"nodeid": nodeId,
"content.title": form.Value("title"),
"content.markdown": form.Value("editormd-markdown-doc"),
"content.html": template.HTML(form.Value("editormd-html-code")),
"content.updatedat": time.Now(),
"content.updatedby": user.Id_.Hex(),
}})
// 如果两次的节点不同,更新节点的主题数量
if topic.NodeId != nodeId {
c = handler.DB.C(NODES)
c.Update(bson.M{"_id": topic.NodeId}, bson.M{"$inc": bson.M{"topiccount": -1}})
c.Update(bson.M{"_id": nodeId}, bson.M{"$inc": bson.M{"topiccount": 1}})
}
http.Redirect(handler.ResponseWriter, handler.Request, "/t/"+topic.Id_.Hex(), http.StatusFound)
return
}
}
handler.renderTemplate("topic/form.html", BASE, map[string]interface{}{
"form": form,
"title": "编辑",
"action": "/t/" + topicId + "/edit",
"active": "topic",
"gtUrl": geeTest.EmbedURL(),
})
}
// URL: /t/{topicId}
// 根据主题的ID,显示主题的信息及回复
func showTopicHandler(handler *Handler) {
testParam()
vars := mux.Vars(handler.Request)
topicId := vars["topicId"]
c := handler.DB.C(CONTENTS)
cusers := handler.DB.C(USERS)
topic := Topic{}
if !bson.IsObjectIdHex(topicId) {
http.NotFound(handler.ResponseWriter, handler.Request)
return
}
err := c.Find(bson.M{"_id": bson.ObjectIdHex(topicId), "content.type": TypeTopic}).One(&topic)
if err != nil {
logger.Println(err)
http.NotFound(handler.ResponseWriter, handler.Request)
return
}
c.UpdateId(bson.ObjectIdHex(topicId), bson.M{"$inc": bson.M{"content.hits": 1}})
user, has := currentUser(handler)
//去除新消息的提醒
if has {
replies := user.RecentReplies
ats := user.RecentAts
pos := -1
for k, v := range replies {
if v.ContentId == topicId {
pos = k
break
}
}
//数组的删除不是这么删的,早知如此就应该存链表了
if pos != -1 {
if pos == len(replies)-1 {
replies = replies[:pos]
} else {
replies = append(replies[:pos], replies[pos+1:]...)
}
cusers.Update(bson.M{"_id": user.Id_}, bson.M{"$set": bson.M{"recentreplies": replies}})
}
pos = -1
for k, v := range ats {
if v.ContentId == topicId {
pos = k
break
}
}
if pos != -1 {
if pos == len(ats)-1 {
ats = ats[:pos]
} else {
ats = append(ats[:pos], ats[pos+1:]...)
}
cusers.Update(bson.M{"_id": user.Id_}, bson.M{"$set": bson.M{"recentats": ats}})
}
}
handler.renderTemplate("topic/show.html", BASE, map[string]interface{}{
"topic": topic,
"active": "topic",
})
}
// URL: /go/{node}
// 列出节点下所有的主题
func topicInNodeHandler(handler *Handler) {
vars := mux.Vars(handler.Request)
nodeId := vars["node"]
c := handler.DB.C(NODES)
node := Node{}
err := c.Find(bson.M{"id": nodeId}).One(&node)
if err != nil {
message(handler, "没有此节点", "请联系管理员创建此节点", "error")
return
}
page, err := getPage(handler.Request)
if err != nil {
message(handler, "页码错误", "页码错误", "error")
return
}
c = handler.DB.C(CONTENTS)
pagination := NewPagination(c.Find(bson.M{"nodeid": node.Id_, "content.type": TypeTopic}).Sort("-latestrepliedat"), "/", 20)
var topics []Topic
query, err := pagination.Page(page)
if err != nil {
message(handler, "没有找到页面", "没有找到页面", "error")
return
}
query.(*mgo.Query).All(&topics)
handler.renderTemplate("/topic/list.html", BASE, map[string]interface{}{
"topics": topics,
"node": node,
"active": "topic",
})
}
// URL: /t/{topicId}/collect/
// 将主题收藏至当前用户的收藏夹
func collectTopicHandler(handler *Handler) {
vars := mux.Vars(handler.Request)
topicId := vars["topicId"]
t := time.Now()
user, _ := currentUser(handler)
for _, v := range user.TopicsCollected {
if v.TopicId == topicId {
return
}
}
user.TopicsCollected = append(user.TopicsCollected, CollectTopic{topicId, t})
c := handler.DB.C(USERS)
c.UpdateId(user.Id_, bson.M{"$set": bson.M{"topicscollected": user.TopicsCollected}})
http.Redirect(handler.ResponseWriter, handler.Request, "/member/"+user.Username+"/collect?p=1", http.StatusFound)
}
// URL: /t/{topicId}/delete
// 删除主题
func deleteTopicHandler(handler *Handler) {
vars := mux.Vars(handler.Request)
topicId := bson.ObjectIdHex(vars["topicId"])
c := handler.DB.C(CONTENTS)
topic := Topic{}
err := c.Find(bson.M{"_id": topicId, "content.type": TypeTopic}).One(&topic)
if err != nil {
fmt.Println("deleteTopic:", err.Error())
return
}
// Node统计数减一
c = handler.DB.C(NODES)
c.Update(bson.M{"_id": topic.NodeId}, bson.M{"$inc": bson.M{"topiccount": -1}})
c = handler.DB.C(STATUS)
// 统计的主题数减一,减去统计的回复数减去该主题的回复数
c.Update(nil, bson.M{"$inc": bson.M{"topiccount": -1, "replycount": -topic.CommentCount}})
//删除评论
c = handler.DB.C(COMMENTS)
if topic.CommentCount > 0 {
c.Remove(bson.M{"contentid": topic.Id_})
}
// 删除Topic记录
c = handler.DB.C(CONTENTS)
c.Remove(bson.M{"_id": topic.Id_})
http.Redirect(handler.ResponseWriter, handler.Request, "/", http.StatusFound)
}
// 列出置顶的主题
func listTopTopicsHandler(handler *Handler) {
var topics []Topic
c := handler.DB.C(CONTENTS)
c.Find(bson.M{"content.type": TypeTopic, "is_top": true}).All(&topics)
handler.renderTemplate("/topic/top_list.html", ADMIN, map[string]interface{}{
"topics": topics,
})
}
// 设置置顶
func setTopTopicHandler(handler *Handler) {
topicId := bson.ObjectIdHex(mux.Vars(handler.Request)["id"])
c := handler.DB.C(CONTENTS)
c.Update(bson.M{"_id": topicId}, bson.M{"$set": bson.M{"is_top": true}})
handler.Redirect("/t/" + topicId.Hex())
}
// 取消置顶
func cancelTopTopicHandler(handler *Handler) {
vars := mux.Vars(handler.Request)
topicId := bson.ObjectIdHex(vars["id"])
c := handler.DB.C(CONTENTS)
c.Update(bson.M{"_id": topicId}, bson.M{"$set": bson.M{"is_top": false}})
handler.Redirect("/admin/top/topics")
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化