Compare commits

..

161 Commits

Author SHA1 Message Date
henry.chen
42b106d582 chore(release): 2.2.1 2023-05-17 15:49:04 +08:00
henry.chen
bb06be36fe fix: try to fix backup symbol not found 2023-05-17 15:49:01 +08:00
henry.chen
cb3fb2d2e7 chore(release): 2.2.0 2023-05-17 14:47:23 +08:00
henry.chen
779a23cb75 feat(backup): add restore flag 2023-05-17 14:42:28 +08:00
Deepzz
e2fa96cd62 Update docker-compose.yml 2023-05-15 08:58:56 +08:00
Deepzz
db26fb51e5 Merge pull request #37 from eiblog/dependabot/go_modules/github.com/gin-gonic/gin-1.9.0
chore(deps): bump github.com/gin-gonic/gin from 1.7.7 to 1.9.0
2023-05-06 09:09:41 +08:00
dependabot[bot]
994be5d508 chore(deps): bump github.com/gin-gonic/gin from 1.7.7 to 1.9.0
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.7.7 to 1.9.0.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.7.7...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-05 02:39:43 +00:00
henry.chen
a5c3d33565 chore: OpenSearch support mozilla firefox 2023-02-24 10:41:13 +08:00
Deepzz
1ffc58eccf Merge pull request #36 from eiblog/dependabot/go_modules/github.com/gin-gonic/gin-1.7.7
chore(deps): bump github.com/gin-gonic/gin from 1.7.4 to 1.7.7
2023-02-10 09:08:56 +08:00
dependabot[bot]
b6ad4e8949 chore(deps): bump github.com/gin-gonic/gin from 1.7.4 to 1.7.7
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.7.4 to 1.7.7.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.7.4...v1.7.7)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-09 22:49:33 +00:00
henry.chen
41704917db chore(release): 2.1.18 2023-01-05 14:15:54 +08:00
henry.chen
f6ba716f55 fix(backup): can not execute 2023-01-05 14:15:46 +08:00
henry.chen
b2e6c168c5 fix: disqus api using http post 2023-01-05 13:51:18 +08:00
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
henry.chen
9c58447e3b chore(release): 2.1.10 2022-04-22 13:45:50 +08:00
Deepzz
34fc5f368c Update release.yml 2022-04-22 13:44:03 +08:00
Deepzz
daa561e67e Update eiblog.conf 2022-03-22 18:16:12 +08:00
henry.chen
364d293660 chore(release): 2.1.9 2022-02-14 17:50:42 +08:00
henry.chen
5170844623 fix(eiblog): comments title error 2022-01-10 15:50:13 +08:00
henry.chen
7047a3599d chore(docs): add docs images 2022-01-06 09:33:06 +08:00
Deepzz
d0c830c1e6 Create FUNDING.yml 2021-12-15 09:20:34 +08:00
henry.chen
bf699e4957 chore(release): 2.1.8 2021-11-18 10:47:10 +08:00
henry.chen
c315737871 chore: clean alpine respositories 2021-11-18 10:47:06 +08:00
henry.chen
d844c0e8f8 chore(release): 2.1.7 2021-11-13 22:21:10 +08:00
henry.chen
f6cb55c00f chore: update swag version & change uuid package 2021-11-13 22:20:48 +08:00
henry.chen
a5292027c0 chore(release): 2.1.6 2021-11-12 15:31:14 +08:00
henry.chen
00cf0b5c9f fix(backup): backup to qiniu, app.yml->validity change to int 2021-11-12 15:30:43 +08:00
henry.chen
6805afa759 chore(release): 2.1.5 2021-10-27 09:07:50 +08:00
henry.chen
4f6f85a85a fix(release): golang env 2021-10-27 09:06:37 +08:00
henry.chen
dc3e6659b5 chore(release): 2.1.4 2021-10-26 16:18:21 +08:00
henry.chen
ca74d13598 chore(release): 2.1.4 2021-10-26 16:18:00 +08:00
henry.chen
82fba0ddb4 fix(release): github action runner 2021-10-26 16:17:50 +08:00
henry.chen
2b6bae1f74 chore(release): 2.1.3 2021-10-15 16:54:47 +08:00
Deepzz
6387412776 Update README.md 2021-09-14 21:32:37 +08:00
razeen
cfaa25e123 fix(auto_save): fix auto save return empty id 2021-08-15 03:51:45 -07:00
Deepzz
6ff6acdbda Merge pull request #32 from eiblog/qiniu_zone
qiniu upload remove zone, support all zone upload
2021-08-12 09:19:56 +08:00
razeen
b3b3be448f chore(qiniu): qiniu upload remove zone, support all zone upload 2021-08-11 08:31:04 -07:00
henry.chen
78f5bfc1ce fix: backup app judge db driver 2021-08-11 18:44:35 +08:00
Deepzz
4b9eb1ff4d Merge pull request #29 from eiblog/fix_init_password
初始化地方参数错了。。。
2021-08-10 16:17:10 +08:00
Razeen
7d04b8f5c0 fix(pwd): fix init pwd 2021-08-10 10:37:42 +08:00
henry.chen
d000090e30 chore(release): 2.1.2 2021-07-27 17:19:04 +08:00
henry.chen
56e396f252 chore: restore app.yml database 2021-07-27 17:18:58 +08:00
henry.chen
f6662fa4c5 chore(release): 2.1.1 2021-07-27 09:15:17 +08:00
henry.chen
a570c783f3 chore(gin): upgrade gin to fix CVE-2020-28483 2021-07-27 09:15:03 +08:00
henry.chen
e2046d0d39 fix: sqlite error 2021-07-25 10:45:33 +08:00
henry.chen
cdbe082764 chore(docs): update backup.md 2021-07-14 12:10:40 +08:00
henry.chen
3a86c6a65c chore: clean go.sum 2021-07-14 10:57:37 +08:00
henry.chen
c1c9e6025a chore: add backup app 2021-07-14 10:54:30 +08:00
deepzz0
a15791a792 chore: clean dir 2021-05-27 13:54:12 +08:00
deepzz0
ea87f4b2e6 chore: update readme 2021-05-17 10:48:22 +08:00
deepzz0
a0653cf67f chore: update README.md 2021-05-16 11:22:13 +08:00
deepzz0
397d47bc00 fix: excerpt for es 2021-05-11 22:55:20 +08:00
deepzz0
00d9df4406 chore(release): 2.0.6 2021-05-08 11:22:28 +08:00
deepzz0
2b277bd901 fix: workdir loop 2021-05-08 11:19:31 +08:00
deepzz0
c18f3b7da9 fix: workdir path error 2021-05-08 10:54:21 +08:00
deepzz0
79ac024312 fix: archive page style 2021-05-07 16:28:26 +08:00
deepzz0
1e4c0afc19 fix: archive & serie 2021-05-07 16:25:34 +08:00
deepzz0
055d7d2164 fix: env PATH 2021-05-07 15:24:57 +08:00
deepzz0
ed7c510744 chore: update docs 2021-05-07 15:18:02 +08:00
deepzz0
488c0f04fe chore: mv conf/tpl->website/template 2021-05-07 15:17:20 +08:00
deepzz0
a99ed9504b chore: add adsense 2021-05-07 15:11:32 +08:00
deepzz0
6cde9323b1 chore: adjust config 2021-05-07 14:58:07 +08:00
deepzz0
2b226977ab chore: update eiblog.conf 2021-05-07 14:28:22 +08:00
deepzz0
aa076e5e3c fix: mongodb current close 2021-05-06 22:50:12 +08:00
deepzz0
26635790e1 chore: update docs 2021-05-06 15:32:32 +08:00
deepzz0
569a1fe2a1 chore: update 2021-05-06 15:29:22 +08:00
deepzz0
182eaff085 Merge branch 'v2' of github.com:eiblog/eiblog into v2 2021-05-06 11:29:37 +08:00
deepzz0
90e7082322 chore: update docs 2021-05-06 11:28:01 +08:00
Deepzz
e71e42571f Set theme jekyll-theme-cayman 2021-05-06 09:18:49 +08:00
deepzz0
3527e11b04 chore: update eiblog.conf 2021-05-06 09:07:27 +08:00
deepzz0
a407bdfe72 chore: update README.md 2021-05-05 20:52:48 +08:00
deepzz0
05aa254e70 chore: add eiblog.conf 2021-04-30 17:11:30 +08:00
deepzz0
4ec3065f9d fix: some bugs 2021-04-30 13:06:16 +08:00
deepzz0
7bec238f9c chore: add rdbms store 2021-04-30 11:01:03 +08:00
deepzz0
605787958d chore: remove some filed in app.yml 2021-04-30 09:50:33 +08:00
deepzz0
c06f02622a fix: tar file 2021-04-29 12:31:30 +08:00
deepzz0
f431bf5adc chore: alpine add tzdata 2021-04-29 11:44:45 +08:00
deepzz0
7922043bc6 chore: add clean 2021-04-29 11:18:31 +08:00
deepzz0
2fbc5fa024 chore: rename blog->eiblog 2021-04-29 11:17:20 +08:00
deepzz0
9c341f88d2 chore: update dockerignore 2021-04-29 10:59:52 +08:00
deepzz0
0ae5e6c9fc chore: default Dockerfile 2021-04-29 10:53:54 +08:00
deepzz0
3e2b746319 chore: rm workflow registry 2021-04-29 10:50:25 +08:00
deepzz0
2cd72e190e chore: update release.yml 2021-04-29 10:48:30 +08:00
deepzz0
1c955769f4 chore: renew github flow 2021-04-29 10:44:43 +08:00
deepzz0
a2769f0913 chore: rm tzdata 2021-04-28 16:34:29 +08:00
deepzz0
bf3d45a404 fix: filed name 2021-04-28 16:33:58 +08:00
deepzz0
4b92636079 fix: bugs 2021-04-28 16:28:14 +08:00
deepzz0
753aaa4ace fix: time location 2021-04-28 15:49:49 +08:00
deepzz0
43d7a26e19 fix: time in location 2021-04-28 15:37:10 +08:00
deepzz0
872d0b1987 chore: rm package utils 2021-04-28 15:10:13 +08:00
deepzz0
897b05d071 fix: ping func 2021-04-28 15:05:37 +08:00
deepzz0
c5a3bd6eab fix: delete trash article 2021-04-28 14:40:28 +08:00
deepzz0
beea4f1746 chore: tags use array 2021-04-28 12:50:06 +08:00
deepzz0
9f563f0ae9 chore: complete api 2021-04-28 11:57:37 +08:00
deepzz0
4749b1e681 chore: add api 2021-04-27 23:46:43 +08:00
deepzz0
a39e3aac3b chore: trash 2021-04-27 18:04:59 +08:00
deepzz0
63b55b2df8 chore: update html 2021-04-27 16:51:19 +08:00
deepzz0
cb091532d5 chore: update html 2021-04-27 16:22:23 +08:00
deepzz0
6b74e1d208 chore: rename field name 2021-04-27 16:16:32 +08:00
deepzz0
1815cea2cd fix: article list 2021-04-27 14:58:32 +08:00
deepzz0
c3fdbfcb78 chore: remove blogger config 2021-04-27 14:02:52 +08:00
deepzz0
990e6abbd8 chore: run blog 2021-04-27 11:08:55 +08:00
deepzz0
bb40570053 chore: mongodb unit test 2021-04-27 10:21:27 +08:00
deepzz0
e2df642a46 chore: store mongodb 2021-04-26 23:02:24 +08:00
deepzz0
68e01cdf1f refactor: eiblog 2021-04-26 15:51:16 +08:00
chenqijing
bd69c62254 chore: replace beian domain: https://beian.miit.gov.cn 2020-12-29 11:50:36 +08:00
chenqijing
970c6068d5 chore: update go.mod 2020-12-22 11:19:12 +08:00
chenqijing
27a1f600de chore: github actions 2020-12-22 11:09:55 +08:00
chenqijing
edf0fbac51 chore: rm .travis.yml 2020-12-17 21:02:48 +08:00
Deepzz
98ca570a36 Merge pull request #24 from eiblog/deepzz0-patch-1
Create release.yml
2020-12-17 20:58:58 +08:00
Deepzz
9a526f97f8 Create release.yml 2020-12-17 20:58:27 +08:00
chenqijing
33a29f5e57 chore: update twitter & print disqus email 2020-12-17 19:15:16 +08:00
henry.chen
30ebf76eda chore(release): 1.4.9 2019-12-18 19:33:08 +08:00
Deepzz
f5c0bcdb99 Merge pull request #23 from eiblog/fix_disqus
Fix disqus
2019-12-18 19:22:11 +08:00
henry.chen
4b53da3801 chore(disqus): rm app.yml/disqus/embed config 2019-12-18 19:17:23 +08:00
henry.chen
1bdfb6abea fix(disqus): connect reset by peer 2019-12-04 13:24:48 +08:00
henry.chen
33f47d8f3a Merge branch 'master' of github.com:eiblog/eiblog 2019-12-04 10:30:26 +08:00
deepzz0
cbd0cfaaf5 fix(blogroll): blogroll.html ul style 2019-11-09 23:45:28 +08:00
henry.chen
080c992a92 chore: update vendor 2019-09-30 15:30:10 +08:00
deepzz0
371b2326ea fix(qiniu): empty file name 2019-03-05 00:41:43 +08:00
deepzz0
2720d11b23 Merge branch 'master' of github.com:eiblog/eiblog 2019-03-03 15:06:33 +08:00
deepzz0
b7751d7b9e fix: qiniu upload file 2019-03-03 15:06:16 +08:00
henry.chen
24d81db8be add vendor & update README.md 2018-11-19 10:24:54 +08:00
deepzz0
010137ebf5 change admin avatar.jpg->avatar.png & fix duplicate id in html 2018-09-18 21:20:13 +08:00
deepzz0
c6a2439c54 use go1.11 with go mod 2018-08-25 18:29:00 +08:00
deepzz0
1d54ff3ac5 fix modify blogroll.html and about.html archived in archive.html 2018-07-17 22:11:11 +08:00
henry.chen
63a4d69209 nginx config: use Expect-CT repleace HPKP 2018-07-17 10:32:54 +08:00
Deepzz
b35d7de58a Merge pull request #14 from vyloy/master
fix doSitemap bug
2018-07-06 22:05:12 +08:00
Michael(Zhiyi Weng)
77ea01b7c1 fix doSitemap bug 2018-07-06 10:02:38 +08:00
Razeen
5f608b638d Update README.md 2018-05-17 18:42:00 +08:00
deepzz0
52da8abceb update 2018-05-07 20:51:30 +08:00
deepzz0
f016b28cb6 fix comments duration 2018-05-07 20:45:37 +08:00
henry.chen
01b7643ca5 Merge branch 'master' of github.com:eiblog/eiblog 2018-05-07 16:52:56 +08:00
henry.chen
375d43761b let's encrypt v2 embedded ct,rm about cert's ct 2018-05-07 16:51:54 +08:00
Deepzz
f3e9727947 Set theme jekyll-theme-cayman 2018-05-02 09:45:41 +08:00
Deepzz
911aa963c7 Update eiblog.conf
X-Real_IP -> X-Real-IP
2018-03-27 10:48:16 +08:00
2136 changed files with 7482 additions and 1206598 deletions

View File

@@ -1,16 +1,12 @@
.git
conf
vendor
setting
docs
static/*.*
!static/favicon.ico
**/.DS_Store
Dockerfile
glide.yaml
glide.lock
*.yml
*.go
*.sh
.gitignore
.dockerignore
# Ignore all files and dirs
*
# Unignore files or dirs
!build
!bin
!conf
!assets
!website
!CHANGELOG.md
!LICENSE
!README.md

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: eiblog
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

53
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: release image & asset
on:
push:
tags:
- "v*"
jobs:
package:
runs-on: ubuntu-latest
steps:
- name: Golang env
uses: actions/setup-go@v2
with:
go-version: ^1.15
- name: Checkout
uses: actions/checkout@v2
- name: Cache mod
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Docker tag
id: vars
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:10})
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Docker login
uses: docker/login-action@v1
with:
password: ${{ secrets.DOCKER_PASSWORD }}
username: ${{ secrets.DOCKER_USERNAME }}
- name: Build image
env:
GOPROXY: https://goproxy.io,direct
run: scripts/run_build.sh deepzz0 ${{ steps.vars.outputs.tag }}
- name: Package tar
env:
GOPROXY: https://goproxy.io,direct
run: scripts/dist_tar.sh ${{ steps.vars.outputs.tag }}
- name: Release push
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
*.tar.gz

25
.gitignore vendored
View File

@@ -1,5 +1,22 @@
**/.DS_Store
# Binaries for programs and plugins
*.exe
conf/ssl/domain.*
eiblog
static/*.*
*.exe~
*.dll
*.so
*.dylib
*.DS_Store
*.tar.gz
*.db
backend
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
bin
assets/*.*
db.sqlite

View File

@@ -1,34 +0,0 @@
sudo: required # 超级权限
dist: trusty # 在ubuntu:trusty
language: go # 声明构建语言环境
go: # 只构建最新版本
- tip
services: # docker环境
- docker
branches: # 限定项目分支
only:
- /^v[0-9](\.[0-9]){2}(-rc[1-9])?$/
install:
- curl https://glide.sh/get | sh # 安装glide包管理
script:
- glide up
- GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build # 编译版本
- docker build -t registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog . # 构建镜像
after_success:
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" registry.cn-hangzhou.aliyuncs.com
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
- docker tag registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:$TRAVIS_TAG
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:$TRAVIS_TAG
before_deploy:
- ./dist.sh
deploy:
provider: releases
api_key:
secure: AGW05sQuQfjy77+JprSV+ohti/VVgFuh7UOTV0+hwxqsOVXSoIQz/ZPOlHWPP1iiSiGGEalspm+UtKRvADcDfllUaEwo7kebfFeMx4X//qxFxQSQ5LJYx7qxsTDpuQ4CF8zifCtND3ynnUAdx0P6FFkxE/67kN2n4CrhIxYCUb8gNPzDDRuS0ZNBC4zzNldJo/vtatbvc2btuFfwKoClYf+xPLy5luLqDvKF+hdjJ8NuZl8BWkWxXE+kk8fW4iUn2IV0qtLRZ3FQUyAF2CumzxqZfViX+rYTXsfbabYY5nYG6opT4mUEF58T4X3uRV0e3Q6Fe73nmLh9cyAoQl1BTSJ1XiyV4eJJWcKEMY7DqJ646lzoUT449YwvTK57klcfBbShpcjFf2alVdEbr9jbEXrCkuWKnssO9VfufhYF6t9h22c79evpexpIbsoncPD+b+n712MzufREtUF4kpUdkIir5n9CgQl/l7S+fV+n+gME+mcA44K7iPXkC80UfxJiw83QizT39OQhExq6SPIwrbt2vlAkBpSLMUS9iAHtTJYUsmH1SsmrxGK3WromKysWeTRJbcAJls2k6V313sn4TuYBWiHTUfsUBhv+objDFA2TsfO+g0g1JsdfZb5EsKrqNvs/2ta1xlzdE0+/TLG/YNKIOPkHnXswAM3DZm3zEss=
skip_cleanup: true
file: "*.tar.gz"
file_glob: true
on:
tags: true
repo: eiblog/eiblog
all_branches: true

View File

@@ -1,80 +1,148 @@
# Eiblog Changelog
# Changelog
## v1.4.2 (2018-02-09)
* 修复博客初始化后about 页面不能够评论 #6
* 修复编辑专题,按钮显示“添加专题”错误
* 优化“添加文章”从同步改为异步推送feedesdisqus。速度显著提升
* **重要*)头像图片从 avatar.jpg 改为 avatar.png透明
* docker-compose.yml mongodb 去掉端口映射,防止用户将端口暴露至外网
* session key 每次重启随机生成等一些细节的修复
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.
## v1.4.1 (2018-01-14)
* 修复创建新文章disqus 不收录bug
* 修复创建新文章归档页面不刷新bug
* 修复能够删除关于页面和友情链接页面bug
* 修复重复添加文章错误
* 注释掉 docker-compose.yml 自动备份内容,请自行解开
* 添加当月数大于12归档页面使用年份归档
* 优化代码逻辑
### [2.2.1](https://github.com/eiblog/eiblog/compare/v2.2.0...v2.2.1) (2023-05-17)
## v1.4.0 (2018-01-01)
* fix 搜索页面 bug
* CGO_ENABLED=0 关闭 cgo
* 更新Makefile ct log 服务器
* 数据库数据终于可以备份了
## v1.3.4 (2017-11-29)
* fix page:admin/write-post autocomplete tag
### Bug Fixes
## v1.3.3 (2017-11-27)
* fix docker image: exec user process caused "no such file or directory"
* try to fix backup symbol not found ([bb06be3](https://github.com/eiblog/eiblog/commit/bb06be36fe016e753ca56aa2321ce7e39bffe3e0))
## v1.3.2 (2017-11-17)
* 修复文章自动保存引起的发布文章不成功的bug
## [2.2.0](https://github.com/eiblog/eiblog/compare/v2.1.18...v2.2.0) (2023-05-17)
## v1.3.1 (2017-11-05)
* 修复调整 关于、友情链接 创建时间出现文章乱序
* 修复评论时间计算错误
* 调整acme文件验证路径
* 更改七牛SDK包为github包。
* 调整七牛配置文件名称app.yml: kodo -> qiniuname -> bucket请提高静态文件版本 staticversion
## v1.3.0 (2017-07-13)
* 更改 app.yml 配置项,将大部分配置归在 general 常规配置下。注意,部署时请先更新 app.yml。
* 静态文件采用动态渲染,即用户不再需要管理 view、static 目录。
* 通过 acme.sh 使用双证书啦,可到 Makefile 查看相关信息。
* 使用 autocert 自动生成证书功能,从此再也不用担心证书过期,移步 [证书更新](https://github.com/eiblog/eiblog/blob/master/docs/autocert.md)。
* 开启配置项 enablehttps 将自动重定向 http 到 https 啦。
* disqus.js 文件由配置指定,请看 app.yml 下的 disqus 相关配置。
### Features
## v1.2.0 (2017-06-14)
* 更新评论功能,基础评论 0 回复也可评论了。
* disqus.js 文件由博主自行更新。
* 更正描述 README.md 描述错误 [#4f996](https://github.com/eiblog/eiblog/commit/4f9965b6bdefe087dd0805c1840afcb2752cd155)。
* docker 镜像版本化。
* **backup:** add restore flag ([779a23c](https://github.com/eiblog/eiblog/commit/779a23cb75ab5059826370d08b754569a4af4aea))
## v1.1.3 (2017-05-12)
* 更新 disqus_78bca4.js 到 disqus_921d24.js具体请参考 docs/install.md
* 更新 vendor
### [2.1.18](https://github.com/eiblog/eiblog/compare/v2.1.17...v2.1.18) (2023-01-05)
## v1.1.2 (2017-03-08)
* 解决添加文章描述错误的bug
* 添加vendor目录
* 添加文档docs目录
* 删除多余注释
## v1.1.1 (2017-02-07)
* 添加文章描述功能。
* 修复评论`jQuery`文件引用错误。
* 修复`.travis.yml`描述错误。
### Bug Fixes
## v1.0.0 (2016-01-09)
首次发布版本
* **backup:** can not execute ([f6ba716](https://github.com/eiblog/eiblog/commit/f6ba716f554cfb638752875c4842e4ffb1b7e9a6))
* disqus api using http post ([b2e6c16](https://github.com/eiblog/eiblog/commit/b2e6c168c5f63b29cf5c2884e04dd99caa677bc0))
* 全站`HTTPS`设计,安全、极速。
* `Elasticsearch`博客搜索系统。
* 开源`Typecho`完整博客后台。
* 全功能`Markdown`编辑器。
* 异步`Google analysts`分析统计。
* `Disqus`评论系统。
* 后台直接对接七牛`CDN`
### [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.9](https://github.com/eiblog/eiblog/compare/v2.1.8...v2.1.9) (2022-02-14)
### Bug Fixes
* **eiblog:** comments title error ([5170844](https://github.com/eiblog/eiblog/commit/517084462336ce01c3f79099c1e54297979f5877))
### [2.1.8](https://github.com/eiblog/eiblog/compare/v2.1.7...v2.1.8) (2021-11-18)
### [2.1.7](https://github.com/eiblog/eiblog/compare/v2.1.6...v2.1.7) (2021-11-13)
### [2.1.6](https://github.com/eiblog/eiblog/compare/v2.1.5...v2.1.6) (2021-11-12)
### Bug Fixes
* **backup:** backup to qiniu, app.yml->validity change to int ([00cf0b5](https://github.com/eiblog/eiblog/commit/00cf0b5c9f787f3f45f1747b7cb1742c417c6dd6))
### [2.1.5](https://github.com/eiblog/eiblog/compare/v2.1.4...v2.1.5) (2021-10-27)
### Bug Fixes
* **release:** golang env ([4f6f85a](https://github.com/eiblog/eiblog/commit/4f6f85a85ae56849c49e91d76bbbce1790f16e29))
### [2.1.4](https://github.com/eiblog/eiblog/compare/v2.1.3...v2.1.4) (2021-10-26)
### Bug Fixes
* **release:** github action runner ([82fba0d](https://github.com/eiblog/eiblog/commit/82fba0ddb47c1f66cb659f775c120c08dd2b4447))
### [2.1.4](https://github.com/eiblog/eiblog/compare/v2.1.3...v2.1.4) (2021-10-26)
### Bug Fixes
* **release:** github action runner ([82fba0d](https://github.com/eiblog/eiblog/commit/82fba0ddb47c1f66cb659f775c120c08dd2b4447))
### [2.1.3](https://github.com/eiblog/eiblog/compare/v2.1.2...v2.1.3) (2021-10-15)
### Bug Fixes
* **auto_save:** fix auto save return empty id ([cfaa25e](https://github.com/eiblog/eiblog/commit/cfaa25e1239aba580e0718d40f1a2bf31158b217))
* backup app judge db driver ([78f5bfc](https://github.com/eiblog/eiblog/commit/78f5bfc1ce017bf77a7442f40963a706e608df51))
* **pwd:** fix init pwd ([7d04b8f](https://github.com/eiblog/eiblog/commit/7d04b8f5c0846bcd0c7e07d0fc3704a535f6360a))
### [2.1.2](https://github.com/eiblog/eiblog/compare/v2.1.1...v2.1.2) (2021-07-27)
### [2.1.1](https://github.com/eiblog/eiblog/compare/v2.1.0...v2.1.1) (2021-07-27)
### Bug Fixes
* sqlite error ([e2046d0](https://github.com/eiblog/eiblog/commit/e2046d0d39d9914473fe7b8fae3b18246ed133ce))
### [2.0.6](https://github.com/eiblog/eiblog/compare/v2.0.5...v2.0.6) (2021-05-08)
### Bug Fixes
* workdir loop ([2b277bd](https://github.com/eiblog/eiblog/commit/2b277bd90188d53b90fddd0f6a8edad00f888f53))
* workdir path error ([c18f3b7](https://github.com/eiblog/eiblog/commit/c18f3b7da96e3181b40867a88f9c8cad042d2f44))
## [1.1.0](https://github.com/deepzz0/appdemo/compare/v1.0.0...v1.1.0) (2020-12-18)
### Features
* **docker:** make build, build docker image ([3ac2b8b](https://github.com/deepzz0/appdemo/commit/3ac2b8b2efadf024dfcf58e7ef8341b1a89cf1b1))
### Bug Fixes
* config path fixed [#1](https://github.com/deepzz0/appdemo/issues/1) ([4343eb4](https://github.com/deepzz0/appdemo/commit/4343eb44e8fffc6825be57393e024c75c4f68b7b))
## 1.0.0 (2020-10-31)

View File

@@ -1,10 +0,0 @@
FROM alpine
MAINTAINER deepzz <deepzz.qi@gmail.com>
RUN apk add --update --no-cache ca-certificates
ADD static/tzdata/Shanghai /etc/localtime
COPY . /eiblog
EXPOSE 9000
WORKDIR /eiblog
CMD ["sh","-c","/eiblog/eiblog"]

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017 deepzz deepzz.qi@gmail.com
Copyright (c) 2020-NOW deepzz0 <deepzz.qi@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,69 +1,31 @@
.PHONY: test build deploy dist gencert dhparams ssticket makedir clean
# use aliyun dns api to auto renew cert.
# env:
# export Ali_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
# export Ali_Secret="jlsdflanljkljlfdsaklkjflsa"
.PHONY: demo build swag
docker_registry?=registry.cn-hangzhou.aliyuncs.com
acme?=~/.acme.sh
acme.sh?=$(acme)/acme.sh
config?=/data/eiblog/conf
tag=`git describe --abbrev=0 --tags`
swag:
@scripts/swag_init.sh
test:
_app:
@scripts/new_app.sh
build:
@echo "go build..."
@CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && \
docker build -t $(docker_registry)/deepzz/eiblog:latest .
# below you should write
deploy:build
@docker push $(docker_registry)/deepzz/eiblog:latest
# run eiblog app
eiblog:
@scripts/run_app.sh eiblog
# run backup app
backup:
@scripts/run_app.sh backup
# dist tar
dist:
@./dist.sh
gencert:makedir
@if [ ! -n "$(sans)" ]; then \
printf "Need one argument [sans=params]\n"; \
printf "example: sans=\"-d domain -d domain\"\n"; \
exit 1; \
fi; \
if [ ! -n "$(cn)" ]; then \
printf "Need one argument [cn=params]\n"; \
printf "example: cn=domain\n"; \
exit 1; \
fi
@if [ ! -f $(acme.sh) ]; then \
curl https://get.acme.sh | sh; \
fi
@echo "generate rsa cert..."
@$(acme.sh) --force --issue --dns dns_ali $(sans) --log \
--renew-hook "ct-submit ctlog-gen2.api.venafi.com < $(config)/ssl/domain.rsa.pem > $(config)/scts/rsa/venafi.sct \
&& ct-submit ctlog.wosign.com < $(config)/ssl/domain.rsa.pem > $(config)/scts/rsa/wosign.sct"
@$(acme.sh) --install-cert -d $(cn) \
--key-file $(config)/ssl/domain.rsa.key \
--fullchain-file $(config)/ssl/domain.rsa.pem \
--reloadcmd "service nginx force-reload"
@echo "generate ecc cert..."
@$(acme.sh) --force --issue --dns dns_ali $(sans) -k ec-256 --log \
--renew-hook "ct-submit ctlog-gen2.api.venafi.com < $(config)/ssl/domain.ecc.pem > $(config)/scts/ecc/venafi.sct \
&& ct-submit ctlog.wosign.com < $(config)/ssl/domain.ecc.pem > $(config)/scts/ecc/wosign.sct"
@$(acme.sh) --install-cert -d $(cn) --ecc \
--key-file $(config)/ssl/domain.ecc.key \
--fullchain-file $(config)/ssl/domain.ecc.pem \
--reloadcmd "service nginx force-reload"
dhparams:
@openssl dhparam -out $(config)/ssl/dhparams.pem 2048
ssticket:
@openssl rand 48 > $(config)/ssl/session_ticket.key
makedir:
@mkdir -p $(config)/ssl $(config)/scts/rsa $(config)/scts/ecc
@scripts/dist_tar.sh $(tag)
# clean
clean:
@rm -rf bin && rm -f *.tar.gz && rm -f backend
# protoc
protoc:
@cd pkg/proto && make protoc

151
README.md
View File

@@ -1,91 +1,106 @@
# EiBlog [![Build Status](https://travis-ci.org/eiblog/eiblog.svg?branch=v1.3.0)](https://travis-ci.org/eiblog/eiblog) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) [![Versuib](https://img.shields.io/github/tag/eiblog/eiblog.svg)](https://github.com/eiblog/eiblog/releases)
> 系统根据[https://imququ.com](https://imququ.com)一系列文章和方向进行搭建期间获得了QuQu的很大帮助在此表示感谢。
> 博客项目结构参考模版https://github.com/deepzz0/appdemo
用过其它博客系统,不喜欢,不够轻,不够快!自己做过共两款博客系统,完美主义的我(毕竟处女座)也实在不想再在这件事情上过多纠结了。`Eiblog` 应该是一个比较稳定的博客系统,且是博主以后使用的博客系统,稳定性和维护你是不用担心的,唯独该系统部署过程太过复杂,并且不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(明显快,安全),等你体验
用过其它博客系统,不喜欢,不够轻,不够快!这是我开发的第二款博客系统,也实在不想再在这件事情上过多纠结了。`EiBlog` 是一个比较稳定的博客系统,现已迭代至 `2.0` 版本,稳定性和维护你是不用担心的
<!--more-->
但它有着部署简单(上线复杂!)的特点,不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(简洁、轻快,安全),等你体验。
### 介绍
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)
* 自动更新证书:
* 接入 [acme/autocert](https://github.com/golang/crypto/tree/master/acme/autocert),在 TLS 层开启全自动更新证书,从此证书的更新再也不用惦记了,不过 Go 的 HTTPS 兼容性不够好(不想兼容),在如部分 IE 和 UC 之类的浏览器不能访问,请悉知。
* 如果你采用如 Nginx 代理,推荐使用 [acme.sh](https://github.com/Neilpang/acme.sh) 实现证书的自动部署。博主实现 aliyun dns 的自动验证方式,详见 [Makefile/gencert](https://github.com/eiblog/eiblog/blob/master/Makefile)。
* `MongoDB`,博客采用 mongodb 作为存储数据库。
* `Elasticsearch`,采用 `elasticsearch` 作为博客的站内搜索,尽管占用内存稍高。
* `Disqus`,作为博客评论系统,国内大部分被墙,故实现两种评论方式。
* `Nginx`,作为反向代理服务器,并做相关 `http header` 和证书的设置。
* `Google Analytics`,作为博客系统的数据分析统计工具。
* `七牛 CDN`,作为博客系统的静态文件存储,博文的图片附件什么上传至这里。
### 快速体验
### 图片展示
**二进制**
可以容易的看到 [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+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到
1、下载压缩包到 [这里](https://github.com/eiblog/eiblog/releases) 下载 eiblog非backup 相应系统压缩包,然后解压缩
相关图片展示:
![show-home](http://7xokm2.com1.z0.glb.clouddn.com/static/img/show-home1.png)
2、启动服务`./backend`
![show-home2](http://7xokm2.com1.z0.glb.clouddn.com/static/img/show-home2.png)
**Docker**
![show-admin](http://7xokm2.com1.z0.glb.clouddn.com/static/img/show-admin.png)
![eiblog-mem](http://7xokm2.com1.z0.glb.clouddn.com/img/eiblog-mem.png)
> `注`图片1图片2是博客界面图片3是后台界面图片4是性能展示。
### 极速体验
1. 到 [这里](https://github.com/eiblog/eiblog/releases) 下载对应平台 `.tar.gz` 文件。
2. 搭建 `MongoDB`(必须)和 `Elasticsearch`(可选)服务,正式部署需要。
3. 修改 `/etc/hosts` 文件,添加 `MongoDB` 数据库 IP 地址,如:`127.0.0.1 mongodb`
4. 执行 `./eiblog`,运行博客系统。看到:
```
...
...
[GIN-debug] Listening and serving HTTP on :9000
$ docker run --name eiblog \
-p 9000:9000 \
deepzz0/eiblog:latest
```
代表运行成功了。
默认监听 `HTTP 9000` 端口,后台 `/admin/login`,默认账号密码均为 `deepzz`。更多详细请查阅 [安装部署](https://github.com/eiblog/eiblog/blob/master/docs/install.md) 文档。
**Compose**
### 特色功能
参考项目根目录下的 [docker-compose.yml](https://github.com/eiblog/eiblog/blob/v2/docker-compose.yml),修改相关配置:
作为博主之心血之作,`Eiblog` 实现了什么功能,有什么特点,做了什么优化呢?
```
$ docker compose up -d
$ docker-compose up -d
```
1. 系统目前只有 `首页``专题``归档``友链``关于``搜索` 界面。相信已经可以满足大部分用户的需求
2. `.js``.css` 等静态文件本地存储,小图片 base64 内置到 css 中,不会产生网络所带来的延迟,加速网页访问。版本控制方式,动态更新静态文件。
3. 采用谷歌统计,并实现异步(将访问信息发给后端,后端提交给谷歌)统计,加速访问速度。
4. 采用直接缓存 markdown 转过的 html 文档的方式,加速后端处理。响应速度均在 3ms 以内,真正极速。
5. 通过 Nginx 的配置,开启压缩缩小传输量,服务器传输证书链、开启 `Session Resumption``Session Ticket``OCSP Stapling `等加速证书握手,再次提高速度。
* `CDN`使用七牛融合CDN`https` 化,实现全站 `https`。七牛可申请免费证书了。
* `CT`,证书透明度检测,提供一个开放的审计和监控系统。可以让任何域名所有者或者 CA 确定证书是否被错误签发或者被恶意使用,从而提高 HTTPS 网站的安全性。
* `OSCP`,在线证书状态协议。用来检验证书合法性的在线查询服务.
* `HSTS`,强制客户端(如浏览器)使用 HTTPS 与服务器创建连接。可以很好的解决 HTTPS 降级攻击。
 * `HPKP`HTTP 公钥固定扩展,防范由「伪造或不正当手段获得网站证书」造成的中间人攻击。该功能让我们选择信任哪些`CA`。请不要轻易尝试 Nginx 线上运行,因为该配置目前只指定了 Letsencrypt X3 和 TrustAsia G5 证书 pin-sha256。
 * `SSL Protocols`,罗列支持的 `TLS` 协议SSLv3 被证实是不安全的。
* `SSL dhparam`,迪菲赫尔曼密钥交换。
* `Cipher suite`,罗列服务器支持加密套件。
6. 文章评论数量(不重要)后端跑定时脚本,定时更新,所以有时评论数是不对的。这样减少了 api 调用,又再次达到加速访问的目的。
7. 针对 `disqus` 被墙原因,实现 [Jerry Qu](https://imququ.com) 的另类评论方式,保证评论的流畅。
8. 开源 `Typecho` 完整后台系统,全功能 `markdown` 编辑器,让你体验什么是简洁清爽。
9. 博客后台直接对接 `七牛 SDK`,实现后台上传文件和删除文件的简单功能。
10. 采用 `elasticsearch` 作为站内搜索,添加 `google opensearch` 功能,搜索更加自然。
11. 自动备份数据库数据到七牛云。
然后访问 `localhost:9000` 就可以了,后台地址 `localhost:9000/admin/login`,默认账户密码 `deepzz/deepzz`
### 文档
> 默认情况下未开启博客搜索 `elasticsearch`,需要的话需要启动 es 服务并修改配置 `app.yml`。
* [证书更新](https://github.com/eiblog/eiblog/blob/master/docs/autocert.md)
* [安装部署](https://github.com/eiblog/eiblog/blob/master/docs/install.md)
* [写作需知](https://github.com/eiblog/eiblog/blob/master/docs/writing.md)
* [好玩的功能](https://github.com/eiblog/eiblog/blob/master/docs/amusing.md)
* [关于备份](https://github.com/eiblog/backup)
**数据库支持**
### 成功搭建者博客
| 类型driver | 地址source示例 |
| -------------- | ------------------------------------------------------------ |
| mongodb | mongodb://localhost:27017 |
| mysql | user:password@tcp(localhost:3306)/eiblog?charset=utf8mb4&parseTime=True&loc=Local |
| postgres | host=localhost port=5432 user=user password=password dbname=eiblog sslmode=disable |
| sqlite | /path/eiblog.db |
| sqlserver | sqlserver://user:password@localhost:9930?database=eiblog |
| clickhouse | tcp://localhost:9000?database=eiblog&username=user&password=password&read_timeout=10&write_timeout=20 |
* [https://razeen.me](https://razeen.me) - Razeen's Blog
### 功能特性
本着博客本质用来分享知识的特点,`EiBlog` 不会有较强的定制功能包括主题CDN支持等仅保持常用简单页面与功能
```
首页、专题、归档、友链、关于、搜索
```
功能说明:
* 博客归档,利用时间线帮助我们将归纳博文,内容少于一年按月归档,大于则按年归档。
* 博客专题,有时候博文是同一系列,专题能够帮助我们很好归纳博文,对阅读者是非常友好的。
* 标签系统,每篇博文都可以打上不同标签,使得在归档和专题不满足的情况下自定义归档,这块辅助搜索简直完美。
* 搜索系统依托ElasticSearch实现的站内搜索速度与效率并存再加上google opensearch搜索只流畅。
* 管理后台,内嵌全功能 `Typecho` 后台系统,全功能 `Markdown` 编辑器让你感觉什么是简洁清爽。
* 谷歌统计由于google api的速度问题从而实现了后端API异步统计使得博客页面加载飞速。
* Disqus评论国内评论系统不友好因此选择disqus又由于众所周知原因国内不能用实现另类disqus评论方式。
* 多存储后端支持mongodb、mysql、postgres、sqlite等存储后端。
* 七牛CDN支持在 `Markdown` 编辑器直接上传附件,让你只考虑编辑内容,解放思想。
* 自动备份支持多存储后端的备份功能备份数据保存到七牛CDN上。
当然,为了让整个系统加载速度更快,还做了更多优化措施:
* 文章评论数量(不重要)通过后端跑定时任务获取,所以有时评论数量是不对的,这样减少了 API 调用。
* 整站内容全部内存缓存,`mardown` 文档全部转换为 html 进行缓存,减少了转换过程。
* `.js``.css` 等静态文件浏览器本地存储,小图片 base64 内置到 css 中,二次访问不会产生网络带来的延迟,加速访问。通过版本控制更新。
* 最佳实践 nginx 配置,可以查看 `eiblog.conf`,开启压缩缩小传输量,服务器传输证书链、开启 `Session Resumption``Session Ticket``OCSP Stapling `等加速证书握手,再次提高速度。
### 博客页面
可以容易的看到 [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-home2](./docs/img/show-home2.png)
![show-admin](./docs/img/show-admin.png)
### 更多文档
* [安装部署](https://eiblog.github.io/eiblog/install)
* [写作须知](https://eiblog.github.io/eiblog/writing)
* [好玩功能](https://eiblog.github.io/eiblog/amusing)
* [如何备份](https://eiblog.github.io/eiblog/backup)
### 贡献成员
![graphs/contributors](https://opencollective.com/eiblog/contributors.svg?width=890&button=false)
### 授权许可
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/eiblog/eiblog/blob/master/LICENSE) 文件中。
如果你的博客使用`Eiblog`搭建,你可以在 [这里](https://github.com/eiblog/eiblog/issues/1) 提交网址。

448
api.go
View File

@@ -1,448 +0,0 @@
package main
import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
"github.com/eiblog/utils/mgo"
"github.com/gin-gonic/gin"
)
const (
// 成功
NOTICE_SUCCESS = "success"
// 注意
NOTICE_NOTICE = "notice"
// 错误
NOTICE_ERROR = "error"
)
// 全局 API
var APIs = make(map[string]func(c *gin.Context))
func init() {
// 更新账号信息
APIs["account"] = apiAccount
// 更新博客信息
APIs["blog"] = apiBlog
// 更新密码
APIs["password"] = apiPassword
// 删除文章
APIs["post-delete"] = apiPostDelete
// 添加文章
APIs["post-add"] = apiPostAdd
// 删除专题
APIs["serie-delete"] = apiSerieDelete
// 添加专题
APIs["serie-add"] = apiSerieAdd
// 专题排序
APIs["serie-sort"] = apiSerieSort
// 删除草稿箱
APIs["draft-delete"] = apiDraftDelete
// 删除回收箱
APIs["trash-delete"] = apiTrashDelete
// 恢复回收箱
APIs["trash-recover"] = apiTrashRecover
// 上传文件
APIs["file-upload"] = apiFileUpload
// 删除文件
APIs["file-delete"] = apiFileDelete
}
// 更新账号信息Email、PhoneNumber、Address
func apiAccount(c *gin.Context) {
e := c.PostForm("email")
pn := c.PostForm("phoneNumber")
ad := c.PostForm("address")
logd.Debug(e, pn, ad)
if (e != "" && !CheckEmail(e)) || (pn != "" && !CheckSMS(pn)) {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
err := UpdateAccountField(mgo.M{"$set": mgo.M{"email": e, "phonen": pn, "address": ad}})
if err != nil {
logd.Error(err)
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
Ei.Email = e
Ei.PhoneN = pn
Ei.Address = ad
responseNotice(c, NOTICE_SUCCESS, "更新成功", "")
}
// 更新博客信息
func apiBlog(c *gin.Context) {
bn := c.PostForm("blogName")
bt := c.PostForm("bTitle")
ba := c.PostForm("beiAn")
st := c.PostForm("subTitle")
ss := c.PostForm("seriessay")
as := c.PostForm("archivessay")
if bn == "" || bt == "" {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
err := UpdateAccountField(mgo.M{"$set": mgo.M{"blogger.blogname": bn,
"blogger.btitle": bt, "blogger.beian": ba, "blogger.subtitle": st,
"blogger.seriessay": ss, "blogger.archivessay": as}})
if err != nil {
logd.Error(err)
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
Ei.BlogName = bn
Ei.BTitle = bt
Ei.BeiAn = ba
Ei.SubTitle = st
Ei.SeriesSay = ss
Ei.ArchivesSay = as
Ei.CH <- SERIES_MD
Ei.CH <- ARCHIVE_MD
responseNotice(c, NOTICE_SUCCESS, "更新成功", "")
}
// 更新密码
func apiPassword(c *gin.Context) {
logd.Debug(c.Request.PostForm.Encode())
od := c.PostForm("old")
nw := c.PostForm("new")
cf := c.PostForm("confirm")
if nw != cf {
responseNotice(c, NOTICE_NOTICE, "两次密码输入不一致", "")
return
}
if !CheckPwd(nw) {
responseNotice(c, NOTICE_NOTICE, "密码格式错误", "")
return
}
if !VerifyPasswd(Ei.Password, Ei.Username, od) {
responseNotice(c, NOTICE_NOTICE, "原始密码不正确", "")
return
}
newPwd := EncryptPasswd(Ei.Username, nw)
err := UpdateAccountField(mgo.M{"$set": mgo.M{"password": newPwd}})
if err != nil {
logd.Error(err)
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
Ei.Password = newPwd
responseNotice(c, NOTICE_SUCCESS, "更新成功", "")
}
// 删除文章,软删除:移入到回收箱
func apiPostDelete(c *gin.Context) {
var ids []int32
for _, v := range c.PostFormArray("cid[]") {
i, err := strconv.Atoi(v)
if err != nil || int32(i) < setting.Conf.General.StartID {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
ids = append(ids, int32(i))
}
err := DelArticles(ids...)
if err != nil {
logd.Error(err)
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
// elasticsearch
err = ElasticDelIndex(ids)
if err != nil {
logd.Error(err)
}
// TODO disqus delete
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
}
func apiPostAdd(c *gin.Context) {
var (
err error
do string
cid int
)
defer func() {
switch do {
case "auto": // 自动保存
if err != nil {
c.JSON(http.StatusOK, gin.H{"fail": FAIL, "time": time.Now().Format("15:04:05 PM"), "cid": cid})
return
}
c.JSON(http.StatusOK, gin.H{"success": SUCCESS, "time": time.Now().Format("15:04:05 PM"), "cid": cid})
case "save", "publish": // 草稿,发布
if err != nil {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
uri := "/admin/manage-draft"
if do == "publish" {
uri = "/admin/manage-posts"
}
c.Redirect(http.StatusFound, uri)
}
}()
do = c.PostForm("do") // auto or save or publish
slug := c.PostForm("slug")
title := c.PostForm("title")
text := c.PostForm("text")
date := CheckDate(c.PostForm("date"))
serie := c.PostForm("serie")
tag := c.PostForm("tags")
update := c.PostForm("update")
if slug == "" || title == "" || text == "" {
err = errors.New("参数错误")
return
}
var tags []string
if tag != "" {
tags = strings.Split(tag, ",")
}
serieid := CheckSerieID(serie)
artc := &Article{
Title: title,
Content: text,
Slug: slug,
CreateTime: date,
IsDraft: do != "publish",
Author: Ei.Username,
SerieID: serieid,
Tags: tags,
}
cid, err = strconv.Atoi(c.PostForm("cid"))
// 新文章
if err != nil || cid < 1 {
err = AddArticle(artc)
if err != nil {
logd.Error(err)
return
}
cid = int(artc.ID)
if !artc.IsDraft {
// 异步执行,快
go func() {
// elastic
ElasticIndex(artc)
// rss
DoPings(slug)
// disqus
ThreadCreate(artc)
}()
}
return
}
// 旧文章
artc.ID = int32(cid)
_, a := GetArticle(artc.ID)
if a != nil {
artc.IsDraft = false
artc.Count = a.Count
artc.UpdateTime = a.UpdateTime
}
if CheckBool(update) {
artc.UpdateTime = time.Now()
}
// 数据库更新
err = UpdateArticle(mgo.M{"id": artc.ID}, artc)
if err != nil {
logd.Error(err)
return
}
if !artc.IsDraft {
ReplaceArticle(a, artc)
// 异步执行,快
go func() {
// elastic
ElasticIndex(artc)
// rss
DoPings(slug)
// disqus
if a == nil {
ThreadCreate(artc)
}
}()
}
}
// 只能逐一删除,专题下不能有文章
func apiSerieDelete(c *gin.Context) {
for _, v := range c.PostFormArray("mid[]") {
id, err := strconv.Atoi(v)
if err != nil || id < 1 {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
err = DelSerie(int32(id))
if err != nil {
logd.Error(err)
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
}
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
}
// 添加专题,如果专题有提交 mid 即更新专题
func apiSerieAdd(c *gin.Context) {
name := c.PostForm("name")
slug := c.PostForm("slug")
desc := c.PostForm("description")
if name == "" || slug == "" || desc == "" {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
mid, err := strconv.Atoi(c.PostForm("mid"))
if err == nil && mid > 0 {
serie := QuerySerie(int32(mid))
if serie == nil {
responseNotice(c, NOTICE_NOTICE, "专题不存在", "")
return
}
serie.Name = name
serie.Slug = slug
serie.Desc = desc
serie.ID = int32(mid)
err = UpdateSerie(serie)
if err != nil {
logd.Error(err)
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
} else {
err = AddSerie(name, slug, desc)
if err != nil {
logd.Error(err)
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
}
responseNotice(c, NOTICE_SUCCESS, "操作成功", "")
}
// NOTE 排序专题,暂未实现
func apiSerieSort(c *gin.Context) {
v := c.PostFormArray("mid[]")
logd.Debug(v)
}
// 删除草稿箱,物理删除
func apiDraftDelete(c *gin.Context) {
for _, v := range c.PostFormArray("mid[]") {
i, err := strconv.Atoi(v)
if err != nil || i < 1 {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
err = RemoveArticle(int32(i))
if err != nil {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
}
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
}
// 删除垃圾箱,物理删除
func apiTrashDelete(c *gin.Context) {
for _, v := range c.PostFormArray("mid[]") {
i, err := strconv.Atoi(v)
if err != nil || i < 1 {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
err = RemoveArticle(int32(i))
if err != nil {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
}
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
}
// 从垃圾箱恢复到草稿箱
func apiTrashRecover(c *gin.Context) {
for _, v := range c.PostFormArray("mid[]") {
i, err := strconv.Atoi(v)
if err != nil || i < 1 {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
err = RecoverArticle(int32(i))
if err != nil {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
return
}
}
responseNotice(c, NOTICE_SUCCESS, "恢复成功", "")
}
// 上传文件到 qiniu 云
func apiFileUpload(c *gin.Context) {
type Size interface {
Size() int64
}
file, header, err := c.Request.FormFile("file")
if err != nil {
logd.Error(err)
c.String(http.StatusBadRequest, err.Error())
return
}
s, ok := file.(Size)
if !ok {
logd.Error("assert failed")
c.String(http.StatusBadRequest, "false")
return
}
filename := strings.ToLower(header.Filename)
url, err := FileUpload(filename, s.Size(), file)
if err != nil {
logd.Error(err)
c.String(http.StatusBadRequest, err.Error())
return
}
typ := header.Header.Get("Content-Type")
c.JSON(http.StatusOK, gin.H{
"title": filename,
"isImage": typ[:5] == "image",
"url": url,
"bytes": fmt.Sprintf("%dkb", s.Size()/1000),
})
}
// 删除七牛 CDN 文件
func apiFileDelete(c *gin.Context) {
defer c.String(http.StatusOK, "删掉了吗?鬼知道。。。")
name := c.PostForm("title")
if name == "" {
logd.Error("参数错误")
return
}
err := FileDelete(name)
if err != nil {
logd.Error(err)
}
}
func responseNotice(c *gin.Context, typ, content, hl string) {
if hl != "" {
c.SetCookie("notice_highlight", hl, 86400, "/", "", true, false)
}
c.SetCookie("notice_type", typ, 86400, "/", "", true, false)
c.SetCookie("notice", fmt.Sprintf("[\"%s\"]", content), 86400, "/", "", true, false)
c.Redirect(http.StatusFound, c.Request.Referer())
}

1
assets/README.md Normal file
View File

@@ -0,0 +1 @@
Other assets to go along with your repository (images, logos, etc).

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 847 B

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

307
back.go
View File

@@ -1,307 +0,0 @@
// Package main provides ...
package main
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"net/http"
"strconv"
"time"
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
"github.com/eiblog/utils/mgo"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
)
// 是否登录
func isLogin(c *gin.Context) bool {
session := sessions.Default(c)
v := session.Get("username")
if v == nil || v.(string) != Ei.Username {
return false
}
return true
}
// 登陆过滤
func AuthFilter() gin.HandlerFunc {
return func(c *gin.Context) {
if !isLogin(c) {
c.Abort()
c.Redirect(http.StatusFound, "/admin/login")
return
}
c.Next()
}
}
// 登录界面
func HandleLogin(c *gin.Context) {
logout := c.Query("logout")
if logout == "true" {
session := sessions.Default(c)
session.Delete("username")
session.Save()
} else if isLogin(c) {
c.Redirect(http.StatusFound, "/admin/profile")
return
}
c.Status(http.StatusOK)
RenderHTMLBack(c, "login.html", gin.H{"BTitle": Ei.BTitle})
}
// 登陆接口
func HandleLoginPost(c *gin.Context) {
user := c.PostForm("user")
pwd := c.PostForm("password")
// code := c.PostForm("code") // 二次验证
if user == "" || pwd == "" {
logd.Print("参数错误", user, pwd)
c.Redirect(http.StatusFound, "/admin/login")
return
}
if Ei.Username != user || !VerifyPasswd(Ei.Password, user, pwd) {
logd.Printf("账号或密码错误 %s, %s\n", user, pwd)
c.Redirect(http.StatusFound, "/admin/login")
return
}
session := sessions.Default(c)
session.Set("username", user)
session.Save()
Ei.LoginIP = c.ClientIP()
Ei.LoginTime = time.Now()
UpdateAccountField(mgo.M{"$set": mgo.M{"loginip": Ei.LoginIP, "logintime": Ei.LoginTime}})
c.Redirect(http.StatusFound, "/admin/profile")
}
func GetBack() gin.H {
return gin.H{"Author": Ei.Username, "Qiniu": setting.Conf.Qiniu}
}
// 个人配置
func HandleProfile(c *gin.Context) {
h := GetBack()
h["Console"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "个人配置 | " + Ei.BTitle
h["Account"] = Ei
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-profile", h)
}
// 写文章==>Write
type T struct {
ID string `json:"id"`
Tags string `json:"tags"`
}
func HandlePost(c *gin.Context) {
h := GetBack()
id, err := strconv.Atoi(c.Query("cid"))
if err == nil && id > 0 {
artc := QueryArticle(int32(id))
if artc != nil {
h["Title"] = "编辑文章 | " + Ei.BTitle
h["Edit"] = artc
}
}
if h["Title"] == nil {
h["Title"] = "撰写文章 | " + Ei.BTitle
}
h["Path"] = c.Request.URL.Path
h["Domain"] = setting.Conf.Mode.Domain
h["Series"] = Ei.Series
var tags []T
for tag, _ := range Ei.Tags {
tags = append(tags, T{tag, tag})
}
str, _ := json.Marshal(tags)
h["Tags"] = string(str)
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-post", h)
}
// 删除草稿
func HandleDraftDelete(c *gin.Context) {
id, err := strconv.Atoi(c.Query("cid"))
if err != nil || id < 1 {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
if err = RemoveArticle(int32(id)); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "删除错误"})
return
}
c.Redirect(http.StatusFound, "/admin/write-post")
}
// 文章管理==>Manage
func HandlePosts(c *gin.Context) {
kw := c.Query("keywords")
tmp := c.Query("serie")
se, err := strconv.Atoi(tmp)
if err != nil || se < 1 {
se = 0
}
pg, err := strconv.Atoi(c.Query("page"))
if err != nil || pg < 1 {
pg = 1
}
vals := c.Request.URL.Query()
h := GetBack()
h["Manage"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "文章管理 | " + Ei.BTitle
h["Series"] = Ei.Series
h["Serie"] = se
h["KW"] = kw
var max int
max, h["List"] = PageListBack(se, kw, false, false, pg, setting.Conf.General.PageSize)
if pg < max {
vals.Set("page", fmt.Sprint(pg+1))
h["Next"] = vals.Encode()
}
if pg > 1 {
vals.Set("page", fmt.Sprint(pg-1))
h["Prev"] = vals.Encode()
}
h["PP"] = make(map[int]string, max)
for i := 0; i < max; i++ {
vals.Set("page", fmt.Sprint(i+1))
h["PP"].(map[int]string)[i+1] = vals.Encode()
}
h["Cur"] = pg
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-posts", h)
}
// 专题列表
func HandleSeries(c *gin.Context) {
h := GetBack()
h["Manage"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "专题管理 | " + Ei.BTitle
h["List"] = Ei.Series
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-series", h)
}
// 编辑专题
func HandleSerie(c *gin.Context) {
h := GetBack()
id, err := strconv.Atoi(c.Query("mid"))
if serie := QuerySerie(int32(id)); err == nil && id > 0 && serie != nil {
h["Title"] = "编辑专题 | " + Ei.BTitle
h["Edit"] = serie
} else {
h["Title"] = "新增专题 | " + Ei.BTitle
}
h["Manage"] = true
h["Path"] = c.Request.URL.Path
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-serie", h)
}
// 标签列表
func HandleTags(c *gin.Context) {
h := GetBack()
h["Manage"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "标签管理 | " + Ei.BTitle
h["List"] = Ei.Tags
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-tags", h)
}
// 草稿箱
func HandleDraft(c *gin.Context) {
h := GetBack()
h["Manage"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "草稿箱 | " + Ei.BTitle
var err error
h["List"], err = LoadDraft()
if err != nil {
logd.Error(err)
c.Status(http.StatusBadRequest)
} else {
c.Status(http.StatusOK)
}
RenderHTMLBack(c, "admin-draft", h)
}
// 回收箱
func HandleTrash(c *gin.Context) {
h := GetBack()
h["Manage"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "回收箱 | " + Ei.BTitle
var err error
h["List"], err = LoadTrash()
if err != nil {
logd.Error(err)
c.HTML(http.StatusBadRequest, "backLayout.html", h)
return
}
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-trash", h)
}
// 基本设置==>Setting
func HandleGeneral(c *gin.Context) {
h := GetBack()
h["Setting"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "基本设置 | " + Ei.BTitle
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-general", h)
}
// 阅读设置
func HandleDiscussion(c *gin.Context) {
h := GetBack()
h["Setting"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "阅读设置 | " + Ei.BTitle
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-discussion", h)
}
// api
func HandleAPI(c *gin.Context) {
action := c.Param("action")
logd.Debug("action=======>", action)
api := APIs[action]
if api == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Invalid API Request"})
return
}
api(c)
}
// 渲染 html
func RenderHTMLBack(c *gin.Context, name string, data gin.H) {
if name == "login.html" {
err := Tmpl.ExecuteTemplate(c.Writer, name, data)
if err != nil {
panic(err)
}
c.Header("Content-Type", "text/html; charset=utf-8")
return
}
var buf bytes.Buffer
err := Tmpl.ExecuteTemplate(&buf, name, data)
if err != nil {
panic(err)
}
data["LayoutContent"] = template.HTML(buf.String())
err = Tmpl.ExecuteTemplate(c.Writer, "backLayout.html", data)
if err != nil {
panic(err)
}
c.Header("Content-Type", "text/html; charset=utf-8")
}

5
build/README.md Normal file
View File

@@ -0,0 +1,5 @@
Packaging and Continuous Integration.
Put your cloud (AMI), container (Docker), OS (deb, rpm, pkg) package configurations and scripts in the `/build/package` directory.
Put your CI (travis, circle, drone) configurations and scripts in the `/build/ci` directory. Note that some of the CI tools (e.g., Travis CI) are very picky about the location of their config files. Try putting the config files in the `/build/ci` directory linking them to the location where the CI tools expect them (when possible).

View File

@@ -0,0 +1,15 @@
FROM alpine:latest
LABEL maintainer="deepzz.qi@gmail.com"
RUN apk add --update --no-cache tzdata ca-certificates mongodb-tools libc6-compat
COPY README.md /app/README.md
COPY CHANGELOG.md /app/CHANGELOG.md
COPY LICENSE /app/LICENSE
COPY bin/backend /app/backend
COPY conf /app/conf
EXPOSE 9001
WORKDIR /app
CMD ["./backend"]

View File

@@ -0,0 +1,17 @@
FROM alpine:latest
LABEL maintainer="deepzz.qi@gmail.com"
RUN apk add --update --no-cache tzdata
COPY README.md /app/README.md
COPY CHANGELOG.md /app/CHANGELOG.md
COPY LICENSE /app/LICENSE
COPY bin/backend /app/backend
COPY conf /app/conf
COPY website /app/website
COPY assets /app/assets
EXPOSE 9000
WORKDIR /app
CMD ["./backend"]

View File

@@ -1,51 +0,0 @@
package main
import (
"regexp"
"strconv"
"time"
)
// 检查 email
func CheckEmail(e string) bool {
reg := regexp.MustCompile(`^(\w)+([\.\-]\w+)*@(\w)+((\.\w+)+)$`)
return reg.MatchString(e)
}
// 检查 domain
func CheckDomain(domain string) bool {
reg := regexp.MustCompile(`^(http://|https://)?[0-9a-zA-Z]+[0-9a-zA-Z\.-]*\.[a-zA-Z]{2,4}$`)
return reg.MatchString(domain)
}
// 检查 sms
func CheckSMS(sms string) bool {
reg := regexp.MustCompile(`^\+\d+$`)
return reg.MatchString(sms)
}
// 检查 password
func CheckPwd(pwd string) bool {
return len(pwd) > 5 && len(pwd) < 19
}
// 检查日期
func CheckDate(date string) time.Time {
if t, err := time.ParseInLocation("2006-01-02 15:04", date, time.Local); err == nil {
return t
}
return time.Now()
}
// 检查 id
func CheckSerieID(sid string) int32 {
if id, err := strconv.Atoi(sid); err == nil {
return int32(id)
}
return 0
}
// bool
func CheckBool(str string) bool {
return str == "true" || str == "1"
}

View File

@@ -1,41 +0,0 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCheckEmail(t *testing.T) {
emails := []string{
"xx@email.com",
"xxxxemail.com",
"xxx#email.com",
}
for i, v := range emails {
if i == 0 {
assert.True(t, CheckEmail(v))
} else {
assert.False(t, CheckEmail(v))
}
}
}
func TestCheckDomain(t *testing.T) {
domains := []string{
"123.com",
"http://123.com",
"https://123.com",
"123#.com",
"123.coooom",
}
for i, v := range domains {
if i > 2 {
assert.False(t, CheckDomain(v))
} else {
assert.True(t, CheckDomain(v))
}
}
}

7
cmd/README.md Normal file
View File

@@ -0,0 +1,7 @@
Main applications for this project.
The directory name for each application should match the name of the executable you want to have (e.g., `/cmd/myapp`).
Don't put a lot of code in the application directory. If you think the code can be imported and used in other projects, then it should live in the `/pkg` directory. If the code is not reusable or if you don't want others to reuse it, put that code in the `/internal` directory. You'll be surprised what others will do, so be explicit about your intentions!
It's common to have a small `main` function that imports and invokes the code from the `/internal` and `/pkg` directories and nothing else.

62
cmd/backup/main.go Normal file
View File

@@ -0,0 +1,62 @@
// Package main provides ...
package main
import (
"flag"
"fmt"
"github.com/eiblog/eiblog/pkg/config"
"github.com/eiblog/eiblog/pkg/core/backup/ping"
"github.com/eiblog/eiblog/pkg/core/backup/swag"
"github.com/eiblog/eiblog/pkg/core/backup/timer"
"github.com/gin-gonic/gin"
)
var restore bool
func init() {
flag.BoolVar(&restore, "restore", false, "restore data into mongodb")
}
func main() {
fmt.Println("Hi, it's App " + config.Conf.BackupApp.Name)
flag.Parse()
endRun := make(chan error, 1)
runCommand(restore, endRun)
runHTTPServer(endRun)
fmt.Println(<-endRun)
}
func runCommand(restore bool, endRun chan error) {
go func() {
endRun <- timer.Start(restore)
}()
}
func runHTTPServer(endRun chan error) {
if !config.Conf.BackupApp.EnableHTTP {
return
}
if config.Conf.RunMode == config.ModeProd {
gin.SetMode(gin.ReleaseMode)
}
e := gin.Default()
// swag
swag.RegisterRoutes(e)
// route
ping.RegisterRoutes(e)
// start
address := fmt.Sprintf(":%d", config.Conf.BackupApp.HTTPPort)
go func() {
endRun <- e.Run(address)
}()
fmt.Println("HTTP server running on: " + address)
}

72
cmd/eiblog/main.go Normal file
View File

@@ -0,0 +1,72 @@
// Package main provides ...
package main
import (
"fmt"
"path/filepath"
"github.com/eiblog/eiblog/pkg/config"
"github.com/eiblog/eiblog/pkg/core/eiblog"
"github.com/eiblog/eiblog/pkg/core/eiblog/admin"
"github.com/eiblog/eiblog/pkg/core/eiblog/file"
"github.com/eiblog/eiblog/pkg/core/eiblog/page"
"github.com/eiblog/eiblog/pkg/core/eiblog/swag"
"github.com/eiblog/eiblog/pkg/mid"
"github.com/gin-gonic/gin"
)
func main() {
fmt.Println("Hi, it's App " + config.Conf.EiBlogApp.Name)
endRun := make(chan error, 1)
runHTTPServer(endRun)
fmt.Println(<-endRun)
}
func runHTTPServer(endRun chan error) {
if !config.Conf.EiBlogApp.EnableHTTP {
return
}
if config.Conf.RunMode == config.ModeProd {
gin.SetMode(gin.ReleaseMode)
}
e := gin.Default()
// middleware
e.Use(mid.UserMiddleware())
e.Use(mid.SessionMiddleware(mid.SessionOpts{
Name: "su",
Secure: config.Conf.RunMode == config.ModeProd,
Secret: []byte("ZGlzvcmUoMTAsICI="),
}))
// swag
swag.RegisterRoutes(e)
// static files, page
root := filepath.Join(config.WorkDir, "assets")
e.Static("/static", root)
// static files
file.RegisterRoutes(e)
// frontend pages
page.RegisterRoutes(e)
// unauthz api
admin.RegisterRoutes(e)
// admin router
group := e.Group("/admin", eiblog.AuthFilter)
{
page.RegisterRoutesAuthz(group)
admin.RegisterRoutesAuthz(group)
}
// start
address := fmt.Sprintf(":%d", config.Conf.EiBlogApp.HTTPPort)
go func() {
endRun <- e.Run(address)
}()
fmt.Println("HTTP server running on: " + address)
}

3
conf/README.md Normal file
View File

@@ -0,0 +1,3 @@
Configuration file templates or default configs.
Put your confd or consul-template template files here.

View File

@@ -1,103 +1,67 @@
# 运行模式 dev or prod
runmode: dev
# 静态文件版本
staticversion: 1
# superfeedr url
feedrurl: https://deepzz.superfeedr.com/
# 热搜词配置
hotwords:
appname: eiblog
database:
driver: sqlite
source: ./db.sqlite
eshost:
eiblogapp:
mode:
name: cmd-eiblog
enablehttp: true
httpport: 9000
host: example.com
staticversion: 1 # 静态文件版本
hotwords: # 热搜词
- docker
- mongodb
- curl
- dns
# ping rpcs 地址
pingrpcs:
- http://ping.baidu.com/ping/RPC2
- http://blogsearch.google.com/ping/RPC2
- http://rpc.pingomatic.com/
# 常规配置
general:
# 首页展示文章数量
pagenum: 10
# 管理界面
pagesize: 20
# 起始ID预留id不时之需, 不用管
startid: 11
# 文章描述前缀
descprefix: "Desc:"
# 截取预览标识
identifier: <!--more-->
# 自动截取预览, 字符数
length: 400
# 回收箱保留48小时
trash: -48
# 定时清理回收箱,每 %d 小时
clean: 1
# 评论相关
disqus:
shortname: xxxxxx
publickey: wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
accesstoken: 50023908f39f4607957e909b495326af
postscount: https://disqus.com/api/3.0/threads/set.json
postslist: https://disqus.com/api/3.0/threads/listPosts.json
postcreate: https://disqus.com/api/3.0/posts/create.json
postapprove: https://disqus.com/api/3.0/posts/approve.json
threadcreate: https://disqus.com/api/3.0/threads/create.json
# disqus.js 文件名
embed: disqus_7d3cf2.js
# 获取评论数量间隔
interval: 5
# 谷歌统计
google:
url: https://www.google-analytics.com/collect
tid: UA-xxxxxx-1
v: "1"
t: pageview
# 七牛CDN
qiniu:
bucket: eiblog
domain: st.deepzz.com
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
# 运行模式
mode:
# http server
enablehttp: true
httpport: 9000
# https server
enablehttps: false
autocert: false
httpsport: 9001
certfile: conf/ssl/domain.rsa.pem
keyfile: conf/ssl/domain.rsa.key
domain: deepzz.com
# twitter地址: twitter.com/chenqijing2
twitter:
card: summary
site: chenqijing2
image: st.deepzz.com/static/img/avatar.jpg
address: twitter.com/chenqijing2
# 数据初始化操作,可到博客后台修改
account:
# *后台登录用户名
username: deepzz
# *登录明文密码
password: deepzz
# 邮箱,用于通知: chenqijing2@163.com
email: chenqijing2@163.com
# 手机号, "+8615100000000"
phonenumber: "+8615100000000"
# 家庭住址
address: ""
blogger:
# left显示名称: Deepzz
blogname: Deepzz
# 小标题: 不抛弃,不放弃
subtitle: 不抛弃,不放弃
# 备案号: 蜀 ICP 备 16021362 号
beian: 蜀 ICP 备 16021362 号
# footer显示名称及tab标题: Deepzz's Blog
btitle: Deepzz's Blog
# 版权声明
copyright: 本站使用「<a href="//creativecommons.org/licenses/by/4.0/">署名 4.0 国际</a>」创作共享协议,转载请注明作者及原网址。
general: # 常规配置
pagenum: 10 # 首页展示文章数量
pagesize: 20 # 管理界面
startid: 11 # 起始ID预留id不时之需, 不用管
descprefix: "Desc:" # 文章描述前缀
identifier: <!--more--> # 截取预览标识
length: 400 # 自动截取预览, 字符数
timezone: Asia/Shanghai # 时区
disqus: # 评论相关
shortname: xxxxxx
publickey: wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
accesstoken: 50023908f39f4607957e909b495326af
google: # 谷歌分析
url: https://www.google-analytics.com/collect
tid: UA-xxxxxx-1
v: "1"
t: pageview
adsense: <script data-ad-client="ca-pub-5384494508691406" async src=""></script>
qiniu: # 七牛OSS
bucket: eiblog
domain: st.deepzz.com
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
twitter: # twitter card
card: summary
site: deepzz02
image: st.deepzz.com/static/img/avatar.jpg
address: twitter.com/deepzz02
feedrpc: # rss ping
feedrurl: https://deepzz.superfeedr.com/
pingrpc:
- http://ping.baidu.com/ping/RPC2
- http://rpc.pingomatic.com/
# 数据初始化操作,可到博客后台修改
account:
username: deepzz # *后台登录用户名
password: deepzz # *登录明文密码
backupapp:
mode:
name: cmd-backup
enablehttp: true
httpport: 9001
backupto: qiniu # 备份到七牛云
interval: 7d # 多久备份一次
validity: 60 # 保存时长days
qiniu: # 七牛OSS
bucket: backup
domain: st.deepzz.com
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf

View File

@@ -1 +0,0 @@
# like 192.168.99.100:true

View File

@@ -1,3 +0,0 @@
ua,user-agent,userAgent
js,javascript
谷歌=>google

View File

@@ -1,17 +0,0 @@
network.host: 0.0.0.0
index:
analysis:
analyzer:
ik_syno:
type: custom
tokenizer: ik_max_word
filter: [my_synonym_filter]
ik_syno_smart:
type: custom
tokenizer: ik_smart
filter: [my_synonym_filter]
filter:
my_synonym_filter:
type: synonym
synonyms_path: analysis/synonym.txt

View File

@@ -1,15 +0,0 @@
# you can override this using by setting a system property, for example -Des.logger.level=DEBUG
es.logger.level: INFO
rootLogger: ${es.logger.level}, console
logger:
# log action execution errors for easier debugging
action: DEBUG
# reduce the logging for aws, too much is logged under the default INFO
com.amazonaws: WARN
appender:
console:
type: console
layout:
type: consolePattern
conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n"

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">custom/mydict.dic;custom/single_word_low_freq.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

View File

@@ -1,31 +0,0 @@
使

View File

@@ -1,14 +0,0 @@
medcl
elastic
elasticsearch
kogstash
kibana
marvel
shield
watcher
beats
packetbeat
filebeat
topbeat
metrixbeat
kimchy

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
使

View File

@@ -1,316 +0,0 @@
世纪
位数
像素
克拉
公亩
公克
公分
公升
公尺
公担
公斤
公里
公顷
分钟
分米
加仑
千克
千米
厘米
周年
小时
平方
平方公尺
平方公里
平方分米
平方厘米
平方码
平方米
平方英寸
平方英尺
平方英里
平米
年代
年级
月份
毫升
毫米
毫克
海里
点钟
盎司
秒钟
立方公尺
立方分米
立方厘米
立方码
立方米
立方英寸
立方英尺
英亩
英寸
英尺
英里
阶段

View File

@@ -1,33 +0,0 @@
a
an
and
are
as
at
be
but
by
for
if
in
into
is
it
no
not
of
on
or
such
that
the
their
then
there
these
they
this
to
was
will
with

View File

@@ -1,37 +0,0 @@
斯基
维奇
诺夫

View File

@@ -1,131 +0,0 @@
万俟
上官
东方
令狐
仲孙
公冶
公孙
公羊
单于
司徒
司空
司马
夏侯
太叔
宇文
宗政
尉迟
慕容
欧阳
淳于
澹台
濮阳
申屠
皇甫
诸葛
赫连
轩辕
钟离
长孙
闻人
闾丘
鲜于

View File

@@ -1,80 +0,0 @@
# Elasticsearch plugin descriptor file
# This file must exist as 'plugin-descriptor.properties' at
# the root directory of all plugins.
#
# A plugin can be 'site', 'jvm', or both.
#
### example site plugin for "foo":
#
# foo.zip <-- zip file for the plugin, with this structure:
# _site/ <-- the contents that will be served
# plugin-descriptor.properties <-- example contents below:
#
# site=true
# description=My cool plugin
# version=1.0
#
### example jvm plugin for "foo"
#
# foo.zip <-- zip file for the plugin, with this structure:
# <arbitrary name1>.jar <-- classes, resources, dependencies
# <arbitrary nameN>.jar <-- any number of jars
# plugin-descriptor.properties <-- example contents below:
#
# jvm=true
# classname=foo.bar.BazPlugin
# description=My cool plugin
# version=2.0.0-rc1
# elasticsearch.version=2.0
# java.version=1.7
#
### mandatory elements for all plugins:
#
# 'description': simple summary of the plugin
description=IK Analyzer for Elasticsearch
#
# 'version': plugin's version
version=1.10.1
#
# 'name': the plugin name
name=analysis-ik
### mandatory elements for site plugins:
#
# 'site': set to true to indicate contents of the _site/
# directory in the root of the plugin should be served.
site=${elasticsearch.plugin.site}
#
### mandatory elements for jvm plugins :
#
# 'jvm': true if the 'classname' class should be loaded
# from jar files in the root directory of the plugin.
# Note that only jar files in the root directory are
# added to the classpath for the plugin! If you need
# other resources, package them into a resources jar.
jvm=true
#
# 'classname': the name of the class to load, fully-qualified.
classname=org.elasticsearch.plugin.analysis.ik.AnalysisIkPlugin
#
# 'java.version' version of java the code is built against
# use the system property java.specification.version
# version string must be a sequence of nonnegative decimal integers
# separated by "."'s and may have leading zeros
java.version=1.7
#
# 'elasticsearch.version' version of elasticsearch compiled against
# You will have to release a new version of the plugin for each new
# elasticsearch release. This version is checked when the plugin
# is loaded so Elasticsearch will refuse to start in the presence of
# plugins with the incorrect elasticsearch.version.
elasticsearch.version=2.4.1
#
### deprecated elements for jvm plugins :
#
# 'isolated': true if the plugin should have its own classloader.
# passing false is deprecated, and only intended to support plugins
# that have hard dependencies against each other. If this is
# not specified, then the plugin is isolated by default.
isolated=${elasticsearch.plugin.isolated}
#

View File

@@ -1,134 +0,0 @@
server {
listen 443 ssl http2 fastopen=3 reuseport;
server_name www.deepzz.com deepzz.com;
server_tokens off;
access_log /data/eiblog/logdata/nginx.log;
# ip 黑名单
include /data/eiblog/conf/nginx/ip.blacklist;
# 现在一般证书是内置的。letsencrypt 暂未
# https://imququ.com/post/certificate-transparency.html#toc-2
ssl_ct on;
# 中间证书 + 根证书
# https://imququ.com/post/why-can-not-turn-on-ocsp-stapling.html
ssl_trusted_certificate /data/eiblog/conf/ssl/full_chained.pem;
# 站点证书 + 中间证书,私钥
ssl_certificate /data/eiblog/conf/ssl/domain.rsa.pem;
ssl_certificate_key /data/eiblog/conf/ssl/domain.rsa.key;
ssl_ct_static_scts /data/eiblog/conf/scts/rsa/;
# ssl_certificate /data/eiblog/conf/ssl/domain.ecc.pem;
# ssl_certificate_key /data/eiblog/conf/ssl/domain.ecc.key;
# ssl_ct_static_scts /data/eiblog/conf/scts/ecc/;
# openssl dhparam -out dhparams.pem 2048
# https://weakdh.org/sysadmin.html
ssl_dhparam /data/eiblog/conf/ssl/dhparams.pem;
# openssl rand 48 > session_ticket.key
# 单机部署可以不指定 ssl_session_ticket_key
# ssl_session_ticket_key /data/eiblog/conf/ssl/session_ticket.key;
# https://github.com/cloudflare/sslconfig/blob/master/conf
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
# 如果启用了 RSA + ECDSA 双证书Cipher Suite 可以参考以下配置:
# ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets on;
# ssl stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 114.114.114.114 8.8.8.8 valid=300s;
resolver_timeout 10s;
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
return 444;
}
if ($host != 'deepzz.com' ) {
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
}
# webmaster 站点验证相关
location ~* (google4c90d18e696bdcf8\.html|BingSiteAuth\.xml)$ {
root /data/eiblog/static;
expires 1d;
}
location ^~ /admin/ {
proxy_http_version 1.1;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
# deny 将完全不允许页面被嵌套,可能会导致一些异常。如果遇到这样的问题,建议改成 SAMEORIGIN
# https://imququ.com/post/web-security-and-response-header.html#toc-1
add_header X-Frame-Options deny;
add_header X-Powered-By eiblog/1.3.0;
add_header X-Content-Type-Options nosniff;
proxy_set_header Connection "";
proxy_set_header Host deepzz.com;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:9000;
}
location / {
proxy_http_version 1.1;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options deny;
add_header X-Content-Type-Options nosniff;
# 改deepzz相关的
add_header Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob: https:; img-src data: https: https://st.deepzz.com; media-src https://st.deepzz.com; style-src 'unsafe-inline' https:; child-src https:; connect-src 'self' https://translate.googleapis.com; frame-src https://disqus.com https://www.slideshare.net";
# 中间证书证书指纹
# https://imququ.com/post/http-public-key-pinning.html
add_header Public-Key-Pins 'pin-sha256="IiSbZ4pMDEyXvtl7Lg8K3FNmJcTAhKUTrB2FQOaAO/s="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000;';
add_header Cache-Control no-cache;
add_header X-Via Aliyun.QingDao;
add_header X-XSS-Protection "1; mode=block";
add_header X-Powered-By eiblog/1.3.0;
proxy_ignore_headers Set-Cookie;
proxy_hide_header Vary;
proxy_set_header Connection "";
proxy_set_header Host deepzz.com;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:9000;
}
}
server {
server_name www.deepzz.com deepzz.com;
server_tokens off;
access_log /dev/null;
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
return 444;
}
# letsencrypt file verify
location ^~ /.well-known/acme-challenge/ {
alias /data/eiblog/challenges/;
try_files $uri =404;
}
location / {
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
}
}

View File

@@ -1,2 +0,0 @@
# example black list
#deny 195.154.211.220;

View File

@@ -1,71 +0,0 @@
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 10240;
}
http {
include mime.types;
default_type application/octet-stream;
charset UTF-8;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
access_log off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
#keepalive_timeout 0;
keepalive_timeout 65;
gzip on;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_min_length 1000;
gzip_proxied any;
gzip_disable "msie6";
gzip_http_version 1.0;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
include /data/eiblog/conf/nginx/domain/*.conf;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,8 +0,0 @@
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEAzkJoGFJJGMXQBVIq0DFom7qI3vD7Z8JMQnfCLpoi9AfqW6kGq/bR
FhK9fuRkO+GdzZasx1mSNRQeX8GdaQM4GUn0yel7fxlxNC59mxo++P8NvmxQ47l4
K9QpIRuqxa5UKIG6g3N5pkLwGjcD9a79v4DJn4XA9cVjRYc4BnYmiArgaMFOmGPy
KmvU/VhFv8fnxSfn8uCmAGSuHmfbjx5TMfCqaeXzmmhyvpSl88JZfGlwOtXcOU0K
O2JhNRKtaicZlevC8gtpFDNYKnf4K9kiUVmq0JLvuzOxN05sQoPYFCvgMFIYf+ND
Jwtv7FWF2hQV3y1Xms7ja4776FcP9QlKuwIBAg==
-----END DH PARAMETERS-----

View File

@@ -1,59 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFXzCCBEegAwIBAgIQFcILcawsNlJ6B2Z9hxvG2DANBgkqhkiG9w0BAQsFADCB
vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0xNjA4MTEwMDAwMDBaFw0yNjA4MTAyMzU5NTlaMIGXMQswCQYDVQQGEwJDTjEl
MCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEfMB0GA1UECxMW
U3ltYW50ZWMgVHJ1c3QgTmV0d29yazEdMBsGA1UECxMURG9tYWluIFZhbGlkYXRl
ZCBTU0wxITAfBgNVBAMTGFRydXN0QXNpYSBEViBTU0wgQ0EgLSBHNjCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6PcUG/2YcVEtzwDU9yj/F5Ed4M5LBW
8ln6HwMRuXuDgKIz6t6OBeF7tqaZPwZ8NRgwwhtEcnyeuDPjS/HYvQqEZZWQpnut
njNGniplXkOngD48f0Bhdo5Rwtqv13UiV59gfP/PSy81atddMqgtt5WeITaseddh
4sVZV0bj2b37lJ0w/2TRm+gzIp12lzliIkZr9OdgAKRWpITs0TtU0jJq6gXR5fe7
aut4GOVov5SZHJorXJSTkhdEIyoYWgUxNAIu83Ykw91/bjeXKFVvq8+tZZN3ct5F
I7qbHRgcrLqRdZQPkKjQWC9hXFrQVZUSwRMjRN2O3TXvmtprFr4od+0CAwEAAaOC
AX0wggF5MBIGA1UdEwEB/wQIMAYBAf8CAQAwNgYDVR0fBC8wLTAroCmgJ4YlaHR0
cDovL3Muc3ltY2IuY29tL3VuaXZlcnNhbC1yb290LmNybDAdBgNVHSUEFjAUBggr
BgEFBQcDAQYIKwYBBQUHAwIwDgYDVR0PAQH/BAQDAgEGMC4GCCsGAQUFBwEBBCIw
IDAeBggrBgEFBQcwAYYSaHR0cDovL3Muc3ltY2QuY29tMGEGA1UdIARaMFgwVgYG
Z4EMAQIBMEwwIwYIKwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1jYi5jb20vY3BzMCUG
CCsGAQUFBwICMBkaF2h0dHBzOi8vZC5zeW1jYi5jb20vcnBhMCkGA1UdEQQiMCCk
HjAcMRowGAYDVQQDExFTeW1hbnRlY1BLSS0yLTYwMjAdBgNVHQ4EFgQU3qQwrOQ/
9ScrMXyvkGcdriKe7GQwHwYDVR0jBBgwFoAUtnf6aUhHn1MS1cLqBzJ2B9GXBxkw
DQYJKoZIhvcNAQELBQADggEBAMS+q79V8jm2p5T8pdMCS8XuYjCVpsJuXh4QqegO
Ors1D4nVEE0Thm7V6fy60UgOxsjXayauQonRoCdApv6bziW9USmUK2qQ5xFsgBBD
LzwJnQd7FDUW9ngDNCo/377J2CLqiVsYFqNnpuVzbbWF/6UR+AxGwFzzAgx9QOLP
Qucpx6kV+d0Ht2cm/+mwGlzmdr/RwcT5iLW0oAQZkcfzbi+s9bBaIprMOCiyvO/F
Gf1YDO4KQHMfda+GO6HpmPl8nDFgP17Bi3QgRjr1kjpZ9WvnPQha1wZLel+VGm/e
xOXNrbei6P5U58U0Hy3GsO5r/IxRE5k55tR5o5E2EB0ilSg=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
7M2CYfE45k+XmCpajQ==
-----END CERTIFICATE-----

View File

@@ -1 +0,0 @@
╟░Щ`*dы≤\ЛвH

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{.Title}}</title>
<link>https://{{.Domain}}</link>
<description>{{.SubTitle}}</description>
<atom:link href="https://{{.Domain}}/rss.html" rel="self" />
<atom:link href="{{.FeedrURL}}" rel="hub" />
<language>zh-CN</language>
<lastBuildDate>{{.BuildDate}}</lastBuildDate>
{{range .Artcs}}
<item>
<title>{{.Title}}</title>
<link>https://{{$.Domain}}/post/{{.Slug}}.html</link>
<comments>https://{{$.Domain}}/post/{{.Slug}}.html#comments</comments>
<guid>https://{{$.Domain}}/post/{{.Slug}}.html</guid>
<description>
<![CDATA[<blockquote>{{.Content}}<p>本文链接:<a href="https://{{$.Domain}}/post/{{.Slug}}.html">https://{{$.Domain}}/post/{{.Slug}}.html</a><a href="https://{{$.Domain}}/post/{{.Slug}}.html#comments">参与评论 »</a></p>]]>
</description>
</item>
{{end}}
</channel>
</rss>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>{{.BTitle}}</ShortName>
<Description>{{.SubTitle}}</Description>
<Url type="text/html" template="https://{{.Domain}}/search.html?q={searchTerms}" />
</OpenSearchDescription>

View File

@@ -1,3 +0,0 @@
User-agent: *
Allow: /
Sitemap: https://{{.Domain}}/sitemap.xml

629
db.go
View File

@@ -1,629 +0,0 @@
// Package main provides ...
package main
import (
"bytes"
"fmt"
"regexp"
"sort"
"strings"
"sync"
"time"
"github.com/eiblog/blackfriday"
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
"github.com/eiblog/utils/mgo"
)
// 数据库及表名
const (
DB = "eiblog"
COLLECTION_ACCOUNT = "account"
COLLECTION_ARTICLE = "article"
COUNTER_SERIE = "serie"
COUNTER_ARTICLE = "article"
SERIES_MD = "series_md"
ARCHIVE_MD = "archive_md"
ADD = "add"
DELETE = "delete"
)
// blackfriday 配置
const (
commonHtmlFlags = 0 |
blackfriday.HTML_TOC |
blackfriday.HTML_USE_XHTML |
blackfriday.HTML_USE_SMARTYPANTS |
blackfriday.HTML_SMARTYPANTS_FRACTIONS |
blackfriday.HTML_SMARTYPANTS_DASHES |
blackfriday.HTML_SMARTYPANTS_LATEX_DASHES |
blackfriday.HTML_NOFOLLOW_LINKS
commonExtensions = 0 |
blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
blackfriday.EXTENSION_TABLES |
blackfriday.EXTENSION_FENCED_CODE |
blackfriday.EXTENSION_AUTOLINK |
blackfriday.EXTENSION_STRIKETHROUGH |
blackfriday.EXTENSION_SPACE_HEADERS |
blackfriday.EXTENSION_HEADER_IDS |
blackfriday.EXTENSION_BACKSLASH_LINE_BREAK |
blackfriday.EXTENSION_DEFINITION_LISTS
)
// Global Account
var (
Ei *Account
lock sync.Mutex
)
func init() {
// 数据库加索引
err := mgo.Index(DB, COLLECTION_ACCOUNT, []string{"username"})
if err != nil {
logd.Fatal(err)
}
err = mgo.Index(DB, COLLECTION_ARTICLE, []string{"id"})
if err != nil {
logd.Fatal(err)
}
err = mgo.Index(DB, COLLECTION_ARTICLE, []string{"slug"})
if err != nil {
logd.Fatal(err)
}
// 读取帐号信息
loadAccount()
// 获取文章数据
loadArticles()
// 生成markdown文档
go generateMarkdown()
// 启动定时器
go timer()
// 获取评论数量
go PostsCount()
}
// 读取或初始化帐号信息
func loadAccount() {
Ei = &Account{}
err := mgo.FindOne(DB, COLLECTION_ACCOUNT, mgo.M{"username": setting.Conf.Account.Username}, Ei)
// 初始化用户数据
if err == mgo.ErrNotFound {
logd.Printf("Initializing account: %s\n", setting.Conf.Account.Username)
Ei = &Account{
Username: setting.Conf.Account.Username,
Password: EncryptPasswd(setting.Conf.Account.Username, setting.Conf.Account.Password),
Email: setting.Conf.Account.Email,
PhoneN: setting.Conf.Account.PhoneNumber,
Address: setting.Conf.Account.Address,
CreateTime: time.Now(),
}
Ei.BlogName = setting.Conf.Blogger.BlogName
Ei.SubTitle = setting.Conf.Blogger.SubTitle
Ei.BeiAn = setting.Conf.Blogger.BeiAn
Ei.BTitle = setting.Conf.Blogger.BTitle
Ei.Copyright = setting.Conf.Blogger.Copyright
err = mgo.Insert(DB, COLLECTION_ACCOUNT, Ei)
generateTopic()
} else if err != nil {
logd.Fatal(err)
}
Ei.CH = make(chan string, 2)
Ei.MapArticles = make(map[string]*Article)
Ei.Tags = make(map[string]SortArticles)
}
func loadArticles() {
err := mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": false, "deletetime": mgo.M{"$eq": time.Time{}}}, &Ei.Articles)
if err != nil {
logd.Fatal(err)
}
sort.Sort(Ei.Articles)
for i, v := range Ei.Articles {
// 渲染文章
GenerateExcerptAndRender(v)
Ei.MapArticles[v.Slug] = v
// 分析文章
if v.ID < setting.Conf.General.StartID {
continue
}
if i > 0 {
v.Prev = Ei.Articles[i-1]
}
if Ei.Articles[i+1].ID >= setting.Conf.General.StartID {
v.Next = Ei.Articles[i+1]
}
upArticle(v, false)
}
Ei.CH <- SERIES_MD
Ei.CH <- ARCHIVE_MD
}
// generate series,archive markdown
func generateMarkdown() {
for {
switch typ := <-Ei.CH; typ {
case SERIES_MD:
sort.Sort(Ei.Series)
var buffer bytes.Buffer
buffer.WriteString(Ei.SeriesSay)
buffer.WriteString("\n\n")
for _, serie := range Ei.Series {
buffer.WriteString(fmt.Sprintf("### %s{#toc-%d}", serie.Name, serie.ID))
buffer.WriteString("\n")
buffer.WriteString(serie.Desc)
buffer.WriteString("\n\n")
for _, artc := range serie.Articles {
//eg. * [标题一](/post/hello-world.html) <span class="date">(Man 02, 2006)</span>
buffer.WriteString("* [" + artc.Title + "](/post/" + artc.Slug +
".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
}
buffer.WriteByte('\n')
}
Ei.PageSeries = string(renderPage(buffer.Bytes()))
case ARCHIVE_MD:
sort.Sort(Ei.Archives)
var buffer bytes.Buffer
buffer.WriteString(Ei.ArchivesSay + "\n")
var (
currentYear string
gt12Month = len(Ei.Archives) > 12
)
for _, archive := range Ei.Archives {
if gt12Month {
year := archive.Time.Format("2006 年")
if currentYear != year {
currentYear = year
buffer.WriteString(fmt.Sprintf("\n### %s\n\n", archive.Time.Format("2006 年")))
}
} else {
buffer.WriteString(fmt.Sprintf("\n### %s\n\n", archive.Time.Format("2006年1月")))
}
for i, artc := range archive.Articles {
if i == 0 && gt12Month {
buffer.WriteString("* *[" + artc.Title + "](/post/" + artc.Slug +
".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>*\n")
} else {
buffer.WriteString("* [" + artc.Title + "](/post/" + artc.Slug +
".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
}
}
}
Ei.PageArchives = string(renderPage(buffer.Bytes()))
}
}
}
// init account: generate blogroll and about page
func generateTopic() {
about := &Article{
ID: mgo.NextVal(DB, COUNTER_ARTICLE),
Author: setting.Conf.Account.Username,
Title: "关于",
Slug: "about",
CreateTime: time.Time{},
UpdateTime: time.Time{},
}
// 推送到 disqus
go func() { ThreadCreate(about) }()
blogroll := &Article{
ID: mgo.NextVal(DB, COUNTER_ARTICLE),
Author: setting.Conf.Account.Username,
Title: "友情链接",
Slug: "blogroll",
CreateTime: time.Time{},
UpdateTime: time.Time{},
}
err := mgo.Insert(DB, COLLECTION_ARTICLE, blogroll)
if err != nil {
logd.Fatal(err)
}
err = mgo.Insert(DB, COLLECTION_ARTICLE, about)
if err != nil {
logd.Fatal(err)
}
}
// render page
func renderPage(md []byte) []byte {
renderer := blackfriday.HtmlRenderer(commonHtmlFlags, "", "")
return blackfriday.Markdown(md, renderer, commonExtensions)
}
// 文章分页
func PageList(p, n int) (prev int, next int, artcs []*Article) {
var l int
for l = len(Ei.Articles); l > 0; l-- {
if Ei.Articles[l-1].ID >= setting.Conf.General.StartID {
break
}
}
if l == 0 {
return 0, 0, nil
}
m := l / n
if d := l % n; d > 0 {
m++
}
if p > m {
p = m
}
if p > 1 {
prev = p - 1
}
if p < m {
next = p + 1
}
s := (p - 1) * n
e := p * n
if e > l {
e = l
}
artcs = Ei.Articles[s:e]
return
}
// 渲染markdown操作和截取摘要操作
var reg = regexp.MustCompile(setting.Conf.General.Identifier)
// header
var regH = regexp.MustCompile("</nav></div>")
func GenerateExcerptAndRender(artc *Article) {
if strings.HasPrefix(artc.Content, setting.Conf.General.DescPrefix) {
index := strings.Index(artc.Content, "\r\n")
artc.Desc = IgnoreHtmlTag(artc.Content[len(setting.Conf.General.DescPrefix):index])
artc.Content = artc.Content[index:]
}
// 查找目录
content := renderPage([]byte(artc.Content))
index := regH.FindIndex(content)
if index != nil {
artc.Header = string(content[0:index[1]])
artc.Content = string(content[index[1]:])
} else {
artc.Content = string(content)
}
index = reg.FindStringIndex(artc.Content)
if index != nil {
artc.Excerpt = IgnoreHtmlTag(artc.Content[0:index[0]])
} else {
uc := []rune(artc.Content)
length := setting.Conf.General.Length
if len(uc) < length {
length = len(uc)
}
artc.Excerpt = IgnoreHtmlTag(string(uc[0:length]))
}
}
// 读取草稿箱
func LoadDraft() (artcs SortArticles, err error) {
err = mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": true}, &artcs)
sort.Sort(artcs)
return
}
// 读取回收箱
func LoadTrash() (artcs SortArticles, err error) {
err = mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"deletetime": mgo.M{"$ne": time.Time{}}}, &artcs)
sort.Sort(artcs)
return
}
// 添加文章到tag、serie、archive
func upArticle(artc *Article, needSort bool) {
// tag
for _, tag := range artc.Tags {
Ei.Tags[tag] = append(Ei.Tags[tag], artc)
if needSort {
sort.Sort(Ei.Tags[tag])
}
}
// serie
for i, serie := range Ei.Series {
if serie.ID == artc.SerieID {
Ei.Series[i].Articles = append(Ei.Series[i].Articles, artc)
if needSort {
sort.Sort(Ei.Series[i].Articles)
Ei.CH <- SERIES_MD
}
break
}
}
// archive
y, m, _ := artc.CreateTime.Date()
for i, archive := range Ei.Archives {
if ay, am, _ := archive.Time.Date(); y == ay && m == am {
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles, artc)
if needSort {
sort.Sort(Ei.Archives[i].Articles)
Ei.CH <- ARCHIVE_MD
}
return
}
}
Ei.Archives = append(Ei.Archives, &Archive{Time: artc.CreateTime,
Articles: SortArticles{artc}})
if needSort {
Ei.CH <- ARCHIVE_MD
}
}
// 删除文章从tag、serie、archive
func dropArticle(artc *Article) {
// tag
for _, tag := range artc.Tags {
for i, v := range Ei.Tags[tag] {
if v == artc {
Ei.Tags[tag] = append(Ei.Tags[tag][0:i], Ei.Tags[tag][i+1:]...)
if len(Ei.Tags[tag]) == 0 {
delete(Ei.Tags, tag)
}
}
}
}
// serie
for i, serie := range Ei.Series {
if serie.ID == artc.SerieID {
for j, v := range serie.Articles {
if v == artc {
Ei.Series[i].Articles = append(Ei.Series[i].Articles[0:j],
Ei.Series[i].Articles[j+1:]...)
Ei.CH <- SERIES_MD
break
}
}
}
}
// archive
for i, archive := range Ei.Archives {
ay, am, _ := archive.Time.Date()
if y, m, _ := artc.CreateTime.Date(); ay == y && am == m {
for j, v := range archive.Articles {
if v == artc {
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles[0:j],
Ei.Archives[i].Articles[j+1:]...)
if len(Ei.Archives[i].Articles) == 0 {
Ei.Archives = append(Ei.Archives[:i], Ei.Archives[i+1:]...)
}
Ei.CH <- ARCHIVE_MD
break
}
}
}
}
}
// 替换文章
func ReplaceArticle(oldArtc *Article, newArtc *Article) {
if oldArtc != nil {
i, artc := GetArticle(oldArtc.ID)
DelFromLinkedList(artc)
Ei.Articles = append(Ei.Articles[:i], Ei.Articles[i+1:]...)
delete(Ei.MapArticles, artc.Slug)
dropArticle(oldArtc)
}
Ei.MapArticles[newArtc.Slug] = newArtc
Ei.Articles = append(Ei.Articles, newArtc)
sort.Sort(Ei.Articles)
GenerateExcerptAndRender(newArtc)
AddToLinkedList(newArtc.ID)
upArticle(newArtc, true)
}
// 添加文章
func AddArticle(artc *Article) error {
// 分配ID, 占位至起始id
for {
if id := mgo.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.General.StartID {
continue
} else {
artc.ID = id
break
}
}
err := mgo.Insert(DB, COLLECTION_ARTICLE, artc)
if err != nil {
return err
}
// 正式发布文章
if !artc.IsDraft {
defer GenerateExcerptAndRender(artc)
Ei.MapArticles[artc.Slug] = artc
Ei.Articles = append([]*Article{artc}, Ei.Articles...)
sort.Sort(Ei.Articles)
AddToLinkedList(artc.ID)
upArticle(artc, true)
}
return nil
}
// 删除文章,移入回收箱
func DelArticles(ids ...int32) error {
lock.Lock()
defer lock.Unlock()
for _, id := range ids {
i, artc := GetArticle(id)
DelFromLinkedList(artc)
Ei.Articles = append(Ei.Articles[:i], Ei.Articles[i+1:]...)
delete(Ei.MapArticles, artc.Slug)
err := UpdateArticle(mgo.M{"id": id}, mgo.M{"$set": mgo.M{"deletetime": time.Now()}})
if err != nil {
return err
}
dropArticle(artc)
}
return nil
}
// 从链表里删除文章
func DelFromLinkedList(artc *Article) {
if artc.Prev == nil && artc.Next != nil {
artc.Next.Prev = nil
} else if artc.Prev != nil && artc.Next == nil {
artc.Prev.Next = nil
} else if artc.Prev != nil && artc.Next != nil {
artc.Prev.Next = artc.Next
artc.Next.Prev = artc.Prev
}
}
// 将文章添加到链表
func AddToLinkedList(id int32) {
i, artc := GetArticle(id)
if i == 0 && Ei.Articles[i+1].ID >= setting.Conf.General.StartID {
artc.Next = Ei.Articles[i+1]
Ei.Articles[i+1].Prev = artc
} else if i > 0 && Ei.Articles[i-1].ID >= setting.Conf.General.StartID {
artc.Prev = Ei.Articles[i-1]
if Ei.Articles[i-1].Next != nil {
artc.Next = Ei.Articles[i-1].Next
Ei.Articles[i-1].Next.Prev = artc
}
Ei.Articles[i-1].Next = artc
}
}
// 从缓存获取文章
func GetArticle(id int32) (int, *Article) {
for i, artc := range Ei.Articles {
if id == artc.ID {
return i, artc
}
}
return -1, nil
}
// 定时清除回收箱文章
func timer() {
delT := time.NewTicker(time.Duration(setting.Conf.General.Clean) * time.Hour)
for {
<-delT.C
mgo.Remove(DB, COLLECTION_ARTICLE, mgo.M{"deletetime": mgo.M{"$gt": time.Time{},
"$lt": time.Now().Add(time.Duration(setting.Conf.General.Trash) * time.Hour)}})
}
}
// 操作帐号字段
func UpdateAccountField(M mgo.M) error {
return mgo.Update(DB, COLLECTION_ACCOUNT, mgo.M{"username": Ei.Username}, M)
}
// 删除草稿箱或回收箱,永久删除
func RemoveArticle(id int32) error {
return mgo.Remove(DB, COLLECTION_ARTICLE, mgo.M{"id": id})
}
// 恢复删除文章到草稿箱
func RecoverArticle(id int32) error {
return mgo.Update(DB, COLLECTION_ARTICLE, mgo.M{"id": id},
mgo.M{"$set": mgo.M{"deletetime": time.Time{}, "isdraft": true}})
}
// 更新文章
func UpdateArticle(query, update interface{}) error {
return mgo.Update(DB, COLLECTION_ARTICLE, query, update)
}
// 编辑文档
func QueryArticle(id int32) *Article {
artc := &Article{}
if err := mgo.FindOne(DB, COLLECTION_ARTICLE, mgo.M{"id": id}, artc); err != nil {
return nil
}
return artc
}
// 添加专题
func AddSerie(name, slug, desc string) error {
serie := &Serie{mgo.NextVal(DB, COUNTER_SERIE), name, slug, desc, time.Now(), nil}
Ei.Series = append(Ei.Series, serie)
sort.Sort(Ei.Series)
Ei.CH <- SERIES_MD
return UpdateAccountField(mgo.M{"$addToSet": mgo.M{"blogger.series": serie}})
}
// 更新专题
func UpdateSerie(serie *Serie) error {
Ei.CH <- SERIES_MD
return mgo.Update(DB, COLLECTION_ACCOUNT, mgo.M{"username": Ei.Username,
"blogger.series.id": serie.ID}, mgo.M{"$set": mgo.M{"blogger.series.$": serie}})
}
// 删除专题
func DelSerie(id int32) error {
for i, serie := range Ei.Series {
if id == serie.ID {
if len(serie.Articles) > 0 {
return fmt.Errorf("请删除该专题下的所有文章")
}
err := UpdateAccountField(mgo.M{"$pull": mgo.M{"blogger.series": mgo.M{"id": id}}})
if err != nil {
return err
}
Ei.Series[i] = nil
Ei.Series = append(Ei.Series[:i], Ei.Series[i+1:]...)
Ei.CH <- SERIES_MD
}
}
return nil
}
// 查找专题
func QuerySerie(id int32) *Serie {
for _, serie := range Ei.Series {
if serie.ID == id {
return serie
}
}
return nil
}
// 后台分页
func PageListBack(se int, kw string, draft, del bool, p, n int) (max int, artcs []*Article) {
M := mgo.M{}
if draft {
M["isdraft"] = true
} else if del {
M["deletetime"] = mgo.M{"$ne": time.Time{}}
} else {
M["isdraft"] = false
M["deletetime"] = mgo.M{"$eq": time.Time{}}
if se > 0 {
M["serieid"] = se
}
if kw != "" {
M["title"] = mgo.M{"$regex": kw, "$options": "$i"}
}
}
ms, c := mgo.Connect(DB, COLLECTION_ARTICLE)
defer ms.Close()
err := c.Find(M).Select(mgo.M{"content": 0}).Sort("-createtime").Limit(n).Skip((p - 1) * n).All(&artcs)
if err != nil {
logd.Error(err)
}
count, err := c.Find(M).Count()
if err != nil {
logd.Error(err)
}
max = count / n
if count%n > 0 {
max++
}
return
}

View File

@@ -1,62 +0,0 @@
package main
import (
"html/template"
"testing"
)
func TestPageListBack(t *testing.T) {
_, artcs := PageListBack(0, "", false, false, 1, 20)
for _, artc := range artcs {
t.Log(*artc)
}
t.Log("------------------------------------------------------------")
_, artcs = PageListBack(0, "", false, false, 2, 10)
for _, artc := range artcs {
t.Log(*artc)
}
t.Log("------------------------------------------------------------")
_, artcs = PageListBack(3, "", false, false, 1, 20)
for _, artc := range artcs {
t.Log(*artc)
}
t.Log("------------------------------------------------------------")
_, artcs = PageListBack(3, "19", false, false, 1, 20)
for _, artc := range artcs {
t.Log(*artc)
}
t.Log("------------------------------------------------------------")
_, artcs = PageListBack(0, "", false, true, 1, 20)
for _, artc := range artcs {
t.Log(*artc)
}
t.Log("------------------------------------------------------------")
_, artcs = PageListBack(0, "", true, false, 1, 20)
for _, artc := range artcs {
t.Log(*artc)
}
}
func TestAddSerie(t *testing.T) {
err := AddSerie("测试", "nothing", "这里是描述")
if err != nil {
t.Error(err)
}
}
func TestRenderPage(t *testing.T) {
data := []byte(`<ul class="links ssl">
<li><a href="https://yryz.net/">一人游走</a><span class="date">「不错的小伙子」</span></li>
<li><a href="https://hsulei.com/">Leo同学</a><span class="date">「小伙子,该干活了」</span></li>
<li><a href="https://razeencheng.com/">razeen同学</a><span class="date">「Stay hungry. Stay foolish.」</span></li>
</ul>
<ul class="links">
<li><a href="http://blog.mirreal.net/">Mirreal Ellison</a><span class="date">「kissing the fire」</span></li>
</ul>`)
t.Log(IgnoreHtmlTag(string(data)))
data = renderPage(data)
t.Log(template.HTML(string(data)))
}

301
disqus.go
View File

@@ -1,301 +0,0 @@
// Package main provides ...
// Get article' comments count
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/deepzz0/logd"
"github.com/eiblog/eiblog/setting"
)
var ErrDisqusConfig = errors.New("disqus config incorrect")
func correctDisqusConfig() bool {
return setting.Conf.Disqus.PostsCount != "" &&
setting.Conf.Disqus.PublicKey != "" &&
setting.Conf.Disqus.ShortName != ""
}
// 定时获取所有文章评论数量
type postsCountResp struct {
Code int
Response []struct {
Id string
Posts int
Identifiers []string
}
}
func PostsCount() error {
if !correctDisqusConfig() {
return ErrDisqusConfig
}
time.AfterFunc(time.Duration(setting.Conf.Disqus.Interval)*time.Hour, func() {
err := PostsCount()
if err != nil {
logd.Error(err)
}
})
vals := url.Values{}
vals.Set("api_key", setting.Conf.Disqus.PublicKey)
vals.Set("forum", setting.Conf.Disqus.ShortName)
var count, index int
for index < len(Ei.Articles) {
for ; index < len(Ei.Articles) && count < 50; index++ {
artc := Ei.Articles[index]
vals.Add("thread:ident", "post-"+artc.Slug)
count++
}
count = 0
resp, err := http.Get(setting.Conf.Disqus.PostsCount + "?" + vals.Encode())
if err != nil {
return err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New(string(b))
}
result := &postsCountResp{}
err = json.Unmarshal(b, result)
if err != nil {
return err
}
for _, v := range result.Response {
i := strings.Index(v.Identifiers[0], "-")
artc := Ei.MapArticles[v.Identifiers[0][i+1:]]
if artc != nil {
artc.Count = v.Posts
artc.Thread = v.Id
}
}
}
return nil
}
// 获取文章评论列表
type postsListResp struct {
Cursor struct {
HasNext bool
Next string
}
Code int
Response []postDetail
}
type postDetail struct {
Parent int
Id string
CreatedAt string
Message string
IsDeleted bool
Author struct {
Name string
ProfileUrl string
Avatar struct {
Cache string
}
}
Thread string
}
func PostsList(slug, cursor string) (*postsListResp, error) {
if !correctDisqusConfig() {
return nil, ErrDisqusConfig
}
vals := url.Values{}
vals.Set("api_key", setting.Conf.Disqus.PublicKey)
vals.Set("forum", setting.Conf.Disqus.ShortName)
vals.Set("thread:ident", "post-"+slug)
vals.Set("cursor", cursor)
vals.Set("limit", "50")
resp, err := http.Get(setting.Conf.Disqus.PostsList + "?" + vals.Encode())
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New(string(b))
}
result := &postsListResp{}
err = json.Unmarshal(b, result)
if err != nil {
return nil, err
}
return result, nil
}
type PostComment struct {
Message string
Parent string
Thread string
AuthorEmail string
AuthorName string
IpAddress string
Identifier string
UserAgent string
}
type postCreateResp struct {
Code int
Response postDetail
}
// 评论文章
func PostCreate(pc *PostComment) (*postCreateResp, error) {
if !correctDisqusConfig() {
return nil, ErrDisqusConfig
}
vals := url.Values{}
vals.Set("api_key", "E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F")
vals.Set("message", pc.Message)
vals.Set("parent", pc.Parent)
vals.Set("thread", pc.Thread)
vals.Set("author_email", pc.AuthorEmail)
vals.Set("author_name", pc.AuthorName)
// vals.Set("state", "approved")
request, err := http.NewRequest("POST", setting.Conf.Disqus.PostCreate, strings.NewReader(vals.Encode()))
if err != nil {
return nil, err
}
request.Header.Set("Referer", "https://disqus.com")
resp, err := http.DefaultClient.Do(request)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New(string(b))
}
result := &postCreateResp{}
err = json.Unmarshal(b, result)
if err != nil {
return nil, err
}
return result, nil
}
// 批准评论通过
type approvedResp struct {
Code int
Response []struct {
Id string
}
}
func PostApprove(post string) error {
if !correctDisqusConfig() {
return ErrDisqusConfig
}
vals := url.Values{}
vals.Set("api_key", setting.Conf.Disqus.PublicKey)
vals.Set("access_token", setting.Conf.Disqus.AccessToken)
vals.Set("post", post)
request, err := http.NewRequest("POST", setting.Conf.Disqus.PostApprove, strings.NewReader(vals.Encode()))
if err != nil {
return err
}
request.Header.Set("Referer", "https://disqus.com")
resp, err := http.DefaultClient.Do(request)
if err != nil {
return err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New(string(b))
}
result := &approvedResp{}
err = json.Unmarshal(b, result)
if err != nil {
return err
}
return nil
}
// 创建thread
type threadCreateResp struct {
Code int
Response struct {
Id string
}
}
func ThreadCreate(artc *Article) error {
if !correctDisqusConfig() {
return ErrDisqusConfig
}
vals := url.Values{}
vals.Set("api_key", setting.Conf.Disqus.PublicKey)
vals.Set("access_token", setting.Conf.Disqus.AccessToken)
vals.Set("forum", setting.Conf.Disqus.ShortName)
vals.Set("title", artc.Title+" | "+Ei.BTitle)
vals.Set("identifier", "post-"+artc.Slug)
urlPath := fmt.Sprintf("https://%s/post/%s.html", setting.Conf.Mode.Domain, artc.Slug)
vals.Set("url", urlPath)
resp, err := http.PostForm(setting.Conf.Disqus.ThreadCreate, 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 := &threadCreateResp{}
err = json.Unmarshal(b, result)
if err != nil {
return err
}
artc.Thread = result.Response.Id
return nil
}

View File

@@ -1,36 +0,0 @@
package main
import (
"testing"
)
func TestDisqus(t *testing.T) {
PostsCount()
}
func TestPostCreate(t *testing.T) {
pc := &PostComment{
Message: "hahahaha",
Thread: "52799014",
AuthorEmail: "deepzz.qi@gmail.com",
AuthorName: "deepzz",
}
id, err := PostCreate(pc)
if err != nil {
t.Error(err)
return
}
t.Log("post success", id)
}
func TestThreadCreate(t *testing.T) {
tc := &Article{
Title: "测试test7",
Slug: "test7",
}
err := ThreadCreate(tc)
if err != nil {
t.Fatal(err)
}
}

18
dist.sh
View File

@@ -1,18 +0,0 @@
#!/bin/bash
# GOFLAGS='-ldflags="-s -w"'
version=`git describe --tags`
arch=$(go env GOARCH)
for os in linux darwin windows; do
echo "... building $version for $os/$arch"
TARGET="eiblog-$version.$os-$arch"
GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build
if [ "$os" == "windows" ]; then
tar czvf $TARGET.tar.gz conf static views eiblog.exe
rm eiblog.exe
else
tar czvf $TARGET.tar.gz conf static views eiblog
rm eiblog
fi
done

View File

@@ -1,46 +1,34 @@
version: '2'
version: '3'
services:
mongodb:
image: mongo:3.2
container_name: eidb
volumes:
- /data/eiblog/mgodb:/data/db
- ${PWD}/mgodb:/data/db
restart: always
elasticsearch:
image: elasticsearch:2.4.1
container_name: eisearch
image: deepzz0/elasticsearch:2.4.1
volumes:
- /data/eiblog/conf/es/config:/usr/share/elasticsearch/config
- /data/eiblog/conf/es/plugins:/usr/share/elasticsearch/plugins
- /data/eiblog/esdata/data:/usr/share/elasticsearch/data
- /data/eiblog/esdata/logs:/usr/share/elasticsearch/logs
environment:
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
- ${PWD}/esdata:/usr/share/elasticsearch/data
restart: always
eiblog:
image: registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
container_name: eiblog
extra_hosts:
- "disqus.com:23.235.33.134"
image: deepzz0/eiblog:latest
volumes:
- /data/eiblog/logdata:/eiblog/logdata
- /data/eiblog/conf:/eiblog/conf
- ${PWD}/conf:/app/conf
extra_hosts:
- "disqus.com:151.101.192.134"
- "deepzz.disqus.com:151.101.192.134"
links:
- elasticsearch
- mongodb
- elasticsearch
- mongodb
environment:
- GODEBUG=netdns=cgo
- RUN_MODE=prod
ports:
- "9000:9000"
- 127.0.0.1:9000:9000
restart: always
backup:
image: deepzz0/backup:latest
volumes:
- ${PWD}/conf:/app/conf
links:
- mongodb
restart: always
# backup:
# image: registry.cn-hangzhou.aliyuncs.com/deepzz/backup
# container_name: backup
# links:
# - mongodb
# environment:
# - QINIU_BUCKET=xxxx
# - QINIU_DOMAIN=xx.example.com
# - ACCESS_KEY=xxxxxxxxxx
# - SECRECT_KEY=xxxxxxxxxx
# restart: always

108
docs/README.md Normal file
View File

@@ -0,0 +1,108 @@
# EiBlog [![Build Status](https://travis-ci.org/eiblog/eiblog.svg?branch=v1.3.0)](https://travis-ci.org/eiblog/eiblog) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) [![Versuib](https://img.shields.io/github/tag/eiblog/eiblog.svg)](https://github.com/eiblog/eiblog/releases)
> 博客项目结构参考模版https://github.com/deepzz0/appdemo
用过其它博客系统,不喜欢,不够轻,不够快!这是我开发的第二款博客系统,也实在不想再在这件事情上过多纠结了。`EiBlog` 是一个比较稳定的博客系统,现已迭代至 `2.0` 版本,稳定性和维护你是不用担心的。
但它有着部署简单(上线复杂!)的特点,不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(简洁、轻快,安全),等你体验。
### 快速体验
这里以 mongodb 为例,更多支持的后端存储服务如下:
| 类型driver | 地址source示例 |
| -------------- | ------------------------------------------------------------ |
| mongodb | mongodb://localhost:27017 |
| mysql | user:password@tcp(localhost:3306)/eiblog?charset=utf8mb4&parseTime=True&loc=Local |
| postgres | host=localhost port=5432 user=user password=password dbname=eiblog sslmode=disable |
| sqlite | /path/eiblog.db |
| sqlserver | sqlserver://user:password@localhost:9930?database=eiblog |
| 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支持等仅保持常用简单页面与功能
```
首页、专题、归档、友链、关于、搜索
```
功能说明:
* 博客归档,利用时间线帮助我们将归纳博文,内容少于一年按月归档,大于则按年归档。
* 博客专题,有时候博文是同一系列,专题能够帮助我们很好归纳博文,对阅读者是非常友好的。
* 标签系统,每篇博文都可以打上不同标签,使得在归档和专题不满足的情况下自定义归档,这块辅助搜索简直完美。
* 搜索系统依托ElasticSearch实现的站内搜索速度与效率并存再加上google opensearch搜索只流畅。
* 管理后台,内嵌全功能 `Typecho` 后台系统,全功能 `Markdown` 编辑器让你感觉什么是简洁清爽。
* 谷歌统计由于google api的速度问题从而实现了后端API异步统计使得博客页面加载飞速。
* Disqus评论国内评论系统不友好因此选择disqus又由于众所周知原因国内不能用实现另类disqus评论方式。
* 多存储后端支持mongodb、mysql、postgres、sqlite等存储后端。
* 七牛CDN支持在 `Markdown` 编辑器直接上传附件,让你只考虑编辑内容,解放思想。
* 自动备份支持多存储后端的备份功能备份数据保存到七牛CDN上。
当然,为了让整个系统加载速度更快,还做了更多优化措施:
* 文章评论数量(不重要)通过后端跑定时任务获取,所以有时评论数量是不对的,这样减少了 API 调用。
* 整站内容全部内存缓存,`mardown` 文档全部转换为 html 进行缓存,减少了转换过程。
* `.js``.css` 等静态文件浏览器本地存储,小图片 base64 内置到 css 中,二次访问不会产生网络带来的延迟,加速访问。通过版本控制更新。
* 最佳实践 nginx 配置,可以查看 `eiblog.conf`,开启压缩缩小传输量,服务器传输证书链、开启 `Session Resumption``Session Ticket``OCSP Stapling `等加速证书握手,再次提高速度。
### 博客页面
可以容易的看到 [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+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
![show-home](./img/show-home.png)
![show-home2](./img/show-home2.png)
![show-admin](./img/show-admin.png)
### 更多文档
* [安装部署](https://eiblog.github.io/eiblog/install)
* [写作须知](https://eiblog.github.io/eiblog/writing)
* [好玩功能](https://eiblog.github.io/eiblog/amusing)
* [如何备份](https://eiblog.github.io/eiblog/backup)
### 贡献成员
![graphs/contributors](https://opencollective.com/eiblog/contributors.svg?width=890&button=false)
### 授权许可
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/eiblog/eiblog/blob/master/LICENSE) 文件中。

1
docs/_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -1,7 +1,7 @@
### Twitter Card
相信很多人不明白为什么会这样专注twitter。首先twitter是一个社交网站国际性的。其次我们可以使用它的Twitter Card功能非常的酷。
相信很多人不明白为什么会这样专注 twitter。首先 twitter 是一个社交网站国际性的。其次我们可以使用它的Twitter Card 功能非常的酷。
当你配置好Twitter相关的参数后`conf/app.yml`
当你配置好 Twitter 相关的参数后`conf/app.yml`
```
# twitter地址: twitter.com/chenqijing2
twitter:
@@ -13,12 +13,13 @@ twitter:
每当你发部一个推文,你如果带上你的网址,它会自动给你展示成卡片的形式
![twitter-card](http://7xokm2.com1.z0.glb.clouddn.com/img/twitter-pub.png)
![twitter-pub](./img/twitter-pub.png)
![twitter-card2](http://7xokm2.com1.z0.glb.clouddn.com/img/twitter-pub2.png)
![twitter-pub2](./img/twitter-pub2.png)
可以看到``之前是没有内容的,该内容是我们文章的描述。
### Google OpenSearch
在 Chrome 浏览器上,你可以在输入网站后按 TAB 键进入搜索模式,如:
![opensearch](http://7xokm2.com1.z0.glb.clouddn.com/opensearch.gif)
![opensearch](./img/opensearch.gif)

View File

@@ -1,69 +0,0 @@
### 证书自动更新
本博客证书自动更新有两种方式:
* [acme/autocert](https://github.com/golang/crypto/tree/master/acme/autocert),博客内部集成,通过 tls-sni 验证,实现全自动更新证书,一键开启关闭。请在裸服务器的情况下使用(不要使用代理)。单证书
* [acme.sh](https://github.com/Neilpang/acme.sh),强大的 acme 脚本,多种自动更新证书方式,满足你各方面的需求。双证书
#### 方式一
什么是 autocert简单点你只需要两步操作
1. 将域名解析到你的服务器。
2. 在服务器上运行开启 autocert 功能的程序(这里不需要配置证书),需要占用 443 端口。
其它过程你不需要过问,即会完成自动申请证书,自动更新证书的功能(默认 30 天)。这个是在 tcp/ip 层的操作,对用户完全透明,非常棒。
一键开启 autocert 功能,只需修改 `conf/app.yml` 文件内容:
```
# 运行模式
mode:
# http server
enablehttp: true
httpport: 9000
# https server
enablehttps: true # 必须开启
autocert: false # autocert 功能开关
httpsport: 9001
certfile:
keyfile:
domain: deepzz.com # 申请证书的域名,也是博客的域名
```
首先,使用 HTTPS 必须启用 `enablehttps`,它有两个作用:
* 如果 `enablehttp` 开启,会自动 301 重定向到 https。
* 作为开启 autocert 的前提条件。
其次, `autocert` 是否开启也有两个作用:
* false服务器将使用 `httpsport``certfile``keyfile` 作为参数启动 https 服务器。
* true服务器直接使用 443 端口启动 https 服务器,并且自动申请证书,且在证书只有 30 天有效期时自动更新证书。域名为 *运行模式* 下的 mode->domain。
#### 方式二
使用方式二,你需要了解 acme.sh 的具体使用方式,非常简单。选择适合自己的方式实现自动更新证书。
博主,这里实现了 aliyun dns 的自动验证,自动更新证书。详情参见 Makefile->gencert。这里实现了自动申请 ecc、rsa 双证书,并且自动申请 scts自动安装自动更新。
基本流程如下:
1. 创建相关目录:`/data/eiblog/conf/ssl``/data/eiblog/conf/scts/rsa``/data/eiblog/conf/scts/ecc`
2. 自动下载安装 acme.sh 脚本。
3. 自动申请 RSA 证书并且自动获取 scts并且自动安装到指定位置。
4. 自动申请 ECC 证书并且自动获取 scts并且自动安装到指定位置。
##### 使用方式
导出环境变量Aliyun dns 的环境变量为:
```
export Ali_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
export Ali_Secret="jlsdflanljkljlfdsaklkjflsa"
```
执行命令:
```
$ make gencert -cn=common_name -sans="-d example.com -d example1.com"
```

42
docs/backup.md Normal file
View File

@@ -0,0 +1,42 @@
### 备份数据
EiBlog 镜像仓库地址https://hub.docker.com/u/deepzz0备份镜像为deepzz0/backup。
目前仅支持同步 mongodb 数据到七牛云,参考 `app.yml`
```
backupapp:
mode:
name: cmd-backup
enablehttp: true
httpport: 9001
backupto: qiniu # 备份到七牛云
interval: 7d # 多久备份一次
validity: 60 # 保存时长days
qiniu: # 七牛OSS
bucket: backup
domain: st.deepzz.com
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
```
### 运行
1、获取备份镜像
```
$ docker pull deepzz0/backup
```
2、启动备份镜像
```
$ docker run --name backup \
-v ${PWD}/conf:/app/conf
```
Docker-compose 请参考项目根目录下的 `docker-compose.yml` 文件。

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/img/article-title.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Some files were not shown because too many files have changed in this diff Show More