Compare commits

...

26 Commits

Author SHA1 Message Date
henry.chen
eca741896f chore(release): 2.1.17 2023-01-05 13:28:48 +08:00
henry.chen
17792e5a7e fix: fist comment of disqus error 2023-01-05 13:25:18 +08:00
henry.chen
04289c633e chore: optimize variable naming 2023-01-05 11:07:56 +08:00
henry.chen
3a5eb6fccc chore(release): 2.1.18 2023-01-05 09:17:39 +08:00
henry.chen
f6d8656c83 fix: 1. template read panic
2. optimization variable naming
2023-01-05 09:17:31 +08:00
henry.chen
4690d5123b chore(release): 2.1.17 2023-01-05 00:00:11 +08:00
henry.chen
a9e8e39d34 fix(disqus): failed to commit disqus comments 2023-01-04 23:58:50 +08:00
henry.chen
c51055a0db chore(release): 2.1.16 2022-11-20 23:40:06 +08:00
henry.chen
445b188517 chore: imgtonormal add at xmlTmpl 2022-11-20 23:37:49 +08:00
henry.chen
4bfff2e5e9 fix: rss image path incorrect: data-src -> src 2022-11-20 23:33:10 +08:00
Deepzz
aa91997c0c fix(backup): error path in compressed file 2022-10-14 14:45:15 +08:00
henry.chen
3b2a6689be chore: update 2022-10-01 21:18:53 +08:00
henry.chen
4c46be3f03 chore: update readme 2022-10-01 21:09:53 +08:00
henry.chen
da47e9880f chore: update readme 2022-09-30 09:51:44 +08:00
henry.chen
4f92e0d619 chore(release): 2.1.15 2022-09-28 19:00:36 +08:00
henry.chen
3a8f7d120b chore: rm dns with cgo 2022-09-28 19:00:26 +08:00
Deepzz
cf0a897ad0 chore(app.yml): default db use sqlite 2022-09-28 18:59:28 +08:00
henry.chen
418b604946 chore(release): 2.1.14 2022-09-28 18:20:41 +08:00
henry.chen
b93c320987 fix: cgo and sqlite build in alpine image closed #28 2022-09-28 18:20:38 +08:00
henry.chen
b24f7c0666 chore(release): 2.1.13 2022-09-27 10:50:22 +08:00
Deepzz
efe80fbc6b Update feedTpl.xml 2022-09-21 09:47:50 +08:00
henry.chen
65b89bcae5 chore(release): 2.1.12 2022-08-09 18:41:21 +08:00
henry.chen
289b8145bc fix: #35 no pubDate added for feed generation 2022-08-09 18:40:52 +08:00
henry.chen
bf0ad811ff chore(release): 2.1.11 2022-07-19 22:35:32 +08:00
henry.chen
db00a9b527 chore: upgrade to yaml.v3 close #1 2022-05-27 09:13:22 +08:00
henry.chen
38aa704198 fix(backup): configuration error 2022-05-11 10:16:38 +08:00
25 changed files with 227 additions and 114 deletions

1
.gitignore vendored
View File

@@ -19,3 +19,4 @@ backend
# vendor/ # vendor/
bin bin
assets/*.* assets/*.*
db.sqlite

View File

@@ -2,6 +2,48 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [2.1.17](https://github.com/eiblog/eiblog/compare/v2.1.16...v2.1.17) (2023-01-05)
### Bug Fixes
* 1. template read panic ([f6d8656](https://github.com/eiblog/eiblog/commit/f6d8656c83591584581383643d109611d7ed2caa))
* **disqus:** failed to commit disqus comments ([a9e8e39](https://github.com/eiblog/eiblog/commit/a9e8e39d342488ec46175997f3df9ab109f2fecf))
* fist comment of disqus error ([17792e5](https://github.com/eiblog/eiblog/commit/17792e5a7edb7e84623d9307555e7983ba306565))
### [2.1.16](https://github.com/eiblog/eiblog/compare/v2.1.15...v2.1.16) (2022-11-20)
### Bug Fixes
* **backup:** error path in compressed file ([aa91997](https://github.com/eiblog/eiblog/commit/aa91997c0caca27e9979692879f8877765dabd9d))
* rss image path incorrect: data-src -> src ([4bfff2e](https://github.com/eiblog/eiblog/commit/4bfff2e5e9b0efb4112a5f2f6bc55eebcef1c6eb))
### [2.1.15](https://github.com/eiblog/eiblog/compare/v2.1.14...v2.1.15) (2022-09-28)
### [2.1.14](https://github.com/eiblog/eiblog/compare/v2.1.13...v2.1.14) (2022-09-28)
### Bug Fixes
* cgo and sqlite build in alpine image closed [#28](https://github.com/eiblog/eiblog/issues/28) ([b93c320](https://github.com/eiblog/eiblog/commit/b93c320987a936db6e5ca50c547022de9ab9a0f1))
### [2.1.13](https://github.com/eiblog/eiblog/compare/v2.1.12...v2.1.13) (2022-09-27)
### [2.1.12](https://github.com/eiblog/eiblog/compare/v2.1.11...v2.1.12) (2022-08-09)
### Bug Fixes
* [#35](https://github.com/eiblog/eiblog/issues/35) no pubDate added for feed generation ([289b814](https://github.com/eiblog/eiblog/commit/289b8145bcdabd0060c9a0d5f40f5df69d3882d3))
### [2.1.11](https://github.com/eiblog/eiblog/compare/v2.1.10...v2.1.11) (2022-07-19)
### Bug Fixes
* **backup:** configuration error ([38aa704](https://github.com/eiblog/eiblog/commit/38aa704198070d3e1436b230b40b1deb3e94c5ac))
### [2.1.10](https://github.com/eiblog/eiblog/compare/v2.1.9...v2.1.10) (2022-04-22) ### [2.1.10](https://github.com/eiblog/eiblog/compare/v2.1.9...v2.1.10) (2022-04-22)
### [2.1.9](https://github.com/eiblog/eiblog/compare/v2.1.8...v2.1.9) (2022-02-14) ### [2.1.9](https://github.com/eiblog/eiblog/compare/v2.1.8...v2.1.9) (2022-02-14)

View File

@@ -6,9 +6,43 @@
但它有着部署简单(上线复杂!)的特点,不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(简洁、轻快,安全),等你体验。 但它有着部署简单(上线复杂!)的特点,不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(简洁、轻快,安全),等你体验。
Docker镜像地址
* 博客服务:[deepzz0/eiblog](https://hub.docker.com/r/deepzz0/eiblog)
* 博客搜索:[deepzz0/elasticsearch](https://hub.docker.com/r/deepzz0/elasticsearch)
* 数据备份:[deepzz0/backup](https://hub.docker.com/r/deepzz0/backup)
### 快速体验 ### 快速体验
这里以 mongodb 为例,更多支持的后端存储服务如下: **二进制**
1、下载压缩包到 [这里](https://github.com/eiblog/eiblog/releases) 下载 eiblog非backup 相应系统压缩包,然后解压缩。
2、启动服务`./backend`
**Docker**
```
$ docker run --name eiblog \
-p 9000:9000 \
deepzz0/eiblog:latest
```
**Compose**
参考项目根目录下的 [docker-compose.yml](https://github.com/eiblog/eiblog/blob/v2/docker-compose.yml),修改相关配置:
```
$ docker compose up -d
$ docker-compose up -d
```
然后访问 `localhost:9000` 就可以了,后台地址 `localhost:9000/admin/login`,默认账户密码 `deepzz/deepzz`
> 默认情况下未开启博客搜索 `elasticsearch`,需要的话需要启动 es 服务并修改配置 `app.yml`。
**数据库支持**
| 类型driver | 地址source示例 | | 类型driver | 地址source示例 |
| -------------- | ------------------------------------------------------------ | | -------------- | ------------------------------------------------------------ |
@@ -19,42 +53,6 @@
| sqlserver | sqlserver://user:password@localhost:9930?database=eiblog | | sqlserver | sqlserver://user:password@localhost:9930?database=eiblog |
| clickhouse | tcp://localhost:9000?database=eiblog&username=user&password=password&read_timeout=10&write_timeout=20 | | clickhouse | tcp://localhost:9000?database=eiblog&username=user&password=password&read_timeout=10&write_timeout=20 |
1、启动依赖服务mongodb、elasticsearch
```
$ docker run --name mongodb \
-p 27017:27017 \
-v ${PWD}/mgodb:/data/db \
mongo:3.2
$ docker run --name elasticsearch \
-p 9200:9200 \
-v ${PWD}/esdata:/usr/share/elasticsearch/data \
deepzz0/elasticsearch:2.4.1
```
2、下载压缩包到 [这里](https://github.com/eiblog/eiblog/releases) 下载 eiblog非backup 相应系统压缩包,然后解压缩。
3、修改配置将数据库与ES地址修改为相应地址
```
# 修改 conf/app.yml 数据库连接配置
database:
driver: mongodb
source: mongodb://localhost:27017
# 修改 conf/app.yml ES连接配置如果不启用搜索功能可以置空
eshost: http://localhost:9200
```
4、启动服务
```
./backend
```
然后访问 `localhost:9000` 就可以了,后台地址 `localhost:9000/admin/login`,默认账户密码 `deepzz/deepzz`
### 功能特性 ### 功能特性
本着博客本质用来分享知识的特点,`EiBlog` 不会有较强的定制功能包括主题CDN支持等仅保持常用简单页面与功能 本着博客本质用来分享知识的特点,`EiBlog` 不会有较强的定制功能包括主题CDN支持等仅保持常用简单页面与功能
@@ -85,7 +83,7 @@ eshost: http://localhost:9200
### 博客页面 ### 博客页面
可以容易的看到 [httpsecurityreport](https://httpsecurityreport.com/?report=deepzz.com) 评分`96`[ssllabs](https://www.ssllabs.com/ssltest/analyze.html?d=deepzz.com&latest) 评分`A+`[myssl](https://myssl.com/deepzz.com) 评分`A+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。 可以容易的看到 [ssllabs](https://www.ssllabs.com/ssltest/analyze.html?d=deepzz.com&latest) 评分`A+`[myssl](https://myssl.com/deepzz.com) 评分`A+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
![show-home](./docs/img/show-home.png) ![show-home](./docs/img/show-home.png)
![show-home2](./docs/img/show-home2.png) ![show-home2](./docs/img/show-home2.png)

View File

@@ -30,7 +30,7 @@ func runTimer(endRun chan error) {
} }
func runHTTPServer(endRun chan error) { func runHTTPServer(endRun chan error) {
if !config.Conf.EiBlogApp.EnableHTTP { if !config.Conf.BackupApp.EnableHTTP {
return return
} }
@@ -46,7 +46,7 @@ func runHTTPServer(endRun chan error) {
ping.RegisterRoutes(e) ping.RegisterRoutes(e)
// start // start
address := fmt.Sprintf(":%d", config.Conf.EiBlogApp.HTTPPort) address := fmt.Sprintf(":%d", config.Conf.BackupApp.HTTPPort)
go func() { go func() {
endRun <- e.Run(address) endRun <- e.Run(address)
}() }()

View File

@@ -1,7 +1,7 @@
appname: eiblog appname: eiblog
database: database:
driver: postgres driver: sqlite
source: host=localhost port=5432 user=postgres dbname=eiblog sslmode=disable password=MTI3LjAuMC4x source: ./db.sqlite
eshost: eshost:
eiblogapp: eiblogapp:
mode: mode:

View File

@@ -21,7 +21,6 @@ services:
- elasticsearch - elasticsearch
- mongodb - mongodb
environment: environment:
- GODEBUG=netdns=cgo
- RUN_MODE=prod - RUN_MODE=prod
ports: ports:
- 127.0.0.1:9000:9000 - 127.0.0.1:9000:9000

3
go.mod
View File

@@ -3,7 +3,6 @@ module github.com/eiblog/eiblog
go 1.15 go 1.15
require ( require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/eiblog/blackfriday v0.0.0-20161010144836-c0ec111761ae github.com/eiblog/blackfriday v0.0.0-20161010144836-c0ec111761ae
github.com/gin-contrib/sessions v0.0.3 github.com/gin-contrib/sessions v0.0.3
github.com/gin-gonic/gin v1.7.4 github.com/gin-gonic/gin v1.7.4
@@ -17,7 +16,7 @@ require (
go.mongodb.org/mongo-driver v1.5.1 go.mongodb.org/mongo-driver v1.5.1
google.golang.org/grpc v1.35.0 google.golang.org/grpc v1.35.0
google.golang.org/protobuf v1.25.0 google.golang.org/protobuf v1.25.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gorm.io/driver/clickhouse v0.1.0 gorm.io/driver/clickhouse v0.1.0
gorm.io/driver/mysql v1.0.6 gorm.io/driver/mysql v1.0.6
gorm.io/driver/postgres v1.0.8 gorm.io/driver/postgres v1.0.8

2
go.sum
View File

@@ -8,8 +8,6 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=

12
pkg/cache/cache.go vendored
View File

@@ -25,9 +25,11 @@ var (
// Ei eiblog cache // Ei eiblog cache
Ei *Cache Ei *Cache
// regenerate pages chan // PagesCh regenerate pages chan
PagesCh = make(chan string, 2) PagesCh = make(chan string, 2)
PageSeries = "series-md" // PageSeries the page series regenerate flag
PageSeries = "series-md"
// PageArchive the page archive regenerate flag
PageArchive = "archive-md" PageArchive = "archive-md"
// ArticleStartID article start id // ArticleStartID article start id
@@ -518,7 +520,7 @@ func (c *Cache) regeneratePages() {
} }
buf.WriteString("\n") buf.WriteString("\n")
} }
c.PageSeries = string(render.RenderPage(buf.Bytes())) c.PageSeries = string(render.PageRender(buf.Bytes()))
case PageArchive: case PageArchive:
sort.Sort(c.Archives) sort.Sort(c.Archives)
buf := bytes.Buffer{} buf := bytes.Buffer{}
@@ -551,7 +553,7 @@ func (c *Cache) regeneratePages() {
} }
} }
} }
c.PageArchives = string(render.RenderPage(buf.Bytes())) c.PageArchives = string(render.PageRender(buf.Bytes()))
} }
} }
} }

View File

@@ -14,7 +14,7 @@ import (
// blackfriday 配置 // blackfriday 配置
const ( const (
commonHtmlFlags = 0 | commonHTMLFlags = 0 |
blackfriday.HTML_TOC | blackfriday.HTML_TOC |
blackfriday.HTML_USE_XHTML | blackfriday.HTML_USE_XHTML |
blackfriday.HTML_USE_SMARTYPANTS | blackfriday.HTML_USE_SMARTYPANTS |
@@ -42,9 +42,9 @@ var (
regHeader = regexp.MustCompile("</nav></div>") regHeader = regexp.MustCompile("</nav></div>")
) )
// RenderPage 渲染markdown // PageRender 渲染markdown
func RenderPage(md []byte) []byte { func PageRender(md []byte) []byte {
renderer := blackfriday.HtmlRenderer(commonHtmlFlags, "", "") renderer := blackfriday.HtmlRenderer(commonHTMLFlags, "", "")
return blackfriday.Markdown(md, renderer, commonExtensions) return blackfriday.Markdown(md, renderer, commonExtensions)
} }
@@ -56,12 +56,12 @@ func GenerateExcerptMarkdown(article *model.Article) {
index := strings.Index(article.Content, "\r\n") index := strings.Index(article.Content, "\r\n")
prefix := article.Content[len(blogapp.General.DescPrefix):index] prefix := article.Content[len(blogapp.General.DescPrefix):index]
article.Desc = tools.IgnoreHtmlTag(prefix) article.Desc = tools.IgnoreHTMLTag(prefix)
article.Content = article.Content[index:] article.Content = article.Content[index:]
} }
// 查找目录 // 查找目录
content := RenderPage([]byte(article.Content)) content := PageRender([]byte(article.Content))
index := regHeader.FindIndex(content) index := regHeader.FindIndex(content)
if index != nil { if index != nil {
article.Header = string(content[0:index[1]]) article.Header = string(content[0:index[1]])
@@ -73,7 +73,7 @@ func GenerateExcerptMarkdown(article *model.Article) {
// excerpt // excerpt
index = regIdentifier.FindStringIndex(article.Content) index = regIdentifier.FindStringIndex(article.Content)
if index != nil { if index != nil {
article.Excerpt = tools.IgnoreHtmlTag(article.Content[:index[0]]) article.Excerpt = tools.IgnoreHTMLTag(article.Content[:index[0]])
return return
} }
uc := []rune(article.Content) uc := []rune(article.Content)
@@ -81,5 +81,5 @@ func GenerateExcerptMarkdown(article *model.Article) {
if len(uc) < length { if len(uc) < length {
length = len(uc) length = len(uc)
} }
article.Excerpt = tools.IgnoreHtmlTag(string(uc[0:length])) article.Excerpt = tools.IgnoreHTMLTag(string(uc[0:length]))
} }

View File

@@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
var ( var (

View File

@@ -44,7 +44,7 @@ func backupFromMongoDB(now time.Time) error {
} }
// tar // tar
name := fmt.Sprintf("eiblog-%s.tar.gz", now.Format("2006-01-02")) name := fmt.Sprintf("eiblog-%s.tar.gz", now.Format("2006-01-02"))
arg = fmt.Sprintf("tar czf /tmp/%s /tmp/eiblog", name) arg = fmt.Sprintf("tar czf /tmp/%s -C /tmp eiblog", name)
cmd = exec.CommandContext(ctx, "sh", "-c", arg) cmd = exec.CommandContext(ctx, "sh", "-c", arg)
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {

View File

@@ -301,7 +301,7 @@ func handleAPIPostCreate(c *gin.Context) {
} }
// 旧文章 // 旧文章
article.ID = cid article.ID = cid
artc, _ := cache.Ei.FindArticleByID(article.ID) artc, _ := cache.Ei.FindArticleByID(article.ID) // cache
if artc != nil { if artc != nil {
article.IsDraft = false article.IsDraft = false
article.Count = artc.Count article.Count = artc.Count

View File

@@ -21,7 +21,8 @@ func init() {
var err error var err error
xmlTmpl, err = template.New("").Funcs(template.FuncMap{ xmlTmpl, err = template.New("").Funcs(template.FuncMap{
"dateformat": tools.DateFormat, "dateformat": tools.DateFormat,
"imgtonormal": tools.ImgToNormal,
}).ParseGlob(root) }).ParseGlob(root)
if err != nil { if err != nil {
panic(err) panic(err)

View File

@@ -50,6 +50,7 @@ func handleAdminProfile(c *gin.Context) {
renderHTMLAdminLayout(c, "admin-profile", params) renderHTMLAdminLayout(c, "admin-profile", params)
} }
// T tag struct
type T struct { type T struct {
ID string `json:"id"` ID string `json:"id"`
Tags string `json:"tags"` Tags string `json:"tags"`

View File

@@ -3,6 +3,7 @@ package page
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
htemplate "html/template" htemplate "html/template"
"io/ioutil" "io/ioutil"
@@ -198,7 +199,8 @@ func handleDisqusList(c *gin.Context) {
slug := c.Param("slug") slug := c.Param("slug")
cursor := c.Query("cursor") cursor := c.Query("cursor")
if artc := cache.Ei.ArticlesMap[slug]; artc != nil { artc := cache.Ei.ArticlesMap[slug]
if artc != nil {
dcs.Data.Thread = artc.Thread dcs.Data.Thread = artc.Thread
} }
postsList, err := internal.PostsList(slug, cursor) postsList, err := internal.PostsList(slug, cursor)
@@ -222,13 +224,25 @@ func handleDisqusList(c *gin.Context) {
ID: v.ID, ID: v.ID,
Name: v.Author.Name, Name: v.Author.Name,
Parent: v.Parent, Parent: v.Parent,
Url: v.Author.ProfileUrl, URL: v.Author.ProfileURL,
Avatar: v.Author.Avatar.Cache, Avatar: v.Author.Avatar.Cache,
CreatedAtStr: tools.ConvertStr(v.CreatedAt), CreatedAtStr: tools.ConvertStr(v.CreatedAt),
Message: v.Message, Message: v.Message,
IsDeleted: v.IsDeleted, IsDeleted: v.IsDeleted,
} }
} }
// query thread & update
if artc != nil && artc.Thread == "" {
if dcs.Data.Thread != "" {
artc.Thread = dcs.Data.Thread
} else if internal.ThreadDetails(artc) == nil {
dcs.Data.Thread = artc.Thread
}
cache.Ei.UpdateArticle(context.Background(), artc.ID,
map[string]interface{}{
"thread": artc.Thread,
})
}
} }
// handleDisqusPage 评论页 // handleDisqusPage 评论页
@@ -261,7 +275,7 @@ type commentsDetail struct {
ID string `json:"id"` ID string `json:"id"`
Parent int `json:"parent"` Parent int `json:"parent"`
Name string `json:"name"` Name string `json:"name"`
Url string `json:"url"` URL string `json:"url"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
CreatedAtStr string `json:"createdAtStr"` CreatedAtStr string `json:"createdAtStr"`
Message string `json:"message"` Message string `json:"message"`
@@ -274,7 +288,7 @@ func handleDisqusCreate(c *gin.Context) {
defer c.JSON(http.StatusOK, resp) defer c.JSON(http.StatusOK, resp)
msg := c.PostForm("message") msg := c.PostForm("message")
email := c.PostForm("author_name") email := c.PostForm("author_email")
name := c.PostForm("author_name") name := c.PostForm("author_name")
thread := c.PostForm("thread") thread := c.PostForm("thread")
identifier := c.PostForm("identifier") identifier := c.PostForm("identifier")
@@ -283,7 +297,7 @@ func handleDisqusCreate(c *gin.Context) {
resp.ErrMsg = "参数错误" resp.ErrMsg = "参数错误"
return return
} }
logrus.Info("email: %s comments: %s", email, thread) logrus.Infof("email: %s comments: %s", email, thread)
comment := internal.PostComment{ comment := internal.PostComment{
Message: msg, Message: msg,
@@ -292,7 +306,7 @@ func handleDisqusCreate(c *gin.Context) {
AuthorEmail: email, AuthorEmail: email,
AuthorName: name, AuthorName: name,
Identifier: identifier, Identifier: identifier,
IpAddress: c.ClientIP(), IPAddress: c.ClientIP(),
} }
postDetail, err := internal.PostCreate(&comment) postDetail, err := internal.PostCreate(&comment)
if err != nil { if err != nil {
@@ -312,7 +326,7 @@ func handleDisqusCreate(c *gin.Context) {
ID: postDetail.Response.ID, ID: postDetail.Response.ID,
Name: name, Name: name,
Parent: postDetail.Response.Parent, Parent: postDetail.Response.Parent,
Url: postDetail.Response.Author.ProfileUrl, URL: postDetail.Response.Author.ProfileURL,
Avatar: postDetail.Response.Author.Avatar.Cache, Avatar: postDetail.Response.Author.Avatar.Cache,
CreatedAtStr: tools.ConvertStr(postDetail.Response.CreatedAt), CreatedAtStr: tools.ConvertStr(postDetail.Response.CreatedAt),
Message: postDetail.Response.Message, Message: postDetail.Response.Message,

View File

@@ -2,6 +2,7 @@
package page package page
import ( import (
"io/fs"
"path/filepath" "path/filepath"
"text/template" "text/template"
@@ -17,10 +18,15 @@ var htmlTmpl *template.Template
func init() { func init() {
htmlTmpl = template.New("eiblog").Funcs(tools.TplFuncMap) htmlTmpl = template.New("eiblog").Funcs(tools.TplFuncMap)
root := filepath.Join(config.WorkDir, "website") root := filepath.Join(config.WorkDir, "website")
files := tools.ReadDirFiles(root, func(name string) bool { files := tools.ReadDirFiles(root, func(fi fs.FileInfo) bool {
name := fi.Name()
if name == ".DS_Store" { if name == ".DS_Store" {
return true return true
} }
// should not read template dir
if fi.IsDir() && name == "template" {
return true
}
return false return false
}) })
_, err := htmlTmpl.ParseFiles(files...) _, err := htmlTmpl.ParseFiles(files...)

View File

@@ -16,11 +16,12 @@ import (
// disqus api // disqus api
const ( const (
apiPostsCount = "https://disqus.com/api/3.0/threads/set.json" apiPostsCount = "https://disqus.com/api/3.0/threads/set.json"
apiPostsList = "https://disqus.com/api/3.0/threads/listPosts.json" apiPostsList = "https://disqus.com/api/3.0/threads/listPosts.json"
apiPostCreate = "https://disqus.com/api/3.0/posts/create.json" apiPostCreate = "https://disqus.com/api/3.0/posts/create.json"
apiPostApprove = "https://disqus.com/api/3.0/posts/approve.json" apiPostApprove = "https://disqus.com/api/3.0/posts/approve.json"
apiThreadCreate = "https://disqus.com/api/3.0/threads/create.json" apiThreadCreate = "https://disqus.com/api/3.0/threads/create.json"
apiThreadDetails = "https://disqus.com/api/3.0/threads/details.json"
) )
func checkDisqusConfig() error { func checkDisqusConfig() error {
@@ -95,8 +96,8 @@ func PostsCount(articles map[string]*model.Article) error {
return nil return nil
} }
// postsListResp 获取评论列表 // PostsListResp 获取评论列表
type postsListResp struct { type PostsListResp struct {
Cursor struct { Cursor struct {
HasNext bool HasNext bool
Next string Next string
@@ -113,7 +114,7 @@ type postDetail struct {
IsDeleted bool IsDeleted bool
Author struct { Author struct {
Name string Name string
ProfileUrl string ProfileURL string
Avatar struct { Avatar struct {
Cache string Cache string
} }
@@ -122,7 +123,7 @@ type postDetail struct {
} }
// PostsList 评论列表 // PostsList 评论列表
func PostsList(slug, cursor string) (*postsListResp, error) { func PostsList(slug, cursor string) (*PostsListResp, error) {
if err := checkDisqusConfig(); err != nil { if err := checkDisqusConfig(); err != nil {
return nil, err return nil, err
} }
@@ -148,7 +149,7 @@ func PostsList(slug, cursor string) (*postsListResp, error) {
return nil, errors.New(string(b)) return nil, errors.New(string(b))
} }
result := &postsListResp{} result := &PostsListResp{}
err = json.Unmarshal(b, result) err = json.Unmarshal(b, result)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -163,18 +164,19 @@ type PostComment struct {
Thread string Thread string
AuthorEmail string AuthorEmail string
AuthorName string AuthorName string
IpAddress string IPAddress string
Identifier string Identifier string
UserAgent string UserAgent string
} }
type postCreateResp struct { // PostCreateResp create comments resp
type PostCreateResp struct {
Code int Code int
Response postDetail Response postDetail
} }
// PostCreate 评论文章 // PostCreate 评论文章
func PostCreate(pc *PostComment) (*postCreateResp, error) { func PostCreate(pc *PostComment) (*PostCreateResp, error) {
if err := checkDisqusConfig(); err != nil { if err := checkDisqusConfig(); err != nil {
return nil, err return nil, err
} }
@@ -201,7 +203,7 @@ func PostCreate(pc *PostComment) (*postCreateResp, error) {
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, errors.New(string(b)) return nil, errors.New(string(b))
} }
result := &postCreateResp{} result := &PostCreateResp{}
err = json.Unmarshal(b, result) err = json.Unmarshal(b, result)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -294,3 +296,46 @@ func ThreadCreate(article *model.Article, btitle string) error {
article.Thread = result.Response.ID article.Thread = result.Response.ID
return nil return nil
} }
// threadDetailsResp thread info
type threadDetailsResp struct {
Code int
Response struct {
ID string
}
}
// ThreadDetails thread详细
func ThreadDetails(article *model.Article) error {
if err := checkDisqusConfig(); err != nil {
return err
}
vals := url.Values{}
vals.Set("api_key", config.Conf.EiBlogApp.Disqus.PublicKey)
vals.Set("access_token", config.Conf.EiBlogApp.Disqus.AccessToken)
vals.Set("forum", config.Conf.EiBlogApp.Disqus.ShortName)
vals.Set("thread:ident", "post-"+article.Slug)
resp, err := httpPost(apiThreadDetails, vals)
if err != nil {
return err
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New(string(b))
}
result := &threadDetailsResp{}
err = json.Unmarshal(b, result)
if err != nil {
return err
}
article.Thread = result.Response.ID
return nil
}

View File

@@ -49,7 +49,7 @@ func checkESConfig() error {
} }
// ElasticSearch 搜索文章 // ElasticSearch 搜索文章
func ElasticSearch(query string, size, from int) (*searchIndexResult, error) { func ElasticSearch(query string, size, from int) (*SearchIndexResult, error) {
if err := checkESConfig(); err != nil { if err := checkESConfig(); err != nil {
return nil, err return nil, err
} }
@@ -114,7 +114,7 @@ func ElasticAddIndex(article *model.Article) error {
img := tools.PickFirstImage(article.Content) img := tools.PickFirstImage(article.Content)
mapping := map[string]interface{}{ mapping := map[string]interface{}{
"title": article.Title, "title": article.Title,
"content": tools.IgnoreHtmlTag(article.Content), "content": tools.IgnoreHTMLTag(article.Content),
"slug": article.Slug, "slug": article.Slug,
"tag": article.Tags, "tag": article.Tags,
"img": img, "img": img,
@@ -241,8 +241,8 @@ func deleteIndexDocument(index, typ string, ids []string) error {
return nil return nil
} }
// searchIndexResult 查询结果 // SearchIndexResult 查询结果
type searchIndexResult struct { type SearchIndexResult struct {
Took float32 `json:"took"` Took float32 `json:"took"`
Hits struct { Hits struct {
Total int `json:"total"` Total int `json:"total"`
@@ -264,7 +264,7 @@ type searchIndexResult struct {
} }
// indexQueryDSL 语句查询文档 // indexQueryDSL 语句查询文档
func indexQueryDSL(index, typ string, size, from int, dsl []byte) (*searchIndexResult, error) { func indexQueryDSL(index, typ string, size, from int, dsl []byte) (*SearchIndexResult, error) {
rawurl := fmt.Sprintf("%s/%s/%s/_search?size=%d&from=%d", config.Conf.ESHost, rawurl := fmt.Sprintf("%s/%s/%s/_search?size=%d&from=%d", config.Conf.ESHost,
index, typ, size, from) index, typ, size, from)
resp, err := httpPost(rawurl, dsl) resp, err := httpPost(rawurl, dsl)
@@ -276,7 +276,7 @@ func indexQueryDSL(index, typ string, size, from int, dsl []byte) (*searchIndexR
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := &searchIndexResult{} result := &SearchIndexResult{}
err = json.Unmarshal(data, result) err = json.Unmarshal(data, result)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -20,6 +20,7 @@ type Article struct {
SerieID int `gorm:"column:serie_id;not null" bson:"serie_id"` // 专题ID SerieID int `gorm:"column:serie_id;not null" bson:"serie_id"` // 专题ID
Tags pq.StringArray `gorm:"column:tags;type:text[]" bson:"tags"` // tags Tags pq.StringArray `gorm:"column:tags;type:text[]" bson:"tags"` // tags
IsDraft bool `gorm:"column:is_draft;not null" bson:"is_draft"` // 是否是草稿 IsDraft bool `gorm:"column:is_draft;not null" bson:"is_draft"` // 是否是草稿
Thread string `gorm:"column:thread" bson:"thread"` // disqus thread
DeletedAt time.Time `gorm:"column:deleted_at;not null" bson:"deleted_at"` // 删除时间 DeletedAt time.Time `gorm:"column:deleted_at;not null" bson:"deleted_at"` // 删除时间
UpdatedAt time.Time `gorm:"column:updated_at;default:current_timestamp" bson:"updated_at"` // 更新时间 UpdatedAt time.Time `gorm:"column:updated_at;default:current_timestamp" bson:"updated_at"` // 更新时间
@@ -28,7 +29,6 @@ type Article struct {
Header string `gorm:"-" bson:"-"` // header Header string `gorm:"-" bson:"-"` // header
Excerpt string `gorm:"-" bson:"-"` // 预览信息 Excerpt string `gorm:"-" bson:"-"` // 预览信息
Desc string `gorm:"-" bson:"-"` // 描述 Desc string `gorm:"-" bson:"-"` // 描述
Thread string `gorm:"-" bson:"-"` // disqus thread
Prev *Article `gorm:"-" bson:"-"` // 上篇文章 Prev *Article `gorm:"-" bson:"-"` // 上篇文章
Next *Article `gorm:"-" bson:"-"` // 下篇文章 Next *Article `gorm:"-" bson:"-"` // 下篇文章
} }

View File

@@ -10,8 +10,8 @@ for file in pkg/core/*; do
# tar platform # tar platform
for os in linux darwin windows; do for os in linux darwin windows; do
_target="$app-$_tag.$os-$_arch.tar.gz" _target="$app-$_tag.$os-$_arch.tar.gz"
CGO_ENABLED=0 GOOS=$os GOARCH=$_arch \ GOOS=$os GOARCH=$_arch \
go build -tags prod -o backend "./cmd/$app" go build -tags prod -ldflags '-extldflags "-static"' -o backend "./cmd/$app"
if [ "$app" = "eiblog" ]; then if [ "$app" = "eiblog" ]; then
tar czf $_target conf website assets backend tar czf $_target conf website assets backend
else else

View File

@@ -20,7 +20,7 @@ mkdir -p ./bin
# build demo app # build demo app
for file in pkg/core/*; do for file in pkg/core/*; do
app="$(basename $file)"; app="$(basename $file)";
CGO_ENABLED=0 go build -tags prod -o bin/backend "./cmd/$app" go build -tags prod -ldflags '-extldflags "-static"' -o bin/backend "./cmd/$app"
# docker image # docker image
docker buildx build --platform "$_platform" \ docker buildx build --platform "$_platform" \
-f "build/package/$app.Dockerfile" \ -f "build/package/$app.Dockerfile" \

View File

@@ -73,3 +73,8 @@ func GetAvatar(domain string) string {
return avatar return avatar
} }
// ImgToNormal replace lazy image attr data-src to src
func ImgToNormal(content string) string {
return strings.ReplaceAll(content, "data-src=", "src=")
}

View File

@@ -5,6 +5,7 @@ import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"io" "io"
"io/fs"
"io/ioutil" "io/ioutil"
"path" "path"
"regexp" "regexp"
@@ -22,13 +23,13 @@ func EncryptPasswd(name, pass string) string {
} }
// ReadDirFiles 读取目录 // ReadDirFiles 读取目录
func ReadDirFiles(dir string, filter func(name string) bool) (files []string) { func ReadDirFiles(dir string, filter func(fi fs.FileInfo) bool) (files []string) {
fileInfos, err := ioutil.ReadDir(dir) fileInfos, err := ioutil.ReadDir(dir)
if err != nil { if err != nil {
return return
} }
for _, fi := range fileInfos { for _, fi := range fileInfos {
if filter(fi.Name()) { if filter(fi) {
continue continue
} }
if fi.IsDir() { if fi.IsDir() {
@@ -42,19 +43,19 @@ func ReadDirFiles(dir string, filter func(name string) bool) (files []string) {
// 2016-10-22T07:03:01 // 2016-10-22T07:03:01
const ( const (
JUST_NOW = "几秒前" JustNow = "几秒前"
MINUTES_AGO = "%d分钟前" MinutesAgo = "%d分钟前"
HOURS_AGO = "%d小时前" HoursAgo = "%d小时前"
DAYS_AGO = "%d天前" DaysAgo = "%d天前"
MONTH_AGO = "%d月前" MonthAgo = "%d月前"
YEARS_AGO = "%d年前" YearsAgo = "%d年前"
) )
// ConvertStr 时间转换为间隔 // ConvertStr 时间转换为间隔
func ConvertStr(str string) string { func ConvertStr(str string) string {
t, err := time.ParseInLocation("2006-01-02T15:04:05", str, time.UTC) t, err := time.ParseInLocation("2006-01-02T15:04:05", str, time.UTC)
if err != nil { if err != nil {
return JUST_NOW return JustNow
} }
now := time.Now().UTC() now := time.Now().UTC()
y1, m1, d1 := t.Date() y1, m1, d1 := t.Date()
@@ -62,17 +63,17 @@ func ConvertStr(str string) string {
h1, mi1, s1 := t.Clock() h1, mi1, s1 := t.Clock()
h2, mi2, s2 := now.Clock() h2, mi2, s2 := now.Clock()
if y := y2 - y1; y > 1 || (y == 1 && m2-m1 >= 0) { if y := y2 - y1; y > 1 || (y == 1 && m2-m1 >= 0) {
return fmt.Sprintf(YEARS_AGO, y) return fmt.Sprintf(YearsAgo, y)
} else if m := y*12 + int(m2-m1); m > 1 || (m == 1 && d2-d1 >= 0) { } else if m := y*12 + int(m2-m1); m > 1 || (m == 1 && d2-d1 >= 0) {
return fmt.Sprintf(MONTH_AGO, m) return fmt.Sprintf(MonthAgo, m)
} else if d := m*dayIn(y1, m1) + d2 - d1; d > 1 || (d == 1 && h2-h1 >= 0) { } else if d := m*dayIn(y1, m1) + d2 - d1; d > 1 || (d == 1 && h2-h1 >= 0) {
return fmt.Sprintf(DAYS_AGO, d) return fmt.Sprintf(DaysAgo, d)
} else if h := d*24 + h2 - h1; h > 1 || (h == 1 && mi2-mi1 >= 0) { } else if h := d*24 + h2 - h1; h > 1 || (h == 1 && mi2-mi1 >= 0) {
return fmt.Sprintf(HOURS_AGO, h) return fmt.Sprintf(HoursAgo, h)
} else if mi := h*60 + mi2 - mi1; mi > 1 || (mi == 1 && s2-s1 >= 0) { } else if mi := h*60 + mi2 - mi1; mi > 1 || (mi == 1 && s2-s1 >= 0) {
return fmt.Sprintf(MINUTES_AGO, mi) return fmt.Sprintf(MinutesAgo, mi)
} }
return JUST_NOW return JustNow
} }
// dayIn 获取天数 // dayIn 获取天数
@@ -120,8 +121,8 @@ var (
regexpEnter = regexp.MustCompile(`\s+`) regexpEnter = regexp.MustCompile(`\s+`)
) )
// IgnoreHtmlTag 去掉 html tag // IgnoreHTMLTag 去掉 html tag
func IgnoreHtmlTag(src string) string { func IgnoreHTMLTag(src string) string {
// 去除所有尖括号内的HTML代码 // 去除所有尖括号内的HTML代码
src = regexpBrackets.ReplaceAllString(src, "") src = regexpBrackets.ReplaceAllString(src, "")
// 去除换行符 // 去除换行符

View File

@@ -15,8 +15,9 @@
<comments>https://{{$.Host}}/post/{{.Slug}}.html#comments</comments> <comments>https://{{$.Host}}/post/{{.Slug}}.html#comments</comments>
<guid>https://{{$.Host}}/post/{{.Slug}}.html</guid> <guid>https://{{$.Host}}/post/{{.Slug}}.html</guid>
<description> <description>
<![CDATA[<blockquote>{{.Content}}<p>本文链接:<a href="https://{{$.Host}}/post/{{.Slug}}.html">https://{{$.Host}}/post/{{.Slug}}.html</a><a href="https://{{$.Host}}/post/{{.Slug}}.html#comments">参与评论 »</a></p>]]> <![CDATA[{{imgtonormal .Content}}<p>本文链接:<a href="https://{{$.Host}}/post/{{.Slug}}.html">https://{{$.Host}}/post/{{.Slug}}.html</a><a href="https://{{$.Host}}/post/{{.Slug}}.html#comments">参与评论 »</a></p>]]>
</description> </description>
<pubDate>{{dateformat .CreatedAt "Mon, 02 Jan 2006 15:04:05 -0700"}}</pubDate>
</item> </item>
{{end}} {{end}}
</channel> </channel>