Compare commits
189 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d86ab71ad9 | ||
|
|
4600ed5094 | ||
|
|
5fcadd1c81 | ||
|
|
42b106d582 | ||
|
|
bb06be36fe | ||
|
|
cb3fb2d2e7 | ||
|
|
779a23cb75 | ||
|
|
e2fa96cd62 | ||
|
|
db26fb51e5 | ||
|
|
994be5d508 | ||
|
|
a5c3d33565 | ||
|
|
1ffc58eccf | ||
|
|
b6ad4e8949 | ||
|
|
41704917db | ||
|
|
f6ba716f55 | ||
|
|
b2e6c168c5 | ||
|
|
eca741896f | ||
|
|
17792e5a7e | ||
|
|
04289c633e | ||
|
|
3a5eb6fccc | ||
|
|
f6d8656c83 | ||
|
|
4690d5123b | ||
|
|
a9e8e39d34 | ||
|
|
c51055a0db | ||
|
|
445b188517 | ||
|
|
4bfff2e5e9 | ||
|
|
aa91997c0c | ||
|
|
3b2a6689be | ||
|
|
4c46be3f03 | ||
|
|
da47e9880f | ||
|
|
4f92e0d619 | ||
|
|
3a8f7d120b | ||
|
|
cf0a897ad0 | ||
|
|
418b604946 | ||
|
|
b93c320987 | ||
|
|
b24f7c0666 | ||
|
|
efe80fbc6b | ||
|
|
65b89bcae5 | ||
|
|
289b8145bc | ||
|
|
bf0ad811ff | ||
|
|
db00a9b527 | ||
|
|
38aa704198 | ||
|
|
9c58447e3b | ||
|
|
34fc5f368c | ||
|
|
daa561e67e | ||
|
|
364d293660 | ||
|
|
5170844623 | ||
|
|
7047a3599d | ||
|
|
d0c830c1e6 | ||
|
|
bf699e4957 | ||
|
|
c315737871 | ||
|
|
d844c0e8f8 | ||
|
|
f6cb55c00f | ||
|
|
a5292027c0 | ||
|
|
00cf0b5c9f | ||
|
|
6805afa759 | ||
|
|
4f6f85a85a | ||
|
|
dc3e6659b5 | ||
|
|
ca74d13598 | ||
|
|
82fba0ddb4 | ||
|
|
2b6bae1f74 | ||
|
|
6387412776 | ||
|
|
cfaa25e123 | ||
|
|
6ff6acdbda | ||
|
|
b3b3be448f | ||
|
|
78f5bfc1ce | ||
|
|
4b9eb1ff4d | ||
|
|
7d04b8f5c0 | ||
|
|
d000090e30 | ||
|
|
56e396f252 | ||
|
|
f6662fa4c5 | ||
|
|
a570c783f3 | ||
|
|
e2046d0d39 | ||
|
|
cdbe082764 | ||
|
|
3a86c6a65c | ||
|
|
c1c9e6025a | ||
|
|
a15791a792 | ||
|
|
ea87f4b2e6 | ||
|
|
a0653cf67f | ||
|
|
397d47bc00 | ||
|
|
00d9df4406 | ||
|
|
2b277bd901 | ||
|
|
c18f3b7da9 | ||
|
|
79ac024312 | ||
|
|
1e4c0afc19 | ||
|
|
055d7d2164 | ||
|
|
ed7c510744 | ||
|
|
488c0f04fe | ||
|
|
a99ed9504b | ||
|
|
6cde9323b1 | ||
|
|
2b226977ab | ||
|
|
aa076e5e3c | ||
|
|
26635790e1 | ||
|
|
569a1fe2a1 | ||
|
|
182eaff085 | ||
|
|
90e7082322 | ||
|
|
e71e42571f | ||
|
|
3527e11b04 | ||
|
|
a407bdfe72 | ||
|
|
05aa254e70 | ||
|
|
4ec3065f9d | ||
|
|
7bec238f9c | ||
|
|
605787958d | ||
|
|
c06f02622a | ||
|
|
f431bf5adc | ||
|
|
7922043bc6 | ||
|
|
2fbc5fa024 | ||
|
|
9c341f88d2 | ||
|
|
0ae5e6c9fc | ||
|
|
3e2b746319 | ||
|
|
2cd72e190e | ||
|
|
1c955769f4 | ||
|
|
a2769f0913 | ||
|
|
bf3d45a404 | ||
|
|
4b92636079 | ||
|
|
753aaa4ace | ||
|
|
43d7a26e19 | ||
|
|
872d0b1987 | ||
|
|
897b05d071 | ||
|
|
c5a3bd6eab | ||
|
|
beea4f1746 | ||
|
|
9f563f0ae9 | ||
|
|
4749b1e681 | ||
|
|
a39e3aac3b | ||
|
|
63b55b2df8 | ||
|
|
cb091532d5 | ||
|
|
6b74e1d208 | ||
|
|
1815cea2cd | ||
|
|
c3fdbfcb78 | ||
|
|
990e6abbd8 | ||
|
|
bb40570053 | ||
|
|
e2df642a46 | ||
|
|
68e01cdf1f | ||
|
|
bd69c62254 | ||
|
|
970c6068d5 | ||
|
|
27a1f600de | ||
|
|
edf0fbac51 | ||
|
|
98ca570a36 | ||
|
|
9a526f97f8 | ||
|
|
33a29f5e57 | ||
|
|
30ebf76eda | ||
|
|
f5c0bcdb99 | ||
|
|
4b53da3801 | ||
|
|
1bdfb6abea | ||
|
|
33f47d8f3a | ||
|
|
cbd0cfaaf5 | ||
|
|
080c992a92 | ||
|
|
371b2326ea | ||
|
|
2720d11b23 | ||
|
|
b7751d7b9e | ||
|
|
24d81db8be | ||
|
|
010137ebf5 | ||
|
|
c6a2439c54 | ||
|
|
1d54ff3ac5 | ||
|
|
63a4d69209 | ||
|
|
b35d7de58a | ||
|
|
77ea01b7c1 | ||
|
|
5f608b638d | ||
|
|
52da8abceb | ||
|
|
f016b28cb6 | ||
|
|
01b7643ca5 | ||
|
|
375d43761b | ||
|
|
f3e9727947 | ||
|
|
911aa963c7 | ||
|
|
fb66b6871e | ||
|
|
5ae76f243e | ||
|
|
051b034e51 | ||
|
|
27439ecc71 | ||
|
|
d02c838447 | ||
|
|
d17acf5325 | ||
|
|
b278ca377f | ||
|
|
93131441e4 | ||
|
|
ddcc6c2d2e | ||
|
|
ef63ae9598 | ||
|
|
2ed9db5c7b | ||
|
|
06a12bc6f9 | ||
|
|
6524b45751 | ||
|
|
ceb9e2690b | ||
|
|
405fbaf24f | ||
|
|
3245c0e0d3 | ||
|
|
badc62e3f0 | ||
|
|
a5561f257b | ||
|
|
eb37b83ebd | ||
|
|
b2fab703fc | ||
|
|
37deb390d9 | ||
|
|
6fa5088352 | ||
|
|
e023a33786 | ||
|
|
6f818c4b5d | ||
|
|
9ad22fb2d9 |
@@ -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
@@ -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']
|
||||
70
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: release image & asset
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
package:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Inspect builder
|
||||
run: |
|
||||
echo "Name: ${{ steps.buildx.outputs.name }}"
|
||||
echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
|
||||
echo "Status: ${{ steps.buildx.outputs.status }}"
|
||||
echo "Flags: ${{ steps.buildx.outputs.flags }}"
|
||||
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
|
||||
|
||||
- name: Docker tag
|
||||
id: vars
|
||||
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:10})
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
|
||||
- name: Build and push eiblog
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./build/package/eiblog.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
deepzz0/eiblog:${{ steps.vars.outputs.tag }}
|
||||
|
||||
- name: Build and push backup
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./build/package/backup.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
deepzz0/backup:${{ 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
@@ -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
|
||||
|
||||
34
.travis.yml
@@ -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 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
|
||||
177
CHANGELOG.md
@@ -1,41 +1,150 @@
|
||||
# Eiblog Changelog
|
||||
# Changelog
|
||||
|
||||
## 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 相关配置。
|
||||
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.2.0 (2017-06-14)
|
||||
* 更新评论功能,基础评论 0 回复也可评论了。
|
||||
* disqus.js 文件由博主自行更新。
|
||||
* 更正描述 README.md 描述错误 [#4f996](https://github.com/eiblog/eiblog/commit/4f9965b6bdefe087dd0805c1840afcb2752cd155)。
|
||||
* docker 镜像版本化。
|
||||
### [2.2.2](https://github.com/eiblog/eiblog/compare/v2.2.1...v2.2.2) (2023-05-25)
|
||||
|
||||
## v1.1.3 (2017-05-12)
|
||||
* 更新 disqus_78bca4.js 到 disqus_921d24.js,具体请参考 docs/install.md
|
||||
* 更新 vendor
|
||||
### [2.2.1](https://github.com/eiblog/eiblog/compare/v2.2.0...v2.2.1) (2023-05-17)
|
||||
|
||||
## 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)
|
||||
首次发布版本
|
||||
* try to fix backup symbol not found ([bb06be3](https://github.com/eiblog/eiblog/commit/bb06be36fe016e753ca56aa2321ce7e39bffe3e0))
|
||||
|
||||
* 全站`HTTPS`设计,安全、极速。
|
||||
* `Elasticsearch`博客搜索系统。
|
||||
* 开源`Typecho`完整博客后台。
|
||||
* 全功能`Markdown`编辑器。
|
||||
* 异步`Google analysts`分析统计。
|
||||
* `Disqus`评论系统。
|
||||
* 后台直接对接七牛`CDN`。
|
||||
## [2.2.0](https://github.com/eiblog/eiblog/compare/v2.1.18...v2.2.0) (2023-05-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **backup:** add restore flag ([779a23c](https://github.com/eiblog/eiblog/commit/779a23cb75ab5059826370d08b754569a4af4aea))
|
||||
|
||||
### [2.1.18](https://github.com/eiblog/eiblog/compare/v2.1.17...v2.1.18) (2023-01-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **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))
|
||||
|
||||
### [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)
|
||||
|
||||
10
Dockerfile
@@ -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"]
|
||||
3
LICENSE
@@ -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.
|
||||
|
||||
|
||||
80
Makefile
@@ -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.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.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
|
||||
|
||||
150
README.md
@@ -1,90 +1,106 @@
|
||||
# EiBlog [](https://travis-ci.org/eiblog/eiblog) [](LICENSE.md) [](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) 相应系统压缩包,然后解压缩。
|
||||
|
||||
相关图片展示:
|
||||

|
||||
2、启动服务:`./backend`
|
||||
|
||||

|
||||
**Docker**
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
> `注`:图片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` 功能,搜索更加自然。
|
||||
然后访问 `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)
|
||||
**数据库支持**
|
||||
|
||||
### 成功搭建者博客
|
||||
| 类型(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
|
||||
* [https://mxthd.me](https://mxthd.me) - 梦醒逃荒岛
|
||||
### 功能特性
|
||||
|
||||
本着博客本质用来分享知识的特点,`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+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### 更多文档
|
||||
|
||||
* [安装部署](https://eiblog.github.io/eiblog/install)
|
||||
* [写作须知](https://eiblog.github.io/eiblog/writing)
|
||||
* [好玩功能](https://eiblog.github.io/eiblog/amusing)
|
||||
* [如何备份](https://eiblog.github.io/eiblog/backup)
|
||||
|
||||
### 贡献成员
|
||||
|
||||

|
||||
|
||||
### 授权许可
|
||||
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/eiblog/eiblog/blob/master/LICENSE) 文件中。
|
||||
|
||||
如果你的博客使用`Eiblog`搭建,你可以在 [这里](https://github.com/eiblog/eiblog/issues/1) 提交网址。
|
||||
|
||||
472
api.go
@@ -1,472 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"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
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
Ei.Password = newPwd
|
||||
responseNotice(c, NOTICE_SUCCESS, "更新成功", "")
|
||||
}
|
||||
|
||||
func apiPostDelete(c *gin.Context) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
|
||||
}()
|
||||
|
||||
err = c.Request.ParseForm()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ids []int32
|
||||
var i int
|
||||
for _, v := range c.Request.PostForm["cid[]"] {
|
||||
i, err = strconv.Atoi(v)
|
||||
if err != nil || i < 1 {
|
||||
err = errors.New("参数错误")
|
||||
return
|
||||
}
|
||||
ids = append(ids, int32(i))
|
||||
}
|
||||
err = DelArticles(ids...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// elasticsearch 删除索引
|
||||
err = ElasticDelIndex(ids)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func apiPostAdd(c *gin.Context) {
|
||||
var err error
|
||||
var do string
|
||||
var 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": // 保存草稿
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusFound, "/admin/manage-draft")
|
||||
case "publish": // 发布
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusFound, "/admin/manage-posts")
|
||||
}
|
||||
}()
|
||||
|
||||
do = c.PostForm("do") // auto or save or publish
|
||||
slug := c.PostForm("slug")
|
||||
title := c.PostForm("title")
|
||||
text := c.PostForm("text")
|
||||
date := c.PostForm("date")
|
||||
serie := c.PostForm("serie")
|
||||
tag := c.PostForm("tags")
|
||||
update := c.PostForm("update")
|
||||
if title == "" || text == "" || slug == "" {
|
||||
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: CheckDate(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 {
|
||||
ElasticIndex(artc)
|
||||
DoPings(slug)
|
||||
}
|
||||
return
|
||||
}
|
||||
artc.ID = int32(cid)
|
||||
i, a := GetArticle(artc.ID)
|
||||
if a != nil {
|
||||
artc.IsDraft = false
|
||||
artc.Count = a.Count
|
||||
artc.UpdateTime = a.UpdateTime
|
||||
Ei.Articles = append(Ei.Articles[0:i], Ei.Articles[i+1:]...)
|
||||
DelFromLinkedList(a)
|
||||
ManageTagsArticle(a, false, DELETE)
|
||||
ManageSeriesArticle(a, false, DELETE)
|
||||
ManageArchivesArticle(a, false, DELETE)
|
||||
delete(Ei.MapArticles, a.Slug)
|
||||
a = nil
|
||||
}
|
||||
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 {
|
||||
Ei.MapArticles[artc.Slug] = artc
|
||||
Ei.Articles = append(Ei.Articles, artc)
|
||||
sort.Sort(Ei.Articles)
|
||||
GenerateExcerptAndRender(artc)
|
||||
// elasticsearch 索引
|
||||
ElasticIndex(artc)
|
||||
DoPings(slug)
|
||||
if artc.ID >= setting.Conf.General.StartID {
|
||||
ManageTagsArticle(artc, true, ADD)
|
||||
ManageSeriesArticle(artc, true, ADD)
|
||||
ManageArchivesArticle(artc, true, ADD)
|
||||
AddToLinkedList(artc.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func apiSerieDelete(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
// 只能逐一删除
|
||||
for _, v := range c.Request.PostForm["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, "删除成功", "")
|
||||
}
|
||||
|
||||
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, "操作成功", "")
|
||||
}
|
||||
|
||||
// 暂未启用
|
||||
func apiSerieSort(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
v := c.Request.PostForm["mid[]"]
|
||||
logd.Debug(v)
|
||||
}
|
||||
|
||||
func apiDraftDelete(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
for _, v := range c.Request.PostForm["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) {
|
||||
logd.Debug(c.PostForm("key"))
|
||||
logd.Debug(c.Request.PostForm)
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
for _, v := range c.Request.PostForm["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) {
|
||||
logd.Debug(c.PostForm("key"))
|
||||
logd.Debug(c.Request.PostForm)
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
for _, v := range c.Request.PostForm["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, "恢复成功", "")
|
||||
}
|
||||
|
||||
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 := c.Request.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),
|
||||
})
|
||||
}
|
||||
|
||||
func apiFileDelete(c *gin.Context) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
}
|
||||
c.String(http.StatusOK, "删掉了吗?鬼知道。。。")
|
||||
}()
|
||||
name := c.PostForm("title")
|
||||
if name == "" {
|
||||
err = errors.New("参数错误")
|
||||
return
|
||||
}
|
||||
err = FileDelete(name)
|
||||
}
|
||||
|
||||
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
@@ -0,0 +1 @@
|
||||
Other assets to go along with your repository (images, logos, etc).
|
||||
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 847 B After Width: | Height: | Size: 847 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
307
back.go
@@ -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
@@ -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).
|
||||
22
build/package/backup.Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM golang:1.20 AS builder
|
||||
|
||||
WORKDIR /eiblog
|
||||
COPY . .
|
||||
RUN ./scripts/build.sh backup
|
||||
|
||||
|
||||
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 --from=builder /eiblog/bin/backend /app/backend
|
||||
COPY conf /app/conf
|
||||
|
||||
EXPOSE 9001
|
||||
|
||||
WORKDIR /app
|
||||
CMD ["./backend"]
|
||||
24
build/package/eiblog.Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
FROM golang:1.20 AS builder
|
||||
|
||||
WORKDIR /eiblog
|
||||
COPY . .
|
||||
RUN ./scripts/build.sh eiblog
|
||||
|
||||
|
||||
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 --from=builder /eiblog/bin/backend /app/backend
|
||||
COPY conf /app/conf
|
||||
COPY website /app/website
|
||||
COPY assets /app/assets
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
WORKDIR /app
|
||||
CMD ["./backend"]
|
||||
51
check.go
@@ -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"
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
Configuration file templates or default configs.
|
||||
|
||||
Put your confd or consul-template template files here.
|
||||
161
conf/app.yml
@@ -1,102 +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: deepzz
|
||||
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
|
||||
# disqus.js 文件名
|
||||
embed: disqus_7d3cf2.js
|
||||
# 获取评论数量间隔
|
||||
interval: 5
|
||||
# 谷歌统计
|
||||
google:
|
||||
url: https://www.google-analytics.com/collect
|
||||
tid: UA-77251712-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
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# like 192.168.99.100:true
|
||||
@@ -1,3 +0,0 @@
|
||||
ua,user-agent,userAgent
|
||||
js,javascript
|
||||
谷歌=>google
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
@@ -1,31 +0,0 @@
|
||||
也
|
||||
了
|
||||
仍
|
||||
从
|
||||
以
|
||||
使
|
||||
则
|
||||
却
|
||||
又
|
||||
及
|
||||
对
|
||||
就
|
||||
并
|
||||
很
|
||||
或
|
||||
把
|
||||
是
|
||||
的
|
||||
着
|
||||
给
|
||||
而
|
||||
被
|
||||
让
|
||||
在
|
||||
还
|
||||
比
|
||||
等
|
||||
当
|
||||
与
|
||||
于
|
||||
但
|
||||
@@ -1,14 +0,0 @@
|
||||
medcl
|
||||
elastic
|
||||
elasticsearch
|
||||
kogstash
|
||||
kibana
|
||||
marvel
|
||||
shield
|
||||
watcher
|
||||
beats
|
||||
packetbeat
|
||||
filebeat
|
||||
topbeat
|
||||
metrixbeat
|
||||
kimchy
|
||||
@@ -1,25 +0,0 @@
|
||||
不
|
||||
也
|
||||
了
|
||||
仍
|
||||
从
|
||||
以
|
||||
使
|
||||
则
|
||||
却
|
||||
又
|
||||
及
|
||||
对
|
||||
就
|
||||
并
|
||||
很
|
||||
或
|
||||
把
|
||||
是
|
||||
的
|
||||
着
|
||||
给
|
||||
而
|
||||
被
|
||||
让
|
||||
但
|
||||
@@ -1,316 +0,0 @@
|
||||
丈
|
||||
下
|
||||
世
|
||||
世纪
|
||||
两
|
||||
个
|
||||
中
|
||||
串
|
||||
亩
|
||||
人
|
||||
介
|
||||
付
|
||||
代
|
||||
件
|
||||
任
|
||||
份
|
||||
伏
|
||||
伙
|
||||
位
|
||||
位数
|
||||
例
|
||||
倍
|
||||
像素
|
||||
元
|
||||
克
|
||||
克拉
|
||||
公亩
|
||||
公克
|
||||
公分
|
||||
公升
|
||||
公尺
|
||||
公担
|
||||
公斤
|
||||
公里
|
||||
公顷
|
||||
具
|
||||
册
|
||||
出
|
||||
刀
|
||||
分
|
||||
分钟
|
||||
分米
|
||||
划
|
||||
列
|
||||
则
|
||||
刻
|
||||
剂
|
||||
剑
|
||||
副
|
||||
加仑
|
||||
勺
|
||||
包
|
||||
匙
|
||||
匹
|
||||
区
|
||||
千克
|
||||
千米
|
||||
升
|
||||
卷
|
||||
厅
|
||||
厘
|
||||
厘米
|
||||
双
|
||||
发
|
||||
口
|
||||
句
|
||||
只
|
||||
台
|
||||
叶
|
||||
号
|
||||
名
|
||||
吨
|
||||
听
|
||||
员
|
||||
周
|
||||
周年
|
||||
品
|
||||
回
|
||||
团
|
||||
圆
|
||||
圈
|
||||
地
|
||||
场
|
||||
块
|
||||
坪
|
||||
堆
|
||||
声
|
||||
壶
|
||||
处
|
||||
夜
|
||||
大
|
||||
天
|
||||
头
|
||||
套
|
||||
女
|
||||
孔
|
||||
字
|
||||
宗
|
||||
室
|
||||
家
|
||||
寸
|
||||
对
|
||||
封
|
||||
尊
|
||||
小时
|
||||
尺
|
||||
尾
|
||||
局
|
||||
层
|
||||
届
|
||||
岁
|
||||
师
|
||||
帧
|
||||
幅
|
||||
幕
|
||||
幢
|
||||
平方
|
||||
平方公尺
|
||||
平方公里
|
||||
平方分米
|
||||
平方厘米
|
||||
平方码
|
||||
平方米
|
||||
平方英寸
|
||||
平方英尺
|
||||
平方英里
|
||||
平米
|
||||
年
|
||||
年代
|
||||
年级
|
||||
度
|
||||
座
|
||||
式
|
||||
引
|
||||
张
|
||||
成
|
||||
战
|
||||
截
|
||||
户
|
||||
房
|
||||
所
|
||||
扇
|
||||
手
|
||||
打
|
||||
批
|
||||
把
|
||||
折
|
||||
担
|
||||
拍
|
||||
招
|
||||
拨
|
||||
拳
|
||||
指
|
||||
掌
|
||||
排
|
||||
撮
|
||||
支
|
||||
文
|
||||
斗
|
||||
斤
|
||||
方
|
||||
族
|
||||
日
|
||||
时
|
||||
曲
|
||||
月
|
||||
月份
|
||||
期
|
||||
本
|
||||
朵
|
||||
村
|
||||
束
|
||||
条
|
||||
来
|
||||
杯
|
||||
枚
|
||||
枝
|
||||
枪
|
||||
架
|
||||
柄
|
||||
柜
|
||||
栋
|
||||
栏
|
||||
株
|
||||
样
|
||||
根
|
||||
格
|
||||
案
|
||||
桌
|
||||
档
|
||||
桩
|
||||
桶
|
||||
梯
|
||||
棵
|
||||
楼
|
||||
次
|
||||
款
|
||||
步
|
||||
段
|
||||
毛
|
||||
毫
|
||||
毫升
|
||||
毫米
|
||||
毫克
|
||||
池
|
||||
洲
|
||||
派
|
||||
海里
|
||||
滴
|
||||
炮
|
||||
点
|
||||
点钟
|
||||
片
|
||||
版
|
||||
环
|
||||
班
|
||||
瓣
|
||||
瓶
|
||||
生
|
||||
男
|
||||
画
|
||||
界
|
||||
盆
|
||||
盎司
|
||||
盏
|
||||
盒
|
||||
盘
|
||||
相
|
||||
眼
|
||||
石
|
||||
码
|
||||
碗
|
||||
碟
|
||||
磅
|
||||
种
|
||||
科
|
||||
秒
|
||||
秒钟
|
||||
窝
|
||||
立方公尺
|
||||
立方分米
|
||||
立方厘米
|
||||
立方码
|
||||
立方米
|
||||
立方英寸
|
||||
立方英尺
|
||||
站
|
||||
章
|
||||
笔
|
||||
等
|
||||
筐
|
||||
筒
|
||||
箱
|
||||
篇
|
||||
篓
|
||||
篮
|
||||
簇
|
||||
米
|
||||
类
|
||||
粒
|
||||
级
|
||||
组
|
||||
维
|
||||
缕
|
||||
缸
|
||||
罐
|
||||
网
|
||||
群
|
||||
股
|
||||
脚
|
||||
船
|
||||
艇
|
||||
艘
|
||||
色
|
||||
节
|
||||
英亩
|
||||
英寸
|
||||
英尺
|
||||
英里
|
||||
行
|
||||
袋
|
||||
角
|
||||
言
|
||||
课
|
||||
起
|
||||
趟
|
||||
路
|
||||
车
|
||||
转
|
||||
轮
|
||||
辆
|
||||
辈
|
||||
连
|
||||
通
|
||||
遍
|
||||
部
|
||||
里
|
||||
重
|
||||
针
|
||||
钟
|
||||
钱
|
||||
锅
|
||||
门
|
||||
间
|
||||
队
|
||||
阶段
|
||||
隅
|
||||
集
|
||||
页
|
||||
顶
|
||||
顷
|
||||
项
|
||||
顿
|
||||
颗
|
||||
餐
|
||||
首
|
||||
@@ -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
|
||||
@@ -1,37 +0,0 @@
|
||||
乡
|
||||
井
|
||||
亭
|
||||
党
|
||||
区
|
||||
厅
|
||||
县
|
||||
园
|
||||
塔
|
||||
家
|
||||
寺
|
||||
局
|
||||
巷
|
||||
市
|
||||
弄
|
||||
所
|
||||
斯基
|
||||
楼
|
||||
江
|
||||
河
|
||||
海
|
||||
湖
|
||||
省
|
||||
维奇
|
||||
署
|
||||
苑
|
||||
街
|
||||
觀
|
||||
观
|
||||
诺夫
|
||||
路
|
||||
部
|
||||
镇
|
||||
阁
|
||||
山
|
||||
子
|
||||
娃
|
||||
@@ -1,131 +0,0 @@
|
||||
丁
|
||||
万
|
||||
万俟
|
||||
上官
|
||||
东方
|
||||
乔
|
||||
于
|
||||
令狐
|
||||
仲孙
|
||||
任
|
||||
何
|
||||
余
|
||||
候
|
||||
傅
|
||||
公冶
|
||||
公孙
|
||||
公羊
|
||||
冯
|
||||
刘
|
||||
单
|
||||
单于
|
||||
卢
|
||||
史
|
||||
叶
|
||||
司徒
|
||||
司空
|
||||
司马
|
||||
吕
|
||||
吴
|
||||
周
|
||||
唐
|
||||
夏
|
||||
夏侯
|
||||
太叔
|
||||
姚
|
||||
姜
|
||||
孔
|
||||
孙
|
||||
孟
|
||||
宇文
|
||||
宋
|
||||
宗政
|
||||
尉迟
|
||||
尹
|
||||
崔
|
||||
常
|
||||
康
|
||||
廖
|
||||
张
|
||||
彭
|
||||
徐
|
||||
慕容
|
||||
戴
|
||||
文
|
||||
方
|
||||
易
|
||||
曹
|
||||
曾
|
||||
朱
|
||||
李
|
||||
杜
|
||||
杨
|
||||
林
|
||||
梁
|
||||
欧阳
|
||||
武
|
||||
段
|
||||
毛
|
||||
江
|
||||
汤
|
||||
沈
|
||||
淳于
|
||||
潘
|
||||
澹台
|
||||
濮阳
|
||||
熊
|
||||
王
|
||||
田
|
||||
申屠
|
||||
白
|
||||
皇甫
|
||||
石
|
||||
秦
|
||||
程
|
||||
罗
|
||||
肖
|
||||
胡
|
||||
苏
|
||||
范
|
||||
董
|
||||
蒋
|
||||
薛
|
||||
袁
|
||||
许
|
||||
诸葛
|
||||
谢
|
||||
谭
|
||||
贺
|
||||
贾
|
||||
赖
|
||||
赫连
|
||||
赵
|
||||
轩辕
|
||||
邓
|
||||
邱
|
||||
邵
|
||||
邹
|
||||
郑
|
||||
郝
|
||||
郭
|
||||
金
|
||||
钟
|
||||
钟离
|
||||
钱
|
||||
长孙
|
||||
闻人
|
||||
闾丘
|
||||
阎
|
||||
陆
|
||||
陈
|
||||
雷
|
||||
韩
|
||||
顾
|
||||
马
|
||||
高
|
||||
魏
|
||||
鲜于
|
||||
黄
|
||||
黎
|
||||
龙
|
||||
龚
|
||||
@@ -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}
|
||||
#
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# example black list
|
||||
#deny 195.154.211.220;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
-----BEGIN DH PARAMETERS-----
|
||||
MIIBCAKCAQEAzkJoGFJJGMXQBVIq0DFom7qI3vD7Z8JMQnfCLpoi9AfqW6kGq/bR
|
||||
FhK9fuRkO+GdzZasx1mSNRQeX8GdaQM4GUn0yel7fxlxNC59mxo++P8NvmxQ47l4
|
||||
K9QpIRuqxa5UKIG6g3N5pkLwGjcD9a79v4DJn4XA9cVjRYc4BnYmiArgaMFOmGPy
|
||||
KmvU/VhFv8fnxSfn8uCmAGSuHmfbjx5TMfCqaeXzmmhyvpSl88JZfGlwOtXcOU0K
|
||||
O2JhNRKtaicZlevC8gtpFDNYKnf4K9kiUVmq0JLvuzOxN05sQoPYFCvgMFIYf+ND
|
||||
Jwtv7FWF2hQV3y1Xms7ja4776FcP9QlKuwIBAg==
|
||||
-----END DH PARAMETERS-----
|
||||
@@ -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-----
|
||||
@@ -1 +0,0 @@
|
||||
╟░Щ`*dы≤\ЛвH
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,3 +0,0 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://{{.Domain}}/sitemap.xml
|
||||
598
db.go
@@ -1,598 +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)
|
||||
}
|
||||
// 读取帐号信息
|
||||
Ei = loadAccount()
|
||||
// 获取文章数据
|
||||
Ei.Articles = loadArticles()
|
||||
// 生成markdown文档
|
||||
go generateMarkdown()
|
||||
// 启动定时器
|
||||
go timer()
|
||||
// 获取评论数量
|
||||
go PostsCount()
|
||||
}
|
||||
|
||||
// 读取或初始化帐号信息
|
||||
func loadAccount() (a *Account) {
|
||||
a = &Account{}
|
||||
err := mgo.FindOne(DB, COLLECTION_ACCOUNT, mgo.M{"username": setting.Conf.Account.Username}, a)
|
||||
// 初始化用户数据
|
||||
if err == mgo.ErrNotFound {
|
||||
a = &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(),
|
||||
}
|
||||
a.BlogName = setting.Conf.Blogger.BlogName
|
||||
a.SubTitle = setting.Conf.Blogger.SubTitle
|
||||
a.BeiAn = setting.Conf.Blogger.BeiAn
|
||||
a.BTitle = setting.Conf.Blogger.BTitle
|
||||
a.Copyright = setting.Conf.Blogger.Copyright
|
||||
err = mgo.Insert(DB, COLLECTION_ACCOUNT, a)
|
||||
generateTopic()
|
||||
} else if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
a.CH = make(chan string, 2)
|
||||
a.MapArticles = make(map[string]*Article)
|
||||
a.Tags = make(map[string]SortArticles)
|
||||
return
|
||||
}
|
||||
|
||||
func loadArticles() (artcs SortArticles) {
|
||||
err := mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": false, "deletetime": mgo.M{"$eq": time.Time{}}}, &artcs)
|
||||
if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
sort.Sort(artcs)
|
||||
for i, v := range artcs {
|
||||
// 渲染文章
|
||||
GenerateExcerptAndRender(v)
|
||||
Ei.MapArticles[v.Slug] = v
|
||||
// 分析文章
|
||||
if v.ID < setting.Conf.General.StartID {
|
||||
continue
|
||||
}
|
||||
if i > 0 {
|
||||
v.Prev = artcs[i-1]
|
||||
}
|
||||
if artcs[i+1].ID >= setting.Conf.General.StartID {
|
||||
v.Next = artcs[i+1]
|
||||
}
|
||||
ManageTagsArticle(v, false, ADD)
|
||||
ManageSeriesArticle(v, false, ADD)
|
||||
ManageArchivesArticle(v, false, ADD)
|
||||
}
|
||||
Ei.CH <- SERIES_MD
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
buffer.WriteString("\n\n")
|
||||
for _, archive := range Ei.Archives {
|
||||
buffer.WriteString(fmt.Sprintf("### %s", archive.Time.Format("2006年01月")))
|
||||
buffer.WriteString("\n\n")
|
||||
for _, artc := range archive.Articles {
|
||||
buffer.WriteString("* [" + artc.Title + "](/post/" + artc.Slug + ".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
|
||||
}
|
||||
buffer.WriteByte('\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{},
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 管理 tag
|
||||
func ManageTagsArticle(artc *Article, s bool, do string) {
|
||||
switch do {
|
||||
case ADD:
|
||||
for _, tag := range artc.Tags {
|
||||
Ei.Tags[tag] = append(Ei.Tags[tag], artc)
|
||||
if s {
|
||||
sort.Sort(Ei.Tags[tag])
|
||||
}
|
||||
}
|
||||
case DELETE:
|
||||
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)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 管理专题
|
||||
func ManageSeriesArticle(artc *Article, s bool, do string) {
|
||||
switch do {
|
||||
case ADD:
|
||||
for i, serie := range Ei.Series {
|
||||
if serie.ID == artc.SerieID {
|
||||
Ei.Series[i].Articles = append(Ei.Series[i].Articles, artc)
|
||||
if s {
|
||||
sort.Sort(Ei.Series[i].Articles)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
case DELETE:
|
||||
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:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 管理归档
|
||||
func ManageArchivesArticle(artc *Article, s bool, do string) {
|
||||
switch do {
|
||||
case ADD:
|
||||
add := false
|
||||
y, m, _ := artc.CreateTime.Date()
|
||||
for i, archive := range Ei.Archives {
|
||||
ay, am, _ := archive.Time.Date()
|
||||
if y == ay && m == am {
|
||||
add = true
|
||||
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles, artc)
|
||||
if s {
|
||||
sort.Sort(Ei.Archives[i].Articles)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !add {
|
||||
Ei.Archives = append(Ei.Archives, &Archive{Time: artc.CreateTime, Articles: SortArticles{artc}})
|
||||
}
|
||||
case DELETE:
|
||||
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:]...)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 添加文章
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
ManageTagsArticle(artc, true, ADD)
|
||||
ManageSeriesArticle(artc, true, ADD)
|
||||
ManageArchivesArticle(artc, true, ADD)
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
if artc.SerieID > 0 {
|
||||
Ei.CH <- SERIES_MD
|
||||
}
|
||||
}
|
||||
return mgo.Insert(DB, COLLECTION_ARTICLE, artc)
|
||||
}
|
||||
|
||||
// 删除文章,移入回收箱
|
||||
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)
|
||||
ManageTagsArticle(artc, false, DELETE)
|
||||
ManageSeriesArticle(artc, false, DELETE)
|
||||
ManageArchivesArticle(artc, false, DELETE)
|
||||
err := UpdateArticle(mgo.M{"id": id}, mgo.M{"$set": mgo.M{"deletetime": time.Now()}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
artc = nil
|
||||
}
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
Ei.CH <- SERIES_MD
|
||||
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
|
||||
}
|
||||
62
db_test.go
@@ -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)))
|
||||
}
|
||||
248
disqus.go
@@ -1,248 +0,0 @@
|
||||
// Package main provides ...
|
||||
// Get article' comments count
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/deepzz0/logd"
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
)
|
||||
|
||||
var ErrDisqusConfig = errors.New("disqus config incorrect")
|
||||
|
||||
// 定时获取所有文章评论数量
|
||||
type postsCountResp struct {
|
||||
Code int
|
||||
Response []struct {
|
||||
Id string
|
||||
Posts int
|
||||
Identifiers []string
|
||||
}
|
||||
}
|
||||
|
||||
func PostsCount() error {
|
||||
if setting.Conf.Disqus.PostsCount == "" ||
|
||||
setting.Conf.Disqus.PublicKey == "" ||
|
||||
setting.Conf.Disqus.ShortName == "" {
|
||||
return ErrDisqusConfig
|
||||
}
|
||||
|
||||
time.AfterFunc(time.Duration(setting.Conf.Disqus.Interval)*time.Hour, func() {
|
||||
err := PostsCount()
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
baseUrl := setting.Conf.Disqus.PostsCount +
|
||||
"?api_key=" + setting.Conf.Disqus.PublicKey +
|
||||
"&forum=" + setting.Conf.Disqus.ShortName + "&"
|
||||
var count, index int
|
||||
for index < len(Ei.Articles) {
|
||||
var threads []string
|
||||
for ; index < len(Ei.Articles) && count < 50; index++ {
|
||||
artc := Ei.Articles[index]
|
||||
threads = append(threads, fmt.Sprintf("thread:ident=post-%s", artc.Slug))
|
||||
count++
|
||||
}
|
||||
count = 0
|
||||
url := baseUrl + strings.Join(threads, "&")
|
||||
resp, err := http.Get(url)
|
||||
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 setting.Conf.Disqus.PostsList == "" ||
|
||||
setting.Conf.Disqus.PublicKey == "" ||
|
||||
setting.Conf.Disqus.ShortName == "" {
|
||||
return nil, ErrDisqusConfig
|
||||
}
|
||||
|
||||
url := setting.Conf.Disqus.PostsList + "?limit=50&api_key=" +
|
||||
setting.Conf.Disqus.PublicKey + "&forum=" + setting.Conf.Disqus.ShortName +
|
||||
"&cursor=" + cursor + "&thread:ident=post-" + slug
|
||||
resp, err := http.Get(url)
|
||||
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 PostCreate struct {
|
||||
Message string `json:"message"`
|
||||
Parent string `json:"parent"`
|
||||
Thread string `json:"thread"`
|
||||
AuthorEmail string `json:"author_email"`
|
||||
AuthorName string `json:"autor_name"`
|
||||
IpAddress string `json:"ip_address"`
|
||||
Identifier string `json:"identifier"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
}
|
||||
|
||||
type postCreateResp struct {
|
||||
Code int
|
||||
Response postDetail
|
||||
}
|
||||
|
||||
// 评论文章
|
||||
func PostComment(pc *PostCreate) (*postCreateResp, error) {
|
||||
if setting.Conf.Disqus.PostsList == "" ||
|
||||
setting.Conf.Disqus.PublicKey == "" ||
|
||||
setting.Conf.Disqus.ShortName == "" {
|
||||
return nil, ErrDisqusConfig
|
||||
}
|
||||
url := setting.Conf.Disqus.PostCreate +
|
||||
"?api_key=E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F" +
|
||||
"&message=" + pc.Message + "&parent=" + pc.Parent +
|
||||
"&thread=" + pc.Thread + "&author_email=" + pc.AuthorEmail +
|
||||
"&author_name=" + pc.AuthorName
|
||||
|
||||
request, err := http.NewRequest("POST", url, nil)
|
||||
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 `json:"code"`
|
||||
Response []struct {
|
||||
Id string `json:"id"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
func PostApprove(post string) error {
|
||||
if setting.Conf.Disqus.PostsList == "" ||
|
||||
setting.Conf.Disqus.PublicKey == "" ||
|
||||
setting.Conf.Disqus.ShortName == "" {
|
||||
return ErrDisqusConfig
|
||||
}
|
||||
|
||||
url := setting.Conf.Disqus.PostApprove +
|
||||
"?api_key=" + setting.Conf.Disqus.PublicKey +
|
||||
"&access_token=" + setting.Conf.Disqus.AccessToken +
|
||||
"&post=" + post
|
||||
request, err := http.NewRequest("POST", url, nil)
|
||||
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
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDisqus(t *testing.T) {
|
||||
PostsCount()
|
||||
}
|
||||
|
||||
func TestPostComment(t *testing.T) {
|
||||
pc := &PostCreate{
|
||||
Message: "hahahaha",
|
||||
Thread: "52799014",
|
||||
AuthorEmail: "deepzz.qi@gmail.com",
|
||||
AuthorName: "deepzz",
|
||||
}
|
||||
|
||||
id, err := PostComment(pc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
t.Log("post success", id)
|
||||
}
|
||||
18
dist.sh
@@ -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
|
||||
@@ -1,37 +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
|
||||
ports:
|
||||
- 27017:27017
|
||||
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
|
||||
|
||||
108
docs/README.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# EiBlog [](https://travis-ci.org/eiblog/eiblog) [](LICENSE.md) [](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+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### 更多文档
|
||||
|
||||
* [安装部署](https://eiblog.github.io/eiblog/install)
|
||||
* [写作须知](https://eiblog.github.io/eiblog/writing)
|
||||
* [好玩功能](https://eiblog.github.io/eiblog/amusing)
|
||||
* [如何备份](https://eiblog.github.io/eiblog/backup)
|
||||
|
||||
### 贡献成员
|
||||
|
||||

|
||||
|
||||
### 授权许可
|
||||
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/eiblog/eiblog/blob/master/LICENSE) 文件中。
|
||||
|
||||
1
docs/_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-cayman
|
||||
@@ -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,8 +13,13 @@ twitter:
|
||||
|
||||
每当你发部一个推文,你如果带上你的网址,它会自动给你展示成卡片的形式
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
可以看到`,`之前是没有内容的,该内容是我们文章的描述。
|
||||
|
||||
### Google OpenSearch
|
||||
在 Chrome 浏览器上,你可以在输入网站后按 TAB 键进入搜索模式,如:
|
||||
|
||||

|
||||
|
||||
@@ -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
@@ -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` 文件。
|
||||
BIN
docs/img/article-description.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
docs/img/article-title.png
Normal file
|
After Width: | Height: | Size: 34 KiB |