diff --git a/conf/app.yml b/conf/app.yml
index 5621927..ad2bd6b 100644
--- a/conf/app.yml
+++ b/conf/app.yml
@@ -54,15 +54,6 @@ blogapp:
account:
username: deepzz # *后台登录用户名
password: deepzz # *登录明文密码
- email: chenqijing2@163.com # 邮箱,用于通知: chenqijing2@163.com
- phonenumber: "+8615100000000" # 手机号, "+8615100000000"
- address: "" # 家庭住址
- blogger:
- blogname: Deepzz # left显示名称: Deepzz
- subtitle: 不抛弃,不放弃 # 小标题: 不抛弃,不放弃
- beian: 蜀 ICP 备 16021362 号 # 备案号: 蜀 ICP 备 16021362 号
- btitle: Deepzz's Blog # footer显示名称及tab标题: Deepzz's Blog
- copyright: 本站使用「署名 4.0 国际」创作共享协议,转载请注明作者及原网址。 # 版权声明
backupapp:
name: cmd-backup
enablehttp: true
diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go
index 3312b5c..c08efc1 100644
--- a/pkg/cache/cache.go
+++ b/pkg/cache/cache.go
@@ -41,17 +41,7 @@ func init() {
TagArticles: make(map[string]model.SortedArticles),
ArticlesMap: make(map[string]*model.Article),
}
- err = Ei.loadAccount()
- if err != nil {
- panic(err)
- }
- // blogger
- err = Ei.loadBlogger()
- if err != nil {
- panic(err)
- }
- // articles
- err = Ei.loadArticles()
+ err = Ei.loadOrInit()
if err != nil {
panic(err)
}
@@ -151,47 +141,60 @@ func (c *Cache) PageArticles(page int, pageSize int) (prev,
return
}
-// loadBlogger 博客信息
-func (c *Cache) loadBlogger() error {
+// loadOrInit 读取数据或初始化
+func (c *Cache) loadOrInit() error {
blogapp := config.Conf.BlogApp
+ // blogger
blogger := &model.Blogger{
- BlogName: blogapp.Blogger.BlogName,
- SubTitle: blogapp.Blogger.SubTitle,
- BeiAn: blogapp.Blogger.BeiAn,
- BTitle: blogapp.Blogger.BTitle,
- Copyright: blogapp.Blogger.Copyright,
+ BlogName: strings.Title(blogapp.Account.Username),
+ SubTitle: "Rome was not built in one day.",
+ BeiAn: "蜀ICP备xxxxxxxx号-1",
+ BTitle: fmt.Sprintf("%s's Blog", strings.Title(blogapp.Account.Username)),
+ Copyright: `本站使用「署名 4.0 国际」创作共享协议,转载请注明作者及原网址。`,
}
- blogger, err := c.LoadInsertBlogger(context.Background(), blogger)
+ created, err := c.LoadInsertBlogger(context.Background(), blogger)
if err != nil {
return err
}
c.Blogger = blogger
- return nil
-}
-
-// loadAccount 账户账户信息
-func (c *Cache) loadAccount() error {
- blogapp := config.Conf.BlogApp
+ if created { // init articles: about blogroll
+ about := &model.Article{
+ ID: 1, // 固定ID
+ Author: blogapp.Account.Username,
+ Title: "关于",
+ Slug: "about",
+ }
+ err = c.InsertArticle(context.Background(), about)
+ if err != nil {
+ return err
+ }
+ // 推送到 disqus
+ go internal.ThreadCreate(about, blogger.BTitle)
+ blogroll := &model.Article{
+ ID: 2, // 固定ID
+ Author: blogapp.Account.Username,
+ Title: "友情链接",
+ Slug: "blogroll",
+ }
+ err = c.InsertArticle(context.Background(), blogroll)
+ if err != nil {
+ return err
+ }
+ }
+ // account
pwd := tools.EncryptPasswd(blogapp.Account.Password,
blogapp.Account.Password)
account := &model.Account{
Username: blogapp.Account.Username,
Password: pwd,
- Email: blogapp.Account.Email,
- PhoneN: blogapp.Account.PhoneNumber,
- Address: blogapp.Account.Address,
}
- account, err := c.LoadInsertAccount(context.Background(), account)
+ _, err = c.LoadInsertAccount(context.Background(), account)
if err != nil {
return err
}
c.Account = account
- return nil
-}
-
-// loadArticles 文章信息
-func (c *Cache) loadArticles() error {
+ // all articles
articles, err := c.LoadAllArticle(context.Background())
if err != nil {
return err
@@ -204,13 +207,13 @@ func (c *Cache) loadArticles() error {
c.ArticlesMap[v.Slug] = v
// 分析文章
- if v.ID < config.Conf.BlogApp.General.StartID {
+ if v.ID < blogapp.General.StartID {
continue
}
if i > 0 {
v.Prev = Ei.Articles[i-1]
}
- if Ei.Articles[i+1].ID >= config.Conf.BlogApp.General.StartID {
+ if Ei.Articles[i+1].ID >= blogapp.General.StartID {
v.Next = Ei.Articles[i+1]
}
c.rebuildArticle(v, false)
diff --git a/pkg/cache/store/mongodb.go b/pkg/cache/store/mongodb.go
index 10f303e..831eae1 100644
--- a/pkg/cache/store/mongodb.go
+++ b/pkg/cache/store/mongodb.go
@@ -50,27 +50,50 @@ func (db *mongodb) Init(source string) (Store, error) {
return nil, err
}
db.Client = client
+ // create index
+ indexModel := mongo.IndexModel{
+ Keys: bson.D{{"username", 1}},
+ Options: options.Index().SetUnique(true).SetSparse(true),
+ }
+ db.Database(mongoDBName).Collection(collectionAccount).
+ Indexes().
+ CreateOne(context.Background(), indexModel)
+ indexModel = mongo.IndexModel{
+ Keys: bson.D{{"slug", 1}},
+ Options: options.Index().SetUnique(true).SetSparse(true),
+ }
+ db.Database(mongoDBName).Collection(collectionArticle).
+ Indexes().
+ CreateOne(context.Background(), indexModel)
+ indexModel = mongo.IndexModel{
+ Keys: bson.D{{"slug", 1}},
+ Options: options.Index().SetUnique(true).SetSparse(true),
+ }
+ db.Database(mongoDBName).Collection(collectionSeries).
+ Indexes().
+ CreateOne(context.Background(), indexModel)
return db, nil
}
// LoadInsertBlogger 读取或创建博客
func (db *mongodb) LoadInsertBlogger(ctx context.Context,
- blogger *model.Blogger) (*model.Blogger, error) {
+ blogger *model.Blogger) (created bool, err error) {
collection := db.Database(mongoDBName).Collection(collectionBlogger)
filter := bson.M{}
result := collection.FindOne(ctx, filter)
- err := result.Err()
+ err = result.Err()
if err != nil {
if err != mongo.ErrNoDocuments {
- return nil, err
+ return
}
_, err = collection.InsertOne(ctx, blogger)
+ created = true
} else {
err = result.Decode(blogger)
}
- return blogger, err
+ return
}
// UpdateBlogger 更新博客
@@ -91,22 +114,23 @@ func (db *mongodb) UpdateBlogger(ctx context.Context,
// LoadInsertAccount 读取或创建账户
func (db *mongodb) LoadInsertAccount(ctx context.Context,
- acct *model.Account) (*model.Account, error) {
+ acct *model.Account) (created bool, err error) {
collection := db.Database(mongoDBName).Collection(collectionAccount)
filter := bson.M{"username": config.Conf.BlogApp.Account.Username}
result := collection.FindOne(ctx, filter)
- err := result.Err()
+ err = result.Err()
if err != nil {
if err != mongo.ErrNoDocuments {
- return nil, err
+ return
}
_, err = collection.InsertOne(ctx, acct)
+ created = true
} else {
err = result.Decode(acct)
}
- return acct, err
+ return
}
// UpdateAccount 更新账户
@@ -185,14 +209,13 @@ func (db *mongodb) LoadAllSeries(ctx context.Context) (model.SortedSeries, error
// InsertArticle 创建文章
func (db *mongodb) InsertArticle(ctx context.Context, article *model.Article) error {
- // 分配ID, 占位至起始id
- for {
+ // 可手动分配ID或者分配ID, 占位至起始id
+ for article.ID == 0 {
id := db.nextValue(ctx, counterNameArticle)
if id < config.Conf.BlogApp.General.StartID {
continue
} else {
article.ID = id
- break
}
}
diff --git a/pkg/cache/store/mongodb_test.go b/pkg/cache/store/mongodb_test.go
index a2e524e..0bbe04c 100644
--- a/pkg/cache/store/mongodb_test.go
+++ b/pkg/cache/store/mongodb_test.go
@@ -135,6 +135,7 @@ func TestLoadAllSeries(t *testing.T) {
}
func TestInsertArticle(t *testing.T) {
+ article.ID = 12
err := store.InsertArticle(context.Background(), article)
if err != nil {
t.Fatal(err)
diff --git a/pkg/cache/store/store.go b/pkg/cache/store/store.go
index 4ca9538..8d2b7b3 100644
--- a/pkg/cache/store/store.go
+++ b/pkg/cache/store/store.go
@@ -18,12 +18,12 @@ var (
// Store 存储后端
type Store interface {
// LoadInsertBlogger 读取或创建博客
- LoadInsertBlogger(ctx context.Context, blogger *model.Blogger) (*model.Blogger, error)
+ LoadInsertBlogger(ctx context.Context, blogger *model.Blogger) (bool, error)
// UpdateBlogger 更新博客
UpdateBlogger(ctx context.Context, fields map[string]interface{}) error
// LoadInsertAccount 读取或创建账户
- LoadInsertAccount(ctx context.Context, acct *model.Account) (*model.Account, error)
+ LoadInsertAccount(ctx context.Context, acct *model.Account) (bool, error)
// UpdateAccount 更新账户
UpdateAccount(ctx context.Context, name string, fields map[string]interface{}) error
@@ -58,6 +58,7 @@ type Store interface {
// Driver 存储驱动
type Driver interface {
+ // Init 数据库初始化, 建表, 加索引操作等
Init(source string) (Store, error)
}
diff --git a/pkg/core/blog/page/page.go b/pkg/core/blog/page/page.go
index 432b686..df64888 100644
--- a/pkg/core/blog/page/page.go
+++ b/pkg/core/blog/page/page.go
@@ -50,7 +50,9 @@ func RegisterRoutes(e *gin.Engine) {
e.GET("/series.html", handleSeriesPage)
e.GET("/archives.html", handleArchivePage)
e.GET("/search.html", handleSearchPage)
+ e.GET("/disqus/post-:slug", handleDisqusList)
e.GET("/disqus/form/post-:slug", handleDisqusPage)
+ e.POST("/disqus/create", handleDisqusCreate)
e.GET("/beacon.html", handleBeaconPage)
}
@@ -80,7 +82,7 @@ func baseParams(c *gin.Context) gin.H {
// handleNotFound not found page
func handleNotFound(c *gin.Context) {
params := baseParams(c)
- params["title"] = "Not Found"
+ params["Title"] = "Not Found"
params["Description"] = "404 Not Found"
params["Path"] = ""
c.Status(http.StatusNotFound)
@@ -90,7 +92,7 @@ func handleNotFound(c *gin.Context) {
// handleHomePage 首页
func handleHomePage(c *gin.Context) {
params := baseParams(c)
- params["title"] = cache.Ei.Blogger.BTitle + " | " + cache.Ei.Blogger.SubTitle
+ params["Title"] = cache.Ei.Blogger.BTitle + " | " + cache.Ei.Blogger.SubTitle
params["Description"] = "博客首页," + cache.Ei.Blogger.SubTitle
params["Path"] = c.Request.URL.Path
params["CurrentPage"] = "blog-home"
@@ -212,6 +214,58 @@ func handleSearchPage(c *gin.Context) {
renderHTMLHomeLayout(c, "search", params)
}
+// disqusComments 服务端获取评论详细
+type disqusComments struct {
+ ErrNo int `json:"errno"`
+ ErrMsg string `json:"errmsg"`
+ Data struct {
+ Next string `json:"next"`
+ Total int `json:"total"`
+ Comments []commentsDetail `json:"comments"`
+ Thread string `json:"thread"`
+ } `json:"data"`
+}
+
+// handleDisqusList 获取评论列表
+func handleDisqusList(c *gin.Context) {
+ dcs := &disqusComments{}
+ defer c.JSON(http.StatusOK, dcs)
+
+ slug := c.Param("slug")
+ cursor := c.Query("cursor")
+ if artc := cache.Ei.ArticlesMap[slug]; artc != nil {
+ dcs.Data.Thread = artc.Thread
+ }
+ postsList, err := internal.PostsList(slug, cursor)
+ if err != nil {
+ logrus.Error("hadnleDisqusList.PostsList: ", err)
+ dcs.ErrNo = 0
+ dcs.ErrMsg = "系统错误"
+ return
+ }
+ dcs.ErrNo = postsList.Code
+ if postsList.Cursor.HasNext {
+ dcs.Data.Next = postsList.Cursor.Next
+ }
+ dcs.Data.Total = len(postsList.Response)
+ dcs.Data.Comments = make([]commentsDetail, len(postsList.Response))
+ for i, v := range postsList.Response {
+ if dcs.Data.Thread == "" {
+ dcs.Data.Thread = v.Thread
+ }
+ dcs.Data.Comments[i] = commentsDetail{
+ ID: v.ID,
+ Name: v.Author.Name,
+ Parent: v.Parent,
+ Url: v.Author.ProfileUrl,
+ Avatar: v.Author.Avatar.Cache,
+ CreatedAtStr: tools.ConvertStr(v.CreatedAt),
+ Message: v.Message,
+ IsDeleted: v.IsDeleted,
+ }
+ }
+}
+
// handleDisqusPage 评论页
func handleDisqusPage(c *gin.Context) {
array := strings.Split(c.Param("slug"), "|")
@@ -221,7 +275,7 @@ func handleDisqusPage(c *gin.Context) {
}
article := cache.Ei.ArticlesMap[array[0]]
params := gin.H{
- "Titile": "发表评论 | " + config.Conf.BlogApp.Blogger.BTitle,
+ "Titile": "发表评论 | " + cache.Ei.Blogger.BTitle,
"ATitle": article.Title,
"Thread": array[1],
"Slug": article.Slug,
@@ -233,6 +287,78 @@ func handleDisqusPage(c *gin.Context) {
c.Header("Content-Type", "text/html; charset=utf-8")
}
+// 发表评论
+// [thread:[5279901489] parent:[] identifier:[post-troubleshooting-https]
+// next:[] author_name:[你好] author_email:[chenqijing2@163.com] message:[fdsfdsf]]
+type disqusCreate struct {
+ ErrNo int `json:"errno"`
+ ErrMsg string `json:"errmsg"`
+ Data commentsDetail `json:"data"`
+}
+
+type commentsDetail struct {
+ ID string `json:"id"`
+ Parent int `json:"parent"`
+ Name string `json:"name"`
+ Url string `json:"url"`
+ Avatar string `json:"avatar"`
+ CreatedAtStr string `json:"createdAtStr"`
+ Message string `json:"message"`
+ IsDeleted bool `json:"isDeleted"`
+}
+
+// handleDisqusCreate 评论文章
+func handleDisqusCreate(c *gin.Context) {
+ resp := &disqusCreate{}
+ defer c.JSON(http.StatusOK, resp)
+
+ msg := c.PostForm("message")
+ email := c.PostForm("author_name")
+ name := c.PostForm("author_name")
+ thread := c.PostForm("thread")
+ identifier := c.PostForm("identifier")
+ if msg == "" || email == "" || name == "" || thread == "" || identifier == "" {
+ resp.ErrNo = 1
+ resp.ErrMsg = "参数错误"
+ return
+ }
+ logrus.Info("email: %s comments: %s", email, thread)
+
+ comment := internal.PostComment{
+ Message: msg,
+ Parent: c.PostForm("parent"),
+ Thread: thread,
+ AuthorEmail: email,
+ AuthorName: name,
+ Identifier: identifier,
+ IpAddress: c.ClientIP(),
+ }
+ postDetail, err := internal.PostCreate(&comment)
+ if err != nil {
+ logrus.Error("handleDisqusCreate.PostCreate: ", err)
+ resp.ErrNo = 1
+ resp.ErrMsg = "提交评论失败,请重试"
+ return
+ }
+ err = internal.PostApprove(postDetail.Response.ID)
+ if err != nil {
+ logrus.Error("handleDisqusCreate.PostApprove: ", err)
+ resp.ErrNo = 1
+ resp.ErrMsg = "提交评论失败,请重试"
+ }
+ resp.ErrNo = 0
+ resp.Data = commentsDetail{
+ ID: postDetail.Response.ID,
+ Name: name,
+ Parent: postDetail.Response.Parent,
+ Url: postDetail.Response.Author.ProfileUrl,
+ Avatar: postDetail.Response.Author.Avatar.Cache,
+ CreatedAtStr: tools.ConvertStr(postDetail.Response.CreatedAt),
+ Message: postDetail.Response.Message,
+ IsDeleted: postDetail.Response.IsDeleted,
+ }
+}
+
// handleBeaconPage 服务端推送谷歌统计
func handleBeaconPage(c *gin.Context) {
ua := c.Request.UserAgent()
@@ -281,7 +407,7 @@ func renderHTMLHomeLayout(c *gin.Context, name string, data gin.H) {
panic(err)
}
data["LayoutContent"] = htemplate.HTML(buf.String())
- err = htmlTmpl.ExecuteTemplate(c.Writer, "homelayout.html", data)
+ err = htmlTmpl.ExecuteTemplate(c.Writer, "homeLayout.html", data)
if err != nil {
panic(err)
}
diff --git a/pkg/internal/pinger.go b/pkg/internal/pinger.go
index b200b83..5849067 100644
--- a/pkg/internal/pinger.go
+++ b/pkg/internal/pinger.go
@@ -10,12 +10,12 @@ import (
"net/url"
"github.com/eiblog/eiblog/pkg/config"
-
+
"github.com/sirupsen/logrus"
)
// feedrPingFunc http://.superfeedr.com/
-var feedrPingFunc = func(slug string) error {
+var feedrPingFunc = func(btitle, slug string) error {
feedrHost := config.Conf.BlogApp.FeedRPC.FeedrURL
if feedrHost == "" {
return nil
@@ -60,13 +60,13 @@ type rpcValue struct {
}
// rpcPingFunc ping rpc
-var rpcPingFunc = func(slug string) error {
+var rpcPingFunc = func(btitle, slug string) error {
if len(config.Conf.BlogApp.FeedRPC.PingRPC) == 0 {
return nil
}
param := rpcPingParam{MethodName: "weblogUpdates.extendedPing"}
param.Params.Param = [4]rpcValue{
- 0: rpcValue{Value: config.Conf.BlogApp.Blogger.BTitle},
+ 0: rpcValue{Value: btitle},
1: rpcValue{Value: "https://" + config.Conf.BlogApp.Host},
2: rpcValue{Value: fmt.Sprintf("https://%s/post/%s.html", config.Conf.BlogApp.Host, slug)},
3: rpcValue{Value: "https://" + config.Conf.BlogApp.Host + "/rss.html"},
@@ -100,12 +100,12 @@ var rpcPingFunc = func(slug string) error {
}
// PingFunc ping blog article to SE
-func PingFunc(slug string) {
- err := feedrPingFunc(slug)
+func PingFunc(btitle, slug string) {
+ err := feedrPingFunc(btitle, slug)
if err != nil {
logrus.Error("pinger: PingFunc feedr: ", err)
}
- err = rpcPingFunc(slug)
+ err = rpcPingFunc(btitle, slug)
if err != nil {
logrus.Error("pinger: PingFunc: rpc: ", err)
}
diff --git a/tools/tools.go b/tools/tools.go
index 33217cc..ef54b65 100644
--- a/tools/tools.go
+++ b/tools/tools.go
@@ -7,6 +7,7 @@ import (
"io"
"io/ioutil"
"path"
+ "time"
)
// EncryptPasswd encrypt password
@@ -37,3 +38,67 @@ func ReadDirFiles(dir string, filter func(name string) bool) (files []string) {
}
return
}
+
+// 2016-10-22T07:03:01
+const (
+ JUST_NOW = "几秒前"
+ MINUTES_AGO = "%d分钟前"
+ HOURS_AGO = "%d小时前"
+ DAYS_AGO = "%d天前"
+ MONTH_AGO = "%d月前"
+ YEARS_AGO = "%d年前"
+)
+
+// ConvertStr 时间转换为间隔
+func ConvertStr(str string) string {
+ t, err := time.ParseInLocation("2006-01-02T15:04:05", str, time.UTC)
+ if err != nil {
+ return JUST_NOW
+ }
+ now := time.Now().UTC()
+ y1, m1, d1 := t.Date()
+ y2, m2, d2 := now.Date()
+ h1, mi1, s1 := t.Clock()
+ h2, mi2, s2 := now.Clock()
+ if y := y2 - y1; y > 1 || (y == 1 && m2-m1 >= 0) {
+ return fmt.Sprintf(YEARS_AGO, y)
+ } else if m := y*12 + int(m2-m1); m > 1 || (m == 1 && d2-d1 >= 0) {
+ return fmt.Sprintf(MONTH_AGO, m)
+ } else if d := m*dayIn(y1, m1) + d2 - d1; d > 1 || (d == 1 && h2-h1 >= 0) {
+ return fmt.Sprintf(DAYS_AGO, d)
+ } else if h := d*24 + h2 - h1; h > 1 || (h == 1 && mi2-mi1 >= 0) {
+ return fmt.Sprintf(HOURS_AGO, h)
+ } else if mi := h*60 + mi2 - mi1; mi > 1 || (mi == 1 && s2-s1 >= 0) {
+ return fmt.Sprintf(MINUTES_AGO, mi)
+ }
+ return JUST_NOW
+}
+
+// dayIn 获取天数
+func dayIn(year int, m time.Month) int {
+ if m == time.February && isLeapYear(year) {
+ return 29
+ }
+ return monthToDays[m]
+}
+
+// monthToDays 月份转换
+var monthToDays = map[time.Month]int{
+ time.January: 31,
+ time.February: 28,
+ time.March: 31,
+ time.April: 30,
+ time.May: 31,
+ time.June: 30,
+ time.July: 31,
+ time.August: 31,
+ time.September: 30,
+ time.October: 31,
+ time.November: 30,
+ time.December: 31,
+}
+
+// isLeapYear是否是闰年
+func isLeapYear(year int) bool {
+ return year%4 == 0 && (year%100 != 0 || year%400 == 0)
+}
diff --git a/website/homeLayout.html b/website/homeLayout.html
index 20c1d48..7436865 100644
--- a/website/homeLayout.html
+++ b/website/homeLayout.html
@@ -1 +1 @@
-{{.Title}}{{if .Version}}{{end}}{{if .Version}}{{else}}{{end}}{{if .Version}}{{else}}{{end}}{{if .Version}}{{else}}{{end}}{{if .Version}}{{else}}{{end}}{{if .Version}}{{else}}{{end}}
+{{.Title}}{{if .Version}}{{end}}{{if .Version}}{{else}}{{end}}{{if .Version}}{{else}}{{end}}{{if .Version}}{{else}}{{end}}{{if .Version}}{{else}}{{end}}{{if .Version}}{{else}}{{end}}