Compare commits

...

277 Commits

Author SHA1 Message Date
henry.chen
bd9a45078b chore(release): 3.0.6 2025-07-25 18:05:33 +08:00
henry.chen
629ad782c4 fix: feed & sitemap not generate 2025-07-25 18:05:23 +08:00
henry.chen
5e63f5e69d chore(release): 3.0.5 2025-07-25 17:55:44 +08:00
henry.chen
33afbd351d fix: mongodb uri error 2025-07-25 17:55:14 +08:00
henry.chen
eaeeaaafb9 fix: RUN_MODE error 2025-07-25 17:21:02 +08:00
henry.chen
ae3fb35435 chore: update 2025-07-25 13:37:49 +08:00
henry.chen
c1d73f1a45 fix: admin login session 2025-07-25 13:35:06 +08:00
henry.chen
fd7981b4f9 chore(release): 3.0.4 2025-07-25 10:59:04 +08:00
henry.chen
9f77fb7b3f fix(ci): golang version 2025-07-25 10:59:02 +08:00
henry.chen
f201499945 chore(release): 3.0.3 2025-07-25 10:10:26 +08:00
henry.chen
d7736abb25 chore: bump go version: v1.23.0 2025-07-25 10:07:43 +08:00
henry.chen
3c4fa6d08a chore(release): 3.0.2 2025-07-25 09:35:43 +08:00
henry.chen
ccb5e4546e fix: ci 2025-07-25 09:35:40 +08:00
henry.chen
6ce6411da0 chore(release): 3.0.1 2025-07-25 09:29:28 +08:00
henry.chen
cb2ed7cb82 fix: dist tar 2025-07-25 09:27:16 +08:00
henry.chen
ea566d1650 chore: update ci 2025-07-24 18:28:41 +08:00
henry.chen
88dccca295 chore(release): 3.0.0 2025-07-24 17:57:09 +08:00
henry.chen
cb11a2b8db chore: replace twitter icon 2025-07-22 11:38:37 +08:00
henry.chen
ed03a264f0 chore: update 2025-07-21 20:10:16 +08:00
henry.chen
aee4194c71 fix: custom page support embed 2025-07-21 20:07:26 +08:00
henry.chen
b69248f6a4 feat: support custom page 2025-07-17 15:55:32 +08:00
henry.chen
24bfe528b2 fix: ci dist tar 2025-07-17 15:37:30 +08:00
henry.chen
23e35dcefa chore: update compose.yml 2025-07-17 15:35:08 +08:00
henry.chen
f4c70b46c1 fix: ci 2025-07-17 15:30:30 +08:00
henry.chen
e5100fa018 feat: twofactor bind 2025-07-17 15:24:46 +08:00
henry.chen
91e1731909 chore: optmize code 2025-07-17 14:01:17 +08:00
henry.chen
4abe528742 chore: mv asset 2025-07-17 10:57:44 +08:00
henry.chen
be0280ac56 chore: update backup 2025-07-17 10:52:43 +08:00
henry.chen
a0b41d08bd chore: update 2025-07-16 19:57:39 +08:00
henry.chen
8fcabd5e15 refactor: refactor eiblog 2025-07-16 19:45:50 +08:00
henry.chen
0a410f09f3 chore(release): 2.2.17 2025-04-22 15:18:23 +08:00
henry.chen
0fe849ae67 fix: backup file auto delete 2025-04-22 15:12:50 +08:00
henry.chen
c06a32a268 chore(release): 2.2.16 2025-03-13 13:40:50 +08:00
henry.chen
79fccb958c fix: empty "beian" display issue 2025-03-13 13:40:33 +08:00
henry.chen
8e2679e49f chore(release): 2.2.15 2025-01-01 02:53:10 +08:00
henry.chen
52fe7303f3 fix: disqus list posts 2025-01-01 02:49:21 +08:00
henry.chen
616248d33f fix: disqus thread not store 2025-01-01 02:19:48 +08:00
henry.chen
6e1965a764 chore: replace ioutil -> io 2024-11-04 10:31:28 +08:00
henry.chen
27bc610a31 chore(release): 2.2.14 2024-10-10 13:39:32 +08:00
henry.chen
b53fc91ce7 fix: 1. bei_an cannot update error
2. CleanArticles deleted all article error in rdbms, fixed #43,fixed #44
2024-10-10 13:38:38 +08:00
henry.chen
720387ecd5 chore: update 2024-05-28 13:09:10 +08:00
henry.chen
88f23bd1a0 fix(serie): update serie did not rerender 2024-01-23 09:43:17 +08:00
henry.chen
6a2d720d36 chore(release): 2.2.13 2024-01-02 21:40:39 +08:00
henry.chen
95e55ee13c fix: load more comments 2024-01-02 21:39:36 +08:00
henry.chen
ca293a4933 chore(release): 2.2.12 2024-01-02 20:01:16 +08:00
henry.chen
c82d73ca34 chore: downgrade mongodb driver 2024-01-02 20:01:14 +08:00
henry.chen
433064de00 chore(release): 2.2.11 2024-01-02 19:09:40 +08:00
henry.chen
9d71ca8198 fix(disqus): fix returned posts list not have parent post 2024-01-02 19:09:37 +08:00
henry.chen
7c938bf024 chore(release): 2.2.10 2023-12-22 14:12:15 +08:00
henry.chen
65fcc69efa chore: bump all gorm & driver version 2023-12-22 14:10:36 +08:00
henry.chen
d06bab72a5 chore: bump gin swagger version to v1.6.0 2023-12-22 14:03:57 +08:00
henry.chen
95900eec1a chore: rm http header: Expect-CT 2023-12-07 09:55:30 +08:00
henry.chen
dfae78891d chore: update ad config 2023-12-01 13:46:29 +08:00
henry.chen
c515e33e2c chore(release): 2.2.9 2023-09-25 18:14:51 +08:00
henry.chen
5b12dd625b chore: update config 2023-09-25 18:14:41 +08:00
henry.chen
9b918caccd fix: google analytics not work, supported ga4 measurement protocol 2023-09-25 18:12:57 +08:00
Deepzz
2be53379e1 Update writing.md 2023-09-13 10:49:51 +08:00
Deepzz
944e27c58a Update app.yml 2023-09-13 10:45:56 +08:00
Deepzz
92baf970bc Update docker-compose.yml 2023-07-12 17:33:18 +08:00
henry.chen
64a754167a chore(release): 2.2.8 2023-07-12 17:30:37 +08:00
henry.chen
af2a20c34a chore: update 2023-07-12 17:30:34 +08:00
henry.chen
f28d0e77e0 fix(backup): restore db and tests 2023-07-12 17:26:44 +08:00
henry.chen
9a1b4db61a chore: update 2023-07-12 15:14:47 +08:00
henry.chen
c9d04aded3 chore(release): 2.2.7 2023-07-12 15:01:40 +08:00
henry.chen
ee51f678cb fix(backup): 数据恢复错误 2023-07-12 14:59:11 +08:00
Deepzz
434c1bf168 Update README.md 2023-06-19 23:19:00 +08:00
henry.chen
d2de603957 chore(release): 2.2.6 2023-06-19 23:13:56 +08:00
henry.chen
e7fdf6b1db chore(backup): backup blog with prefix: blog 2023-06-19 23:13:45 +08:00
Deepzz
305ae0ee70 Merge pull request #40 from eiblog/dependabot/go_modules/github.com/gin-gonic/gin-1.9.1
chore(deps): bump github.com/gin-gonic/gin from 1.9.0 to 1.9.1
2023-06-12 16:55:16 +08:00
dependabot[bot]
a5749fdab6 chore(deps): bump github.com/gin-gonic/gin from 1.9.0 to 1.9.1
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.9.0...v1.9.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-01 20:35:02 +00:00
henry.chen
391f19e2a9 chore(release): 2.2.5 2023-05-26 09:57:35 +08:00
henry.chen
2c6c5bb977 fix(backup): libresolv.so.2: No such file or directory 2023-05-26 09:57:30 +08:00
henry.chen
860a85e209 chore: update go.mod 2023-05-25 16:46:26 +08:00
henry.chen
2a8d9b3bbd chore(release): 2.2.4 2023-05-25 16:44:34 +08:00
henry.chen
1509a68cda fix(ci): fix .dockerignore 2023-05-25 16:44:31 +08:00
henry.chen
7f26503247 chore(release): 2.2.3 2023-05-25 16:16:32 +08:00
henry.chen
235145ed46 chore(ci): print log 2023-05-25 16:16:28 +08:00
henry.chen
20e89d6a76 fix(ci): script file name 2023-05-25 16:08:45 +08:00
henry.chen
d86ab71ad9 chore(release): 2.2.2 2023-05-25 16:06:36 +08:00
henry.chen
4600ed5094 chore(ci): adjust ci 2023-05-25 16:06:31 +08:00
henry.chen
5fcadd1c81 chore(ci): golang version to 1.20 2023-05-17 15:58:44 +08:00
henry.chen
42b106d582 chore(release): 2.2.1 2023-05-17 15:49:04 +08:00
henry.chen
bb06be36fe fix: try to fix backup symbol not found 2023-05-17 15:49:01 +08:00
henry.chen
cb3fb2d2e7 chore(release): 2.2.0 2023-05-17 14:47:23 +08:00
henry.chen
779a23cb75 feat(backup): add restore flag 2023-05-17 14:42:28 +08:00
Deepzz
e2fa96cd62 Update docker-compose.yml 2023-05-15 08:58:56 +08:00
Deepzz
db26fb51e5 Merge pull request #37 from eiblog/dependabot/go_modules/github.com/gin-gonic/gin-1.9.0
chore(deps): bump github.com/gin-gonic/gin from 1.7.7 to 1.9.0
2023-05-06 09:09:41 +08:00
dependabot[bot]
994be5d508 chore(deps): bump github.com/gin-gonic/gin from 1.7.7 to 1.9.0
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.7.7 to 1.9.0.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.7.7...v1.9.0)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-09 22:49:33 +00:00
henry.chen
41704917db chore(release): 2.1.18 2023-01-05 14:15:54 +08:00
henry.chen
f6ba716f55 fix(backup): can not execute 2023-01-05 14:15:46 +08:00
henry.chen
b2e6c168c5 fix: disqus api using http post 2023-01-05 13:51:18 +08:00
henry.chen
eca741896f chore(release): 2.1.17 2023-01-05 13:28:48 +08:00
henry.chen
17792e5a7e fix: fist comment of disqus error 2023-01-05 13:25:18 +08:00
henry.chen
04289c633e chore: optimize variable naming 2023-01-05 11:07:56 +08:00
henry.chen
3a5eb6fccc chore(release): 2.1.18 2023-01-05 09:17:39 +08:00
henry.chen
f6d8656c83 fix: 1. template read panic
2. optimization variable naming
2023-01-05 09:17:31 +08:00
henry.chen
4690d5123b chore(release): 2.1.17 2023-01-05 00:00:11 +08:00
henry.chen
a9e8e39d34 fix(disqus): failed to commit disqus comments 2023-01-04 23:58:50 +08:00
henry.chen
c51055a0db chore(release): 2.1.16 2022-11-20 23:40:06 +08:00
henry.chen
445b188517 chore: imgtonormal add at xmlTmpl 2022-11-20 23:37:49 +08:00
henry.chen
4bfff2e5e9 fix: rss image path incorrect: data-src -> src 2022-11-20 23:33:10 +08:00
Deepzz
aa91997c0c fix(backup): error path in compressed file 2022-10-14 14:45:15 +08:00
henry.chen
3b2a6689be chore: update 2022-10-01 21:18:53 +08:00
henry.chen
4c46be3f03 chore: update readme 2022-10-01 21:09:53 +08:00
henry.chen
da47e9880f chore: update readme 2022-09-30 09:51:44 +08:00
henry.chen
4f92e0d619 chore(release): 2.1.15 2022-09-28 19:00:36 +08:00
henry.chen
3a8f7d120b chore: rm dns with cgo 2022-09-28 19:00:26 +08:00
Deepzz
cf0a897ad0 chore(app.yml): default db use sqlite 2022-09-28 18:59:28 +08:00
henry.chen
418b604946 chore(release): 2.1.14 2022-09-28 18:20:41 +08:00
henry.chen
b93c320987 fix: cgo and sqlite build in alpine image closed #28 2022-09-28 18:20:38 +08:00
henry.chen
b24f7c0666 chore(release): 2.1.13 2022-09-27 10:50:22 +08:00
Deepzz
efe80fbc6b Update feedTpl.xml 2022-09-21 09:47:50 +08:00
henry.chen
65b89bcae5 chore(release): 2.1.12 2022-08-09 18:41:21 +08:00
henry.chen
289b8145bc fix: #35 no pubDate added for feed generation 2022-08-09 18:40:52 +08:00
henry.chen
bf0ad811ff chore(release): 2.1.11 2022-07-19 22:35:32 +08:00
henry.chen
db00a9b527 chore: upgrade to yaml.v3 close #1 2022-05-27 09:13:22 +08:00
henry.chen
38aa704198 fix(backup): configuration error 2022-05-11 10:16:38 +08:00
henry.chen
9c58447e3b chore(release): 2.1.10 2022-04-22 13:45:50 +08:00
Deepzz
34fc5f368c Update release.yml 2022-04-22 13:44:03 +08:00
Deepzz
daa561e67e Update eiblog.conf 2022-03-22 18:16:12 +08:00
henry.chen
364d293660 chore(release): 2.1.9 2022-02-14 17:50:42 +08:00
henry.chen
5170844623 fix(eiblog): comments title error 2022-01-10 15:50:13 +08:00
henry.chen
7047a3599d chore(docs): add docs images 2022-01-06 09:33:06 +08:00
Deepzz
d0c830c1e6 Create FUNDING.yml 2021-12-15 09:20:34 +08:00
henry.chen
bf699e4957 chore(release): 2.1.8 2021-11-18 10:47:10 +08:00
henry.chen
c315737871 chore: clean alpine respositories 2021-11-18 10:47:06 +08:00
henry.chen
d844c0e8f8 chore(release): 2.1.7 2021-11-13 22:21:10 +08:00
henry.chen
f6cb55c00f chore: update swag version & change uuid package 2021-11-13 22:20:48 +08:00
henry.chen
a5292027c0 chore(release): 2.1.6 2021-11-12 15:31:14 +08:00
henry.chen
00cf0b5c9f fix(backup): backup to qiniu, app.yml->validity change to int 2021-11-12 15:30:43 +08:00
henry.chen
6805afa759 chore(release): 2.1.5 2021-10-27 09:07:50 +08:00
henry.chen
4f6f85a85a fix(release): golang env 2021-10-27 09:06:37 +08:00
henry.chen
dc3e6659b5 chore(release): 2.1.4 2021-10-26 16:18:21 +08:00
henry.chen
ca74d13598 chore(release): 2.1.4 2021-10-26 16:18:00 +08:00
henry.chen
82fba0ddb4 fix(release): github action runner 2021-10-26 16:17:50 +08:00
henry.chen
2b6bae1f74 chore(release): 2.1.3 2021-10-15 16:54:47 +08:00
Deepzz
6387412776 Update README.md 2021-09-14 21:32:37 +08:00
razeen
cfaa25e123 fix(auto_save): fix auto save return empty id 2021-08-15 03:51:45 -07:00
Deepzz
6ff6acdbda Merge pull request #32 from eiblog/qiniu_zone
qiniu upload remove zone, support all zone upload
2021-08-12 09:19:56 +08:00
razeen
b3b3be448f chore(qiniu): qiniu upload remove zone, support all zone upload 2021-08-11 08:31:04 -07:00
henry.chen
78f5bfc1ce fix: backup app judge db driver 2021-08-11 18:44:35 +08:00
Deepzz
4b9eb1ff4d Merge pull request #29 from eiblog/fix_init_password
初始化地方参数错了。。。
2021-08-10 16:17:10 +08:00
Razeen
7d04b8f5c0 fix(pwd): fix init pwd 2021-08-10 10:37:42 +08:00
henry.chen
d000090e30 chore(release): 2.1.2 2021-07-27 17:19:04 +08:00
henry.chen
56e396f252 chore: restore app.yml database 2021-07-27 17:18:58 +08:00
henry.chen
f6662fa4c5 chore(release): 2.1.1 2021-07-27 09:15:17 +08:00
henry.chen
a570c783f3 chore(gin): upgrade gin to fix CVE-2020-28483 2021-07-27 09:15:03 +08:00
henry.chen
e2046d0d39 fix: sqlite error 2021-07-25 10:45:33 +08:00
henry.chen
cdbe082764 chore(docs): update backup.md 2021-07-14 12:10:40 +08:00
henry.chen
3a86c6a65c chore: clean go.sum 2021-07-14 10:57:37 +08:00
henry.chen
c1c9e6025a chore: add backup app 2021-07-14 10:54:30 +08:00
deepzz0
a15791a792 chore: clean dir 2021-05-27 13:54:12 +08:00
deepzz0
ea87f4b2e6 chore: update readme 2021-05-17 10:48:22 +08:00
deepzz0
a0653cf67f chore: update README.md 2021-05-16 11:22:13 +08:00
deepzz0
397d47bc00 fix: excerpt for es 2021-05-11 22:55:20 +08:00
deepzz0
00d9df4406 chore(release): 2.0.6 2021-05-08 11:22:28 +08:00
deepzz0
2b277bd901 fix: workdir loop 2021-05-08 11:19:31 +08:00
deepzz0
c18f3b7da9 fix: workdir path error 2021-05-08 10:54:21 +08:00
deepzz0
79ac024312 fix: archive page style 2021-05-07 16:28:26 +08:00
deepzz0
1e4c0afc19 fix: archive & serie 2021-05-07 16:25:34 +08:00
deepzz0
055d7d2164 fix: env PATH 2021-05-07 15:24:57 +08:00
deepzz0
ed7c510744 chore: update docs 2021-05-07 15:18:02 +08:00
deepzz0
488c0f04fe chore: mv conf/tpl->website/template 2021-05-07 15:17:20 +08:00
deepzz0
a99ed9504b chore: add adsense 2021-05-07 15:11:32 +08:00
deepzz0
6cde9323b1 chore: adjust config 2021-05-07 14:58:07 +08:00
deepzz0
2b226977ab chore: update eiblog.conf 2021-05-07 14:28:22 +08:00
deepzz0
aa076e5e3c fix: mongodb current close 2021-05-06 22:50:12 +08:00
deepzz0
26635790e1 chore: update docs 2021-05-06 15:32:32 +08:00
deepzz0
569a1fe2a1 chore: update 2021-05-06 15:29:22 +08:00
deepzz0
182eaff085 Merge branch 'v2' of github.com:eiblog/eiblog into v2 2021-05-06 11:29:37 +08:00
deepzz0
90e7082322 chore: update docs 2021-05-06 11:28:01 +08:00
Deepzz
e71e42571f Set theme jekyll-theme-cayman 2021-05-06 09:18:49 +08:00
deepzz0
3527e11b04 chore: update eiblog.conf 2021-05-06 09:07:27 +08:00
deepzz0
a407bdfe72 chore: update README.md 2021-05-05 20:52:48 +08:00
deepzz0
05aa254e70 chore: add eiblog.conf 2021-04-30 17:11:30 +08:00
deepzz0
4ec3065f9d fix: some bugs 2021-04-30 13:06:16 +08:00
deepzz0
7bec238f9c chore: add rdbms store 2021-04-30 11:01:03 +08:00
deepzz0
605787958d chore: remove some filed in app.yml 2021-04-30 09:50:33 +08:00
deepzz0
c06f02622a fix: tar file 2021-04-29 12:31:30 +08:00
deepzz0
f431bf5adc chore: alpine add tzdata 2021-04-29 11:44:45 +08:00
deepzz0
7922043bc6 chore: add clean 2021-04-29 11:18:31 +08:00
deepzz0
2fbc5fa024 chore: rename blog->eiblog 2021-04-29 11:17:20 +08:00
deepzz0
9c341f88d2 chore: update dockerignore 2021-04-29 10:59:52 +08:00
deepzz0
0ae5e6c9fc chore: default Dockerfile 2021-04-29 10:53:54 +08:00
deepzz0
3e2b746319 chore: rm workflow registry 2021-04-29 10:50:25 +08:00
deepzz0
2cd72e190e chore: update release.yml 2021-04-29 10:48:30 +08:00
deepzz0
1c955769f4 chore: renew github flow 2021-04-29 10:44:43 +08:00
deepzz0
a2769f0913 chore: rm tzdata 2021-04-28 16:34:29 +08:00
deepzz0
bf3d45a404 fix: filed name 2021-04-28 16:33:58 +08:00
deepzz0
4b92636079 fix: bugs 2021-04-28 16:28:14 +08:00
deepzz0
753aaa4ace fix: time location 2021-04-28 15:49:49 +08:00
deepzz0
43d7a26e19 fix: time in location 2021-04-28 15:37:10 +08:00
deepzz0
872d0b1987 chore: rm package utils 2021-04-28 15:10:13 +08:00
deepzz0
897b05d071 fix: ping func 2021-04-28 15:05:37 +08:00
deepzz0
c5a3bd6eab fix: delete trash article 2021-04-28 14:40:28 +08:00
deepzz0
beea4f1746 chore: tags use array 2021-04-28 12:50:06 +08:00
deepzz0
9f563f0ae9 chore: complete api 2021-04-28 11:57:37 +08:00
deepzz0
4749b1e681 chore: add api 2021-04-27 23:46:43 +08:00
deepzz0
a39e3aac3b chore: trash 2021-04-27 18:04:59 +08:00
deepzz0
63b55b2df8 chore: update html 2021-04-27 16:51:19 +08:00
deepzz0
cb091532d5 chore: update html 2021-04-27 16:22:23 +08:00
deepzz0
6b74e1d208 chore: rename field name 2021-04-27 16:16:32 +08:00
deepzz0
1815cea2cd fix: article list 2021-04-27 14:58:32 +08:00
deepzz0
c3fdbfcb78 chore: remove blogger config 2021-04-27 14:02:52 +08:00
deepzz0
990e6abbd8 chore: run blog 2021-04-27 11:08:55 +08:00
deepzz0
bb40570053 chore: mongodb unit test 2021-04-27 10:21:27 +08:00
deepzz0
e2df642a46 chore: store mongodb 2021-04-26 23:02:24 +08:00
deepzz0
68e01cdf1f refactor: eiblog 2021-04-26 15:51:16 +08:00
chenqijing
bd69c62254 chore: replace beian domain: https://beian.miit.gov.cn 2020-12-29 11:50:36 +08:00
chenqijing
970c6068d5 chore: update go.mod 2020-12-22 11:19:12 +08:00
chenqijing
27a1f600de chore: github actions 2020-12-22 11:09:55 +08:00
chenqijing
edf0fbac51 chore: rm .travis.yml 2020-12-17 21:02:48 +08:00
Deepzz
98ca570a36 Merge pull request #24 from eiblog/deepzz0-patch-1
Create release.yml
2020-12-17 20:58:58 +08:00
Deepzz
9a526f97f8 Create release.yml 2020-12-17 20:58:27 +08:00
chenqijing
33a29f5e57 chore: update twitter & print disqus email 2020-12-17 19:15:16 +08:00
henry.chen
30ebf76eda chore(release): 1.4.9 2019-12-18 19:33:08 +08:00
Deepzz
f5c0bcdb99 Merge pull request #23 from eiblog/fix_disqus
Fix disqus
2019-12-18 19:22:11 +08:00
henry.chen
4b53da3801 chore(disqus): rm app.yml/disqus/embed config 2019-12-18 19:17:23 +08:00
henry.chen
1bdfb6abea fix(disqus): connect reset by peer 2019-12-04 13:24:48 +08:00
henry.chen
33f47d8f3a Merge branch 'master' of github.com:eiblog/eiblog 2019-12-04 10:30:26 +08:00
deepzz0
cbd0cfaaf5 fix(blogroll): blogroll.html ul style 2019-11-09 23:45:28 +08:00
henry.chen
080c992a92 chore: update vendor 2019-09-30 15:30:10 +08:00
deepzz0
371b2326ea fix(qiniu): empty file name 2019-03-05 00:41:43 +08:00
deepzz0
2720d11b23 Merge branch 'master' of github.com:eiblog/eiblog 2019-03-03 15:06:33 +08:00
deepzz0
b7751d7b9e fix: qiniu upload file 2019-03-03 15:06:16 +08:00
henry.chen
24d81db8be add vendor & update README.md 2018-11-19 10:24:54 +08:00
deepzz0
010137ebf5 change admin avatar.jpg->avatar.png & fix duplicate id in html 2018-09-18 21:20:13 +08:00
deepzz0
c6a2439c54 use go1.11 with go mod 2018-08-25 18:29:00 +08:00
deepzz0
1d54ff3ac5 fix modify blogroll.html and about.html archived in archive.html 2018-07-17 22:11:11 +08:00
henry.chen
63a4d69209 nginx config: use Expect-CT repleace HPKP 2018-07-17 10:32:54 +08:00
Deepzz
b35d7de58a Merge pull request #14 from vyloy/master
fix doSitemap bug
2018-07-06 22:05:12 +08:00
Michael(Zhiyi Weng)
77ea01b7c1 fix doSitemap bug 2018-07-06 10:02:38 +08:00
Razeen
5f608b638d Update README.md 2018-05-17 18:42:00 +08:00
deepzz0
52da8abceb update 2018-05-07 20:51:30 +08:00
deepzz0
f016b28cb6 fix comments duration 2018-05-07 20:45:37 +08:00
henry.chen
01b7643ca5 Merge branch 'master' of github.com:eiblog/eiblog 2018-05-07 16:52:56 +08:00
henry.chen
375d43761b let's encrypt v2 embedded ct,rm about cert's ct 2018-05-07 16:51:54 +08:00
Deepzz
f3e9727947 Set theme jekyll-theme-cayman 2018-05-02 09:45:41 +08:00
Deepzz
911aa963c7 Update eiblog.conf
X-Real_IP -> X-Real-IP
2018-03-27 10:48:16 +08:00
henry.chen
fb66b6871e release v1.4.3 2018-02-09 16:15:34 +08:00
henry.chen
5ae76f243e fixed #6,发布文章异步提交,随机 session key等 2018-02-09 13:50:34 +08:00
deepzz0
051b034e51 1. 修复编辑专题:按钮显示"新增专题"错误 2. 编辑专题链接移动到专题名称 2018-02-04 12:39:35 +08:00
Deepzz
27439ecc71 Update install.md 2018-02-01 21:24:00 +08:00
henry.chen
d02c838447 fix archive page bug 2018-01-25 23:09:59 +08:00
Deepzz
d17acf5325 Update amusing.md 2018-01-17 19:16:08 +08:00
deepzz0
b278ca377f update changelog.md 2018-01-14 13:53:32 +08:00
deepzz0
93131441e4 update 2018-01-14 13:38:26 +08:00
deepzz0
ddcc6c2d2e auto archiving by year when the month great than 12 2018-01-14 13:12:59 +08:00
henry.chen
ef63ae9598 fix page archive unable auto update 2018-01-14 02:40:11 +08:00
henry.chen
2ed9db5c7b code logical adjust 2018-01-14 02:02:12 +08:00
deepzz0
06a12bc6f9 update vendor 2018-01-13 18:23:03 +08:00
deepzz0
6524b45751 adjust the code 2018-01-13 18:19:54 +08:00
henry.chen
ceb9e2690b 添加 disqus thread 创建接口 2018-01-13 02:56:35 +08:00
deepzz0
405fbaf24f fix can delete blogroll and about page & fix delete and readd article bug 2018-01-07 20:30:14 +08:00
deepzz0
3245c0e0d3 update vendor & fix upload file url & fix judge file type 2018-01-06 23:24:27 +08:00
Deepzz
badc62e3f0 Update README.md 2018-01-06 11:47:20 +08:00
deepzz0
a5561f257b comment docker-compose.yml backup 2018-01-02 20:21:45 +08:00
deepzz0
eb37b83ebd update README.md 2018-01-01 19:03:16 +08:00
deepzz0
b2fab703fc Merge branch 'master' of github.com:eiblog/eiblog 2018-01-01 18:59:30 +08:00
deepzz0
37deb390d9 docker-compose.yml 添加数据库备份镜像 2018-01-01 18:59:10 +08:00
Deepzz
6fa5088352 更新 ct 服务器地址 2017-12-30 13:50:19 +08:00
Deepzz
e023a33786 Update app.yml
移除 disqus 评论及 Google 分析私人信息配置
2017-12-08 12:19:01 +08:00
henry.chen
6f818c4b5d fix search.html <no value> 2017-12-05 15:08:32 +08:00
henry.chen
9ad22fb2d9 don't use dynamic link: CGO_ENABLED=0 2017-11-30 10:04:54 +08:00
henry.chen
fc37d5e093 fix page:admin/write-post autocomplete tag 2017-11-29 16:17:58 +08:00
henry.chen
61024bfebd update 2017-11-27 18:34:03 +08:00
henry.chen
f20c4a6063 fix docker image: exec user process caused "no such file or directory" 2017-11-27 18:17:41 +08:00
henry.chen
c24e6bf7bd update .travis.yml 2017-11-27 16:43:30 +08:00
henry.chen
ade94168d3 update .travis.yml 2017-11-27 16:32:39 +08:00
henry.chen
552d010650 fix background turn page 2017-11-27 15:21:28 +08:00
deepzz0
1c3106cbb0 update vendor 2017-11-24 22:58:59 +08:00
henry.chen
168937f1b2 fix gopkg.in/mgo import conflict 2017-11-23 13:57:20 +08:00
henry.chen
730cffcb5b 修复文章自动保存bug+发布文章不成功bug 2017-11-17 13:30:47 +08:00
deepzz0
8c3f1c2aba update travis.yml 2017-11-05 13:03:52 +08:00
2134 changed files with 7539 additions and 1201640 deletions

View File

@@ -1,16 +1,12 @@
.git
conf
vendor
setting
# Ignore all files and dirs
# Unignore files or dirs
.github
bin
docs
static/*.*
!static/favicon.ico
**/.DS_Store
Dockerfile
glide.yaml
glide.lock
*.yml
*.go
*.sh
.gitignore
.dockerignore
db.sqlite
docker-compose.yml
eiblog.conf
Makefile

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

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

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

@@ -0,0 +1,169 @@
name: Release Image & Asset
on:
push:
tags:
- "v*"
permissions:
contents: write
packages: write
id-token: write # for SLSA provenance
attestations: write # for attestations
env:
REGISTRY: docker.io
GOPROXY: https://goproxy.io,direct
jobs:
# Job 1: 打包源码 tar 文件
package-source:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.vars.outputs.tag }}
sha: ${{ steps.vars.outputs.sha }}
date: ${{ steps.vars.outputs.date }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Extract metadata
id: vars
run: |
echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "sha=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
echo "date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
- name: Package tar archive
run: scripts/dist_tar.sh ${{ steps.vars.outputs.tag }}
- name: Upload tar artifacts
uses: actions/upload-artifact@v4
with:
name: release-archives
path: "*.tar.gz"
retention-days: 7
# Job 2: 构建并推送 Docker 镜像
build-images:
runs-on: ubuntu-latest
needs: package-source
strategy:
fail-fast: false
matrix:
app: [eiblog, backup]
include:
- app: eiblog
port: 9000
- app: backup
port: 9001
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/deepzz0/${{ matrix.app }}
tags: |
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
labels: |
org.opencontainers.image.title=${{ matrix.app }}
org.opencontainers.image.description=eiblog ${{ matrix.app }} service
org.opencontainers.image.vendor=deepzz
org.opencontainers.image.revision=${{ needs.package-source.outputs.sha }}
org.opencontainers.image.created=${{ needs.package-source.outputs.date }}
- name: Build and push ${{ matrix.app }} image
id: build
uses: docker/build-push-action@v5
with:
context: .
file: ./build/package/${{ matrix.app }}/Dockerfile
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.app }}
cache-to: type=gha,mode=max,scope=${{ matrix.app }}
provenance: true
sbom: true
- name: Generate SLSA attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/deepzz0/${{ matrix.app }}
subject-digest: ${{ steps.build.outputs.digest }}
# Job 3: 创建 GitHub Release
create-release:
runs-on: ubuntu-latest
needs: [package-source, build-images]
steps:
- name: Download tar artifacts
uses: actions/download-artifact@v4
with:
name: release-archives
- name: Create release summary
run: |
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
# 🎉 Release ${{ needs.package-source.outputs.tag }} Created!
## 📦 Docker Images
- **eiblog**: `deepzz0/eiblog:${{ needs.package-source.outputs.tag }}`
- **backup**: `deepzz0/backup:${{ needs.package-source.outputs.tag }}`
## 🏗️ Build Info
- **Tag**: ${{ needs.package-source.outputs.tag }}
- **Commit**: ${{ needs.package-source.outputs.sha }}
- **Built**: ${{ needs.package-source.outputs.date }}
- **Platforms**: linux/amd64, linux/arm64, linux/arm/v7
## 🔐 Security
- ✅ SLSA Build Provenance
- ✅ SBOM (Software Bill of Materials)
- ✅ Container Signing
EOF
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
files: "*.tar.gz"
generate_release_notes: true
name: Release ${{ needs.package-source.outputs.tag }}
body: |
## Docker Images
```bash
docker pull deepzz0/eiblog:${{ needs.package-source.outputs.tag }}
docker pull deepzz0/backup:${{ needs.package-source.outputs.tag }}
```
## Multi-Architecture Support
- linux/amd64
- linux/arm64
- linux/arm/v7
Built with commit ${{ needs.package-source.outputs.sha }} on ${{ needs.package-source.outputs.date }}

26
.gitignore vendored
View File

@@ -1,5 +1,23 @@
**/.DS_Store
# Binaries for programs and plugins
*.exe
conf/ssl/domain.*
eiblog
static/*.*
*.exe~
*.dll
*.so
*.dylib
*.DS_Store
*.tar.gz
*.db
# 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
cmd/eiblog/etc/assets/*.xml
cmd/eiblog/etc/assets/*.txt
db.sqlite
cmd/*/backend

View File

@@ -1,44 +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:
# - if [ "$TRAVIS_BRANCH" =~ ^v[0-9](\.[0-9])+.*$ ]; then
# docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" registry.cn-hangzhou.aliyuncs.com;
# docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog;
# fi
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:$TRAVIS_TAG
before_deploy:
- ./dist.sh
deploy:
provider: releases
api_key:
secure: AGW05sQuQfjy77+JprSV+ohti/VVgFuh7UOTV0+hwxqsOVXSoIQz/ZPOlHWPP1iiSiGGEalspm+UtKRvADcDfllUaEwo7kebfFeMx4X//qxFxQSQ5LJYx7qxsTDpuQ4CF8zifCtND3ynnUAdx0P6FFkxE/67kN2n4CrhIxYCUb8gNPzDDRuS0ZNBC4zzNldJo/vtatbvc2btuFfwKoClYf+xPLy5luLqDvKF+hdjJ8NuZl8BWkWxXE+kk8fW4iUn2IV0qtLRZ3FQUyAF2CumzxqZfViX+rYTXsfbabYY5nYG6opT4mUEF58T4X3uRV0e3Q6Fe73nmLh9cyAoQl1BTSJ1XiyV4eJJWcKEMY7DqJ646lzoUT449YwvTK57klcfBbShpcjFf2alVdEbr9jbEXrCkuWKnssO9VfufhYF6t9h22c79evpexpIbsoncPD+b+n712MzufREtUF4kpUdkIir5n9CgQl/l7S+fV+n+gME+mcA44K7iPXkC80UfxJiw83QizT39OQhExq6SPIwrbt2vlAkBpSLMUS9iAHtTJYUsmH1SsmrxGK3WromKysWeTRJbcAJls2k6V313sn4TuYBWiHTUfsUBhv+objDFA2TsfO+g0g1JsdfZb5EsKrqNvs/2ta1xlzdE0+/TLG/YNKIOPkHnXswAM3DZm3zEss=
skip_cleanup: true
file: "*.tar.gz"
file_glob: true
on:
tags: true
repo: eiblog/eiblog
all_branches: true

View File

@@ -1,41 +1,287 @@
# 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 镜像版本化。
### [3.0.6](https://github.com/eiblog/eiblog/compare/v3.0.5...v3.0.6) (2025-07-25)
## v1.1.3 (2017-05-12)
* 更新 disqus_78bca4.js 到 disqus_921d24.js具体请参考 docs/install.md
* 更新 vendor
## v1.1.2 (2017-03-08)
* 解决添加文章描述错误的bug
* 添加vendor目录
* 添加文档docs目录
* 删除多余注释
### Bug Fixes
## v1.1.1 (2017-02-07)
* 添加文章描述功能。
* 修复评论`jQuery`文件引用错误。
* 修复`.travis.yml`描述错误。
* feed & sitemap not generate ([629ad78](https://github.com/eiblog/eiblog/commit/629ad782c45fae7af9efdf99513bafdf87e7758c))
## v1.0.0 (2016-01-09)
首次发布版本
### [3.0.5](https://github.com/eiblog/eiblog/compare/v3.0.4...v3.0.5) (2025-07-25)
* 全站`HTTPS`设计,安全、极速。
* `Elasticsearch`博客搜索系统。
* 开源`Typecho`完整博客后台。
* 全功能`Markdown`编辑器。
* 异步`Google analysts`分析统计。
* `Disqus`评论系统。
* 后台直接对接七牛`CDN`
### Bug Fixes
* admin login session ([c1d73f1](https://github.com/eiblog/eiblog/commit/c1d73f1a453af62d15c25a79c382d9cefd8a3d2e))
* mongodb uri error ([33afbd3](https://github.com/eiblog/eiblog/commit/33afbd351d2b41f9edf36959908c3f183745d903))
* RUN_MODE error ([eaeeaaa](https://github.com/eiblog/eiblog/commit/eaeeaaafb98a4aa0a42dba74b411b1d361faf1d5))
### [3.0.4](https://github.com/eiblog/eiblog/compare/v3.0.3...v3.0.4) (2025-07-25)
### Bug Fixes
* **ci:** golang version ([9f77fb7](https://github.com/eiblog/eiblog/commit/9f77fb7b3f283aacb9d9c2a0e2f79c8a6c412edb))
### [3.0.2](https://github.com/eiblog/eiblog/compare/v3.0.1...v3.0.2) (2025-07-25)
### Bug Fixes
* ci ([ccb5e45](https://github.com/eiblog/eiblog/commit/ccb5e4546e224182c949e72e9eae82fbbe1a02fe))
### [3.0.1](https://github.com/eiblog/eiblog/compare/v3.0.0...v3.0.1) (2025-07-25)
### Bug Fixes
* dist tar ([cb2ed7c](https://github.com/eiblog/eiblog/commit/cb2ed7cb8244dda8cbd8c5966c7ed02e177777e5))
## [3.0.0](https://github.com/eiblog/eiblog/compare/v2.2.17...v3.0.0) (2025-07-24)
### Features
* support custom page ([b69248f](https://github.com/eiblog/eiblog/commit/b69248f6a4937f8157d2393bd44c6c55174ae3e7))
* twofactor bind ([e5100fa](https://github.com/eiblog/eiblog/commit/e5100fa018e2955c3d649b70245e3a379658d7ba))
### Bug Fixes
* ci ([f4c70b4](https://github.com/eiblog/eiblog/commit/f4c70b46c19d8761c252b767c92016c195299c0c))
* ci dist tar ([24bfe52](https://github.com/eiblog/eiblog/commit/24bfe528b28b995c3d5b2c13314c8849a036942b))
* custom page support embed ([aee4194](https://github.com/eiblog/eiblog/commit/aee4194c71dd98a7a84da96810a060f716d1ea57))
### [2.2.16](https://github.com/eiblog/eiblog/compare/v2.2.15...v2.2.16) (2025-03-13)
### Bug Fixes
* empty "beian" display issue ([79fccb9](https://github.com/eiblog/eiblog/commit/79fccb958c3eb8e21615389359c1d613c5fb079b))
### [2.2.15](https://github.com/eiblog/eiblog/compare/v2.2.14...v2.2.15) (2024-12-31)
### Bug Fixes
* disqus list posts ([52fe730](https://github.com/eiblog/eiblog/commit/52fe7303f3345421c0f2e2989a6c174d5b1a689e))
* disqus thread not store ([616248d](https://github.com/eiblog/eiblog/commit/616248d33fdf44dbc3aed41e92adae001a4f5577))
### [2.2.14](https://github.com/eiblog/eiblog/compare/v2.2.13...v2.2.14) (2024-10-10)
### Bug Fixes
* 1. bei_an cannot update error ([b53fc91](https://github.com/eiblog/eiblog/commit/b53fc91ce7b67c3811c232ad6236898f84bc391b)), closes [#43](https://github.com/eiblog/eiblog/issues/43) [#44](https://github.com/eiblog/eiblog/issues/44)
* **serie:** update serie did not rerender ([88f23bd](https://github.com/eiblog/eiblog/commit/88f23bd1a0d6183d6de484cb79303506f0506d15))
### [2.2.13](https://github.com/eiblog/eiblog/compare/v2.2.12...v2.2.13) (2024-01-02)
### Bug Fixes
* load more comments ([95e55ee](https://github.com/eiblog/eiblog/commit/95e55ee13c1af13e2bb2149ccbe60013c93e4e69))
### [2.2.12](https://github.com/eiblog/eiblog/compare/v2.2.11...v2.2.12) (2024-01-02)
### [2.2.11](https://github.com/eiblog/eiblog/compare/v2.2.10...v2.2.11) (2024-01-02)
### Bug Fixes
* **disqus:** fix returned posts list not have parent post ([9d71ca8](https://github.com/eiblog/eiblog/commit/9d71ca81988bfc614d13fcb02079f0dba9ef43cc))
### [2.2.10](https://github.com/eiblog/eiblog/compare/v2.2.9...v2.2.10) (2023-12-22)
### [2.2.9](https://github.com/eiblog/eiblog/compare/v2.2.8...v2.2.9) (2023-09-25)
### Bug Fixes
* google analytics not work, supported ga4 measurement protocol ([9b918ca](https://github.com/eiblog/eiblog/commit/9b918caccd9fd7faff7d253693e075ccd0150c17))
### [2.2.8](https://github.com/eiblog/eiblog/compare/v2.2.7...v2.2.8) (2023-07-12)
### Bug Fixes
* **backup:** restore db and tests ([f28d0e7](https://github.com/eiblog/eiblog/commit/f28d0e77e06dd435dc13a1867f18a011e34b8f53))
### [2.2.7](https://github.com/eiblog/eiblog/compare/v2.2.6...v2.2.7) (2023-07-12)
### Bug Fixes
* **backup:** 数据恢复错误 ([ee51f67](https://github.com/eiblog/eiblog/commit/ee51f678cbf93332fedccf927704e78a6063289c))
### [2.2.6](https://github.com/eiblog/eiblog/compare/v2.2.5...v2.2.6) (2023-06-19)
### [2.2.5](https://github.com/eiblog/eiblog/compare/v2.2.4...v2.2.5) (2023-05-26)
### Bug Fixes
* **backup:** libresolv.so.2: No such file or directory ([2c6c5bb](https://github.com/eiblog/eiblog/commit/2c6c5bb97708683bed8c889a7a1076f1f88ee5a8))
### [2.2.4](https://github.com/eiblog/eiblog/compare/v2.2.3...v2.2.4) (2023-05-25)
### Bug Fixes
* **ci:** fix .dockerignore ([1509a68](https://github.com/eiblog/eiblog/commit/1509a68cda74ace6b4060b5015f20143303ca36f))
### [2.2.3](https://github.com/eiblog/eiblog/compare/v2.2.2...v2.2.3) (2023-05-25)
### Bug Fixes
* **ci:** script file name ([20e89d6](https://github.com/eiblog/eiblog/commit/20e89d6a76f01aee60b98f8ae43a2c65b4fb3904))
### [2.2.2](https://github.com/eiblog/eiblog/compare/v2.2.1...v2.2.2) (2023-05-25)
### [2.2.1](https://github.com/eiblog/eiblog/compare/v2.2.0...v2.2.1) (2023-05-17)
### Bug Fixes
* try to fix backup symbol not found ([bb06be3](https://github.com/eiblog/eiblog/commit/bb06be36fe016e753ca56aa2321ce7e39bffe3e0))
## [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)

View File

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

View File

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

View File

@@ -1,69 +1,27 @@
.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
clean:
@scripts/dist_tar.sh $(tag)
# protoc
protoc:
@cd pkg/proto && make protoc

152
README.md
View File

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

472
api.go
View File

@@ -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/gin-gonic/gin"
"gopkg.in/mgo.v2/bson"
)
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(bson.M{"$set": bson.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(bson.M{"$set": bson.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(bson.M{"$set": bson.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(bson.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
View File

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

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

305
back.go
View File

@@ -1,305 +0,0 @@
// Package main provides ...
package main
import (
"bytes"
"fmt"
"html/template"
"net/http"
"strconv"
"time"
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
"gopkg.in/mgo.v2/bson"
)
// 是否登录
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(bson.M{"$set": bson.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})
}
h["Tags"] = tags
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-post", h)
}
// 删除草稿
func HandleDraftDelete(c *gin.Context) {
id, err := strconv.Atoi(c.Query("cid"))
if err != nil || id < 1 {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
if err = RemoveArticle(int32(id)); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "删除错误"})
return
}
c.Redirect(http.StatusFound, "/admin/write-post")
}
// 文章管理==>Manage
func HandlePosts(c *gin.Context) {
kw := c.Query("keywords")
tmp := c.Query("serie")
se, err := strconv.Atoi(tmp)
if err != nil || se < 1 {
se = 0
}
pg, err := strconv.Atoi(c.Query("page"))
if err != nil || pg < 1 {
pg = 1
}
vals := c.Request.URL.Query()
h := GetBack()
h["Manage"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "文章管理 | " + Ei.BTitle
h["Series"] = Ei.Series
h["Serie"] = se
h["KW"] = kw
var max int
max, h["List"] = PageListBack(se, kw, false, false, pg, setting.Conf.General.PageSize)
if pg < max {
vals.Set("page", fmt.Sprint(pg+1))
h["Next"] = vals.Encode()
}
if pg > 1 {
vals.Set("page", fmt.Sprint(pg-1))
h["Prev"] = vals.Encode()
}
h["PP"] = make(map[int]string, max)
for i := 0; i < max; i++ {
vals.Set("page", fmt.Sprint(i+1))
h["PP"].(map[int]string)[i+1] = vals.Encode()
}
h["Cur"] = pg
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-posts", h)
}
// 专题列表
func HandleSeries(c *gin.Context) {
h := GetBack()
h["Manage"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "专题管理 | " + Ei.BTitle
h["List"] = Ei.Series
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-series", h)
}
// 编辑专题
func HandleSerie(c *gin.Context) {
h := GetBack()
id, err := strconv.Atoi(c.Query("mid"))
if serie := QuerySerie(int32(id)); err == nil && id > 0 && serie != nil {
h["Title"] = "编辑专题 | " + Ei.BTitle
h["Edit"] = serie
} else {
h["Title"] = "新增专题 | " + Ei.BTitle
}
h["Manage"] = true
h["Path"] = c.Request.URL.Path
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-serie", h)
}
// 标签列表
func HandleTags(c *gin.Context) {
h := GetBack()
h["Manage"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "标签管理 | " + Ei.BTitle
h["List"] = Ei.Tags
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-tags", h)
}
// 草稿箱
func HandleDraft(c *gin.Context) {
h := GetBack()
h["Manage"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "草稿箱 | " + Ei.BTitle
var err error
h["List"], err = LoadDraft()
if err != nil {
logd.Error(err)
c.Status(http.StatusBadRequest)
} else {
c.Status(http.StatusOK)
}
RenderHTMLBack(c, "admin-draft", h)
}
// 回收箱
func HandleTrash(c *gin.Context) {
h := GetBack()
h["Manage"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "回收箱 | " + Ei.BTitle
var err error
h["List"], err = LoadTrash()
if err != nil {
logd.Error(err)
c.HTML(http.StatusBadRequest, "backLayout.html", h)
return
}
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-trash", h)
}
// 基本设置==>Setting
func HandleGeneral(c *gin.Context) {
h := GetBack()
h["Setting"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "基本设置 | " + Ei.BTitle
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-general", h)
}
// 阅读设置
func HandleDiscussion(c *gin.Context) {
h := GetBack()
h["Setting"] = true
h["Path"] = c.Request.URL.Path
h["Title"] = "阅读设置 | " + Ei.BTitle
c.Status(http.StatusOK)
RenderHTMLBack(c, "admin-discussion", h)
}
// api
func HandleAPI(c *gin.Context) {
action := c.Param("action")
logd.Debug("action=======>", action)
api := APIs[action]
if api == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Invalid API Request"})
return
}
api(c)
}
// 渲染 html
func RenderHTMLBack(c *gin.Context, name string, data gin.H) {
if name == "login.html" {
err := Tmpl.ExecuteTemplate(c.Writer, name, data)
if err != nil {
panic(err)
}
c.Header("Content-Type", "text/html; charset=utf-8")
return
}
var buf bytes.Buffer
err := Tmpl.ExecuteTemplate(&buf, name, data)
if err != nil {
panic(err)
}
data["LayoutContent"] = template.HTML(buf.String())
err = Tmpl.ExecuteTemplate(c.Writer, "backLayout.html", data)
if err != nil {
panic(err)
}
c.Header("Content-Type", "text/html; charset=utf-8")
}

5
build/README.md Normal file
View File

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

View File

@@ -0,0 +1,23 @@
FROM golang:1.23 AS builder
WORKDIR /eiblog
COPY . .
RUN scripts/run_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 gcompat
COPY README.md /app/README.md
COPY CHANGELOG.md /app/CHANGELOG.md
COPY LICENSE /app/LICENSE
COPY --from=builder /eiblog/cmd/backup/backend /app/backend
COPY cmd/backup/etc /app/etc
EXPOSE 9001
WORKDIR /app
CMD ["./backend"]

View File

@@ -0,0 +1,22 @@
FROM golang:1.23 AS builder
WORKDIR /eiblog
COPY . .
RUN scripts/run_build.sh eiblog
FROM alpine:latest
LABEL maintainer="deepzz.qi@gmail.com"
RUN apk add --update --no-cache tzdata ca-certificates
COPY README.md /app/README.md
COPY CHANGELOG.md /app/CHANGELOG.md
COPY LICENSE /app/LICENSE
COPY --from=builder /eiblog/cmd/eiblog/backend /app/backend
COPY cmd/eiblog/etc /app/etc
EXPOSE 9000
WORKDIR /app
CMD ["./backend"]

View File

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

View File

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

7
cmd/README.md Normal file
View File

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

View File

@@ -0,0 +1,67 @@
package config
import (
"os"
"path/filepath"
"strings"
"github.com/eiblog/eiblog/pkg/config"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
// Config config
type Config struct {
config.APIMode
Database config.Database // 数据库配置
BackupTo string // 备份到, default: qiniu
Interval string // 备份周期, default: 7d
Validity int // 备份保留时间, default: 60
Qiniu config.Qiniu // 七牛OSS配置
}
// Conf 配置
var Conf Config
// load config file
func init() {
// run mode
mode := config.RunMode(os.Getenv("RUN_MODE"))
if !mode.IsRunMode() {
panic("config: unsupported env RUN_MODE: " + mode)
}
logrus.Infof("Run mode:%s", mode)
// 加载配置文件
etc, err := config.WorkEtcPath()
if err != nil {
panic(err)
}
path := filepath.Join(etc, "app.yml")
data, err := os.ReadFile(path)
if err != nil {
panic(err)
}
err = yaml.Unmarshal(data, &Conf)
if err != nil {
panic(err)
}
Conf.RunMode = mode
// read env
readDatabaseEnv()
}
func readDatabaseEnv() {
key := strings.ToUpper(Conf.Name) + "_DB_DRIVER"
if d := os.Getenv(key); d != "" {
Conf.Database.Driver = d
}
key = strings.ToUpper(Conf.Name) + "_DB_SOURCE"
if s := os.Getenv(key); s != "" {
Conf.Database.Source = s
}
}

60
cmd/backup/docs/docs.go Normal file
View File

@@ -0,0 +1,60 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/ping": {
"get": {
"description": "ping",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"ping"
],
"summary": "ping",
"responses": {
"200": {
"description": "it's ok",
"schema": {
"type": "string"
}
}
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "",
Schemes: []string{},
Title: "backup API",
Description: "This is a backup server.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

View File

@@ -0,0 +1,34 @@
{
"swagger": "2.0",
"info": {
"description": "This is a backup server.",
"title": "backup API",
"contact": {},
"version": "1.0"
},
"paths": {
"/ping": {
"get": {
"description": "ping",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"ping"
],
"summary": "ping",
"responses": {
"200": {
"description": "it's ok",
"schema": {
"type": "string"
}
}
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
info:
contact: {}
description: This is a backup server.
title: backup API
version: "1.0"
paths:
/ping:
get:
consumes:
- application/json
description: ping
produces:
- application/json
responses:
"200":
description: it's ok
schema:
type: string
summary: ping
tags:
- ping
swagger: "2.0"

14
cmd/backup/etc/app.yml Normal file
View File

@@ -0,0 +1,14 @@
apimode:
name: cmd-backup
listen: 0.0.0.0:9000
database: # 数据库配置
driver: mongodb
source: mongodb://localhost:27017
backupto: qiniu # 备份到, default: qiniu
interval: 7d # 备份周期, default: 7d
validity: 60 # 备份保留时间, default: 60
qiniu: # 七牛OSS
bucket: eiblog
domain: st.deepzz.com
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf

View File

@@ -0,0 +1,17 @@
package internal
import (
"github.com/eiblog/eiblog/cmd/backup/config"
"github.com/eiblog/eiblog/pkg/third/qiniu"
)
// QiniuClient 七牛客户端
var QiniuClient *qiniu.QiniuClient
func init() {
var err error
QiniuClient, err = qiniu.NewQiniuClient(config.Conf.Qiniu)
if err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,24 @@
package ping
import (
"net/http"
"github.com/gin-gonic/gin"
)
// RegisterRoutes register routes
func RegisterRoutes(group gin.IRoutes) {
group.GET("/ping", handlePing)
}
// handlePing ping
// @Summary ping
// @Description ping
// @Tags ping
// @Accept json
// @Produce json
// @Success 200 {string} string "it's ok"
// @Router /ping [get]
func handlePing(c *gin.Context) {
c.String(http.StatusOK, "it's ok")
}

View File

@@ -0,0 +1,14 @@
package swag
import (
_ "github.com/eiblog/eiblog/cmd/eiblog/docs" // docs
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
// RegisterRoutes register routes
func RegisterRoutes(group gin.IRoutes) {
group.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}

View File

@@ -0,0 +1,7 @@
package db
// Storage 备份恢复器
type Storage interface {
Backup(name string) (string, error)
Restore(path string) error
}

View File

@@ -0,0 +1,72 @@
package db
import (
"context"
"fmt"
"net/url"
"os/exec"
"time"
"github.com/eiblog/eiblog/cmd/backup/config"
pdb "github.com/eiblog/eiblog/pkg/connector/db"
)
// MongoStorage 备份恢复器
type MongoStorage struct{}
// Backup 备份
func (r MongoStorage) Backup(name string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
// dump
u, err := url.Parse(config.Conf.Database.Source)
if err != nil {
return "", err
}
arg := fmt.Sprintf("mongodump -h %s -d eiblog -o /tmp", u.Host)
cmd := exec.CommandContext(ctx, "sh", "-c", arg)
err = cmd.Run()
if err != nil {
return "", err
}
// tar
arg = fmt.Sprintf("tar czf /tmp/%s -C /tmp eiblog", name)
cmd = exec.CommandContext(ctx, "sh", "-c", arg)
err = cmd.Run()
if err != nil {
return "", err
}
return "/tmp/" + name, nil
}
// Restore 恢复
func (r MongoStorage) Restore(path string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
// drop database
database, err := pdb.NewMDB(ctx, config.Conf.Database)
if err != nil {
return err
}
err = database.Drop(ctx)
if err != nil {
return err
}
// unarchive
arg := fmt.Sprintf("tar xzf %s -C /tmp", path)
cmd := exec.CommandContext(ctx, "sh", "-c", arg)
err = cmd.Run()
if err != nil {
return err
}
// restore
u, err := url.Parse(config.Conf.Database.Source)
if err != nil {
return err
}
arg = fmt.Sprintf("mongorestore -h %s -d eiblog /tmp/eiblog", u.Host)
cmd = exec.CommandContext(ctx, "sh", "-c", arg)
return cmd.Run()
}

View File

@@ -0,0 +1,95 @@
package timer
import (
"errors"
"fmt"
"strconv"
"time"
"github.com/eiblog/eiblog/cmd/backup/config"
"github.com/eiblog/eiblog/cmd/backup/handler/timer/db"
"github.com/eiblog/eiblog/cmd/backup/handler/timer/to"
"github.com/sirupsen/logrus"
)
// Start to backup with ticker
func Start(restore bool) (err error) {
var (
storage db.Storage
backupTo to.BackupRestorer
)
// backup from
switch config.Conf.Database.Driver {
case "mongodb":
storage = db.MongoStorage{}
default:
return errors.New("timer: unknown backup from driver: " +
config.Conf.Database.Driver)
}
// backup to
switch config.Conf.BackupTo {
case "qiniu":
backupTo = to.QiniuBackupRestorer{}
default:
return errors.New("timer: unknown backup to driver: " +
config.Conf.BackupTo)
}
// restore
if restore {
path, err := backupTo.Download()
if err != nil {
return err
}
err = storage.Restore(path)
if err != nil {
return err
}
logrus.Info("timer: Restore success")
}
// backup
interval, err := ParseDuration(config.Conf.Interval)
if err != nil {
return err
}
t := time.NewTicker(interval)
for now := range t.C {
name := fmt.Sprintf("eiblog-%s.tar.gz", now.Format("2006-01-02"))
path, err := storage.Backup(name)
if err != nil {
logrus.Error("timer: Start.Backup: ", now.Format(time.RFC3339), err)
continue
}
err = backupTo.Upload(path)
if err != nil {
logrus.Error("timer: Start.Backup: ", now.Format(time.RFC3339), err)
}
}
return nil
}
// ParseDuration parse string to duration
func ParseDuration(d string) (time.Duration, error) {
if len(d) == 0 {
return 0, errors.New("timer: incorrect duration input")
}
length := len(d)
switch d[length-1] {
case 's', 'm', 'h':
return time.ParseDuration(d)
case 'd':
di, err := strconv.Atoi(d[:length-1])
if err != nil {
return 0, err
}
return time.Duration(di) * time.Hour * 24, nil
}
return 0, errors.New("timer: unsupported duration:" + d)
}

View File

@@ -0,0 +1,66 @@
package to
import (
"os"
"path/filepath"
"github.com/eiblog/eiblog/cmd/backup/config"
"github.com/eiblog/eiblog/cmd/backup/handler/internal"
"github.com/eiblog/eiblog/pkg/third/qiniu"
)
// QiniuBackupRestorer qiniu backup restorer
type QiniuBackupRestorer struct{}
// Upload implements timer.BackupRestorer
func (s QiniuBackupRestorer) Upload(path string) error {
name := filepath.Base(path)
// upload file
f, err := os.Open(path)
if err != nil {
return err
}
fi, err := f.Stat()
if err != nil {
return err
}
uploadParams := qiniu.UploadParams{
Name: filepath.Join("blog", name), // blog/eiblog-xx.tar.gz
Size: fi.Size(),
Data: f,
NoCompletePath: true,
}
_, err = internal.QiniuClient.Upload(uploadParams)
if err != nil {
return err
}
// after days delete
deleteParams := qiniu.DeleteParams{
Name: filepath.Join("blog", name), // blog/eiblog-xx.tar.gz
Days: config.Conf.Validity,
NoCompletePath: true,
}
return internal.QiniuClient.Delete(deleteParams)
}
// Download implements timer.BackupRestorer
func (s QiniuBackupRestorer) Download() (string, error) {
// backup file
params := qiniu.ContentParams{
Prefix: "blog/",
}
raw, err := internal.QiniuClient.Content(params)
if err != nil {
return "", err
}
path := filepath.Join("/tmp", "eiblog.tar.gz")
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return "", err
}
_, _ = f.Write(raw)
defer f.Close()
return path, nil
}

View File

@@ -0,0 +1,7 @@
package to
// BackupRestorer 备份存储接口
type BackupRestorer interface {
Upload(path string) error
Download() (path string, err error)
}

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

@@ -0,0 +1,61 @@
// Package main provides ...
package main
import (
"flag"
"github.com/eiblog/eiblog/cmd/backup/config"
"github.com/eiblog/eiblog/cmd/backup/handler/ping"
"github.com/eiblog/eiblog/cmd/backup/handler/swag"
"github.com/eiblog/eiblog/cmd/backup/handler/timer"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// @title backup API
// @version 1.0
// @description This is a backup server.
var restore bool
func init() {
flag.BoolVar(&restore, "restore", false, "restore data into mongodb")
}
func main() {
logrus.Info("Hi, it's App " + config.Conf.Name)
flag.Parse()
endRun := make(chan error, 1)
runCommand(restore, endRun)
runHTTPServer(endRun)
logrus.Fatal(<-endRun)
}
func runCommand(restore bool, endRun chan error) {
go func() {
endRun <- timer.Start(restore)
}()
}
func runHTTPServer(endRun chan error) {
if config.Conf.RunMode.IsReleaseMode() {
gin.SetMode(gin.ReleaseMode)
}
e := gin.Default()
// swag
swag.RegisterRoutes(e)
// route
ping.RegisterRoutes(e)
// start
go func() {
endRun <- e.Run(config.Conf.Listen)
}()
logrus.Info("HTTP server running on: " + config.Conf.Listen)
}

5
cmd/eiblog/CHANGELOG.md Normal file
View File

@@ -0,0 +1,5 @@
# Changelog
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.
### [3.0.3](https://github.com/eiblog/eiblog/compare/v3.0.2...v3.0.3) (2025-07-25)

View File

@@ -0,0 +1,83 @@
package config
import (
"os"
"path/filepath"
"strings"
"github.com/eiblog/eiblog/pkg/config"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
var (
// Conf 配置
Conf Config
// EtcDir 工作目录
EtcDir string
)
// Config config
type Config struct {
config.APIMode
// 静态资源版本, 每次更改了 js/css 都需要提高该值
StaticVersion int
// 数据库配置
Database config.Database
// 热词, 手动指定, 用于搜索
HotWords []string
// Elasticsearch 配置
ESHost string
General config.General
Disqus config.Disqus
Google config.Google
Qiniu config.Qiniu
Twitter config.Twitter
FeedRPC config.FeedRPC
Account config.Account
Pages []config.CustomPage
}
// init 初始化配置
func init() {
// run mode
mode := config.RunMode(os.Getenv("RUN_MODE"))
if !mode.IsRunMode() {
panic("config: unsupported env RUN_MODE: " + mode)
}
logrus.Infof("Run mode:%s", mode)
// 加载配置文件
var err error
EtcDir, err = config.WorkEtcPath()
if err != nil {
panic(err)
}
path := filepath.Join(EtcDir, "app.yml")
data, err := os.ReadFile(path)
if err != nil {
panic(err)
}
err = yaml.Unmarshal(data, &Conf)
if err != nil {
panic(err)
}
Conf.RunMode = mode
// 读取环境变量配置
readDatabaseEnv()
}
func readDatabaseEnv() {
key := strings.ToUpper(Conf.Name) + "_DB_DRIVER"
if d := os.Getenv(key); d != "" {
Conf.Database.Driver = d
}
key = strings.ToUpper(Conf.Name) + "_DB_SOURCE"
if s := os.Getenv(key); s != "" {
Conf.Database.Source = s
}
}

36
cmd/eiblog/docs/docs.go Normal file
View File

@@ -0,0 +1,36 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "",
Host: "",
BasePath: "",
Schemes: []string{},
Title: "",
Description: "",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

View File

@@ -0,0 +1,7 @@
{
"swagger": "2.0",
"info": {
"contact": {}
},
"paths": {}
}

View File

@@ -0,0 +1,4 @@
info:
contact: {}
paths: {}
swagger: "2.0"

59
cmd/eiblog/etc/app.yml Normal file
View File

@@ -0,0 +1,59 @@
apimode:
name: cmd-eiblog
listen: 0.0.0.0:9000
host: example.com
database: # 数据库配置
driver: sqlite
source: ./db.sqlite
hotwords: # 热搜词
- docker
- mongodb
- curl
- dns
staticversion: 1
eshost: # http://elasticsearch:9200
pages:
- name: 自定义页面
path: /page/custom.html
showinnav: true
isembed: false
- name: 自定义页面2
path: /page/custom2.html
showinnav: true
isembed: true
general: # 常规配置
pagenum: 10 # 首页展示文章数量
pagesize: 20 # 管理界面
descprefix: "Desc:" # 文章描述前缀
identifier: <!--more--> # 截取预览标识
length: 400 # 自动截取预览, 字符数
timezone: Asia/Shanghai # 时区
twofactor: true # 是否启用两步验证
disqus: # 评论相关
shortname: xxxxxx
publickey: wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
accesstoken: 50023908f39f4607957e909b495326af
google: # 谷歌分析
url: https://www.google-analytics.com/g/collect
tid: G-xxxxxxxxxx
v: "2"
adsense: <script async src="https://pagead2.googlesyndication.com/xxx" crossorigin="anonymous"></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 # *登录明文密码

View File

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 847 B

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,8 @@
<html>
<head>
<title>Custom Page</title>
</head>
<body>
<h1>Custom Page</h1>
</body>
</html>

View File

@@ -0,0 +1,10 @@
{{define "custom2.html"}}
<div id=content class=inner>
<article class="post" itemscope itemtype="http://schema.org/Article">
<h1 class=title itemprop=headline>Custom Page</h1>
<div class="entry-content" itemprop=articleBody>
<p>Custom Page</p>
</div>
</article>
</div>
{{end}}

View File

@@ -0,0 +1 @@
This is the place to put your project's website data if you are not using GitHub pages.

View File

@@ -60,7 +60,7 @@
</ul>
</nav>
<div class="operate">
<a target="_self" title="{{.LastLogin}}" href="/admin/profile" class="author">{{.Author}}</a><a class="exit" href="/admin/login?logout=true">登出</a><a target="_back" href="/">网站</a>
<a target="_self" title="{{.LastLogin}}" href="/admin/profile" class="author">{{.Author}}</a><a class="exit" href="/admin/login?logout=true">登出</a><a target="_blank" href="/">网站</a>
</div>
</div>
<div class="main">

View File

@@ -50,8 +50,8 @@
<td><a href="/admin/write-post?cid={{.ID}}">{{.Title}}</a></td>
<td>{{.Author}}</td>
<td>{{if gt .SerieID 0}}专题ID:{{.SerieID}}{{else}}--{{end}}</td>
<td>{{dateformat .CreateTime "2006/01/02 15:04"}}</td>
<td>{{dateformat .UpdateTime "2006/01/02 15:04"}}</td>
<td>{{dateformat .CreatedAt "2006/01/02 15:04"}}</td>
<td>{{dateformat .UpdatedAt "2006/01/02 15:04"}}</td>
</tr>
{{end}}
</tbody>

View File

@@ -22,10 +22,10 @@
<label for=password class="sr-only">密码</label>
<input type=password id=password name=password class="text-l w-100" placeholder="密码">
</p>
<!-- <p>
{{if .TwoFactor}}<p>
<label for=code class="sr-only">两步验证</label>
<input type=text id=code name=code class="text-l w-100" placeholder="两步验证">
</p> -->
</p>{{end}}
<p class=submit>
<button type=submit class="btn btn-l w-100 primary">登录</button>
</p>

View File

@@ -35,7 +35,7 @@
<section class="typecho-post-option" role="application">
<label for="date" class="typecho-label">发布日期</label>
<p>
<input class="typecho-date w-100" type="text" name="date" id="date" {{with .Edit}}value="{{dateformat .CreateTime "2006-01-02 15:04"}}"{{end}} />
<input class="typecho-date w-100" type="text" name="date" id="date" {{with .Edit}}value="{{dateformat .CreatedAt "2006-01-02 15:04"}}"{{end}} />
</p>
</section>
<section class="typecho-post-option category-option">
@@ -265,7 +265,6 @@ $(document).ready(function() {
function autoSaveListener() {
setInterval(function() {
idInput.val(cid);
var data = form.serialize();
if (savedData != data && !locked) {
@@ -275,7 +274,7 @@ $(document).ready(function() {
$.post(formAction, data + '&do=auto', function(o) {
savedData = data;
lastSaveTime = o.time;
cid = o.cid;
idInput.val(o.cid);
autoSave.text('已保存' + ' (' + o.time + ')').effect('highlight', 1000);
locked = false;
}, 'json');

View File

@@ -64,12 +64,12 @@
<td><a href="/post/{{.Slug}}.html#comments" class="balloon-button size-1">{{.Count}}</a></td>
<td>
<a href="/admin/write-post?cid={{.ID}}">{{.Title}}</a>
<a target="_black" href="/post/{{.Slug}}.html" title="浏览 {{.Title}}"><i class="i-exlink"></i></a>
<a target="_blank" href="/post/{{.Slug}}.html" title="浏览 {{.Title}}"><i class="i-exlink"></i></a>
</td>
<td>{{.Author}}</td>
<td>{{if gt .SerieID 0}}专题ID:{{.SerieID}}{{else}}--{{end}}</td>
<td>{{dateformat .CreateTime "06/01/02 15:04"}}</td>
<td>{{dateformat .UpdateTime "06/01/02 15:04"}}</td>
<td>{{dateformat .CreatedAt "06/01/02 15:04"}}</td>
<td>{{dateformat .UpdatedAt "06/01/02 15:04"}}</td>
</tr>
{{end}}
</tbody>

View File

@@ -3,15 +3,41 @@
<div class="typecho-page-title">
<h2>个人设置</h2>
</div>
{{with .Account}}
{{with .Ei}}
<div class="row typecho-page-main">
<div class="col-mb-12 col-tb-3">
<p>
<img class="profile-avatar" src="//{{$.Qiniu.Domain}}/static/img/avatar.jpg" alt="{{.BlogName}}" />
<img class="profile-avatar" src="//{{$.Qiniu.Domain}}/static/img/avatar.png" alt="{{.Blogger.BlogName}}" />
</p>
<h2>{{.BlogName}}</h2>
<p>{{.SubTitle}}</p>
<p>最后登录: {{dateformat .LoginTime "2006/01/02 15:04"}}</p>
<h2>{{.Blogger.BlogName}}</h2>
<p>{{.Blogger.SubTitle}}</p>
<p>最后登录: {{dateformat .Account.LoginAt "2006/01/02 15:04"}}</p>
{{if .Account.TwoFactorSecret}}
<strong>2FA 已绑定 <a class="unbind-2fa" href="/admin/profile?unbind=true">解绑</a></strong>
{{end}}
{{if $.TwoFactorSecret}}
<strong>2FA 绑定</strong>
<form action="/admin/api/twofactor" method="post" enctype="application/x-www-form-urlencoded">
<ul class="typecho-option">
<li>
<img src="{{$.TwoFactorSecret}}" alt="2fa code" style="width:180px;height:180px;">
</li>
</ul>
<ul class="typecho-option">
<li>
<input id="2fa-0-1" name="code" type="text" class="text" />
<p class="description">
输入 2FA 验证码, 用于确认绑定.</p>
</li>
</ul>
<ul class="typecho-option typecho-option-submit">
<li>
<button type="submit" class="btn primary">
确认绑定</button>
</li>
</ul>
</form>
{{end}}
</div>
<div class="col-mb-12 col-tb-6 col-tb-offset-1 typecho-content-panel" role="form">
<section>
@@ -21,7 +47,7 @@
<li>
<label class="typecho-label" for="info-0-1">
个人邮箱</label>
<input id="info-0-1" name="email" type="text" class="text" value="{{.Email}}" />
<input id="info-0-1" name="email" type="text" class="text" value="{{.Account.Email}}" />
<p class="description">
用于发送告警邮件及其它通知, 建议填写, 如: example@163.com.</p>
</li>
@@ -30,7 +56,7 @@
<li>
<label class="typecho-label" for="info-0-2">
移动电话</label>
<input id="info-0-2" name="phoneNumber" type="text" class="text" value="{{.PhoneN}}" />
<input id="info-0-2" name="phoneNumber" type="text" class="text" value="{{.Account.PhoneN}}" />
<p class="description">
选择填写, 如: 8615123456789.</p>
</li>
@@ -39,7 +65,7 @@
<li>
<label class="typecho-label" for="info-0-3">
家庭住址</label>
<input id="info-0-3" name="address" type="text" class="text" value="{{.Address}}" />
<input id="info-0-3" name="address" type="text" class="text" value="{{.Account.Address}}" />
<p class="description">
选择填写, 如: xx省xx市xx区(县)xxxx小区xxx号.</p>
</li>
@@ -60,7 +86,7 @@
<li>
<label class="typecho-label" for="blog-0-1">
博客昵称 *</label>
<input id="blog-0-1" name="blogName" type="text" class="text" value="{{.BlogName}}" />
<input id="blog-0-1" name="blogName" type="text" class="text" value="{{.Blogger.BlogName}}" />
<p class="description">
用户昵称可以与用户名不同, 用于前台显示.
<br />如果你将此项留空, 将默认使用登录用户名.</p>
@@ -68,45 +94,45 @@
</ul>
<ul class="typecho-option">
<li>
<label class="typecho-label" for="blog-0-4">
<label class="typecho-label" for="blog-0-2">
标题显示 *</label>
<input id="blog-0-4" name="bTitle" type="text" class="text" value="{{.BTitle}}" />
<input id="blog-0-2" name="bTitle" type="text" class="text" value="{{.Blogger.BTitle}}" />
<p class="description">
用于所有页面的title组成, 如: Deepzz's Blog</p>
</li>
</ul>
<ul class="typecho-option">
<li>
<label class="typecho-label" for="blog-0-2">
<label class="typecho-label" for="blog-0-3">
个人格言</label>
<input id="blog-0-2" name="subTitle" type="text" class="text" value="{{.SubTitle}}" />
<input id="blog-0-3" name="subTitle" type="text" class="text" value="{{.Blogger.SubTitle}}" />
<p class="description">
简介或格言, 如: 生活百般滋味, 人生需要笑对.</p>
</li>
</ul>
<ul class="typecho-option">
<li>
<label class="typecho-label" for="blog-0-3">
<label class="typecho-label" for="blog-0-4">
备案号</label>
<input id="blog-0-3" name="beiAn" type="text" class="text" value="{{.BeiAn}}" />
<input id="blog-0-4" name="beiAn" type="text" class="text" value="{{.Blogger.BeiAn}}" />
<p class="description">
用于底部显示, 不添加则不显示, 如: 蜀 ICP 备 16021362 号</p>
</li>
</ul>
<ul class="typecho-option">
<li>
<label class="typecho-label" for="blog-0-4">
<label class="typecho-label" for="blog-0-5">
专题前说</label>
<textarea id="blog-0-4" name="seriessay">{{.SeriesSay}}</textarea>
<textarea id="blog-0-5" name="seriessay">{{.Blogger.SeriesSay}}</textarea>
<p class="description">
此文字用于专题前述, 会在专题最前方显示.</p>
</li>
</ul>
<ul class="typecho-option">
<li>
<label class="typecho-label" for="blog-0-5">
<label class="typecho-label" for="blog-0-6">
归档前说</label>
<textarea id="blog-0-5" name="archivessay">{{.ArchivesSay}}</textarea>
<textarea id="blog-0-6" name="archivessay">{{.Blogger.ArchivesSay}}</textarea>
<p class="description">
此文字用于归档前述, 会在归档最前方显示.</p>
</li>
@@ -123,7 +149,7 @@
<section id="change-password">
<h3>密码修改</h3>
<form action="/admin/api/password" method="post" enctype="application/x-www-form-urlencoded">
<ul class="typecho-option" id="typecho-option-item-password-10">
<ul class="typecho-option" id="typecho-option-item-oldpwd-11">
<li>
<label class="typecho-label" for="password-0-11">
原始密码</label>
@@ -132,26 +158,26 @@
该账户旧密码.</p>
</li>
</ul>
<ul class="typecho-option" id="typecho-option-item-password-10">
<ul class="typecho-option" id="typecho-option-item-newpwd-12">
<li>
<label class="typecho-label" for="password-0-11">
<label class="typecho-label" for="password-0-12">
用户密码</label>
<input id="password-0-11" name="new" type="password" class="w-60" />
<input id="password-0-12" name="new" type="password" class="w-60" />
<p class="description">
为此用户分配一个密码.
<br />建议使用特殊字符与字母、数字的混编样式,以增加系统安全性.</p>
</li>
</ul>
<ul class="typecho-option" id="typecho-option-item-confirm-11">
<ul class="typecho-option" id="typecho-option-item-confirm-13">
<li>
<label class="typecho-label" for="confirm-0-12">
<label class="typecho-label" for="confirm-0-13">
用户密码确认</label>
<input id="confirm-0-12" name="confirm" type="password" class="w-60" />
<input id="confirm-0-13" name="confirm" type="password" class="w-60" />
<p class="description">
请确认你的密码, 与上面输入的密码保持一致.</p>
</li>
</ul>
<ul class="typecho-option typecho-option-submit" id="typecho-option-item-submit-13">
<ul class="typecho-option typecho-option-submit" id="typecho-option-item-submit-14">
<li>
<button type="submit" class="btn primary">
更新密码</button>

View File

@@ -39,7 +39,7 @@
<ul class="typecho-option typecho-option-submit" id="typecho-option-item--6">
<li>
<button type="submit" class="btn primary">
增加专题</button>
{{if .Edit}}更新专题{{else}}新增专题{{end}}</button>
</li>
</ul>
</form>

View File

@@ -45,11 +45,12 @@
<td>
<input type="checkbox" value="{{.ID}}" name="mid[]" />
</td>
<td><a href="/admin/add-serie?mid={{.ID}}">{{.ID}}</a>
<a href="/series.html#toc-{{.ID}}" title="浏览 {{.Name}}"><i class="i-exlink"></i></a>
<td>{{.ID}}</td>
<td>
<a href="/admin/add-serie?mid={{.ID}}">{{.Name}}</a>
<a target="_blank" href="/series.html#toc-{{.ID}}" title="浏览 {{.Name}}"><i class="i-exlink"></i></a>
</td>
<td>{{.Name}}</td>
<td>{{dateformat .CreateTime "2006/01/02 15:04"}}</td>
<td>{{dateformat .CreatedAt "2006/01/02 15:04"}}</td>
<td><a class="balloon-button left size-50" href="#">{{len .Articles}}</a></td>
</tr>
{{end}}

View File

@@ -51,8 +51,8 @@
<td>{{.Title}}</td>
<td>{{.Author}}</td>
<td>{{if gt .SerieID 0}}专题ID:{{.SerieID}}{{else}}--{{end}}</td>
<td>{{dateformat .CreateTime "2006/01/02 15:04"}}</td>
<td>{{dateformat .DeleteTime "2006/01/02 15:04"}}</td>
<td>{{dateformat .CreatedAt "2006/01/02 15:04"}}</td>
<td>{{dateformat .DeletedAt "2006/01/02 15:04"}}</td>
</tr>
{{end}}
</tbody>

View File

@@ -0,0 +1 @@
{{define "article"}}<div id=content class=inner>{{with .Article}}<article class="post post-{{.ID}}" itemscope itemtype="http://schema.org/Article"><div class=meta><div class=date><time itemprop=datePublished content="{{dateformat .CreatedAt "2006-01-02T15:04:05Z07:00"}}">{{dateformat .CreatedAt "Jan 02, 2006"}}</time></div><div class="date-modified"><time itemprop=dateModified content="{{dateformat .UpdatedAt "2006-01-02T15:04:05Z07:00"}}">{{dateformat .UpdatedAt "Jan 02, 2006"}}</time></div><div class=comment><a href="#comments">{{.Count}} Comments</a></div></div><h1 class=title itemprop=headline>{{.Title}}</h1><div class="entry-content" itemprop=articleBody>{{str2html .Header}}{{str2html .Content}}<p>本文链接:<a rel="bookmark" title="Permalink to {{.Title}}" href="//{{$.Domain}}/post/{{.Slug}}.html" itemprop="url">https://{{$.Domain}}/post/{{.Slug}}.html</a><a href="//{{$.Domain}}/post/{{.Slug}}.html#comments">参与评论 »</a></p><p>--<acronym title="End of File">EOF</acronym>--</p><p class="post-info">发表于 <span class="date">{{dateformat .CreatedAt "2006-01-02 15:04:05"}}</span>{{if gt (.Tags|len) 0}},并被添加「{{range $index, $elem := .Tags}}{{if gt $index 0}}、{{end}}<a href="/search.html?q=tag:{{$elem}}">{{$elem}}</a>{{end}}」标签{{end}}{{if .UpdatedAt|isnotzero}},最后修改于 <span class="date">{{dateformat .UpdatedAt "2006-01-02 15:04:05"}}</span>{{end}}。</p>{{with $.Copyright}}<p class="copyright-info">{{str2html $.Copyright}}<a href="//{{$.Domain}}/post/about.html#toc_1">更多说明 »</a></p>{{end}}{{if gt $.Days 100}}<p class="expire-tips">提醒:本文最后更新于 {{$.Days}} 天前,文中所描述的信息可能已发生改变,请谨慎使用。</p>{{end}}{{with $.Serie}}<div class="entry-series"><h3>专题「{{.Name}}」的其它文章 <a href="/series.html#toc-{{.ID}}" title="更多">»</a></h3><ul>{{range .Articles}}{{if ne .ID $.Article.ID}}<li><a href="/post/{{.Slug}}.html">{{.Title}}</a> <span class="date">({{dateformat .CreatedAt "Jan 02, 2006"}})</span></li>{{end}}{{end}}</ul></div>{{end}}</div></article><nav class="page-navi">{{with .Prev}}<a href="/post/{{.Slug}}.html" class=prev>« {{.Title}}</a>{{end}}{{with .Next}}<a href="/post/{{.Slug}}.html" class=next>{{.Title}} »</a>{{end}}</nav><section id=comments><h1 class=title>Comments</h1><div class=total_thread data-identifier="post-{{.Slug}}" data-url="https://{{$.Domain}}/post/{{.Slug}}.html"></div></section>{{end}}</div>{{end}}

View File

@@ -0,0 +1 @@
{{define "blogroll"}}<div id=content class=inner>{{with .Article}}<article class="post post-{{.ID}}" itemscope itemtype="http://schema.org/Article"><h1 class=title itemprop=headline>{{.Title}}</h1><div class="entry-content" itemprop=articleBody><style>.links.ssl li{position:relative;padding-left:22px}.links.ssl li::before{content:'';display:block;position:absolute;top:6px;left:0;width:16px;height:16px;background-size:16px 16px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAD/0lEQVR4Ab2XA5hjOxTHO9batrdce8yrtW2b9dgq1jae1vbueOpn2zbzcoqP06luJ993epXk/0vOOUnK8abkq6Ttb1r2UY8+PJzx4L1DZY8/PrzrxYqySYqsXeEcf5Z+I9r3WlI0MjfzVoKx4Gnaj4XP0pDd/sl/kvq1/HJsxXSxcHP7Hs1bsS4+fSt3RXkt9ZVaz6DyehqpDTTSWI3503bF7+sopDEyqOBxqnks02Mia+IzxTyp1jwZlVSRVlHpS3F3qfWDV49I7TZmFNGdP4rqHj1LIhDn3Es2qfU0Kq0hAfLncVN7pvksTq0fNH+fxSZeVkN+kbSk/0xnddt1jYyanyGSaoz031BfpaO+5U7qyPVavAe3VdeSavIzq3gt+RUe0Qg3Z2wFnol/wSXSl+NuRzQPCfIKYGnOCDn4FHf2H71+8CJP2q7fN3Y/xIrWxKCYWX0SPBZv2SkyPOd+qlFVTyH5lfjaiGYhwZ607y1o07e0mvxBraPRhiMTD3qecsPbDsDB9CtE93ylUMLxouy+EHNLjdsrriXoIluGBrrdMDAoMHjslB7RkGoQ1ROm95wRGBQQFBwSGOqu4fqBC7OGlWuNDKwTH3Qf3KpLUHBgSKPCe8/u5G0+Lri343S0Hk/7e0UVxH9gslfi3ttxepJu59loo7sG9TNvJX5RXEn8hwH+FF+KfQ2/N6zbxz11+9Gl1g0C3Hm/5GB+VRyOeArSDvIZ7mEWIJg8NlU9beurGveFDe7zq+LRa9/cndsgwPU3C09kPYq2Lq+Y/C/xC7FPdpyZdAuT3/HW8EzcVF5NMJRUEdZ+oX/TF7eXOgM4BhWAfFX5aA2HpdK2a1SE4mpCJZ4JB8DiRgEg8meJBWs5LBY8E+fBBW4DzJEJN3s12u5RUQ29x+645HeAntzW3fIfp9bMkQv2NDlAL17rbtn3kusdy+88pXB3UwGAeA8Qh82n8Gka2mue3HQANvEkPYhDmmnNDMK74Va/uKBF27DQlWWjFH1Fbfs4F+dv9UsQtu0a2XrrsQmnD745FSmvJ1RNmNFrEr7Wldc7F2cVIGFxP/rw29MQPoxal2ks/BdcnYuzDBASFhQ0cw9/J5wNi+ynYbjC8ywxf4sTXfaDEIvZIJ4TIA4j3wTvmwrAAbH70NvTfnFn5H5Lw1FEt+Fw9RvAbIlgA6ub0ZnoC24DwL8ffJwSswmAT0Q3SmtcAxyHCvgYhnIfpnyGITJnywQ78Oay21ubLRVsx2cLbXEF8Xvh87TGAW6+U3gq8+FElP80GRU+T0HlOhKp9L5bWT2B148Ua79Zjyciy9dOTkTnKwqogidp76mqZnyjqpzxNdumrp75bfa9lNqK1272d2j+D+LY/CDPYPo9AAAAAElFTkSuQmCC)}.links li .more{color:#999;font-size:14px}</style>{{str2html .Content}}<p>注:为了节约本站用户的宝贵时间,长期无法访问的链接会被移除!另本站只有友情链接,不接受链接交换。</p></div></article>{{end}}</div>{{end}}

View File

@@ -0,0 +1 @@
{{define "home"}}<div id="content" class="inner">{{range .List}}<article class="post post-list"><div class="meta"><div class="date"><time>{{dateformat .CreatedAt "Jan 02, 2006"}}</time></div><div class="comment"><a href="/post/{{.Slug}}.html#comments">{{.Count}} Comments</a></div></div><h1 class="title"><a href="/post/{{.Slug}}.html">{{.Title}}</a></h1><div class="entry-content"><p>{{str2html .Excerpt}}[...]</p><p><a href="/post/{{.Slug}}.html" class="more-link">继续阅读 »</a></p></div></article>{{end}}<nav class="page-navi">{{if gt .Prev 0}}<a href="?pn={{.Prev}}" class="prev">« 上一页</a>{{end}}{{if gt .Next 0}}<a href="?pn={{.Next}}" class="next">下一页 »</a>{{end}}<div class="center"><a href="/archives.html">博客归档</a></div></nav></div>{{end}}

View File

@@ -0,0 +1 @@
<!DOCTYPE html><html lang="zh-cn"><head><meta charset="utf-8"><meta content="width=device-width,minimum-scale=1.0" name="viewport"><meta name="format-detection" content="telephone=no"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="referrer" content="always"><title>{{.Title}}</title>{{.AdSense}}<script>!function(n,t){function e(){o("nls",1)}function c(){t.documentElement.style.display="none",u(),location.reload()}function r(n){var t="";try{t=f[n]||"",t.length<99&&c()}catch(e){u()}return t}function i(n,t){try{f[n]=t,t!==f[n]&&u()}catch(e){u()}}function o(n,e){var c=999;e||(c=-1),c=new Date(+new Date+864e5*c).toGMTString();var r=n+"="+e+";path=/;secure;expires="+c;t.cookie=r}function a(n){var e=t.getElementById(n).innerHTML;i(n,e)}function l(e,c){var i=r(e),o=t.createElement(c?"script":"style");return n.execScript&&c?n.execScript(i):(o.innerHTML=i,void t.head.appendChild(o))}function u(){o("v",0)}var f,h=function(){},d=n.L={h:h,l:h,c:h};try{f=localStorage,d.h=a,d.l=l,d.c=o}catch(p){e()}}(this,document);</script>{{if .Version}}<script>L.c('v', {{.Version}})</script>{{end}}<link rel="apple-touch-icon" href="//{{.Qiniu.Domain}}/static/img/favicon.ico"><link rel="search" type="application/opensearchdescription+xml" href="//{{.Domain}}/opensearch.xml" title="{{.BTitle}}">{{if .Version}}<style id="blog_css">{{template "blog_css" .}}</style><script>L.h('blog_css')</script>{{else}}<script>L.l("blog_css")</script>{{end}}<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="//{{.Domain}}/rss.html"><meta name="description" content="{{.Description}}"><meta name="twitter:card" content={{.Twitter.Card}}><meta name="twitter:site" content="@{{.Twitter.Site}}"><meta name="twitter:title" content="{{.Title}}"><meta name="twitter:description" content="{{.Description}}"><meta name="twitter:image" content="https://{{.Twitter.Image}}"></head><body><div class="container"><div class="left-col"><div class="intrude-less"><header id="header" class="inner"><div class="profilepic"><a href="/"></a></div><h1><a href="/">{{.BlogName}}</a></h1><p class="subtitle">{{.SubTitle}}</p><nav id="main-nav"><ul><li {{if eq .Path "/"}}class="on" {{end}}><a href="/"><span>首页</span></a></li><li {{if eq .Path "/series.html"}}class="on" {{end}}><a href="/series.html"><span>专题</span></a></li><li {{if eq .Path "/archives.html"}}class="on" {{end}}><a href="/archives.html"><span>归档</span></a></li><li {{if eq .Path "/post/blogroll.html"}}class="on" {{end}}><a href="/post/blogroll.html"><span>友链</span></a></li><li {{if eq .Path "/post/about.html"}}class="on" {{end}}><a href="/post/about.html"><span>关于</span></a></li>{{range $index, $element := .Pages}}{{if .ShowInNav}}<li {{if eq $.Path .Path}}class="on" {{end}}><a href="{{.Path}}" {{if not .IsEmbed}}target="_blank"{{end}}><span>{{.Name}}</span></a></li>{{end}}{{end}}</ul></nav><nav id="sub-nav"><div class="social"><a target="_blank" class="twitter external" rel="nofollow" href="//{{.Twitter.Address}}" title="Twitter" aria-label="Twitter">Twitter</a><a target="_blank" class="rss" href="//{{.Domain}}/rss.html" title="RSS 订阅" aria-label="RSS 订阅">RSS</a><a class="search" href="/search.html" title="站内搜索" aria-label="站内搜索">Search</a></div></nav></header></div></div><div class="mid-col"><div class="mid-col-container">{{.LayoutContent}}</div><footer id=footer class=inner>© {{.CopyYear}} - {{.BTitle}}{{if .BeiAn}} - <a target="_blank" rel="nofollow designer" class="external beian" href="https://beian.miit.gov.cn">{{.BeiAn}}</a>{{end}}<br>Powered by <a target=_blank href="//github.com/eiblog/eiblog">Eiblog</a> & <a target=_blank rel="nofollow designer" class=external href="//github.com/deepzz0">Deepzz</a></footer></div></div><input type=hidden id=CURRENT_PAGE value="{{.CurrentPage}}"><input type=hidden id=CDN_DOMAIN value="//{{.Qiniu.Domain}}">{{if .Version}}<script id="ana_js">{{template "ana_js"}}</script><script>L.h('ana_js')</script>{{else}}<script>L.l('ana_js', 1)</script>{{end}}{{if .Version}}<script id="jq_js">{{template "jq_js"}}</script><script>L.h('jq_js')</script>{{else}}<script>L.l('jq_js', 1)</script>{{end}}{{if .Version}}<script id="hl_js">{{template "hl_js"}}</script><script>L.h('hl_js')</script>{{else}}<script>L.l('hl_js', 1)</script>{{end}}{{if .Version}}<script id="blog_js">{{template "blog_js" .}}</script><script>L.h('blog_js')</script>{{else}}<script>L.l('blog_js', 1)</script>{{end}}</body></html>

View File

@@ -0,0 +1 @@
{{define "search"}}<div id="content" class="inner"><article class="post post-search"><h1 class="title">站内搜索</h1><div class="entry-content"><div id="search"><form action="/search.html"><div class="wrapper"><input maxlength="80" placeholder="请输入关键字..." id="keyword" name="q" {{if .Word}}value="{{.Word}}"{{end}} type="search" required></div><input class="submit" type="submit" value="搜索"></form></div><div id="searchResult">{{if .Word}}{{with .SearchResult}}{{if gt (.Hits.Hits|len) 0}}<div class="info">本次搜索共找到结果 {{.Hits.Total}} 条 (用时 {{.Took}} 秒)</div>{{range .Hits.Hits}}<div class="item"><div class="title"><a href="/post/{{.Source.Slug}}.html">{{if .Highlight.Title}}{{str2html (join .Highlight.Title "")}}{{else}}{{.Source.Title}}{{end}}</a></div><div class="desc">{{if .Source.Img}}<div class="img"><img data-src="{{.Source.Img}}?imageView2/1/w/216/h/162"></div>{{end}}<div class="summary"><span class="date">{{dateformat .Source.Date "2006-01-02"}}</span> ... {{str2html (join .Highlight.Content "...")}} ...</div></div></div>{{end}}{{else}}<div class="no-result">没有找到任何结果,请更换查询词试试~</div><div class="item"><div class="title">或者试试 Google 站内搜索:<a target="_blank" href="//www.google.com/#q=site:{{$.Domain}} {{$.Word}}">site:{{$.Domain}} {{$.Word}}</a></div></div>{{end}}{{end}}{{else}}<div class="hot-words">热搜词:{{range .HotWords}}<a href="?q={{.}}">{{.}}</a>{{end}}</div><div class="intro"><p>支持的搜索格式:</p><ol><li>输入关键词全文搜索:<a href="?q=Let's Encrypt">Let's Encrypt</a></li><li>指定时间段搜索:<a href="?q=date:2016">date:2016</a><a href="?q=date:2016-10">date:2016-10</a></li><li>指定标签搜索:<a href="?q=tag:github">tag:github</a><a href="?q=tag:HTTPS">tag:HTTPS</a></li><li>组合搜索:<a href="?q=date:2016 tag:docker">date:2016 tag:docker</a></li></ol></div>{{end}}</div></div></article>{{if or .Prev .Next}}<nav class="page-navi">{{with .Prev}}<a href="?{{html .}}" class="prev">« 上一页</a>{{end}}{{with .Next}}<a href="?{{html .}}" class="next">下一页 »</a>{{end}}</nav>{{end}}</div>{{end}}

View File

@@ -0,0 +1,3 @@
{{define "ana_js"}}
!function(e,n,o){var t=e.screen,a=encodeURIComponent,r=["dt="+a(n.title),"dr="+a(n.referrer),"ul="+(o.language||o.browserLanguage).toLowerCase(),"sd="+t.colorDepth+"-bit","sr="+t.width+"x"+t.height,"_="+ +new Date],i="?"+r.join("&");e.__beacon_img=new Image,e.__beacon_img.src="/beacon.html"+i}(window,document,navigator,location);
{{end}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
<?xml version="1.0"?>
<cross-domain-policy>
<allow-access-from domain="*.{{.Domain}}" />
<allow-access-from domain="*.{{.Host}}" />
</cross-domain-policy>

View File

@@ -0,0 +1,24 @@
<?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://{{.Host}}</link>
<description>{{.SubTitle}}</description>
<atom:link href="https://{{.Host}}/rss.html" rel="self" />
<atom:link href="{{.FeedrURL}}" rel="hub" />
<language>zh-CN</language>
<lastBuildDate>{{.BuildDate}}</lastBuildDate>
{{range .Articles}}
<item>
<title>{{.Title}}</title>
<link>https://{{$.Host}}/post/{{.Slug}}.html</link>
<comments>https://{{$.Host}}/post/{{.Slug}}.html#comments</comments>
<guid>https://{{$.Host}}/post/{{.Slug}}.html</guid>
<description>
<![CDATA[{{imgtonormal .Content}}<p>本文链接:<a href="https://{{$.Host}}/post/{{.Slug}}.html">https://{{$.Host}}/post/{{.Slug}}.html</a><a href="https://{{$.Host}}/post/{{.Slug}}.html#comments">参与评论 »</a></p>]]>
</description>
<pubDate>{{dateformat .CreatedAt "Mon, 02 Jan 2006 15:04:05 -0700"}}</pubDate>
</item>
{{end}}
</channel>
</rss>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
xmlns:moz="http://www.mozilla.org/2006/browser/search/">
<ShortName>{{.BTitle}}</ShortName>
<Description>{{.SubTitle}}</Description>
<InputEncoding>UTF-8</InputEncoding>
<Url type="text/html" method="get" template="https://{{.Host}}/search.html?q={searchTerms}" />
<moz:SearchForm>https://{{.Host}}/search.html</moz:SearchForm>
</OpenSearchDescription>

View File

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

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