mirror of
https://github.com/eiblog/eiblog.git
synced 2026-02-04 13:52:26 +08:00
Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6a2439c54 | ||
|
|
1d54ff3ac5 | ||
|
|
63a4d69209 | ||
|
|
b35d7de58a | ||
|
|
77ea01b7c1 | ||
|
|
5f608b638d | ||
|
|
52da8abceb | ||
|
|
f016b28cb6 | ||
|
|
01b7643ca5 | ||
|
|
375d43761b | ||
|
|
f3e9727947 | ||
|
|
911aa963c7 | ||
|
|
fb66b6871e | ||
|
|
5ae76f243e | ||
|
|
051b034e51 | ||
|
|
27439ecc71 | ||
|
|
d02c838447 | ||
|
|
d17acf5325 | ||
|
|
b278ca377f | ||
|
|
93131441e4 | ||
|
|
ddcc6c2d2e | ||
|
|
ef63ae9598 | ||
|
|
2ed9db5c7b | ||
|
|
06a12bc6f9 | ||
|
|
6524b45751 | ||
|
|
ceb9e2690b | ||
|
|
405fbaf24f | ||
|
|
3245c0e0d3 | ||
|
|
badc62e3f0 | ||
|
|
a5561f257b | ||
|
|
eb37b83ebd | ||
|
|
b2fab703fc | ||
|
|
37deb390d9 | ||
|
|
6fa5088352 | ||
|
|
e023a33786 | ||
|
|
6f818c4b5d | ||
|
|
9ad22fb2d9 | ||
|
|
fc37d5e093 | ||
|
|
61024bfebd | ||
|
|
f20c4a6063 | ||
|
|
c24e6bf7bd | ||
|
|
ade94168d3 | ||
|
|
552d010650 | ||
|
|
1c3106cbb0 | ||
|
|
168937f1b2 | ||
|
|
730cffcb5b | ||
|
|
8c3f1c2aba | ||
|
|
ea375ea76c | ||
|
|
275a6c0c31 | ||
|
|
360204995d | ||
|
|
c9fc0cc75a | ||
|
|
41daaa322e | ||
|
|
894535fbe5 | ||
|
|
6fc5af1b0f | ||
|
|
5ce806a7d7 | ||
|
|
25cb23fdb3 | ||
|
|
a89a1a2bc9 | ||
|
|
93e170f9ac | ||
|
|
59d9a616aa | ||
|
|
2ff0934206 | ||
|
|
cde7cba2f0 | ||
|
|
2be7501afe | ||
|
|
487d35dae2 | ||
|
|
19af9376cb | ||
|
|
3ddd2a0b33 | ||
|
|
ee7523b124 | ||
|
|
cc1dbac1f0 | ||
|
|
04532ba8a6 | ||
|
|
0a2a132b11 | ||
|
|
3ff712d407 | ||
|
|
27162d2205 | ||
|
|
f150974566 | ||
|
|
b94fc825b3 | ||
|
|
d8f0e30285 | ||
|
|
e0a5f0ebca | ||
|
|
c18d9c0bef | ||
|
|
e1ec5cd08a | ||
|
|
5efdd72e58 | ||
|
|
b9470fa14c | ||
|
|
a932d2906d | ||
|
|
3ff5977941 | ||
|
|
da7b726e8d | ||
|
|
3923bc70f1 | ||
|
|
8dc73fd67c | ||
|
|
2825bbfeae | ||
|
|
66811830b0 | ||
|
|
c4bf59ce5d | ||
|
|
6cea283f86 | ||
|
|
3992db49ba | ||
|
|
055c2307cb | ||
|
|
11b0f486cd | ||
|
|
ed0a50b626 | ||
|
|
cf2b2d6d34 | ||
|
|
00456806bb | ||
|
|
54f5289d6b | ||
|
|
1634418a13 | ||
|
|
a84a474504 | ||
|
|
b64cf5985a | ||
|
|
4f9965b6bd | ||
|
|
daffa6c294 | ||
|
|
0bd738438e | ||
|
|
309339492c | ||
|
|
694036c65f | ||
|
|
cafdaac9f4 | ||
|
|
f6956f592f | ||
|
|
2382f76cf6 | ||
|
|
a66a3c0198 | ||
|
|
31c398700e | ||
|
|
9296147a0f | ||
|
|
6932799cba | ||
|
|
b1ff8b59af | ||
|
|
5f047c2c27 | ||
|
|
5d24af11e5 | ||
|
|
d03f327fb4 | ||
|
|
ef9e64469b | ||
|
|
86e7374997 | ||
|
|
4f24b80107 | ||
|
|
2c49a1ec8d | ||
|
|
3eaab0fb1f | ||
|
|
8e6404a90a | ||
|
|
7775ea35a2 | ||
|
|
931d7b0683 | ||
|
|
562f4d86c6 | ||
|
|
4ebbc38cc0 | ||
|
|
9509cd66e6 | ||
|
|
48756a2810 | ||
|
|
ec8297c3f6 |
@@ -1,18 +1,16 @@
|
||||
.git
|
||||
conf
|
||||
vendor
|
||||
setting
|
||||
conf
|
||||
static
|
||||
views
|
||||
docs
|
||||
!static/tzdata
|
||||
static/*.*
|
||||
!static/favicon.ico
|
||||
**/.DS_Store
|
||||
Dockerfile
|
||||
glide.yaml
|
||||
glide.lock
|
||||
*.yml
|
||||
*.md
|
||||
*.go
|
||||
*.sh
|
||||
*.DS_Store
|
||||
.gitignore
|
||||
.dockerignore
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,10 +1,5 @@
|
||||
*.DS_Store
|
||||
**/.DS_Store
|
||||
*.exe
|
||||
vendor
|
||||
vendor/**
|
||||
conf/ssl/domain.*
|
||||
eiblog
|
||||
static/feed.xml
|
||||
static/opensearch.xml
|
||||
static/sitemap.xml
|
||||
|
||||
static/*.*
|
||||
|
||||
26
.travis.yml
26
.travis.yml
@@ -1,37 +1,25 @@
|
||||
sudo: required # 超级权限
|
||||
|
||||
dist: trusty # 在ubuntu:trusty
|
||||
|
||||
language: go # 声明构建语言环境
|
||||
|
||||
go: # 只构建最新版本
|
||||
- tip
|
||||
|
||||
services: # docker环境
|
||||
services: # docker环境
|
||||
- docker
|
||||
|
||||
branches: # 限定项目分支
|
||||
only:
|
||||
- /^v[0-9](\.[0-9]){2}(-rc[1-9])?$/
|
||||
|
||||
before_install:
|
||||
- curl https://glide.sh/get | sh # 安装glide包管理
|
||||
|
||||
install:
|
||||
- go mod download
|
||||
script:
|
||||
- glide up
|
||||
- GOOS=linux GOARCH=amd64 go build # 编译版本
|
||||
- GOOS=linux GOARCH=amd64 CGO_ENABLED=0 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 login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" registry.cn-hangzhou.aliyuncs.com
|
||||
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
||||
|
||||
- docker tag registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:$TRAVIS_TAG
|
||||
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:$TRAVIS_TAG
|
||||
before_deploy:
|
||||
- ./dist.sh
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
|
||||
70
CHANGELOG.md
70
CHANGELOG.md
@@ -1,5 +1,75 @@
|
||||
# Eiblog Changelog
|
||||
|
||||
## v1.4.4 (2018-05-07)
|
||||
* 修复基础评论分钟数计算错误
|
||||
* let's encrypt v2证书内嵌ct,故移除有关ct内容
|
||||
|
||||
## v1.4.3 (2018-02-09)
|
||||
* 修复博客初始化后,about 页面不能够评论 #6
|
||||
* 修复编辑专题,按钮显示“添加专题”错误
|
||||
* 优化“添加文章”从同步改为异步推送:feed,es,disqus。速度显著提升
|
||||
* (**重要*)头像图片从 avatar.jpg 改为 avatar.png(透明)
|
||||
* docker-compose.yml mongodb 去掉端口映射,防止用户将端口暴露至外网
|
||||
* session key 每次重启随机生成等一些细节的修复
|
||||
|
||||
## v1.4.2 (2018-01-25)
|
||||
* fix archive page bug
|
||||
|
||||
## v1.4.1 (2018-01-14)
|
||||
* 修复创建新文章,disqus 不收录bug
|
||||
* 修复创建新文章,归档页面不刷新bug
|
||||
* 修复能够删除关于页面和友情链接页面bug
|
||||
* 修复重复添加文章错误
|
||||
* 注释掉 docker-compose.yml 自动备份内容,请自行解开
|
||||
* 添加当月数大于12,归档页面使用年份归档
|
||||
* 优化代码逻辑
|
||||
|
||||
## v1.4.0 (2018-01-01)
|
||||
* fix 搜索页面 bug
|
||||
* CGO_ENABLED=0 关闭 cgo
|
||||
* 更新Makefile ct log 服务器
|
||||
* 数据库数据终于可以备份了
|
||||
|
||||
## v1.3.4 (2017-11-29)
|
||||
* fix page:admin/write-post autocomplete tag
|
||||
|
||||
## v1.3.3 (2017-11-27)
|
||||
* fix docker image: exec user process caused "no such file or directory"
|
||||
|
||||
## v1.3.2 (2017-11-17)
|
||||
* 修复文章自动保存引起的发布文章不成功的bug
|
||||
|
||||
## v1.3.1 (2017-11-05)
|
||||
* 修复调整 关于、友情链接 创建时间出现文章乱序
|
||||
* 修复评论时间计算错误
|
||||
* 调整acme文件验证路径
|
||||
* 更改七牛SDK包为github包。
|
||||
* 调整七牛配置文件名称,app.yml: kodo -> qiniu,name -> bucket,请提高静态文件版本 staticversion
|
||||
|
||||
## v1.3.0 (2017-07-13)
|
||||
* 更改 app.yml 配置项,将大部分配置归在 general 常规配置下。注意,部署时请先更新 app.yml。
|
||||
* 静态文件采用动态渲染,即用户不再需要管理 view、static 目录。
|
||||
* 通过 acme.sh 使用双证书啦,可到 Makefile 查看相关信息。
|
||||
* 使用 autocert 自动生成证书功能,从此再也不用担心证书过期,移步 [证书更新](https://github.com/eiblog/eiblog/blob/master/docs/autocert.md)。
|
||||
* 开启配置项 enablehttps, 将自动重定向 http 到 https 啦。
|
||||
* disqus.js 文件由配置指定,请看 app.yml 下的 disqus 相关配置。
|
||||
|
||||
## v1.2.0 (2017-06-14)
|
||||
* 更新评论功能,基础评论 0 回复也可评论了。
|
||||
* disqus.js 文件由博主自行更新。
|
||||
* 更正描述 README.md 描述错误 [#4f996](https://github.com/eiblog/eiblog/commit/4f9965b6bdefe087dd0805c1840afcb2752cd155)。
|
||||
* docker 镜像版本化。
|
||||
|
||||
## 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目录
|
||||
* 删除多余注释
|
||||
|
||||
## v1.1.1 (2017-02-07)
|
||||
* 添加文章描述功能。
|
||||
* 修复评论`jQuery`文件引用错误。
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
FROM alpine
|
||||
MAINTAINER deepzz <deepzz.qi@gmail.com>
|
||||
|
||||
RUN apk update
|
||||
RUN apk add ca-certificates
|
||||
RUN apk add --update --no-cache ca-certificates
|
||||
ADD static/tzdata/Shanghai /etc/localtime
|
||||
|
||||
COPY . /eiblog
|
||||
EXPOSE 9000
|
||||
WORKDIR /eiblog
|
||||
ENTRYPOINT ["./eiblog"]
|
||||
CMD ["sh","-c","/eiblog/eiblog"]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Manuel Martínez-Almeida
|
||||
Copyright (c) 2017 deepzz 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
|
||||
|
||||
65
Makefile
Normal file
65
Makefile
Normal file
@@ -0,0 +1,65 @@
|
||||
.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"
|
||||
|
||||
docker_registry?=registry.cn-hangzhou.aliyuncs.com
|
||||
acme?=~/.acme.sh
|
||||
acme.sh?=$(acme)/acme.sh
|
||||
config?=/data/eiblog/conf
|
||||
|
||||
|
||||
test:
|
||||
|
||||
build:
|
||||
@echo "go build..."
|
||||
@CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && \
|
||||
docker build -t $(docker_registry)/deepzz/eiblog:latest .
|
||||
|
||||
deploy:build
|
||||
@docker push $(docker_registry)/deepzz/eiblog:latest
|
||||
|
||||
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) \
|
||||
--renew-hook "$(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 \
|
||||
--renew-hook "$(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
|
||||
|
||||
clean:
|
||||
|
||||
321
README.md
321
README.md
@@ -1,57 +1,28 @@
|
||||
# EiBlog [](https://travis-ci.org/eiblog/eiblog)
|
||||
# EiBlog [](https://travis-ci.org/eiblog/eiblog) [](LICENSE.md) [](https://github.com/eiblog/eiblog/releases)
|
||||
|
||||
> 系统根据[https://imququ.com](https://imququ.com)一系列文章和方向进行搭建,期间获得了QuQu的很大帮助,在此表示感谢。
|
||||
|
||||
用过其它博客系统,不喜欢,不够轻,不够快!自己做过共两款博客系统,完美主义的我(毕竟处女座)也实在是不想再在这件事情上过多纠结了。`Eiblog`应该是一个比较稳定的博客系统,且是博主以后使用的博客系统,稳定性和维护你是不用担心的,唯独该系统部署过程太过复杂,并且不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(明显快,安全),等你体验。
|
||||
用过其它博客系统,不喜欢,不够轻,不够快!自己做过共两款博客系统,完美主义的我(毕竟处女座)也实在是不想再在这件事情上过多纠结了。`Eiblog` 应该是一个比较稳定的博客系统,且是博主以后使用的博客系统,稳定性和维护你是不用担心的,唯独该系统部署过程太过复杂,并且不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(明显快,安全),等你体验。
|
||||
|
||||
<!--more-->
|
||||
|
||||
### 介绍
|
||||
|
||||
整个博客系统涉及到模块如下:
|
||||
|
||||
* 自动更新证书:
|
||||
* 接入 [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`作为博客的站内搜索,尽管占用内存稍高。
|
||||
* `Elasticsearch`,采用 `elasticsearch` 作为博客的站内搜索,尽管占用内存稍高。
|
||||
* `Disqus`,作为博客评论系统,国内大部分被墙,故实现两种评论方式。
|
||||
* `Nginx`,作为反向代理服务器,并做相关`http header`和证书的设置。
|
||||
* `Nginx`,作为反向代理服务器,并做相关 `http header` 和证书的设置。
|
||||
* `Google Analytics`,作为博客系统的数据分析统计工具。
|
||||
* `七牛 CDN`,作为博客系统的静态文件存储,博文的图片附件什么上传至这里。
|
||||
|
||||
相关技术有:
|
||||
### 图片展示
|
||||
|
||||
* `Golang`,博客系统后端采用golang编写,并开源至[Eiblog](https://github.com/eiblog/eiblog)。
|
||||
* `HTML Javascript CSS`,博客系统的前端采用`html`和`jquery`编写,样式采用`CSS`。
|
||||
* `Glide`, golang 编写。作为博客系统的包依赖管理器,其开源地址是[Glide](https://github.com/Masterminds/glide)。
|
||||
* `Docker`,博客系统可 docker 部署,方便,快捷。
|
||||
* `Docker Compose`,博客系统可完全 docker 运行,compose起到很好管理作用。
|
||||
* `SSL 证书`,`https`是未来的趋势,整个博客系统都将围绕着`证书`进行,请事先准备好一张有效的 ssl 证书。
|
||||
* `Travis`,作为博客系统的自动构建工具,自动构建docker镜像并推送到镜像仓库。
|
||||
* `Yaml`,博客系统的配置文件使用`yaml`,请悉知。
|
||||
|
||||
作为博主之心血之作,`Eiblog`实现了什么功能,有什么特点,做了什么优化呢?
|
||||
|
||||
1. 系统目前只有`首页`、`专题`、`归档`、`友链`、`关于`、`搜索`界面。相信已经可以满足大部分用户的需求。
|
||||
2. `.js`、`.css`等静态文件本地存储,小图片 base64 内置到 css 中,不会产生网络所带来的延迟,加速网页访问。版本控制方式,动态更新静态文件。
|
||||
3. 采用谷歌统计,并实现异步(将访问信息发给后端,后端提交给谷歌)统计,加速访问速度。
|
||||
4. 采用直接缓存 markdown 转过的 html 文档的方式,加速后端处理。响应速度均在 3ms 以内,真正极速。
|
||||
5. 通过 Nginx 的配置,开启压缩缩小传输量,服务器传输证书链、开启`Session Resumption`、`Session Ticket`、`OCSP Stapling`等加速证书握手,再次提高速度。
|
||||
6. 文章评论数量(不重要)后端跑定时脚本,定时更新,所以有时评论数是不对的。这样减少了 api 调用,又再次达到加速访问的目的。
|
||||
7. 针对`disqus`被墙原因,实现[Jerry Qu](https://imququ.com)的另类评论方式,保证评论的流畅。
|
||||
8. 开源`Typecho`完整后台系统,全功能`markdown`编辑器,让你体验什么是简洁清爽。
|
||||
9. 博客后台直接对接`七牛 SDK`,实现后台上传文件和删除文件的简单功能。
|
||||
10. 采用`elasticsearch`作为站内搜索,添加`google opensearch`功能,搜索更加自然。
|
||||
|
||||
当然,在信息安全方面也没少下功夫,虽然我们只是一个小小的博客系统。
|
||||
|
||||
1. `CDN`,使用七牛融合CDN,并`https`化,实现全站`https`。七牛可申请免费证书了。
|
||||
2. `CT`,证书透明度检测,提供一个开放的审计和监控系统。可以让任何域名所有者或者 CA 确定证书是否被错误签发或者被恶意使用,从而提高 HTTPS 网站的安全性。
|
||||
3. `OSCP`,在线证书状态协议。用来检验证书合法性的在线查询服务.
|
||||
4. `HSTS`,强制客户端(如浏览器)使用 HTTPS 与服务器创建连接。可以很好的解决 HTTPS 降级攻击。
|
||||
5. `HPKP`,HTTP公钥固定扩展,防范由「伪造或不正当手段获得网站证书」造成的中间人攻击。该功能让我们选择信任哪些`CA`。
|
||||
5. `SSL Protocols`,罗列支持的`TLS`协议,SSLv3被证实是不安全的。
|
||||
6. `SSL dhparam`,迪菲赫尔曼密钥交换。
|
||||
7. `Cipher suite`,罗列服务器支持加密套件。
|
||||
|
||||
可以容易的看到[httpsecurityreport](https://httpsecurityreport.com/?report=deepzz.com)评分`96`,[ssllabs](https://www.ssllabs.com/ssltest/analyze.html?d=deepzz.com&latest)评分`A+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
|
||||
可以容易的看到 [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+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
|
||||
|
||||
相关图片展示:
|
||||

|
||||
@@ -60,251 +31,61 @@
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
> `注`:图片1,图片2是博客界面,图片3是后台界面,图片4是性能展示。
|
||||
|
||||
好了,说了那么多,吹了那么多,我们实际来动手搭建一个`Eiblog`吧。
|
||||
### 极速体验
|
||||
1. 到 [这里](https://github.com/eiblog/eiblog/releases) 下载对应平台 `.tar.gz` 文件。
|
||||
|
||||
### 安装
|
||||
1、`Eiblog`提供多个平台的压缩包下载,可到[Eiblog release](https://github.com/eiblog/eiblog/releases)选择相应版本和平台下载。也可通过:
|
||||
``` sh
|
||||
$ curl -L https://github.com/eiblog/eiblog/releases/download/v1.0.0/eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz > eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz
|
||||
2. 搭建 `MongoDB`(必须)和 `Elasticsearch`(可选)服务,正式部署需要。
|
||||
|
||||
3. 修改 `/etc/hosts` 文件,添加 `MongoDB` 数据库 IP 地址,如:`127.0.0.1 mongodb`。
|
||||
|
||||
4. 执行 `./eiblog`,运行博客系统。看到:
|
||||
```
|
||||
|
||||
2、如果有幸你也是`Gopher`,相信你会亲自动手,你可以通过:
|
||||
``` sh
|
||||
$ go get https://github.com/eiblog/eiblog
|
||||
...
|
||||
...
|
||||
[GIN-debug] Listening and serving HTTP on :9000
|
||||
```
|
||||
进行源码编译二进制文件运行。
|
||||
代表运行成功了。
|
||||
|
||||
3、如果你对`docker`技术也有研究的话,你也可以通过`docker`来安装:
|
||||
``` sh
|
||||
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
||||
默认监听 `HTTP 9000` 端口,后台 `/admin/login`,默认账号密码均为 `deepzz`。更多详细请查阅 [安装部署](https://github.com/eiblog/eiblog/blob/master/docs/install.md) 文档。
|
||||
|
||||
```
|
||||
镜像内部只提供了`eiblog`的二进制文件,因为其它内容定制化的需求过高。所以需要将`conf`、`static`、`views`目录映射出来,后面会具体说到。
|
||||
### 特色功能
|
||||
|
||||
### 本地测试
|
||||
在我们下载好可执行程序之后,我们可以开始本地测试的工作了。
|
||||
作为博主之心血之作,`Eiblog` 实现了什么功能,有什么特点,做了什么优化呢?
|
||||
|
||||
本地测试需要搭建两个服务`mongodb`和`elasticsearch2.4.1`(可选,搜索服务不可用)。
|
||||
1. 系统目前只有 `首页`、`专题`、`归档`、`友链`、`关于`、`搜索` 界面。相信已经可以满足大部分用户的需求。
|
||||
2. `.js`、`.css` 等静态文件本地存储,小图片 base64 内置到 css 中,不会产生网络所带来的延迟,加速网页访问。版本控制方式,动态更新静态文件。
|
||||
3. 采用谷歌统计,并实现异步(将访问信息发给后端,后端提交给谷歌)统计,加速访问速度。
|
||||
4. 采用直接缓存 markdown 转过的 html 文档的方式,加速后端处理。响应速度均在 3ms 以内,真正极速。
|
||||
5. 通过 Nginx 的配置,开启压缩缩小传输量,服务器传输证书链、开启 `Session Resumption`、`Session Ticket`、`OCSP Stapling `等加速证书握手,再次提高速度。
|
||||
* `CDN`,使用七牛融合CDN,并 `https` 化,实现全站 `https`。七牛可申请免费证书了。
|
||||
* `CT`,证书透明度检测,提供一个开放的审计和监控系统。可以让任何域名所有者或者 CA 确定证书是否被错误签发或者被恶意使用,从而提高 HTTPS 网站的安全性。
|
||||
* `OSCP`,在线证书状态协议。用来检验证书合法性的在线查询服务.
|
||||
* `HSTS`,强制客户端(如浏览器)使用 HTTPS 与服务器创建连接。可以很好的解决 HTTPS 降级攻击。
|
||||
* `HPKP`,HTTP 公钥固定扩展,防范由「伪造或不正当手段获得网站证书」造成的中间人攻击。该功能让我们选择信任哪些`CA`。请不要轻易尝试 Nginx 线上运行,因为该配置目前只指定了 Letsencrypt X3 和 TrustAsia G5 证书 pin-sha256。
|
||||
* `SSL Protocols`,罗列支持的 `TLS` 协议,SSLv3 被证实是不安全的。
|
||||
* `SSL dhparam`,迪菲赫尔曼密钥交换。
|
||||
* `Cipher suite`,罗列服务器支持加密套件。
|
||||
6. 文章评论数量(不重要)后端跑定时脚本,定时更新,所以有时评论数是不对的。这样减少了 api 调用,又再次达到加速访问的目的。
|
||||
7. 针对 `disqus` 被墙原因,实现 [Jerry Qu](https://imququ.com) 的另类评论方式,保证评论的流畅。
|
||||
8. 开源 `Typecho` 完整后台系统,全功能 `markdown` 编辑器,让你体验什么是简洁清爽。
|
||||
9. 博客后台直接对接 `七牛 SDK`,实现后台上传文件和删除文件的简单功能。
|
||||
10. 采用 `elasticsearch` 作为站内搜索,添加 `google opensearch` 功能,搜索更加自然。
|
||||
11. 自动备份数据库数据到七牛云。
|
||||
|
||||
`Eiblog`默认会连接`hostname`为`eidb`和`eisearch`,因此你需要将信息填入`/etc/hosts`下。假如你搭建的`mongodb`地址为`127.0.0.1:27017`,`elasticsearch`地址为`192.168.99.100:9200`,如:
|
||||
``` sh
|
||||
$ sudo vi /etc/hosts
|
||||
|
||||
# 在末尾加上两行
|
||||
127.0.0.1 eidb
|
||||
192.168.99.100 eisearch
|
||||
```
|
||||
|
||||
#### MongoDB 搭建
|
||||
1、`MongoDB`搭建,Mac 可通过`brew install mongo`进行安装,其它平台请查询资料。
|
||||
#### Elasticsearch 搭建
|
||||
2、`Elasticsearch`搭建,它的搭建要些许复杂。博主尚未接触如何直接安装,因此建议通过`docker`搭建。需要注意的是 es 自带的分析器对中文分词是不友好的,这里采用了`elasticsearch-analysis-ik`分词器。如果你想了解更多[Github](https://github.com/medcl/elasticsearch-analysis-ik)或则如何实现[博客站内搜索](https://imququ.com/post/elasticsearch.html)。
|
||||
|
||||
* pull 镜像`docker pull elasticsearch:2.4.1`,必需使用该版本。
|
||||
* 添加环境变量`ES_JAVA_OPTS: "-Xms512m -Xmx512m"`,除非你想让你的服务器爆掉。
|
||||
* 映射相关目录:
|
||||
|
||||
```
|
||||
conf/es/config:/usr/share/elasticsearch/config
|
||||
conf/es/plugins:/usr/share/elasticsearch/plugins
|
||||
conf/es/data:/usr/share/elasticsearch/data
|
||||
conf/es/logs:/usr/share/elasticsearch/logs
|
||||
```
|
||||
请将这四个目录映射至`eiblog`下的`conf`目录。如果你想查看更多,请查看`docker-compose.yml`文件。
|
||||
|
||||
总结一下,`docker`运行 es 的命令为:
|
||||
``` sh
|
||||
$ docker run -d --name eisearch \
|
||||
-p 9200:9200 \
|
||||
-e ES_JAVA_OPTS: "-Xms512m -Xmx512m" \
|
||||
-v conf/es/config:/usr/share/elasticsearch/config \
|
||||
-v conf/es/plugins:/usr/share/elasticsearch/plugins \
|
||||
-v conf/es/data:/usr/share/elasticsearch/data \
|
||||
-v conf/es/logs:/usr/share/elasticsearch/logs \
|
||||
elasticsearch:2.4.1
|
||||
```
|
||||
|
||||
之后执行`./eiblog`,咱们的`eiblog`就可以运行起来了。
|
||||
|
||||
通过`127.0.0.1:9000`可以进入博客首页,`127.0.0.1:9000/admin/login`进入后台登陆,账号密码为`eiblog/conf/app.yml`下的`username`和`password`。也就是初始账号密码`deepz`、`deepzz`。
|
||||
|
||||
> `注意`,因为配置`conf/app.yml`均是博主自用配置。有些操作可能(如评论)会评论到我的博客,还请尽量避免,谢谢。
|
||||
|
||||
### 准备部署
|
||||
如果你在感受了该博客的魅力了之后,仍然坚持想要搭建它。那么,恭喜你,获得的一款不想再更换的博客系统。下面,我们跟随步骤对部署流程进一步说明。
|
||||
|
||||
这里只提供`Docker`的相关部署说明。你如果需要其它方式部署,请参考该方式。
|
||||
|
||||
#### 前提准备
|
||||
这里需要准备一些必要的东西,如果你已准备好。请跳过。
|
||||
|
||||
* `一台服务器`。
|
||||
* `一个域名`,国内服务器需备案。
|
||||
* `有效的证书`。一般使用免费的就可以。如:`Let‘s Encrypt`,另外`qcloud`、`七牛`也提供了免费证书的申请,均是全球可信。
|
||||
* `七牛CDN`。博客只设计接入了七牛cdn,相信该CDN服务商不会让你失望。
|
||||
* `Disqus`。作为博客评论系统,你得有翻墙的能力注册到该账号,具体配置我想又可以写一片博客了。简单说需要`shorname`和`public key`。
|
||||
* `Google Analystic`。数据统计分析工具。
|
||||
* `Superfeedr`。加速 RSS 订阅。
|
||||
* `Twitter`。希望你能够有一个twitter账号。
|
||||
|
||||
是不是这么多要求,很费解。其实当初该博客系统只是为个人而设计的,是自己心中想要的那一款。博主些这篇文章不是想要多少人来用该博客,而是希望对那些追求至极的朋友说:你需要这款博客系统。
|
||||
#### 文件准备
|
||||
尽管大多数文件已经准备好。但有些默认的文件需要特别指出来,需要你在 CDN 上写特殊的路径。
|
||||
|
||||
假如你的 CDN 域名为`st.example.com`,那么:
|
||||
|
||||
* `favicon.ico`,其 URL 应该是`st.example.com/static/img/favicon.ico`。故你在 CDN 中的文件名为`static/img/favicon.ico`,以下如是。
|
||||
* `左侧背景图片`,`500*1200`左右,CDN 中文件名:`static/img/bg04.jpg`。如需更改,请在`eiblog/view/st_blog.css`中替换该名称。
|
||||
* `头像`,`160*160~256*256`之间,CDN 文件名:`static/img/avatar.jpg`。另外你需要将该图片 `Base64` 编码后替换掉`eiblog/views/st_blog.css`中合适位置的图片。
|
||||
* `blank.gif`,CDN 文件名:`static/img/blank.gif`。该图片请从[这里](https://st.deepzz.com/static/img/blank.gif)下载并上传至你的 CDN。
|
||||
* `default_avatar.png`,CDN 文件名:`static/img/default_avatar.png`,请从[这里](https://st.deepzz.com/static/img/default_avatar.png)下载并上传至你的 CDN。
|
||||
* `disqus.js`,该文件名是会变的,每次更新如果没有提及就没有改变,更新说明在[这里](https://github.com/eiblog/eiblog/blob/master/CHANGELOG.md)。CDN 文件名格式是:`static/js/name.js`。在我写这篇文章是使用的是:`static/js/disqus_a9d3fd.js`,请从[这里](https://st.deepzz.com/static/js/disqus_a9d3fd.js)下载并上传至你的 CDN。
|
||||
|
||||
> `注意`:本人 CDN 做了防盗链处理,故请将这些资源上传至您的 CDN ,以免静态资源不能访问,请悉知。
|
||||
|
||||
#### 配置说明
|
||||
走到这里,我相信只走到`60%`的路程。放弃还来得及。
|
||||
|
||||
这里会对`eiblog/conf`下的所有文件做说明,希望你做好准备。
|
||||
```
|
||||
├── app.yml # 博客配置文件
|
||||
├── blackip.yml # 博客ip黑名单
|
||||
├── es # elasticsearch配置
|
||||
│ ├── config # 配置文件
|
||||
│ │ ├── analysis # 同义词
|
||||
│ │ ├── elasticsearch.yml # 具体配置
|
||||
│ │ ├── logging.yml # 日志配置
|
||||
│ │ └── scripts # 脚本文件夹
|
||||
│ └── plugins # 插件文件夹
|
||||
│ └── ik1.10.1 # ik分词器
|
||||
├── nginx # nginx配置
|
||||
│ ├── domain # 域名配置,nginx会读区改文件夹下的.conf文件
|
||||
│ │ └── deepzz.conf
|
||||
│ ├── ip.blacklist # nginx ip黑名单
|
||||
│ └── nginx.conf # nginx配置,请替换原有配置
|
||||
├── scts # ct文件
|
||||
│ ├── aviator.sct
|
||||
│ └── digicert.sct
|
||||
├── ssl # 证书文件,具体请看deepzz.conf
|
||||
│ ├── dhparams.pem
|
||||
│ ├── domain.key
|
||||
│ ├── domain.pem
|
||||
│ ├── full_chained.pem
|
||||
│ └── session_ticket.key
|
||||
└── tpl # 模版文件
|
||||
├── feedTpl.xml
|
||||
├── opensearchTpl.xml
|
||||
└── sitemapTpl.xml
|
||||
|
||||
```
|
||||
1、app.yml,整个程序的配置文件,里面已经列出了所有配置项的说明,这里不再阐述。
|
||||
2、blackip.yml,如果没有使用`Nginx`,博客内置`ip`过滤系统。
|
||||
3、`es`全名`elasticsearch`,非常强大的分布式搜索引擎,`github`用的就是它。里面的配置基本不用修改,但`es/analysis/synonym.txt`是同义词,你可以照着已有的随意增加。
|
||||
```
|
||||
├── es
|
||||
│ ├── config
|
||||
│ │ ├── analysis
|
||||
│ │ │ └── synonym.txt #同义词配置
|
||||
│ │ ├── elasticsearch.yml #分词器配置
|
||||
│ │ ├── logging.yml #日志配置
|
||||
│ │ └── scripts #脚本
|
||||
│ └── plugins #中文分词插件
|
||||
│ └── ik1.10.0
|
||||
│
|
||||
```
|
||||
|
||||
> `注意`,scripts文件夹虽然是空的,但必需存在,不然elasticsearch报错。
|
||||
|
||||
4、`nginx`,系统采用`nginx`作为代理(相信博客系统也不会独占一台服务器~)。请使用`nginx.conf`替换原`nginx`的配置。博客系统的配置文件是`domain/deepzz.conf`,或则重命名(只要是满足`*.conf`)。`deepzz.conf`文件里面学问是最多的。或许你想一一弄懂,或许…。
|
||||
|
||||
> 注意本配置需要更新nginx到最新版,openssl更新到1.0.2j,具体请到 Jerry Qu 的[本博客 Nginx 配置之完整篇](https://imququ.com/post/my-nginx-conf.html)查看,了解详情。
|
||||
|
||||
5、`scts`,存放 ct 文件。
|
||||
|
||||
6、`ssl`,这里存放了所有证书相关的内容。
|
||||
```
|
||||
├── dhparams.pem #参见eiblog/conf/nginx/domain/deepzz.conf
|
||||
├── domain.key #证书私钥,一般颁发者处下载
|
||||
├── domain.pem #证书链,一般从证书颁发者那可以直接下载到
|
||||
├── full_chained.pem #参见eiblog/conf/nginx/domain/deepzz.conf
|
||||
└── session_ticket.key #参见eiblog/conf/nginx/domain/deepzz.conf
|
||||
```
|
||||
|
||||
7、`tpl`模版相关,不用修改。
|
||||
|
||||
### 开始部署
|
||||
|
||||
#### docker
|
||||
请确定你已经完成了上面所说的所有步骤,在本地已经测试成功。服务器上`MognoDB`和`Elasticsearch`已经安装并已经运行成功。
|
||||
|
||||
首先,请将本地测试好的`conf`,`static`,`views`文件夹上传至服务器,建议存储到服务器`/data/eiblog`下。
|
||||
``` sh
|
||||
$ tree /data/eiblog -L 1
|
||||
|
||||
├── conf
|
||||
├── static
|
||||
├── views
|
||||
```
|
||||
|
||||
然后,将镜像 PULL 到服务器本地。
|
||||
``` sh
|
||||
# PULL下Eiblog镜像
|
||||
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
||||
```
|
||||
|
||||
最后,执行`docker run`命令,希望你能成功。
|
||||
``` sh
|
||||
$ docker run -d --name eiblog --restart=always \
|
||||
--add-host disqus.com:23.235.33.134 \
|
||||
--link eidb --link eisearch \
|
||||
-p 9000:9000 \
|
||||
-e GODEBUG=netdns=cgo \
|
||||
-v /data/eiblog/logdata:/eiblog/logdata \
|
||||
-v /data/eiblog/conf:/eiblog/conf \
|
||||
-v /data/eiblog/static:/eiblog/static \
|
||||
-v /data/eiblog/views:/eiblog/views \
|
||||
registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
||||
```
|
||||
这里默认`MongDB`和`Elasticsearch`均为`docker`部署,且名称为`eidb`,`eisearch`。
|
||||
|
||||
#### nginx + docker
|
||||
通过`Nginx+docker`部署,是博主推荐的方式。这里采用`Docker Compose`管理我们整个博客系统。
|
||||
|
||||
请确认你已经成功安装好`Nginx`、`docker`、`docker-compose`。Nginx 请一定参照 Jerry Qu 的[Nginx 配置完整篇](https://imququ.com/post/my-nginx-conf.html)。
|
||||
|
||||
首先,请将本地测试好的`conf`,`static`,`views`,`docker-compose.yml`文件夹和文件上传至服务器。前三个文件夹建议存储到服务器`/data/eiblog`下,`docker-compose.yml`存放在你使用方便的地方。
|
||||
|
||||
> 注意`conf/es/config/scripts`空文件夹是否存在
|
||||
|
||||
``` sh
|
||||
$ tree /data/eiblog -L 1
|
||||
|
||||
├── conf
|
||||
├── static
|
||||
├── views
|
||||
|
||||
$ ls ~/
|
||||
|
||||
docker-compose.yml
|
||||
```
|
||||
|
||||
然后,执行:
|
||||
``` sh
|
||||
$ cd ~
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
等待些许时间,成功运行。
|
||||
### 文档
|
||||
|
||||
* [证书更新](https://github.com/eiblog/eiblog/blob/master/docs/autocert.md)
|
||||
* [安装部署](https://github.com/eiblog/eiblog/blob/master/docs/install.md)
|
||||
* [写作需知](https://github.com/eiblog/eiblog/blob/master/docs/writing.md)
|
||||
* [好玩的功能](https://github.com/eiblog/eiblog/blob/master/docs/amusing.md)
|
||||
* [关于备份](https://github.com/eiblog/backup)
|
||||
|
||||
### 成功搭建者博客
|
||||
|
||||
* [https://razeencheng.com/](https://razeencheng.com/) - Razeen's Blog
|
||||
* [https://blog.netcj.com](https://blog.netcj.com) - Razeen's Blog
|
||||
|
||||
如果你的博客使用`Eiblog`搭建,你可以在[这里](https://github.com/eiblog/eiblog/issues/1)提交网址。
|
||||
如果你的博客使用`Eiblog`搭建,你可以在 [这里](https://github.com/eiblog/eiblog/issues/1) 提交网址。
|
||||
|
||||
207
api.go
207
api.go
@@ -4,23 +4,26 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
"github.com/eiblog/utils/logd"
|
||||
"github.com/eiblog/utils/mgo"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
const (
|
||||
// 成功
|
||||
NOTICE_SUCCESS = "success"
|
||||
NOTICE_NOTICE = "notice"
|
||||
NOTICE_ERROR = "error"
|
||||
// 注意
|
||||
NOTICE_NOTICE = "notice"
|
||||
// 错误
|
||||
NOTICE_ERROR = "error"
|
||||
)
|
||||
|
||||
// 全局 API
|
||||
var APIs = make(map[string]func(c *gin.Context))
|
||||
|
||||
func init() {
|
||||
@@ -52,6 +55,7 @@ func init() {
|
||||
APIs["file-delete"] = apiFileDelete
|
||||
}
|
||||
|
||||
// 更新账号信息,Email、PhoneNumber、Address
|
||||
func apiAccount(c *gin.Context) {
|
||||
e := c.PostForm("email")
|
||||
pn := c.PostForm("phoneNumber")
|
||||
@@ -61,8 +65,10 @@ func apiAccount(c *gin.Context) {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
return
|
||||
}
|
||||
err := UpdateAccountField(bson.M{"$set": bson.M{"email": e, "phonen": pn, "address": ad}})
|
||||
|
||||
err := UpdateAccountField(mgo.M{"$set": mgo.M{"email": e, "phonen": pn, "address": ad}})
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
@@ -72,6 +78,7 @@ func apiAccount(c *gin.Context) {
|
||||
responseNotice(c, NOTICE_SUCCESS, "更新成功", "")
|
||||
}
|
||||
|
||||
// 更新博客信息
|
||||
func apiBlog(c *gin.Context) {
|
||||
bn := c.PostForm("blogName")
|
||||
bt := c.PostForm("bTitle")
|
||||
@@ -83,8 +90,12 @@ func apiBlog(c *gin.Context) {
|
||||
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}})
|
||||
|
||||
err := UpdateAccountField(mgo.M{"$set": mgo.M{"blogger.blogname": bn,
|
||||
"blogger.btitle": bt, "blogger.beian": ba, "blogger.subtitle": st,
|
||||
"blogger.seriessay": ss, "blogger.archivessay": as}})
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
@@ -99,6 +110,7 @@ func apiBlog(c *gin.Context) {
|
||||
responseNotice(c, NOTICE_SUCCESS, "更新成功", "")
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
func apiPassword(c *gin.Context) {
|
||||
logd.Debug(c.Request.PostForm.Encode())
|
||||
od := c.PostForm("old")
|
||||
@@ -117,8 +129,10 @@ func apiPassword(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
newPwd := EncryptPasswd(Ei.Username, nw)
|
||||
err := UpdateAccountField(bson.M{"$set": bson.M{"password": newPwd}})
|
||||
|
||||
err := UpdateAccountField(mgo.M{"$set": mgo.M{"password": newPwd}})
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
@@ -126,45 +140,39 @@ func apiPassword(c *gin.Context) {
|
||||
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("参数错误")
|
||||
for _, v := range c.PostFormArray("cid[]") {
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil || int32(i) < setting.Conf.General.StartID {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
return
|
||||
}
|
||||
ids = append(ids, int32(i))
|
||||
}
|
||||
err = DelArticles(ids...)
|
||||
err := DelArticles(ids...)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
// elasticsearch 删除索引
|
||||
|
||||
// elasticsearch
|
||||
err = ElasticDelIndex(ids)
|
||||
if err != nil {
|
||||
return
|
||||
logd.Error(err)
|
||||
}
|
||||
// TODO disqus delete
|
||||
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
|
||||
}
|
||||
|
||||
func apiPostAdd(c *gin.Context) {
|
||||
var err error
|
||||
var do string
|
||||
var cid int
|
||||
var (
|
||||
err error
|
||||
do string
|
||||
cid int
|
||||
)
|
||||
defer func() {
|
||||
switch do {
|
||||
case "auto": // 自动保存
|
||||
@@ -173,29 +181,28 @@ func apiPostAdd(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": SUCCESS, "time": time.Now().Format("15:04:05 PM"), "cid": cid})
|
||||
case "save": // 保存草稿
|
||||
case "save", "publish": // 草稿,发布
|
||||
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
|
||||
uri := "/admin/manage-draft"
|
||||
if do == "publish" {
|
||||
uri = "/admin/manage-posts"
|
||||
}
|
||||
c.Redirect(http.StatusFound, "/admin/manage-posts")
|
||||
c.Redirect(http.StatusFound, uri)
|
||||
}
|
||||
}()
|
||||
|
||||
do = c.PostForm("do") // auto or save or publish
|
||||
slug := c.PostForm("slug")
|
||||
title := c.PostForm("title")
|
||||
text := c.PostForm("text")
|
||||
date := c.PostForm("date")
|
||||
date := CheckDate(c.PostForm("date"))
|
||||
serie := c.PostForm("serie")
|
||||
tag := c.PostForm("tags")
|
||||
update := c.PostForm("update")
|
||||
if title == "" || text == "" || slug == "" {
|
||||
if slug == "" || title == "" || text == "" {
|
||||
err = errors.New("参数错误")
|
||||
return
|
||||
}
|
||||
@@ -208,13 +215,14 @@ func apiPostAdd(c *gin.Context) {
|
||||
Title: title,
|
||||
Content: text,
|
||||
Slug: slug,
|
||||
CreateTime: CheckDate(date),
|
||||
CreateTime: date,
|
||||
IsDraft: do != "publish",
|
||||
Author: Ei.Username,
|
||||
SerieID: serieid,
|
||||
Tags: tags,
|
||||
}
|
||||
cid, err = strconv.Atoi(c.PostForm("cid"))
|
||||
// 新文章
|
||||
if err != nil || cid < 1 {
|
||||
err = AddArticle(artc)
|
||||
if err != nil {
|
||||
@@ -223,58 +231,55 @@ func apiPostAdd(c *gin.Context) {
|
||||
}
|
||||
cid = int(artc.ID)
|
||||
if !artc.IsDraft {
|
||||
ElasticIndex(artc)
|
||||
DoPings(slug)
|
||||
// 异步执行,快
|
||||
go func() {
|
||||
// elastic
|
||||
ElasticIndex(artc)
|
||||
// rss
|
||||
DoPings(slug)
|
||||
// disqus
|
||||
ThreadCreate(artc)
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 旧文章
|
||||
artc.ID = int32(cid)
|
||||
i, a := GetArticle(artc.ID)
|
||||
_, 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)
|
||||
// 数据库更新
|
||||
err = UpdateArticle(mgo.M{"id": artc.ID}, artc)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
}
|
||||
if !artc.IsDraft {
|
||||
Ei.MapArticles[artc.Slug] = artc
|
||||
Ei.Articles = append(Ei.Articles, artc)
|
||||
sort.Sort(Ei.Articles)
|
||||
GenerateExcerptAndRender(artc)
|
||||
// elasticsearch 索引
|
||||
ElasticIndex(artc)
|
||||
DoPings(slug)
|
||||
if artc.ID >= setting.Conf.StartID {
|
||||
ManageTagsArticle(artc, true, ADD)
|
||||
ManageSeriesArticle(artc, true, ADD)
|
||||
ManageArchivesArticle(artc, true, ADD)
|
||||
AddToLinkedList(artc.ID)
|
||||
}
|
||||
ReplaceArticle(a, artc)
|
||||
// 异步执行,快
|
||||
go func() {
|
||||
// elastic
|
||||
ElasticIndex(artc)
|
||||
// rss
|
||||
DoPings(slug)
|
||||
// disqus
|
||||
if a == nil {
|
||||
ThreadCreate(artc)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// 只能逐一删除,专题下不能有文章
|
||||
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[]"] {
|
||||
for _, v := range c.PostFormArray("mid[]") {
|
||||
id, err := strconv.Atoi(v)
|
||||
if err != nil || id < 1 {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
@@ -290,6 +295,7 @@ func apiSerieDelete(c *gin.Context) {
|
||||
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
|
||||
}
|
||||
|
||||
// 添加专题,如果专题有提交 mid 即更新专题
|
||||
func apiSerieAdd(c *gin.Context) {
|
||||
name := c.PostForm("name")
|
||||
slug := c.PostForm("slug")
|
||||
@@ -326,24 +332,15 @@ func apiSerieAdd(c *gin.Context) {
|
||||
responseNotice(c, NOTICE_SUCCESS, "操作成功", "")
|
||||
}
|
||||
|
||||
// 暂未启用
|
||||
// NOTE 排序专题,暂未实现
|
||||
func apiSerieSort(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
v := c.Request.PostForm["mid[]"]
|
||||
v := c.PostFormArray("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[]"] {
|
||||
for _, v := range c.PostFormArray("mid[]") {
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil || i < 1 {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
@@ -358,15 +355,9 @@ func apiDraftDelete(c *gin.Context) {
|
||||
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[]"] {
|
||||
for _, v := range c.PostFormArray("mid[]") {
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil || i < 1 {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
@@ -381,15 +372,9 @@ func apiTrashDelete(c *gin.Context) {
|
||||
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[]"] {
|
||||
for _, v := range c.PostFormArray("mid[]") {
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil || i < 1 {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
@@ -405,6 +390,7 @@ func apiTrashRecover(c *gin.Context) {
|
||||
responseNotice(c, NOTICE_SUCCESS, "恢复成功", "")
|
||||
}
|
||||
|
||||
// 上传文件到 qiniu 云
|
||||
func apiFileUpload(c *gin.Context) {
|
||||
type Size interface {
|
||||
Size() int64
|
||||
@@ -428,7 +414,7 @@ func apiFileUpload(c *gin.Context) {
|
||||
c.String(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
typ := c.Request.Header.Get("Content-Type")
|
||||
typ := header.Header.Get("Content-Type")
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"title": filename,
|
||||
"isImage": typ[:5] == "image",
|
||||
@@ -437,20 +423,19 @@ func apiFileUpload(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// 删除七牛 CDN 文件
|
||||
func apiFileDelete(c *gin.Context) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
}
|
||||
c.String(http.StatusOK, "删掉了吗?鬼知道。。。")
|
||||
}()
|
||||
defer c.String(http.StatusOK, "删掉了吗?鬼知道。。。")
|
||||
|
||||
name := c.PostForm("title")
|
||||
if name == "" {
|
||||
err = errors.New("参数错误")
|
||||
logd.Error("参数错误")
|
||||
return
|
||||
}
|
||||
err = FileDelete(name)
|
||||
err := FileDelete(name)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func responseNotice(c *gin.Context, typ, content, hl string) {
|
||||
|
||||
20
back.go
20
back.go
@@ -3,6 +3,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
@@ -11,11 +12,12 @@ import (
|
||||
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
"github.com/eiblog/utils/logd"
|
||||
"github.com/eiblog/utils/mgo"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// 是否登录
|
||||
func isLogin(c *gin.Context) bool {
|
||||
session := sessions.Default(c)
|
||||
v := session.Get("username")
|
||||
@@ -25,6 +27,7 @@ func isLogin(c *gin.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 登陆过滤
|
||||
func AuthFilter() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if !isLogin(c) {
|
||||
@@ -51,6 +54,7 @@ func HandleLogin(c *gin.Context) {
|
||||
RenderHTMLBack(c, "login.html", gin.H{"BTitle": Ei.BTitle})
|
||||
}
|
||||
|
||||
// 登陆接口
|
||||
func HandleLoginPost(c *gin.Context) {
|
||||
user := c.PostForm("user")
|
||||
pwd := c.PostForm("password")
|
||||
@@ -61,7 +65,7 @@ func HandleLoginPost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if Ei.Username != user || !VerifyPasswd(Ei.Password, user, pwd) {
|
||||
logd.Printf("账号或密码错误 %s, %s", user, pwd)
|
||||
logd.Printf("账号或密码错误 %s, %s\n", user, pwd)
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
@@ -70,12 +74,12 @@ func HandleLoginPost(c *gin.Context) {
|
||||
session.Save()
|
||||
Ei.LoginIP = c.ClientIP()
|
||||
Ei.LoginTime = time.Now()
|
||||
UpdateAccountField(bson.M{"$set": bson.M{"loginip": Ei.LoginIP, "logintime": Ei.LoginTime}})
|
||||
UpdateAccountField(mgo.M{"$set": mgo.M{"loginip": Ei.LoginIP, "logintime": Ei.LoginTime}})
|
||||
c.Redirect(http.StatusFound, "/admin/profile")
|
||||
}
|
||||
|
||||
func GetBack() gin.H {
|
||||
return gin.H{"Author": Ei.Username, "Kodo": setting.Conf.Kodo}
|
||||
return gin.H{"Author": Ei.Username, "Qiniu": setting.Conf.Qiniu}
|
||||
}
|
||||
|
||||
// 个人配置
|
||||
@@ -115,11 +119,13 @@ func HandlePost(c *gin.Context) {
|
||||
for tag, _ := range Ei.Tags {
|
||||
tags = append(tags, T{tag, tag})
|
||||
}
|
||||
h["Tags"] = tags
|
||||
str, _ := json.Marshal(tags)
|
||||
h["Tags"] = string(str)
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "admin-post", h)
|
||||
}
|
||||
|
||||
// 删除草稿
|
||||
func HandleDraftDelete(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Query("cid"))
|
||||
if err != nil || id < 1 {
|
||||
@@ -154,7 +160,7 @@ func HandlePosts(c *gin.Context) {
|
||||
h["Serie"] = se
|
||||
h["KW"] = kw
|
||||
var max int
|
||||
max, h["List"] = PageListBack(se, kw, false, false, pg, setting.Conf.PageSize)
|
||||
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()
|
||||
@@ -184,6 +190,7 @@ func HandleSeries(c *gin.Context) {
|
||||
RenderHTMLBack(c, "admin-series", h)
|
||||
}
|
||||
|
||||
// 编辑专题
|
||||
func HandleSerie(c *gin.Context) {
|
||||
h := GetBack()
|
||||
id, err := strconv.Atoi(c.Query("mid"))
|
||||
@@ -276,6 +283,7 @@ func HandleAPI(c *gin.Context) {
|
||||
api(c)
|
||||
}
|
||||
|
||||
// 渲染 html
|
||||
func RenderHTMLBack(c *gin.Context, name string, data gin.H) {
|
||||
if name == "login.html" {
|
||||
err := Tmpl.ExecuteTemplate(c.Writer, name, data)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "go build..."
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && \
|
||||
|
||||
domain="registry.cn-hangzhou.aliyuncs.com" && \
|
||||
docker build -t $domain/deepzz/eiblog . && \
|
||||
read -p "是否上传到服务器(y/n):" word && \
|
||||
if [ $word = "y" ] ;then
|
||||
docker push $domain/deepzz/eiblog
|
||||
fi
|
||||
7
check.go
7
check.go
@@ -6,25 +6,30 @@ import (
|
||||
"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
|
||||
@@ -32,6 +37,7 @@ func CheckDate(date string) time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// 检查 id
|
||||
func CheckSerieID(sid string) int32 {
|
||||
if id, err := strconv.Atoi(sid); err == nil {
|
||||
return int32(id)
|
||||
@@ -39,6 +45,7 @@ func CheckSerieID(sid string) int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// bool
|
||||
func CheckBool(str string) bool {
|
||||
return str == "true" || str == "1"
|
||||
}
|
||||
|
||||
@@ -2,28 +2,40 @@ package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCheckEmail(t *testing.T) {
|
||||
e := "xx@email.com"
|
||||
e1 := "xxxxemail.com"
|
||||
e2 := "xxx#email.com"
|
||||
emails := []string{
|
||||
"xx@email.com",
|
||||
"xxxxemail.com",
|
||||
"xxx#email.com",
|
||||
}
|
||||
|
||||
t.Log(CheckEmail(e))
|
||||
t.Log(CheckEmail(e1))
|
||||
t.Log(CheckEmail(e2))
|
||||
for i, v := range emails {
|
||||
if i == 0 {
|
||||
assert.True(t, CheckEmail(v))
|
||||
} else {
|
||||
assert.False(t, CheckEmail(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDomain(t *testing.T) {
|
||||
d := "123.com"
|
||||
d1 := "http://123.com"
|
||||
d2 := "https://123.com"
|
||||
d3 := "123#.com"
|
||||
d4 := "123.coooom"
|
||||
domains := []string{
|
||||
"123.com",
|
||||
"http://123.com",
|
||||
"https://123.com",
|
||||
"123#.com",
|
||||
"123.coooom",
|
||||
}
|
||||
|
||||
t.Log(CheckDomain(d))
|
||||
t.Log(CheckDomain(d1))
|
||||
t.Log(CheckDomain(d2))
|
||||
t.Log(CheckDomain(d3))
|
||||
t.Log(CheckDomain(d4))
|
||||
for i, v := range domains {
|
||||
if i > 2 {
|
||||
assert.False(t, CheckDomain(v))
|
||||
} else {
|
||||
assert.True(t, CheckDomain(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
100
conf/app.yml
100
conf/app.yml
@@ -1,56 +1,75 @@
|
||||
# 运行模式 dev or prod
|
||||
runmode: dev
|
||||
# 回收箱保留48小时
|
||||
trash: -48
|
||||
# 定时清理回收箱,%d小时
|
||||
clean: 1
|
||||
# 首页展示文章数量
|
||||
pagenum: 10
|
||||
# 管理界面
|
||||
pagesize: 20
|
||||
# 自动截取预览, 字符数
|
||||
length: 400
|
||||
# 截取预览标识
|
||||
identifier: <!--more-->
|
||||
# 文章描述前缀
|
||||
description: "Desc:"
|
||||
# 起始ID,预留id不时之需, 不用管
|
||||
startid: 11
|
||||
# elasticsearch url
|
||||
searchurl: http://elasticsearch:9200
|
||||
# 评论相关
|
||||
disqus:
|
||||
shortname: deepzz
|
||||
publickey: wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
|
||||
postscount: https://disqus.com/api/3.0/threads/set.json
|
||||
postslist: https://disqus.com/api/3.0/threads/listPosts.json
|
||||
postcreate: https://disqus.com/api/3.0/posts/create.json
|
||||
interval: 5
|
||||
# 静态文件版本
|
||||
staticversion: 1
|
||||
# superfeedr url
|
||||
feedrurl: https://deepzz.superfeedr.com/
|
||||
# 热搜词配置
|
||||
hotwords:
|
||||
- docker
|
||||
- mongodb
|
||||
- curl
|
||||
- dns
|
||||
# ping rpcs 地址
|
||||
pingrpcs:
|
||||
- http://ping.baidu.com/ping/RPC2
|
||||
- http://blogsearch.google.com/ping/RPC2
|
||||
- http://rpc.pingomatic.com/
|
||||
# 常规配置
|
||||
general:
|
||||
# 首页展示文章数量
|
||||
pagenum: 10
|
||||
# 管理界面
|
||||
pagesize: 20
|
||||
# 起始ID,预留id不时之需, 不用管
|
||||
startid: 11
|
||||
# 文章描述前缀
|
||||
descprefix: "Desc:"
|
||||
# 截取预览标识
|
||||
identifier: <!--more-->
|
||||
# 自动截取预览, 字符数
|
||||
length: 400
|
||||
# 回收箱保留48小时
|
||||
trash: -48
|
||||
# 定时清理回收箱,每 %d 小时
|
||||
clean: 1
|
||||
# 评论相关
|
||||
disqus:
|
||||
shortname: xxxxxx
|
||||
publickey: wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
|
||||
accesstoken: 50023908f39f4607957e909b495326af
|
||||
postscount: https://disqus.com/api/3.0/threads/set.json
|
||||
postslist: https://disqus.com/api/3.0/threads/listPosts.json
|
||||
postcreate: https://disqus.com/api/3.0/posts/create.json
|
||||
postapprove: https://disqus.com/api/3.0/posts/approve.json
|
||||
threadcreate: https://disqus.com/api/3.0/threads/create.json
|
||||
# disqus.js 文件名
|
||||
embed: disqus_7d3cf2.js
|
||||
# 获取评论数量间隔
|
||||
interval: 5
|
||||
# 谷歌统计
|
||||
google:
|
||||
tid: UA-77251712-1
|
||||
url: https://www.google-analytics.com/collect
|
||||
tid: UA-xxxxxx-1
|
||||
v: "1"
|
||||
t: pageview
|
||||
# 静态文件版本
|
||||
staticversion: 1
|
||||
# 七牛CDN
|
||||
kodo:
|
||||
name: eiblog
|
||||
qiniu:
|
||||
bucket: eiblog
|
||||
domain: st.deepzz.com
|
||||
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
|
||||
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
|
||||
# 运行模式
|
||||
mode:
|
||||
# you can fix certfile, keyfile, domain
|
||||
# http server
|
||||
enablehttp: true
|
||||
httpport: 9000
|
||||
# https server
|
||||
enablehttps: false
|
||||
httpsport: 443
|
||||
certfile: conf/certs/domain.pem
|
||||
keyfile: conf/certs/domain.key
|
||||
autocert: false
|
||||
httpsport: 9001
|
||||
certfile: conf/ssl/domain.rsa.pem
|
||||
keyfile: conf/ssl/domain.rsa.key
|
||||
domain: deepzz.com
|
||||
# twitter地址: twitter.com/chenqijing2
|
||||
twitter:
|
||||
@@ -58,15 +77,8 @@ twitter:
|
||||
site: chenqijing2
|
||||
image: st.deepzz.com/static/img/avatar.jpg
|
||||
address: twitter.com/chenqijing2
|
||||
# superfeedr url
|
||||
feedrurl: https://deepzz.superfeedr.com/
|
||||
# ping rpcs 地址
|
||||
pingrpcs:
|
||||
- http://ping.baidu.com/ping/RPC2
|
||||
- http://blogsearch.google.com/ping/RPC2
|
||||
- http://rpc.pingomatic.com/
|
||||
|
||||
# 以下数据初始化用,可在后台修改
|
||||
# 数据初始化操作,可到博客后台修改
|
||||
account:
|
||||
# *后台登录用户名
|
||||
username: deepzz
|
||||
@@ -87,5 +99,5 @@ blogger:
|
||||
beian: 蜀 ICP 备 16021362 号
|
||||
# footer显示名称及tab标题: Deepzz's Blog
|
||||
btitle: Deepzz's Blog
|
||||
# 版权声明: 本站使用「<a href="//creativecommons.org/licenses/by/4.0/">署名 4.0 国际</a>」创作共享协议,转载请注明作者及原网址。
|
||||
# 版权声明
|
||||
copyright: 本站使用「<a href="//creativecommons.org/licenses/by/4.0/">署名 4.0 国际</a>」创作共享协议,转载请注明作者及原网址。
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# like 192.168.99.100:true
|
||||
|
||||
0
conf/es/config/scripts/empty.groovy
Normal file
0
conf/es/config/scripts/empty.groovy
Normal file
@@ -1,181 +0,0 @@
|
||||
server {
|
||||
listen 443 ssl http2 fastopen=3 reuseport;
|
||||
|
||||
server_name www.deepzz.com deepzz.com;
|
||||
server_tokens off;
|
||||
|
||||
include /data/eiblog/conf/nginx/ip.blacklist;
|
||||
|
||||
# 现在一般证书是内置的。可以注释该项
|
||||
# https://imququ.com/post/certificate-transparency.html#toc-2
|
||||
# ssl_ct on;
|
||||
# ssl_ct_static_scts /data/eiblog/conf/scts;
|
||||
|
||||
# 中间证书 + 站点证书
|
||||
ssl_certificate /data/eiblog/conf/ssl/domain.pem;
|
||||
|
||||
# 创建 CSR 文件时用的密钥
|
||||
ssl_certificate_key /data/eiblog/conf/ssl/domain.key;
|
||||
|
||||
# openssl dhparam -out dhparams.pem 2048
|
||||
# https://weakdh.org/sysadmin.html
|
||||
ssl_dhparam /data/eiblog/conf/ssl/dhparams.pem;
|
||||
|
||||
# https://github.com/cloudflare/sslconfig/blob/master/conf
|
||||
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||
|
||||
# 如果启用了 RSA + ECDSA 双证书,Cipher Suite 可以参考以下配置:
|
||||
# ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
|
||||
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_session_timeout 1d;
|
||||
|
||||
ssl_session_tickets on;
|
||||
|
||||
# openssl rand 48 > session_ticket.key
|
||||
# 单机部署可以不指定 ssl_session_ticket_key
|
||||
# ssl_session_ticket_key /data/eiblog/conf/ssl/session_ticket.key;
|
||||
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
|
||||
# 根证书 + 中间证书
|
||||
# https://imququ.com/post/why-can-not-turn-on-ocsp-stapling.html
|
||||
ssl_trusted_certificate /data/eiblog/conf/ssl/full_chained.pem;
|
||||
|
||||
resolver 8.8.8.8 114.114.114.114 valid=300s;
|
||||
resolver_timeout 10s;
|
||||
|
||||
access_log /data/eiblog/logdata/nginx.log;
|
||||
|
||||
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
|
||||
return 444;
|
||||
}
|
||||
|
||||
if ($host != 'deepzz.com' ) {
|
||||
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
|
||||
}
|
||||
|
||||
# webmaster 站点验证相关
|
||||
location ~* (robots\.txt|favicon\.ico|crossdomain\.xml|google4c90d18e696bdcf8\.html|BingSiteAuth\.xml)$ {
|
||||
root /data/eiblog/static;
|
||||
expires 1d;
|
||||
}
|
||||
|
||||
location ^~ /static/uploads/ {
|
||||
root /home/jerry/www/imququ.com/www;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
|
||||
set $expires_time max;
|
||||
|
||||
valid_referers blocked none server_names *.qgy18.com *.inoreader.com feedly.com *.feedly.com www.udpwork.com theoldreader.com digg.com *.feiworks.com *.newszeit.com r.mail.qq.com yuedu.163.com *.w3ctech.com;
|
||||
if ($invalid_referer) {
|
||||
set $expires_time -1;
|
||||
return 403;
|
||||
}
|
||||
|
||||
expires $expires_time;
|
||||
}
|
||||
|
||||
location ^~ /static/ {
|
||||
root /data/eiblog;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
expires max;
|
||||
}
|
||||
|
||||
location ^~ /admin/ {
|
||||
proxy_http_version 1.1;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
||||
|
||||
# deny 将完全不允许页面被嵌套,可能会导致一些异常。如果遇到这样的问题,建议改成 SAMEORIGIN
|
||||
# https://imququ.com/post/web-security-and-response-header.html#toc-1
|
||||
add_header X-Frame-Options deny;
|
||||
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host deepzz.com;
|
||||
proxy_set_header X-Real_IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
proxy_pass http://127.0.0.1:9000;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
||||
add_header X-Frame-Options deny;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
# 改deepzz相关的
|
||||
add_header Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob: https:; img-src data: https: https://st.deepzz.com; style-src 'unsafe-inline' https:; child-src https:; connect-src 'self' https://translate.googleapis.com; frame-src https://disqus.com https://www.slideshare.net";
|
||||
# 中间证书证书指纹
|
||||
# https://imququ.com/post/http-public-key-pinning.html
|
||||
add_header Public-Key-Pins 'pin-sha256="lnsM2T/O9/J84sJFdnrpsFp3awZJ+ZZbYpCWhGloaHI="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000; includeSubDomains';
|
||||
add_header Cache-Control no-cache;
|
||||
add_header X-Via Aliyun.QingDao;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
|
||||
proxy_ignore_headers Set-Cookie;
|
||||
|
||||
proxy_hide_header Vary;
|
||||
proxy_hide_header X-Powered-By;
|
||||
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host deepzz.com;
|
||||
proxy_set_header X-Real_IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
proxy_pass http://127.0.0.1:9000;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
server_name www.deepzz.com deepzz.com;
|
||||
server_tokens off;
|
||||
|
||||
access_log /dev/null;
|
||||
|
||||
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
|
||||
return 444;
|
||||
}
|
||||
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
alias /home/jerry/www/challenges/;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
|
||||
}
|
||||
}
|
||||
|
||||
# 原博客,内部做的转发
|
||||
server {
|
||||
server_name blog.deepzz.com;
|
||||
access_log /dev/null;
|
||||
|
||||
location / {
|
||||
rewrite ^/(.*)$ https://blog.deepzz.com/$1 permanent;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
server_name blog.deepzz.com;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000";
|
||||
add_header X-Frame-Options deny;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-Xss-Protection "1; mode=block;";
|
||||
|
||||
|
||||
location / {
|
||||
proxy_pass https://127.0.0.1:9010;
|
||||
}
|
||||
}
|
||||
138
conf/nginx/domain/eiblog.conf
Normal file
138
conf/nginx/domain/eiblog.conf
Normal file
@@ -0,0 +1,138 @@
|
||||
server {
|
||||
listen 443 ssl http2 fastopen=3 reuseport;
|
||||
|
||||
server_name www.deepzz.com deepzz.com;
|
||||
server_tokens off;
|
||||
|
||||
access_log /data/eiblog/logdata/nginx.log;
|
||||
|
||||
# IP黑名单.
|
||||
include /data/eiblog/conf/nginx/ip.blacklist;
|
||||
|
||||
# letsencrypt v2已内置, 忽略.
|
||||
# https://imququ.com/post/certificate-transparency.html#toc-2
|
||||
#ssl_ct on;
|
||||
#ssl_ct_static_scts /data/eiblog/conf/scts/rsa/;
|
||||
#ssl_ct_static_scts /data/eiblog/conf/scts/ecc/;
|
||||
|
||||
# 中间证书 + 根证书.
|
||||
# https://imququ.com/post/why-can-not-turn-on-ocsp-stapling.html
|
||||
ssl_trusted_certificate /data/eiblog/conf/ssl/full_chained.pem;
|
||||
|
||||
# 站点证书 + 中间证书, 私钥.
|
||||
ssl_certificate /data/eiblog/conf/ssl/domain.rsa.pem;
|
||||
ssl_certificate_key /data/eiblog/conf/ssl/domain.rsa.key;
|
||||
# ssl_certificate /data/eiblog/conf/ssl/domain.ecc.pem;
|
||||
# ssl_certificate_key /data/eiblog/conf/ssl/domain.ecc.key;
|
||||
|
||||
# openssl dhparam -out dhparams.pem 2048
|
||||
# https://weakdh.org/sysadmin.html
|
||||
ssl_dhparam /data/eiblog/conf/ssl/dhparams.pem;
|
||||
|
||||
# 单机部署可以不指定.
|
||||
# openssl rand 48 > session_ticket.key
|
||||
# ssl_session_ticket_key /data/eiblog/conf/ssl/session_ticket.key;
|
||||
|
||||
ssl_prefer_server_ciphers on;
|
||||
# https://github.com/cloudflare/sslconfig/blob/master/conf
|
||||
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||
|
||||
# 如果启用 RSA + ECDSA 双证书, Cipher Suite 可以参考以下配置.
|
||||
# ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_tickets on;
|
||||
|
||||
# ssl stapling
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
|
||||
resolver 114.114.114.114 8.8.8.8 valid=300s;
|
||||
resolver_timeout 10s;
|
||||
|
||||
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
|
||||
return 444;
|
||||
}
|
||||
|
||||
if ($host != 'deepzz.com' ) {
|
||||
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
|
||||
}
|
||||
|
||||
# webmaster 站点验证相关.
|
||||
location ~* (google4c90d18e696bdcf8\.html|BingSiteAuth\.xml)$ {
|
||||
root /data/eiblog/static;
|
||||
expires 1d;
|
||||
}
|
||||
|
||||
location ^~ /admin/ {
|
||||
proxy_http_version 1.1;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
||||
|
||||
# deny 将完全不允许页面被嵌套,可能会导致一些异常。如果遇到这样的问题,建议改成 SAMEORIGIN.
|
||||
# https://imququ.com/post/web-security-and-response-header.html#toc-1
|
||||
add_header X-Frame-Options deny;
|
||||
add_header X-Powered-By eiblog/1.3.0;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host deepzz.com;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
proxy_pass http://127.0.0.1:9000;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
||||
add_header X-Frame-Options deny;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
# 改deepzz相关的.
|
||||
add_header Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob: https:; img-src data: https: https://st.deepzz.com; media-src https://st.deepzz.com; style-src 'unsafe-inline' https:; child-src https:; connect-src 'self' https://translate.googleapis.com; frame-src https://disqus.com https://www.slideshare.net";
|
||||
# 中间证书证书指纹, chrome69 将忽略, 用 expect-ct 替代.
|
||||
# https://imququ.com/post/http-public-key-pinning.html
|
||||
#add_header Public-Key-Pins 'pin-sha256="IiSbZ4pMDEyXvtl7Lg8K3FNmJcTAhKUTrB2FQOaAO/s="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000;';
|
||||
# 期望 ct.
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT
|
||||
add_header Expect-CT "max-age=180";
|
||||
add_header Cache-Control no-cache;
|
||||
add_header X-Via Aliyun.QingDao;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Powered-By eiblog/1.3.0;
|
||||
|
||||
proxy_ignore_headers Set-Cookie;
|
||||
proxy_hide_header Vary;
|
||||
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host deepzz.com;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
proxy_pass http://127.0.0.1:9000;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
server_name www.deepzz.com deepzz.com;
|
||||
server_tokens off;
|
||||
|
||||
access_log /dev/null;
|
||||
|
||||
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
|
||||
return 444;
|
||||
}
|
||||
|
||||
# letsencrypt file verify
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
alias /data/eiblog/challenges/;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# example black list
|
||||
#deny 195.154.211.220;
|
||||
@@ -10,19 +10,20 @@ worker_processes 1;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
worker_connections 10240;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
charset UTF-8;
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
charset UTF-8;
|
||||
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
# '$status $body_bytes_sent "$http_referer" '
|
||||
# '"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
#access_log logs/access.log main;
|
||||
access_log off;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
@@ -58,74 +59,13 @@ http {
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
#error_page 404 /404.html;
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
|
||||
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
|
||||
#
|
||||
#location ~ \.php$ {
|
||||
# proxy_pass http://127.0.0.1;
|
||||
#}
|
||||
|
||||
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
|
||||
#
|
||||
#location ~ \.php$ {
|
||||
# root html;
|
||||
# fastcgi_pass 127.0.0.1:9000;
|
||||
# fastcgi_index index.php;
|
||||
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
|
||||
# include fastcgi_params;
|
||||
#}
|
||||
|
||||
# deny access to .htaccess files, if Apache's document root
|
||||
# concurs with nginx's one
|
||||
#
|
||||
#location ~ /\.ht {
|
||||
# deny all;
|
||||
#}
|
||||
}
|
||||
|
||||
|
||||
# another virtual host using mix of IP-, name-, and port-based configuration
|
||||
#
|
||||
#server {
|
||||
# listen 8000;
|
||||
# listen somename:8080;
|
||||
# server_name somename alias another.alias;
|
||||
|
||||
# location / {
|
||||
# root html;
|
||||
# index index.html index.htm;
|
||||
# }
|
||||
#}
|
||||
|
||||
|
||||
# HTTPS server
|
||||
#
|
||||
#server {
|
||||
# listen 443 ssl;
|
||||
# server_name localhost;
|
||||
|
||||
# ssl_certificate cert.pem;
|
||||
# ssl_certificate_key cert.key;
|
||||
|
||||
# ssl_session_cache shared:SSL:1m;
|
||||
# ssl_session_timeout 5m;
|
||||
|
||||
# ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
# ssl_prefer_server_ciphers on;
|
||||
|
||||
# location / {
|
||||
# root html;
|
||||
# index index.html index.htm;
|
||||
# }
|
||||
#}
|
||||
|
||||
include /data/eiblog/conf/nginx/domain/*.conf;
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
4
conf/tpl/crossdomainTpl.xml
Normal file
4
conf/tpl/crossdomainTpl.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<cross-domain-policy>
|
||||
<allow-access-from domain="*.{{.Domain}}" />
|
||||
</cross-domain-policy>
|
||||
@@ -1,21 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>{{.Title}}</title>
|
||||
<link>https://{{.Domain}}</link>
|
||||
<description>{{.SubTitle}}</description>
|
||||
<atom:link href="https://{{.Domain}}/rss.html" rel="self"/>
|
||||
<atom:link href="{{.FeedrURL}}" rel="hub"/>
|
||||
<language>zh-CN</language>
|
||||
<lastBuildDate>{{.BuildDate}}</lastBuildDate>
|
||||
{{range .Artcs}}
|
||||
<item>
|
||||
<title>{{.Title}}</title>
|
||||
<link>https://{{$.Domain}}/post/{{.Slug}}.html</link>
|
||||
<comments>https://{{$.Domain}}/post/{{.Slug}}.html#comments</comments>
|
||||
<guid>https://{{$.Domain}}/post/{{.Slug}}.html</guid>
|
||||
<description><![CDATA[<blockquote>{{.Content}}<p>本文链接:<a href="https://{{$.Domain}}/post/{{.Slug}}.html">https://{{$.Domain}}/post/{{.Slug}}.html</a>,<a href="https://{{$.Domain}}/post/{{.Slug}}.html#comments">参与评论 »</a></p>]]></description>
|
||||
</item>
|
||||
{{end}}
|
||||
</channel>
|
||||
<channel>
|
||||
<title>{{.Title}}</title>
|
||||
<link>https://{{.Domain}}</link>
|
||||
<description>{{.SubTitle}}</description>
|
||||
<atom:link href="https://{{.Domain}}/rss.html" rel="self" />
|
||||
<atom:link href="{{.FeedrURL}}" rel="hub" />
|
||||
<language>zh-CN</language>
|
||||
<lastBuildDate>{{.BuildDate}}</lastBuildDate>
|
||||
{{range .Artcs}}
|
||||
<item>
|
||||
<title>{{.Title}}</title>
|
||||
<link>https://{{$.Domain}}/post/{{.Slug}}.html</link>
|
||||
<comments>https://{{$.Domain}}/post/{{.Slug}}.html#comments</comments>
|
||||
<guid>https://{{$.Domain}}/post/{{.Slug}}.html</guid>
|
||||
<description>
|
||||
<![CDATA[<blockquote>{{.Content}}<p>本文链接:<a href="https://{{$.Domain}}/post/{{.Slug}}.html">https://{{$.Domain}}/post/{{.Slug}}.html</a>,<a href="https://{{$.Domain}}/post/{{.Slug}}.html#comments">参与评论 »</a></p>]]>
|
||||
</description>
|
||||
</item>
|
||||
{{end}}
|
||||
</channel>
|
||||
</rss>
|
||||
|
||||
3
conf/tpl/robotsTpl.xml
Normal file
3
conf/tpl/robotsTpl.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://{{.Domain}}/sitemap.xml
|
||||
435
db.go
435
db.go
@@ -13,11 +13,10 @@ import (
|
||||
"github.com/eiblog/blackfriday"
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
"github.com/eiblog/utils/logd"
|
||||
db "github.com/eiblog/utils/mgo"
|
||||
"gopkg.in/mgo.v2"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"github.com/eiblog/utils/mgo"
|
||||
)
|
||||
|
||||
// 数据库及表名
|
||||
const (
|
||||
DB = "eiblog"
|
||||
COLLECTION_ACCOUNT = "account"
|
||||
@@ -30,6 +29,7 @@ const (
|
||||
DELETE = "delete"
|
||||
)
|
||||
|
||||
// blackfriday 配置
|
||||
const (
|
||||
commonHtmlFlags = 0 |
|
||||
blackfriday.HTML_TOC |
|
||||
@@ -60,44 +60,24 @@ var (
|
||||
|
||||
func init() {
|
||||
// 数据库加索引
|
||||
ms, c := db.Connect(DB, COLLECTION_ACCOUNT)
|
||||
index := mgo.Index{
|
||||
Key: []string{"username"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
}
|
||||
if err := c.EnsureIndex(index); err != nil {
|
||||
err := mgo.Index(DB, COLLECTION_ACCOUNT, []string{"username"})
|
||||
if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
ms.Close()
|
||||
ms, c = db.Connect(DB, COLLECTION_ARTICLE)
|
||||
index = mgo.Index{
|
||||
Key: []string{"id"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
}
|
||||
if err := c.EnsureIndex(index); err != nil {
|
||||
|
||||
err = mgo.Index(DB, COLLECTION_ARTICLE, []string{"id"})
|
||||
if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
index = mgo.Index{
|
||||
Key: []string{"slug"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
}
|
||||
if err := c.EnsureIndex(index); err != nil {
|
||||
|
||||
err = mgo.Index(DB, COLLECTION_ARTICLE, []string{"slug"})
|
||||
if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
ms.Close()
|
||||
// 读取帐号信息
|
||||
Ei = loadAccount()
|
||||
// 获取文章
|
||||
Ei.Articles = loadArticles()
|
||||
loadAccount()
|
||||
// 获取文章数据
|
||||
loadArticles()
|
||||
// 生成markdown文档
|
||||
go generateMarkdown()
|
||||
// 启动定时器
|
||||
@@ -107,12 +87,13 @@ func init() {
|
||||
}
|
||||
|
||||
// 读取或初始化帐号信息
|
||||
func loadAccount() (a *Account) {
|
||||
a = &Account{}
|
||||
err := db.FindOne(DB, COLLECTION_ACCOUNT, bson.M{"username": setting.Conf.Account.Username}, a)
|
||||
func loadAccount() {
|
||||
Ei = &Account{}
|
||||
err := mgo.FindOne(DB, COLLECTION_ACCOUNT, mgo.M{"username": setting.Conf.Account.Username}, Ei)
|
||||
// 初始化用户数据
|
||||
if err == mgo.ErrNotFound {
|
||||
a = &Account{
|
||||
logd.Printf("Initializing account: %s\n", setting.Conf.Account.Username)
|
||||
Ei = &Account{
|
||||
Username: setting.Conf.Account.Username,
|
||||
Password: EncryptPasswd(setting.Conf.Account.Username, setting.Conf.Account.Password),
|
||||
Email: setting.Conf.Account.Email,
|
||||
@@ -120,49 +101,45 @@ func loadAccount() (a *Account) {
|
||||
Address: setting.Conf.Account.Address,
|
||||
CreateTime: time.Now(),
|
||||
}
|
||||
a.BlogName = setting.Conf.Blogger.BlogName
|
||||
a.SubTitle = setting.Conf.Blogger.SubTitle
|
||||
a.BeiAn = setting.Conf.Blogger.BeiAn
|
||||
a.BTitle = setting.Conf.Blogger.BTitle
|
||||
a.Copyright = setting.Conf.Blogger.Copyright
|
||||
err = db.Insert(DB, COLLECTION_ACCOUNT, a)
|
||||
Ei.BlogName = setting.Conf.Blogger.BlogName
|
||||
Ei.SubTitle = setting.Conf.Blogger.SubTitle
|
||||
Ei.BeiAn = setting.Conf.Blogger.BeiAn
|
||||
Ei.BTitle = setting.Conf.Blogger.BTitle
|
||||
Ei.Copyright = setting.Conf.Blogger.Copyright
|
||||
err = mgo.Insert(DB, COLLECTION_ACCOUNT, Ei)
|
||||
generateTopic()
|
||||
} else if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
a.CH = make(chan string, 2)
|
||||
a.MapArticles = make(map[string]*Article)
|
||||
a.Tags = make(map[string]SortArticles)
|
||||
return
|
||||
Ei.CH = make(chan string, 2)
|
||||
Ei.MapArticles = make(map[string]*Article)
|
||||
Ei.Tags = make(map[string]SortArticles)
|
||||
}
|
||||
|
||||
func loadArticles() (artcs SortArticles) {
|
||||
err := db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"isdraft": false, "deletetime": bson.M{"$eq": time.Time{}}}, &artcs)
|
||||
func loadArticles() {
|
||||
err := mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": false, "deletetime": mgo.M{"$eq": time.Time{}}}, &Ei.Articles)
|
||||
if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
sort.Sort(artcs)
|
||||
for i, v := range artcs {
|
||||
sort.Sort(Ei.Articles)
|
||||
for i, v := range Ei.Articles {
|
||||
// 渲染文章
|
||||
GenerateExcerptAndRender(v)
|
||||
Ei.MapArticles[v.Slug] = v
|
||||
// 分析文章
|
||||
if v.ID < setting.Conf.StartID {
|
||||
if v.ID < setting.Conf.General.StartID {
|
||||
continue
|
||||
}
|
||||
if i > 0 {
|
||||
v.Prev = artcs[i-1]
|
||||
v.Prev = Ei.Articles[i-1]
|
||||
}
|
||||
if artcs[i+1].ID >= setting.Conf.StartID {
|
||||
v.Next = artcs[i+1]
|
||||
if Ei.Articles[i+1].ID >= setting.Conf.General.StartID {
|
||||
v.Next = Ei.Articles[i+1]
|
||||
}
|
||||
ManageTagsArticle(v, false, ADD)
|
||||
ManageSeriesArticle(v, false, ADD)
|
||||
ManageArchivesArticle(v, false, ADD)
|
||||
upArticle(v, false)
|
||||
}
|
||||
Ei.CH <- SERIES_MD
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
return
|
||||
}
|
||||
|
||||
// generate series,archive markdown
|
||||
@@ -181,7 +158,8 @@ func generateMarkdown() {
|
||||
buffer.WriteString("\n\n")
|
||||
for _, artc := range serie.Articles {
|
||||
//eg. * [标题一](/post/hello-world.html) <span class="date">(Man 02, 2006)</span>
|
||||
buffer.WriteString("* [" + artc.Title + "](/post/" + artc.Slug + ".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
|
||||
buffer.WriteString("* [" + artc.Title + "](/post/" + artc.Slug +
|
||||
".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
|
||||
}
|
||||
buffer.WriteByte('\n')
|
||||
}
|
||||
@@ -189,15 +167,31 @@ func generateMarkdown() {
|
||||
case ARCHIVE_MD:
|
||||
sort.Sort(Ei.Archives)
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString(Ei.ArchivesSay)
|
||||
buffer.WriteString("\n\n")
|
||||
buffer.WriteString(Ei.ArchivesSay + "\n")
|
||||
|
||||
var (
|
||||
currentYear string
|
||||
gt12Month = len(Ei.Archives) > 12
|
||||
)
|
||||
for _, archive := range Ei.Archives {
|
||||
buffer.WriteString(fmt.Sprintf("### %s", archive.Time.Format("2006年01月")))
|
||||
buffer.WriteString("\n\n")
|
||||
for _, artc := range archive.Articles {
|
||||
buffer.WriteString("* [" + artc.Title + "](/post/" + artc.Slug + ".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
|
||||
if gt12Month {
|
||||
year := archive.Time.Format("2006 年")
|
||||
if currentYear != year {
|
||||
currentYear = year
|
||||
buffer.WriteString(fmt.Sprintf("\n### %s\n\n", archive.Time.Format("2006 年")))
|
||||
}
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf("\n### %s\n\n", archive.Time.Format("2006年1月")))
|
||||
}
|
||||
for i, artc := range archive.Articles {
|
||||
if i == 0 && gt12Month {
|
||||
buffer.WriteString("* *[" + artc.Title + "](/post/" + artc.Slug +
|
||||
".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>*\n")
|
||||
} else {
|
||||
buffer.WriteString("* [" + artc.Title + "](/post/" + artc.Slug +
|
||||
".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
|
||||
}
|
||||
}
|
||||
buffer.WriteByte('\n')
|
||||
}
|
||||
Ei.PageArchives = string(renderPage(buffer.Bytes()))
|
||||
}
|
||||
@@ -207,26 +201,29 @@ func generateMarkdown() {
|
||||
// init account: generate blogroll and about page
|
||||
func generateTopic() {
|
||||
about := &Article{
|
||||
ID: db.NextVal(DB, COUNTER_ARTICLE),
|
||||
ID: mgo.NextVal(DB, COUNTER_ARTICLE),
|
||||
Author: setting.Conf.Account.Username,
|
||||
Title: "关于",
|
||||
Slug: "about",
|
||||
CreateTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
CreateTime: time.Time{},
|
||||
UpdateTime: time.Time{},
|
||||
}
|
||||
// 推送到 disqus
|
||||
go func() { ThreadCreate(about) }()
|
||||
|
||||
blogroll := &Article{
|
||||
ID: db.NextVal(DB, COUNTER_ARTICLE),
|
||||
ID: mgo.NextVal(DB, COUNTER_ARTICLE),
|
||||
Author: setting.Conf.Account.Username,
|
||||
Title: "友情链接",
|
||||
Slug: "blogroll",
|
||||
UpdateTime: time.Now(),
|
||||
CreateTime: time.Now(),
|
||||
CreateTime: time.Time{},
|
||||
UpdateTime: time.Time{},
|
||||
}
|
||||
err := db.Insert(DB, COLLECTION_ARTICLE, blogroll)
|
||||
err := mgo.Insert(DB, COLLECTION_ARTICLE, blogroll)
|
||||
if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
err = db.Insert(DB, COLLECTION_ARTICLE, about)
|
||||
err = mgo.Insert(DB, COLLECTION_ARTICLE, about)
|
||||
if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
@@ -242,7 +239,7 @@ func renderPage(md []byte) []byte {
|
||||
func PageList(p, n int) (prev int, next int, artcs []*Article) {
|
||||
var l int
|
||||
for l = len(Ei.Articles); l > 0; l-- {
|
||||
if Ei.Articles[l-1].ID >= setting.Conf.StartID {
|
||||
if Ei.Articles[l-1].ID >= setting.Conf.General.StartID {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -271,106 +268,20 @@ func PageList(p, n int) (prev int, next int, artcs []*Article) {
|
||||
return
|
||||
}
|
||||
|
||||
func ManageTagsArticle(artc *Article, s bool, do string) {
|
||||
switch do {
|
||||
case ADD:
|
||||
for _, tag := range artc.Tags {
|
||||
Ei.Tags[tag] = append(Ei.Tags[tag], artc)
|
||||
if s {
|
||||
sort.Sort(Ei.Tags[tag])
|
||||
}
|
||||
}
|
||||
case DELETE:
|
||||
for _, tag := range artc.Tags {
|
||||
for i, v := range Ei.Tags[tag] {
|
||||
if v == artc {
|
||||
Ei.Tags[tag] = append(Ei.Tags[tag][0:i], Ei.Tags[tag][i+1:]...)
|
||||
if len(Ei.Tags[tag]) == 0 {
|
||||
delete(Ei.Tags, tag)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ManageSeriesArticle(artc *Article, s bool, do string) {
|
||||
switch do {
|
||||
case ADD:
|
||||
for i, serie := range Ei.Series {
|
||||
if serie.ID == artc.SerieID {
|
||||
Ei.Series[i].Articles = append(Ei.Series[i].Articles, artc)
|
||||
if s {
|
||||
sort.Sort(Ei.Series[i].Articles)
|
||||
Ei.CH <- SERIES_MD
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
case DELETE:
|
||||
for i, serie := range Ei.Series {
|
||||
if serie.ID == artc.SerieID {
|
||||
for j, v := range serie.Articles {
|
||||
if v == artc {
|
||||
Ei.Series[i].Articles = append(Ei.Series[i].Articles[0:j], Ei.Series[i].Articles[j+1:]...)
|
||||
Ei.CH <- SERIES_MD
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ManageArchivesArticle(artc *Article, s bool, do string) {
|
||||
switch do {
|
||||
case ADD:
|
||||
add := false
|
||||
y, m, _ := artc.CreateTime.Date()
|
||||
for i, archive := range Ei.Archives {
|
||||
ay, am, _ := archive.Time.Date()
|
||||
if y == ay && m == am {
|
||||
add = true
|
||||
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles, artc)
|
||||
if s {
|
||||
sort.Sort(Ei.Archives[i].Articles)
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !add {
|
||||
Ei.Archives = append(Ei.Archives, &Archive{Time: artc.CreateTime, Articles: SortArticles{artc}})
|
||||
}
|
||||
case DELETE:
|
||||
for i, archive := range Ei.Archives {
|
||||
ay, am, _ := archive.Time.Date()
|
||||
if y, m, _ := artc.CreateTime.Date(); ay == y && am == m {
|
||||
for j, v := range archive.Articles {
|
||||
if v == artc {
|
||||
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles[0:j], Ei.Archives[i].Articles[j+1:]...)
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染markdown操作和截取摘要操作
|
||||
var reg = regexp.MustCompile(setting.Conf.Identifier)
|
||||
var reg = regexp.MustCompile(setting.Conf.General.Identifier)
|
||||
|
||||
// header
|
||||
var regH = regexp.MustCompile("</nav></div>")
|
||||
|
||||
func GenerateExcerptAndRender(artc *Article) {
|
||||
if strings.HasPrefix(artc.Content, setting.Conf.Description) {
|
||||
if strings.HasPrefix(artc.Content, setting.Conf.General.DescPrefix) {
|
||||
index := strings.Index(artc.Content, "\r\n")
|
||||
artc.Desc = IgnoreHtmlTag(artc.Content[len(setting.Conf.Description):index])
|
||||
artc.Desc = IgnoreHtmlTag(artc.Content[len(setting.Conf.General.DescPrefix):index])
|
||||
artc.Content = artc.Content[index:]
|
||||
}
|
||||
|
||||
// 查找目录
|
||||
content := renderPage([]byte(artc.Content))
|
||||
index := regH.FindIndex(content)
|
||||
if index != nil {
|
||||
@@ -384,7 +295,7 @@ func GenerateExcerptAndRender(artc *Article) {
|
||||
artc.Excerpt = IgnoreHtmlTag(artc.Content[0:index[0]])
|
||||
} else {
|
||||
uc := []rune(artc.Content)
|
||||
length := setting.Conf.Length
|
||||
length := setting.Conf.General.Length
|
||||
if len(uc) < length {
|
||||
length = len(uc)
|
||||
}
|
||||
@@ -394,44 +305,152 @@ func GenerateExcerptAndRender(artc *Article) {
|
||||
|
||||
// 读取草稿箱
|
||||
func LoadDraft() (artcs SortArticles, err error) {
|
||||
err = db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"isdraft": true}, &artcs)
|
||||
err = mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": true}, &artcs)
|
||||
sort.Sort(artcs)
|
||||
return
|
||||
}
|
||||
|
||||
// 读取回收箱
|
||||
func LoadTrash() (artcs SortArticles, err error) {
|
||||
err = db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"deletetime": bson.M{"$ne": time.Time{}}}, &artcs)
|
||||
err = mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"deletetime": mgo.M{"$ne": time.Time{}}}, &artcs)
|
||||
sort.Sort(artcs)
|
||||
return
|
||||
}
|
||||
|
||||
// 添加文章到tag、serie、archive
|
||||
func upArticle(artc *Article, needSort bool) {
|
||||
// tag
|
||||
for _, tag := range artc.Tags {
|
||||
Ei.Tags[tag] = append(Ei.Tags[tag], artc)
|
||||
if needSort {
|
||||
sort.Sort(Ei.Tags[tag])
|
||||
}
|
||||
}
|
||||
// serie
|
||||
for i, serie := range Ei.Series {
|
||||
if serie.ID == artc.SerieID {
|
||||
Ei.Series[i].Articles = append(Ei.Series[i].Articles, artc)
|
||||
if needSort {
|
||||
sort.Sort(Ei.Series[i].Articles)
|
||||
Ei.CH <- SERIES_MD
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// archive
|
||||
y, m, _ := artc.CreateTime.Date()
|
||||
for i, archive := range Ei.Archives {
|
||||
if ay, am, _ := archive.Time.Date(); y == ay && m == am {
|
||||
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles, artc)
|
||||
if needSort {
|
||||
sort.Sort(Ei.Archives[i].Articles)
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
Ei.Archives = append(Ei.Archives, &Archive{Time: artc.CreateTime,
|
||||
Articles: SortArticles{artc}})
|
||||
if needSort {
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文章从tag、serie、archive
|
||||
func dropArticle(artc *Article) {
|
||||
// tag
|
||||
for _, tag := range artc.Tags {
|
||||
for i, v := range Ei.Tags[tag] {
|
||||
if v == artc {
|
||||
Ei.Tags[tag] = append(Ei.Tags[tag][0:i], Ei.Tags[tag][i+1:]...)
|
||||
if len(Ei.Tags[tag]) == 0 {
|
||||
delete(Ei.Tags, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// serie
|
||||
for i, serie := range Ei.Series {
|
||||
if serie.ID == artc.SerieID {
|
||||
for j, v := range serie.Articles {
|
||||
if v == artc {
|
||||
Ei.Series[i].Articles = append(Ei.Series[i].Articles[0:j],
|
||||
Ei.Series[i].Articles[j+1:]...)
|
||||
Ei.CH <- SERIES_MD
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// archive
|
||||
for i, archive := range Ei.Archives {
|
||||
ay, am, _ := archive.Time.Date()
|
||||
if y, m, _ := artc.CreateTime.Date(); ay == y && am == m {
|
||||
for j, v := range archive.Articles {
|
||||
if v == artc {
|
||||
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles[0:j],
|
||||
Ei.Archives[i].Articles[j+1:]...)
|
||||
if len(Ei.Archives[i].Articles) == 0 {
|
||||
Ei.Archives = append(Ei.Archives[:i], Ei.Archives[i+1:]...)
|
||||
}
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 替换文章
|
||||
func ReplaceArticle(oldArtc *Article, newArtc *Article) {
|
||||
Ei.MapArticles[newArtc.Slug] = newArtc
|
||||
GenerateExcerptAndRender(newArtc)
|
||||
if newArtc.ID < setting.Conf.General.StartID {
|
||||
return
|
||||
}
|
||||
if oldArtc != nil {
|
||||
i, artc := GetArticle(oldArtc.ID)
|
||||
DelFromLinkedList(artc)
|
||||
Ei.Articles = append(Ei.Articles[:i], Ei.Articles[i+1:]...)
|
||||
|
||||
dropArticle(oldArtc)
|
||||
}
|
||||
|
||||
Ei.Articles = append(Ei.Articles, newArtc)
|
||||
sort.Sort(Ei.Articles)
|
||||
AddToLinkedList(newArtc.ID)
|
||||
|
||||
upArticle(newArtc, true)
|
||||
}
|
||||
|
||||
// 添加文章
|
||||
func AddArticle(artc *Article) error {
|
||||
// 分配ID, 占位至起始id
|
||||
for {
|
||||
if id := db.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.StartID {
|
||||
if id := mgo.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.General.StartID {
|
||||
continue
|
||||
} else {
|
||||
artc.ID = id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err := mgo.Insert(DB, COLLECTION_ARTICLE, artc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 正式发布文章
|
||||
if !artc.IsDraft {
|
||||
defer GenerateExcerptAndRender(artc)
|
||||
Ei.MapArticles[artc.Slug] = artc
|
||||
Ei.Articles = append([]*Article{artc}, Ei.Articles...)
|
||||
sort.Sort(Ei.Articles)
|
||||
AddToLinkedList(artc.ID)
|
||||
ManageTagsArticle(artc, true, ADD)
|
||||
ManageSeriesArticle(artc, true, ADD)
|
||||
ManageArchivesArticle(artc, true, ADD)
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
if artc.SerieID > 0 {
|
||||
Ei.CH <- SERIES_MD
|
||||
}
|
||||
|
||||
upArticle(artc, true)
|
||||
}
|
||||
return db.Insert(DB, COLLECTION_ARTICLE, artc)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除文章,移入回收箱
|
||||
@@ -443,20 +462,17 @@ func DelArticles(ids ...int32) error {
|
||||
DelFromLinkedList(artc)
|
||||
Ei.Articles = append(Ei.Articles[:i], Ei.Articles[i+1:]...)
|
||||
delete(Ei.MapArticles, artc.Slug)
|
||||
ManageTagsArticle(artc, false, DELETE)
|
||||
ManageSeriesArticle(artc, false, DELETE)
|
||||
ManageArchivesArticle(artc, false, DELETE)
|
||||
err := UpdateArticle(bson.M{"id": id}, bson.M{"$set": bson.M{"deletetime": time.Now()}})
|
||||
|
||||
err := UpdateArticle(mgo.M{"id": id}, mgo.M{"$set": mgo.M{"deletetime": time.Now()}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
artc = nil
|
||||
dropArticle(artc)
|
||||
}
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
Ei.CH <- SERIES_MD
|
||||
return nil
|
||||
}
|
||||
|
||||
// 从链表里删除文章
|
||||
func DelFromLinkedList(artc *Article) {
|
||||
if artc.Prev == nil && artc.Next != nil {
|
||||
artc.Next.Prev = nil
|
||||
@@ -468,12 +484,13 @@ func DelFromLinkedList(artc *Article) {
|
||||
}
|
||||
}
|
||||
|
||||
// 将文章添加到链表
|
||||
func AddToLinkedList(id int32) {
|
||||
i, artc := GetArticle(id)
|
||||
if i == 0 && Ei.Articles[i+1].ID >= setting.Conf.StartID {
|
||||
if i == 0 && Ei.Articles[i+1].ID >= setting.Conf.General.StartID {
|
||||
artc.Next = Ei.Articles[i+1]
|
||||
Ei.Articles[i+1].Prev = artc
|
||||
} else if i > 0 && Ei.Articles[i-1].ID >= setting.Conf.StartID {
|
||||
} else if i > 0 && Ei.Articles[i-1].ID >= setting.Conf.General.StartID {
|
||||
artc.Prev = Ei.Articles[i-1]
|
||||
if Ei.Articles[i-1].Next != nil {
|
||||
artc.Next = Ei.Articles[i-1].Next
|
||||
@@ -495,37 +512,39 @@ func GetArticle(id int32) (int, *Article) {
|
||||
|
||||
// 定时清除回收箱文章
|
||||
func timer() {
|
||||
delT := time.NewTicker(time.Duration(setting.Conf.Clean) * time.Hour)
|
||||
delT := time.NewTicker(time.Duration(setting.Conf.General.Clean) * time.Hour)
|
||||
for {
|
||||
<-delT.C
|
||||
db.Remove(DB, COLLECTION_ARTICLE, bson.M{"deletetime": bson.M{"$gt": time.Time{}, "$lt": time.Now().Add(time.Duration(setting.Conf.Trash) * time.Hour)}})
|
||||
mgo.Remove(DB, COLLECTION_ARTICLE, mgo.M{"deletetime": mgo.M{"$gt": time.Time{},
|
||||
"$lt": time.Now().Add(time.Duration(setting.Conf.General.Trash) * time.Hour)}})
|
||||
}
|
||||
}
|
||||
|
||||
// 操作帐号字段
|
||||
func UpdateAccountField(M bson.M) error {
|
||||
return db.Update(DB, COLLECTION_ACCOUNT, bson.M{"username": Ei.Username}, M)
|
||||
func UpdateAccountField(M mgo.M) error {
|
||||
return mgo.Update(DB, COLLECTION_ACCOUNT, mgo.M{"username": Ei.Username}, M)
|
||||
}
|
||||
|
||||
// 删除草稿箱或回收箱,永久删除
|
||||
func RemoveArticle(id int32) error {
|
||||
return db.Remove(DB, COLLECTION_ARTICLE, bson.M{"id": id})
|
||||
return mgo.Remove(DB, COLLECTION_ARTICLE, mgo.M{"id": id})
|
||||
}
|
||||
|
||||
// 恢复删除文章到草稿箱
|
||||
func RecoverArticle(id int32) error {
|
||||
return db.Update(DB, COLLECTION_ARTICLE, bson.M{"id": id}, bson.M{"$set": bson.M{"deletetime": time.Time{}, "isdraft": true}})
|
||||
return mgo.Update(DB, COLLECTION_ARTICLE, mgo.M{"id": id},
|
||||
mgo.M{"$set": mgo.M{"deletetime": time.Time{}, "isdraft": true}})
|
||||
}
|
||||
|
||||
// 更新文章
|
||||
func UpdateArticle(query, update interface{}) error {
|
||||
return db.Update(DB, COLLECTION_ARTICLE, query, update)
|
||||
return mgo.Update(DB, COLLECTION_ARTICLE, query, update)
|
||||
}
|
||||
|
||||
// 编辑文档
|
||||
func QueryArticle(id int32) *Article {
|
||||
artc := &Article{}
|
||||
if err := db.FindOne(DB, COLLECTION_ARTICLE, bson.M{"id": id}, artc); err != nil {
|
||||
if err := mgo.FindOne(DB, COLLECTION_ARTICLE, mgo.M{"id": id}, artc); err != nil {
|
||||
return nil
|
||||
}
|
||||
return artc
|
||||
@@ -533,17 +552,18 @@ func QueryArticle(id int32) *Article {
|
||||
|
||||
// 添加专题
|
||||
func AddSerie(name, slug, desc string) error {
|
||||
serie := &Serie{db.NextVal(DB, COUNTER_SERIE), name, slug, desc, time.Now(), nil}
|
||||
serie := &Serie{mgo.NextVal(DB, COUNTER_SERIE), name, slug, desc, time.Now(), nil}
|
||||
Ei.Series = append(Ei.Series, serie)
|
||||
sort.Sort(Ei.Series)
|
||||
Ei.CH <- SERIES_MD
|
||||
return UpdateAccountField(bson.M{"$addToSet": bson.M{"blogger.series": serie}})
|
||||
return UpdateAccountField(mgo.M{"$addToSet": mgo.M{"blogger.series": serie}})
|
||||
}
|
||||
|
||||
// 更新专题
|
||||
func UpdateSerie(serie *Serie) error {
|
||||
Ei.CH <- SERIES_MD
|
||||
return db.Update(DB, COLLECTION_ACCOUNT, bson.M{"username": Ei.Username, "blogger.series.id": serie.ID}, bson.M{"$set": bson.M{"blogger.series.$": serie}})
|
||||
return mgo.Update(DB, COLLECTION_ACCOUNT, mgo.M{"username": Ei.Username,
|
||||
"blogger.series.id": serie.ID}, mgo.M{"$set": mgo.M{"blogger.series.$": serie}})
|
||||
}
|
||||
|
||||
// 删除专题
|
||||
@@ -553,7 +573,7 @@ func DelSerie(id int32) error {
|
||||
if len(serie.Articles) > 0 {
|
||||
return fmt.Errorf("请删除该专题下的所有文章")
|
||||
}
|
||||
err := UpdateAccountField(bson.M{"$pull": bson.M{"blogger.series": bson.M{"id": id}}})
|
||||
err := UpdateAccountField(mgo.M{"$pull": mgo.M{"blogger.series": mgo.M{"id": id}}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -575,25 +595,26 @@ func QuerySerie(id int32) *Serie {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 后台分页
|
||||
func PageListBack(se int, kw string, draft, del bool, p, n int) (max int, artcs []*Article) {
|
||||
M := bson.M{}
|
||||
M := mgo.M{}
|
||||
if draft {
|
||||
M["isdraft"] = true
|
||||
} else if del {
|
||||
M["deletetime"] = bson.M{"$ne": time.Time{}}
|
||||
M["deletetime"] = mgo.M{"$ne": time.Time{}}
|
||||
} else {
|
||||
M["isdraft"] = false
|
||||
M["deletetime"] = bson.M{"$eq": time.Time{}}
|
||||
M["deletetime"] = mgo.M{"$eq": time.Time{}}
|
||||
if se > 0 {
|
||||
M["serieid"] = se
|
||||
}
|
||||
if kw != "" {
|
||||
M["title"] = bson.M{"$regex": kw, "$options": "$i"}
|
||||
M["title"] = mgo.M{"$regex": kw, "$options": "$i"}
|
||||
}
|
||||
}
|
||||
ms, c := db.Connect(DB, COLLECTION_ARTICLE)
|
||||
ms, c := mgo.Connect(DB, COLLECTION_ARTICLE)
|
||||
defer ms.Close()
|
||||
err := c.Find(M).Select(bson.M{"content": 0}).Sort("-createtime").Limit(n).Skip((p - 1) * n).All(&artcs)
|
||||
err := c.Find(M).Select(mgo.M{"content": 0}).Sort("-createtime").Limit(n).Skip((p - 1) * n).All(&artcs)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
}
|
||||
|
||||
18
db_test.go
18
db_test.go
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -42,3 +43,20 @@ func TestAddSerie(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderPage(t *testing.T) {
|
||||
data := []byte(`<ul class="links ssl">
|
||||
<li><a href="https://yryz.net/">一人游走</a><span class="date">「不错的小伙子」</span></li>
|
||||
<li><a href="https://hsulei.com/">Leo同学</a><span class="date">「小伙子,该干活了」</span></li>
|
||||
<li><a href="https://razeencheng.com/">razeen同学</a><span class="date">「Stay hungry. Stay foolish.」</span></li>
|
||||
</ul>
|
||||
|
||||
<ul class="links">
|
||||
<li><a href="http://blog.mirreal.net/">Mirreal Ellison</a><span class="date">「kissing the fire」</span></li>
|
||||
</ul>`)
|
||||
|
||||
t.Log(IgnoreHtmlTag(string(data)))
|
||||
data = renderPage(data)
|
||||
|
||||
t.Log(template.HTML(string(data)))
|
||||
}
|
||||
|
||||
306
disqus.go
306
disqus.go
@@ -4,182 +4,298 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/deepzz0/logd"
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
"github.com/eiblog/utils/logd"
|
||||
)
|
||||
|
||||
type result struct {
|
||||
var ErrDisqusConfig = errors.New("disqus config incorrect")
|
||||
|
||||
func correctDisqusConfig() bool {
|
||||
return setting.Conf.Disqus.PostsCount != "" &&
|
||||
setting.Conf.Disqus.PublicKey != "" &&
|
||||
setting.Conf.Disqus.ShortName != ""
|
||||
}
|
||||
|
||||
// 定时获取所有文章评论数量
|
||||
type postsCountResp struct {
|
||||
Code int
|
||||
Response []struct {
|
||||
Id string
|
||||
Posts int
|
||||
Identifiers []string
|
||||
}
|
||||
}
|
||||
|
||||
func PostsCount() {
|
||||
if setting.Conf.Disqus.PostsCount == "" || setting.Conf.Disqus.PublicKey == "" || setting.Conf.Disqus.ShortName == "" {
|
||||
return
|
||||
func PostsCount() error {
|
||||
if !correctDisqusConfig() {
|
||||
return ErrDisqusConfig
|
||||
}
|
||||
baseUrl := setting.Conf.Disqus.PostsCount +
|
||||
"?api_key=" + setting.Conf.Disqus.PublicKey +
|
||||
"&forum=" + setting.Conf.Disqus.ShortName + "&"
|
||||
|
||||
time.AfterFunc(time.Duration(setting.Conf.Disqus.Interval)*time.Hour, func() {
|
||||
err := PostsCount()
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", setting.Conf.Disqus.PublicKey)
|
||||
vals.Set("forum", setting.Conf.Disqus.ShortName)
|
||||
|
||||
var count, index int
|
||||
for index < len(Ei.Articles) {
|
||||
logd.Debugf("count=====%d, index=======%d, length=======%d, bool=========%t\n", count, index, len(Ei.Articles), index < len(Ei.Articles) && count < 50)
|
||||
var threads []string
|
||||
for ; index < len(Ei.Articles) && count < 50; index++ {
|
||||
artc := Ei.Articles[index]
|
||||
threads = append(threads, fmt.Sprintf("thread:ident=post-%s", artc.Slug))
|
||||
vals.Add("thread:ident", "post-"+artc.Slug)
|
||||
count++
|
||||
}
|
||||
count = 0
|
||||
url := baseUrl + strings.Join(threads, "&")
|
||||
resp, err := http.Get(url)
|
||||
resp, err := http.Get(setting.Conf.Disqus.PostsCount + "?" + vals.Encode())
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
break
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
break
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logd.Error(string(b))
|
||||
break
|
||||
return errors.New(string(b))
|
||||
}
|
||||
rst := result{}
|
||||
err = json.Unmarshal(b, &rst)
|
||||
|
||||
result := &postsCountResp{}
|
||||
err = json.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
break
|
||||
return err
|
||||
}
|
||||
for _, v := range rst.Response {
|
||||
for _, v := range result.Response {
|
||||
i := strings.Index(v.Identifiers[0], "-")
|
||||
artc := Ei.MapArticles[v.Identifiers[0][i+1:]]
|
||||
if artc != nil {
|
||||
artc.Count = v.Posts
|
||||
artc.Thread = v.Id
|
||||
}
|
||||
}
|
||||
}
|
||||
time.AfterFunc(time.Duration(setting.Conf.Disqus.Interval)*time.Hour, PostsCount)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type postsList struct {
|
||||
// 获取文章评论列表
|
||||
type postsListResp struct {
|
||||
Cursor struct {
|
||||
HasNext bool
|
||||
Next string
|
||||
}
|
||||
Code int
|
||||
Response []struct {
|
||||
Parent int
|
||||
Id string
|
||||
CreatedAt string
|
||||
Message string
|
||||
Author struct {
|
||||
Name string
|
||||
ProfileUrl string
|
||||
Avatar struct {
|
||||
Cache string
|
||||
}
|
||||
}
|
||||
Thread string
|
||||
}
|
||||
Response []postDetail
|
||||
}
|
||||
|
||||
func PostsList(slug, cursor string) *postsList {
|
||||
if setting.Conf.Disqus.PostsList == "" || setting.Conf.Disqus.PublicKey == "" || setting.Conf.Disqus.ShortName == "" {
|
||||
return nil
|
||||
type postDetail struct {
|
||||
Parent int
|
||||
Id string
|
||||
CreatedAt string
|
||||
Message string
|
||||
IsDeleted bool
|
||||
Author struct {
|
||||
Name string
|
||||
ProfileUrl string
|
||||
Avatar struct {
|
||||
Cache string
|
||||
}
|
||||
}
|
||||
url := setting.Conf.Disqus.PostsList + "?limit=50&api_key=" +
|
||||
setting.Conf.Disqus.PublicKey + "&forum=" + setting.Conf.Disqus.ShortName +
|
||||
"&cursor=" + cursor + "&thread:ident=post-" + slug
|
||||
resp, err := http.Get(url)
|
||||
Thread string
|
||||
}
|
||||
|
||||
func PostsList(slug, cursor string) (*postsListResp, error) {
|
||||
if !correctDisqusConfig() {
|
||||
return nil, ErrDisqusConfig
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", setting.Conf.Disqus.PublicKey)
|
||||
vals.Set("forum", setting.Conf.Disqus.ShortName)
|
||||
vals.Set("thread:ident", "post-"+slug)
|
||||
vals.Set("cursor", cursor)
|
||||
vals.Set("limit", "50")
|
||||
|
||||
resp, err := http.Get(setting.Conf.Disqus.PostsList + "?" + vals.Encode())
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logd.Error(string(b))
|
||||
return nil
|
||||
return nil, errors.New(string(b))
|
||||
}
|
||||
pl := &postsList{}
|
||||
err = json.Unmarshal(b, pl)
|
||||
|
||||
result := &postsListResp{}
|
||||
err = json.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
return pl
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type PostCreate struct {
|
||||
Message string `json:"message"`
|
||||
Parent string `json:"parent"`
|
||||
Thread string `json:"thread"`
|
||||
AuthorEmail string `json:"author_email"`
|
||||
AuthorName string `json:"autor_name"`
|
||||
IpAddress string `json:"ip_address"`
|
||||
Identifier string `json:"identifier"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
type PostComment struct {
|
||||
Message string
|
||||
Parent string
|
||||
Thread string
|
||||
AuthorEmail string
|
||||
AuthorName string
|
||||
IpAddress string
|
||||
Identifier string
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
type PostResponse struct {
|
||||
Code int `json:"code"`
|
||||
Response struct {
|
||||
Id string `json:"id"`
|
||||
} `json:"response"`
|
||||
type postCreateResp struct {
|
||||
Code int
|
||||
Response postDetail
|
||||
}
|
||||
|
||||
func PostComment(pc *PostCreate) string {
|
||||
if setting.Conf.Disqus.PostsList == "" || setting.Conf.Disqus.PublicKey == "" || setting.Conf.Disqus.ShortName == "" {
|
||||
return ""
|
||||
// 评论文章
|
||||
func PostCreate(pc *PostComment) (*postCreateResp, error) {
|
||||
if !correctDisqusConfig() {
|
||||
return nil, ErrDisqusConfig
|
||||
}
|
||||
url := setting.Conf.Disqus.PostCreate +
|
||||
"?api_key=E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F" +
|
||||
"&message=" + pc.Message + "&parent=" + pc.Parent +
|
||||
"&thread=" + pc.Thread + "&author_email=" + pc.AuthorEmail +
|
||||
"&author_name=" + pc.AuthorName
|
||||
|
||||
request, err := http.NewRequest("POST", url, nil)
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", "E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F")
|
||||
vals.Set("message", pc.Message)
|
||||
vals.Set("parent", pc.Parent)
|
||||
vals.Set("thread", pc.Thread)
|
||||
vals.Set("author_email", pc.AuthorEmail)
|
||||
vals.Set("author_name", pc.AuthorName)
|
||||
// vals.Set("state", "approved")
|
||||
|
||||
request, err := http.NewRequest("POST", setting.Conf.Disqus.PostCreate, strings.NewReader(vals.Encode()))
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return ""
|
||||
return nil, err
|
||||
}
|
||||
request.Header.Set("Referer", "https://disqus.com")
|
||||
resp, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return ""
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return ""
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logd.Error(string(b))
|
||||
return ""
|
||||
return nil, errors.New(string(b))
|
||||
}
|
||||
pr := &PostResponse{}
|
||||
err = json.Unmarshal(b, pr)
|
||||
result := &postCreateResp{}
|
||||
err = json.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return ""
|
||||
return nil, err
|
||||
}
|
||||
logd.Print(pr.Response.Id)
|
||||
return pr.Response.Id
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 批准评论通过
|
||||
type approvedResp struct {
|
||||
Code int
|
||||
Response []struct {
|
||||
Id string
|
||||
}
|
||||
}
|
||||
|
||||
func PostApprove(post string) error {
|
||||
if !correctDisqusConfig() {
|
||||
return ErrDisqusConfig
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", setting.Conf.Disqus.PublicKey)
|
||||
vals.Set("access_token", setting.Conf.Disqus.AccessToken)
|
||||
vals.Set("post", post)
|
||||
|
||||
request, err := http.NewRequest("POST", setting.Conf.Disqus.PostApprove, strings.NewReader(vals.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Header.Set("Referer", "https://disqus.com")
|
||||
resp, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(string(b))
|
||||
}
|
||||
|
||||
result := &approvedResp{}
|
||||
err = json.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建thread
|
||||
type threadCreateResp struct {
|
||||
Code int
|
||||
Response struct {
|
||||
Id string
|
||||
}
|
||||
}
|
||||
|
||||
func ThreadCreate(artc *Article) error {
|
||||
if !correctDisqusConfig() {
|
||||
return ErrDisqusConfig
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", setting.Conf.Disqus.PublicKey)
|
||||
vals.Set("access_token", setting.Conf.Disqus.AccessToken)
|
||||
vals.Set("forum", setting.Conf.Disqus.ShortName)
|
||||
vals.Set("title", artc.Title+" | "+Ei.BTitle)
|
||||
vals.Set("identifier", "post-"+artc.Slug)
|
||||
urlPath := fmt.Sprintf("https://%s/post/%s.html", setting.Conf.Mode.Domain, artc.Slug)
|
||||
vals.Set("url", urlPath)
|
||||
|
||||
resp, err := http.PostForm(setting.Conf.Disqus.ThreadCreate, vals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(string(b))
|
||||
}
|
||||
|
||||
result := &threadCreateResp{}
|
||||
err = json.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
artc.Thread = result.Response.Id
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,18 +8,29 @@ func TestDisqus(t *testing.T) {
|
||||
PostsCount()
|
||||
}
|
||||
|
||||
func TestPostComment(t *testing.T) {
|
||||
pc := &PostCreate{
|
||||
func TestPostCreate(t *testing.T) {
|
||||
pc := &PostComment{
|
||||
Message: "hahahaha",
|
||||
Thread: "52799014",
|
||||
AuthorEmail: "deepzz.qi@gmail.com",
|
||||
AuthorName: "deepzz",
|
||||
}
|
||||
|
||||
id := PostComment(pc)
|
||||
if id == "" {
|
||||
t.Error("post failed")
|
||||
id, err := PostCreate(pc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
t.Log("post success")
|
||||
t.Log("post success", id)
|
||||
}
|
||||
|
||||
func TestThreadCreate(t *testing.T) {
|
||||
tc := &Article{
|
||||
Title: "测试test7",
|
||||
Slug: "test7",
|
||||
}
|
||||
err := ThreadCreate(tc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ services:
|
||||
volumes:
|
||||
- /data/eiblog/logdata:/eiblog/logdata
|
||||
- /data/eiblog/conf:/eiblog/conf
|
||||
- /data/eiblog/static:/eiblog/static
|
||||
- /data/eiblog/views:/eiblog/views
|
||||
links:
|
||||
- elasticsearch
|
||||
- mongodb
|
||||
@@ -35,3 +33,14 @@ services:
|
||||
ports:
|
||||
- "9000:9000"
|
||||
restart: always
|
||||
# backup:
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/deepzz/backup
|
||||
# container_name: backup
|
||||
# links:
|
||||
# - mongodb
|
||||
# environment:
|
||||
# - QINIU_BUCKET=xxxx
|
||||
# - QINIU_DOMAIN=xx.example.com
|
||||
# - ACCESS_KEY=xxxxxxxxxx
|
||||
# - SECRECT_KEY=xxxxxxxxxx
|
||||
# restart: always
|
||||
|
||||
1
docs/_config.yml
Normal file
1
docs/_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-cayman
|
||||
24
docs/amusing.md
Normal file
24
docs/amusing.md
Normal file
@@ -0,0 +1,24 @@
|
||||
### Twitter Card
|
||||
相信很多人不明白为什么会这样专注twitter。首先twitter是一个社交网站,国际性的。其次我们可以使用它的Twitter Card功能非常的酷。
|
||||
|
||||
当你配置好Twitter相关的参数后`conf/app.yml`:
|
||||
```
|
||||
# twitter地址: twitter.com/chenqijing2
|
||||
twitter:
|
||||
card: summary
|
||||
site: chenqijing2
|
||||
image: st.deepzz.com/static/img/avatar.jpg
|
||||
address: twitter.com/chenqijing2
|
||||
```
|
||||
|
||||
每当你发部一个推文,你如果带上你的网址,它会自动给你展示成卡片的形式
|
||||
|
||||

|
||||
|
||||

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

|
||||
69
docs/autocert.md
Normal file
69
docs/autocert.md
Normal file
@@ -0,0 +1,69 @@
|
||||
### 证书自动更新
|
||||
|
||||
本博客证书自动更新有两种方式:
|
||||
|
||||
* [acme/autocert](https://github.com/golang/crypto/tree/master/acme/autocert),博客内部集成,通过 tls-sni 验证,实现全自动更新证书,一键开启关闭。请在裸服务器的情况下使用(不要使用代理)。单证书
|
||||
* [acme.sh](https://github.com/Neilpang/acme.sh),强大的 acme 脚本,多种自动更新证书方式,满足你各方面的需求。双证书
|
||||
|
||||
#### 方式一
|
||||
什么是 autocert,简单点,你只需要两步操作:
|
||||
|
||||
1. 将域名解析到你的服务器。
|
||||
2. 在服务器上运行开启 autocert 功能的程序(这里不需要配置证书),需要占用 443 端口。
|
||||
|
||||
其它过程你不需要过问,即会完成自动申请证书,自动更新证书的功能(默认 30 天)。这个是在 tcp/ip 层的操作,对用户完全透明,非常棒。
|
||||
|
||||
一键开启 autocert 功能,只需修改 `conf/app.yml` 文件内容:
|
||||
|
||||
```
|
||||
# 运行模式
|
||||
mode:
|
||||
# http server
|
||||
enablehttp: true
|
||||
httpport: 9000
|
||||
# https server
|
||||
enablehttps: true # 必须开启
|
||||
autocert: false # autocert 功能开关
|
||||
httpsport: 9001
|
||||
certfile:
|
||||
keyfile:
|
||||
domain: deepzz.com # 申请证书的域名,也是博客的域名
|
||||
```
|
||||
|
||||
首先,使用 HTTPS 必须启用 `enablehttps`,它有两个作用:
|
||||
|
||||
* 如果 `enablehttp` 开启,会自动 301 重定向到 https。
|
||||
* 作为开启 autocert 的前提条件。
|
||||
|
||||
其次, `autocert` 是否开启也有两个作用:
|
||||
|
||||
* false,服务器将使用 `httpsport`、`certfile`、`keyfile` 作为参数启动 https 服务器。
|
||||
* true,服务器直接使用 443 端口启动 https 服务器,并且自动申请证书,且在证书只有 30 天有效期时自动更新证书。域名为 *运行模式* 下的 mode->domain。
|
||||
|
||||
#### 方式二
|
||||
|
||||
使用方式二,你需要了解 acme.sh 的具体使用方式,非常简单。选择适合自己的方式实现自动更新证书。
|
||||
|
||||
博主,这里实现了 aliyun dns 的自动验证,自动更新证书。详情参见 Makefile->gencert。这里实现了自动申请 ecc、rsa 双证书,并且自动申请 scts,自动安装,自动更新。
|
||||
|
||||
基本流程如下:
|
||||
|
||||
1. 创建相关目录:`/data/eiblog/conf/ssl`、`/data/eiblog/conf/scts/rsa`、`/data/eiblog/conf/scts/ecc`。
|
||||
2. 自动下载安装 acme.sh 脚本。
|
||||
3. 自动申请 RSA 证书并且自动获取 scts,并且自动安装到指定位置。
|
||||
4. 自动申请 ECC 证书并且自动获取 scts,并且自动安装到指定位置。
|
||||
|
||||
##### 使用方式
|
||||
|
||||
导出环境变量,Aliyun dns 的环境变量为:
|
||||
|
||||
```
|
||||
export Ali_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
|
||||
export Ali_Secret="jlsdflanljkljlfdsaklkjflsa"
|
||||
```
|
||||
|
||||
执行命令:
|
||||
|
||||
```
|
||||
$ make gencert -cn=common_name -sans="-d example.com -d example1.com"
|
||||
```
|
||||
207
docs/install.md
Normal file
207
docs/install.md
Normal file
@@ -0,0 +1,207 @@
|
||||
### 安装
|
||||
1、`Eiblog` 提供多个平台的压缩包下载,可到 [Eiblog release](https://github.com/eiblog/eiblog/releases) 选择相应版本和平台下载。也可通过:
|
||||
``` sh
|
||||
$ curl -L https://github.com/eiblog/eiblog/releases/download/v1.0.0/eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz > eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz
|
||||
```
|
||||
|
||||
2、如果有幸你也是 `Gopher`,相信你会亲自动手,你可以通过:
|
||||
``` sh
|
||||
$ git clone https://github.com/eiblog/eiblog.git
|
||||
```
|
||||
进行源码编译二进制文件运行。
|
||||
|
||||
3、如果你对 `docker` 技术也有研究的话,你也可以通过 `docker` 来安装:
|
||||
``` sh
|
||||
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:v1.2.0
|
||||
```
|
||||
`注意`,镜像内部没有提供 conf 文件夹内的配置内容,因为该内容定制化的需求过高。所以需要将 `conf` 目录映射出来,后面会具体说到。
|
||||
|
||||
### 本地测试
|
||||
采用二进制包进行测试,在下载好可执行程序之后,我们可以开始本地测试的工作了。本地测试需要搭建两个服务 `mongodb` (必须)和 `elasticsearch2.4.1`(可选,搜索服务不可用)。
|
||||
|
||||
`Eiblog ` 默认会连接 `hostname` 为 `mongodb` 和 `elasticsearch` 的地址,因此你需要将信息填入 `/etc/hosts` 下。假如你搭建的 `mongodb` 地址为 `127.0.0.1:27017`,`elasticsearch` 地址为 `192.168.99.100:9200`,如:
|
||||
``` sh
|
||||
$ sudo vi /etc/hosts
|
||||
|
||||
# 在末尾加上两行
|
||||
172.42.0.1 mongodb
|
||||
192.168.99.100 elasticsearch
|
||||
```
|
||||
|
||||
下面先看两个服务的搭建。
|
||||
|
||||
#### MongoDB 搭建
|
||||
|
||||
`MongoDB` 搭建,Mac 可通过 `brew install mongo` 进行安装,其它平台请查询资料。
|
||||
#### Elasticsearch 搭建
|
||||
`Elasticsearch `搭建,它的搭建要些许复杂。建议通过 `docker` 搭建。需要注意的是 es 自带的分析器对中文分词是不友好的,这里采用了 `elasticsearch-analysis-ik` 分词器。如果你想了解更多 [Github](https://github.com/medcl/elasticsearch-analysis-ik) 或则如何实现 [博客站内搜索](https://imququ.com/post/elasticsearch.html)。
|
||||
|
||||
1. pull 镜像 `docker pull elasticsearch:2.4.1`。
|
||||
|
||||
2. 添加环境变量 `ES_JAVA_OPTS: "-Xms512m -Xmx512m"`,除非你想让你的服务器爆掉。
|
||||
|
||||
3. 映射相关目录:
|
||||
|
||||
```
|
||||
$PWD/conf/es/config:/usr/share/elasticsearch/config
|
||||
$PWD/conf/es/plugins:/usr/share/elasticsearch/plugins
|
||||
```
|
||||
|
||||
博主已经准备好了必要的 es 配置文件,请将这四个目录映射至 `eiblog` 下的 `conf` 目录。如果你想查看更多,请查看 `docker-compose.yml` 文件。
|
||||
|
||||
总结一下,`docker` 运行 es 的命令为:
|
||||
``` sh
|
||||
$ docker run -d --name eisearch \
|
||||
-p 9200:9200 \
|
||||
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
|
||||
-v $PWD/conf/es/config:/usr/share/elasticsearch/config \
|
||||
-v $PWD/conf/es/plugins:/usr/share/elasticsearch/plugins \
|
||||
elasticsearch:2.4.1
|
||||
```
|
||||
|
||||
之后执行 `./eiblog`,咱们的 `eiblog` 就可以运行起来了。
|
||||
|
||||
通过 `127.0.0.1:9000` 可以进入博客首页,`127.0.0.1:9000/admin/login` 进入后台登陆,账号密码为 `eiblog/conf/app.yml` 下的 `username` 和 `password`。初始账号密码 `deepz`、`deepzz`。
|
||||
|
||||
> `注意`,因为配置 `conf/app.yml` 均是博主自用配置。有些操作可能(如评论)会评论到我的博客,还请尽量避免,谢谢。
|
||||
|
||||
### 准备部署
|
||||
如果你在感受了该博客的魅力了之后,仍然坚持想要搭建它。那么,恭喜你,获得的一款不想再更换的博客系统。下面,我们跟随步骤对部署流程进一步说明。
|
||||
|
||||
这里只提供 `Docker` 的相关部署说明。你如果需要其它方式部署,请参考该方式。
|
||||
|
||||
#### 前提准备
|
||||
这里需要准备一些必要的东西,如果你已准备好。请跳过。
|
||||
|
||||
* `一台服务器`。
|
||||
* `一个域名`,国内服务器需备案。
|
||||
* `有效的证书`。通过开启 autocert 可自动申请更新证书。也可去七牛、qcloud 申请一年有效证书。
|
||||
* `七牛CDN`。博客只设计接入了 七牛cdn,相信该 CDN 服务商不会让你失望。
|
||||
* `Disqus`。作为博客评论系统,你得有翻墙的能力注册到该账号,具体配置我想又可以写一片博客了。简单说需要 `shorname` 和 `public key`。
|
||||
* `Google Analystic`。数据统计分析工具。
|
||||
* `Superfeedr`。加速 RSS 订阅。
|
||||
* `Twitter`。希望你能够有一个 twitter 账号。
|
||||
|
||||
是不是这么多要求,很费解。其实当初该博客系统只是为个人而设计的,是自己心中想要的那一款。博主些这篇文章不是想要多少人来用该博客,而是希望对那些追求至极的朋友说:你需要这款博客系统。
|
||||
#### 文件准备
|
||||
博主是一个有强迫症的人,一些文件的路径我使用了固定的路径,请大家见谅。假如你的 cdn 域名为 `st.example.com`,你需要确定这些文件已经在你的 cdn 中,它们路径分别是:
|
||||
|
||||
| 文件 | 地址 | 描述 |
|
||||
| ------------------ | ---------------------------------------- | ---------------------------------------- |
|
||||
| favicon.ico | st.example.com/static/img/favicon.ico | cdn 中的文件名为 `static/img/favicon.ico`。你也可以复制 favicon.ico 到 static 文件夹下,通过 example.com/favicon.ico 也是能够访问到。docker 用户可能需要重新打包镜像。 |
|
||||
| bg04.jpg | st.example.com/static/img/bg04.jpg | 首页左侧的大背景图,需要更名请到 views/st_blog.css 修改。 |
|
||||
| avatar.png | st.example.com/static/img/avatar.png | 头像 |
|
||||
| blank.gif | st.example.com/static/img/blank.gif | 空白图片,[下载](https://st.deepzz.com/static/img/blank.gif) |
|
||||
| default_avatar.png | st.example.com/static/img/default_avatar.png | disqus 默认图片,[下载](https://st.deepzz.com/static/img/default_avatar.png) |
|
||||
| disqus.js | st.example.com/static/js/disqus_xxx.js | disqus 文件,你可以通过 https://short_name.disqus.com/embed.js 下载你的专属文件,并上传到七牛。更新配置文件 app.yml。 |
|
||||
|
||||
> 注意,cdn 提到的文件下载,请复制链接进行下载,因为博主使用了防盗链功能,还有:
|
||||
1、每次修改 app.yml 文件(如:更换 cdn 域名或更新头像),如果你不知道是否应该提高 staticversion 一个版本,那么最好提高一个 +1。
|
||||
2、每次手动修改 views 内的以 `st_` 开头的文件,请将 `app.yml` 中的 staticversion 提高一个版本。
|
||||
|
||||
#### 配置说明
|
||||
走到这里,我相信只走到 `60%` 的路程。放弃还来得及。
|
||||
|
||||
这里会对 `eiblog/conf` 下的所有文件做说明,希望你做好准备。
|
||||
```
|
||||
├── app.yml # 博客配置文件
|
||||
├── blackip.yml # 博客 ip 黑名单
|
||||
├── es # elasticsearch 配置
|
||||
│ ├── config # 配置文件
|
||||
│ │ ├── analysis # 同义词
|
||||
│ │ ├── elasticsearch.yml # 具体配置
|
||||
│ │ ├── logging.yml # 日志配置
|
||||
│ │ └── scripts # 脚本文件夹
|
||||
│ └── plugins # 插件文件夹
|
||||
│ └── ik1.10.1 # ik 分词器
|
||||
├── nginx # nginx 配置
|
||||
│ ├── domain # 域名配置,nginx 会读区改文件夹下的 .conf 文件
|
||||
│ │ └── eiblog.conf
|
||||
│ ├── ip.blacklist # nginx ip黑名单
|
||||
│ └── nginx.conf # nginx 配置,请替换 nginx 原有配置
|
||||
├── scts # ct 透明
|
||||
│ ├── ecc
|
||||
│ │ ├── aviator.sct
|
||||
│ │ └── digicert.sct
|
||||
│ └── rsa
|
||||
│ ├── aviator.sct
|
||||
│ └── digicert.sct
|
||||
├── ssl # 证书相关文件,可参考 eiblog.conf 生成
|
||||
│ ├── dhparams.pem
|
||||
│ ├── domain.rsa.key
|
||||
│ ├── domain.rsa.pem
|
||||
│ ├── full_chained.pem
|
||||
│ └── session_ticket.key
|
||||
└── tpl # 模版文件
|
||||
├── crossdomainTpl.xml
|
||||
├── feedTpl.xml
|
||||
├── opensearchTpl.xml
|
||||
├── robotsTpl.xml
|
||||
└── sitemapTpl.xml
|
||||
```
|
||||
| 名称 | 描述 |
|
||||
| ----------- | ---------------------------------------- |
|
||||
| app.yml | 整个程序的配置文件,里面已经列出了所有配置项的说明,这里不再阐述。 |
|
||||
| blackip.yml | 如果没有使用 `Nginx`,博客内置 `ip` 过滤系统。 |
|
||||
| es | elasticsearch,非常强大的分布式搜索引擎,`github` 用的就是它。里面的配置基本不用修改,但 `es/analysis/synonym.txt` 是同义词,你可以照着已有的随意增加。scripts 是 es 的脚本文件夹 |
|
||||
| nginx | 系统采用 `nginx` 作为代理(相信博客系统也不会独占一台服务器~)。请使用 `nginx.conf` 替换原 `nginx` 的配置。博客系统的配置文件是 `domain/eiblog.conf`,或则重命名(只要是满足`*.conf`)。`eiblog.conf`文件里面学问是最多的。或许你想一一弄懂,或许…。注意本配置需要更新 nginx 到最新版,openssl 更新到1.0.2j,具体请到 Jerry Qu 的 [本博客 Nginx 配置之完整篇](https://imququ.com/post/my-nginx-conf.html) 查看,了解详情。 |
|
||||
| scts | 存放 ct 文件。 |
|
||||
| ssl | 这里存放了所有证书相关的内容。 |
|
||||
| tpl | 模版相关,不用修改。 |
|
||||
|
||||
### 开始部署
|
||||
|
||||
#### docker
|
||||
请确定你已经完成了上面所说的所有步骤,在本地已经测试成功。服务器上 `MognoDB` 和`Elasticsearch` 已经安装并已经运行成功。
|
||||
|
||||
首先,请将本地测试好的 `conf` 文件夹上传至服务器,建议存储到服务器 `/data/eiblog` 下。
|
||||
``` sh
|
||||
$ tree /data/eiblog -L 1
|
||||
|
||||
├── conf
|
||||
```
|
||||
|
||||
然后,将镜像 PULL 到服务器本地。
|
||||
``` sh
|
||||
# PULL下Eiblog镜像
|
||||
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
||||
```
|
||||
|
||||
最后,执行 `docker run` 命令,希望你能成功。
|
||||
``` sh
|
||||
$ docker run -d --name eiblog --restart=always \
|
||||
--add-host disqus.com:23.235.33.134 \
|
||||
--add-host mongodb:172.42.0.1 \
|
||||
--add-host elasticsearch:192.168.99.100 \
|
||||
-p 9000:9000 \
|
||||
-e GODEBUG=netdns=cgo \
|
||||
-v /data/eiblog/logdata:/eiblog/logdata \
|
||||
-v /data/eiblog/conf:/eiblog/conf \
|
||||
registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
||||
```
|
||||
这里默认 `MongDB` 和 `Elasticsearch` 均为 `docker` 部署,且名称为`eidb`,`eisearch`。
|
||||
|
||||
#### nginx + docker
|
||||
通过 `Nginx+docker` 部署,是博主推荐的方式。这里采用 `Docker Compose` 管理我们整个博客系统。
|
||||
|
||||
请确认你已经成功安装好 `Nginx`、`docker`、`docker-compose`。Nginx 请一定参照 Jerry Qu 的[Nginx 配置完整篇](https://imququ.com/post/my-nginx-conf.html)。
|
||||
|
||||
首先,请将本地测试好的 `conf`,`docker-compose.yml` 文件夹和文件上传至服务器。`conf` 建议存储到服务器 `/data/eiblog` 下,`docker-compose.yml` 存放在你使用方便的地方。
|
||||
|
||||
``` sh
|
||||
$ tree /data/eiblog -L 1
|
||||
|
||||
├── conf
|
||||
|
||||
$ ls ~/
|
||||
|
||||
docker-compose.yml
|
||||
```
|
||||
|
||||
然后,执行:
|
||||
``` sh
|
||||
$ cd ~
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
等待些许时间,成功运行。
|
||||
74
docs/writing.md
Normal file
74
docs/writing.md
Normal file
@@ -0,0 +1,74 @@
|
||||
### 郑重提醒
|
||||
**标题**、**slug**、**内容**。在你点击保存的时候一定确保三者不能为空,否则页面刷新内容就没了。所以,养成一个良好的写作习惯很重要。
|
||||
|
||||
当然,博客的自动保存功能也非常的好。在你不确定是否发布前,你可以将之保存到草稿,以便下次继续编辑。
|
||||
|
||||
### 文章标题
|
||||
文章标题,这个可能要看个人习惯。我习惯从三级标题开始(###),依次往下四级标题,五级标题...。要注意的是一定不能跳级:
|
||||
```
|
||||
### 标题一
|
||||
|
||||
#### 标题1.1
|
||||
#### 标题1.2
|
||||
##### 标题1.2.1
|
||||
##### 标题1.2.2
|
||||
|
||||
### 标题二
|
||||
|
||||
##### 标题2.1
|
||||
|
||||
##### 标题2.2
|
||||
###### 标题2.2.1
|
||||
###### 标题2.2.2
|
||||
```
|
||||
|
||||
结果是:
|
||||

|
||||
|
||||
### 文章描述
|
||||
文章描述,主要是给`html->head->meta`中的 name 为 description 用的。现采用了一个临时的办法:在文章的第一行通过前缀识别(只看第一行)。
|
||||
|
||||
该前缀可到`conf/app.yml`设置,默认为`Desc:`,如:
|
||||
|
||||

|
||||
|
||||
### 图片懒加载
|
||||
博客系统提供图片懒加载功能(浏览到某个位置,图片才会加载),以此来提高页面加载速度。我们可根据需要是否使用。
|
||||
|
||||
当然由此带来的坏处就是rss不能够正确加载图片。后续看是否解决这个问题或朋友提PR。
|
||||
|
||||
首先看下图片的`markdown`标准写法:
|
||||
```
|
||||

|
||||
```
|
||||
如:
|
||||
```
|
||||

|
||||
```
|
||||

|
||||
|
||||
懒加载,需要为该图片指定大小(长高):
|
||||
```
|
||||

|
||||
```
|
||||
|
||||
x 为小写字母(x,y,z)中的 x。使页面未加载时也占了相应的位置大小,这样设计是为了让读者在浏览页面时不会感到抖动。
|
||||
|
||||
如:
|
||||
```
|
||||

|
||||
```
|
||||
|
||||
### 摘要截取
|
||||
摘要截取主要是提供给首页显示,如:
|
||||

|
||||
|
||||
红框中圈出来的就是截取出来的内容。在 `conf/app.yml` 的配置项有两个:
|
||||
```
|
||||
# 自动截取预览, 字符数
|
||||
length: 400
|
||||
# 截取预览标识
|
||||
identifier: <!--more-->
|
||||
|
||||
```
|
||||
当程序不能检查到 identifier 的标识符时,会采用长度的方式进行截取。
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
"github.com/eiblog/utils/logd"
|
||||
)
|
||||
|
||||
@@ -24,13 +24,25 @@ const (
|
||||
ES_DATE = `{"range":{"date":{"gte":"%s","lte": "%s","format": "yyyy-MM-dd||yyyy-MM||yyyy"}}}` // 2016-10||/M
|
||||
)
|
||||
|
||||
var es *ElasticService
|
||||
var (
|
||||
ErrUninitializedES = errors.New("uninitialized elasticsearch")
|
||||
|
||||
es *ElasticService
|
||||
)
|
||||
|
||||
// 初始化 Elasticsearch 服务器
|
||||
func init() {
|
||||
es = &ElasticService{url: setting.Conf.SearchURL, c: new(http.Client)}
|
||||
_, err := net.LookupIP("elasticsearch")
|
||||
if err != nil {
|
||||
logd.Info(err)
|
||||
return
|
||||
}
|
||||
|
||||
es = &ElasticService{url: "http://elasticsearch:9200", c: new(http.Client)}
|
||||
initIndex()
|
||||
}
|
||||
|
||||
// 创建索引
|
||||
func initIndex() {
|
||||
mappings := fmt.Sprintf(`{"mappings":{"%s":{"properties":{"content":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"},"date":{"index":"not_analyzed","type":"date"},"slug":{"type":"string"},"tag":{"index":"not_analyzed","type":"string"},"title":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"}}}}}`, TYPE)
|
||||
err := CreateIndexAndMappings(INDEX, TYPE, []byte(mappings))
|
||||
@@ -39,7 +51,12 @@ func initIndex() {
|
||||
}
|
||||
}
|
||||
|
||||
func Elasticsearch(qStr string, size, from int) *ESSearchResult {
|
||||
// 查询
|
||||
func Elasticsearch(qStr string, size, from int) (*ESSearchResult, error) {
|
||||
if es == nil {
|
||||
return nil, ErrUninitializedES
|
||||
}
|
||||
|
||||
// 分析查询字符串
|
||||
reg := regexp.MustCompile(`(tag|slug|date):`)
|
||||
indexs := reg.FindAllStringIndex(qStr, -1)
|
||||
@@ -90,13 +107,17 @@ func Elasticsearch(qStr string, size, from int) *ESSearchResult {
|
||||
}
|
||||
docs, err := IndexQueryDSL(INDEX, TYPE, size, from, []byte(dsl))
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
return docs
|
||||
return docs, nil
|
||||
}
|
||||
|
||||
// 添加或更新索引
|
||||
func ElasticIndex(artc *Article) error {
|
||||
if es == nil {
|
||||
return ErrUninitializedES
|
||||
}
|
||||
|
||||
img := PickFirstImage(artc.Content)
|
||||
mapping := map[string]interface{}{
|
||||
"title": artc.Title,
|
||||
@@ -110,7 +131,12 @@ func ElasticIndex(artc *Article) error {
|
||||
return IndexOrUpdateDocument(INDEX, TYPE, artc.ID, b)
|
||||
}
|
||||
|
||||
// 删除索引
|
||||
func ElasticDelIndex(ids []int32) error {
|
||||
if es == nil {
|
||||
return ErrUninitializedES
|
||||
}
|
||||
|
||||
var target []string
|
||||
for _, id := range ids {
|
||||
target = append(target, fmt.Sprint(id))
|
||||
@@ -128,10 +154,12 @@ type IndicesCreateResult struct {
|
||||
Acknowledged bool `json:"acknowledged"`
|
||||
}
|
||||
|
||||
// 返回 url
|
||||
func (s *ElasticService) ParseURL(format string, params ...interface{}) string {
|
||||
return fmt.Sprintf(s.url+format, params...)
|
||||
}
|
||||
|
||||
// Elastic 相关操作请求
|
||||
func (s *ElasticService) Do(req *http.Request) (interface{}, error) {
|
||||
resp, err := s.c.Do(req)
|
||||
if err != nil {
|
||||
@@ -153,11 +181,8 @@ func (s *ElasticService) Do(req *http.Request) (interface{}, error) {
|
||||
return b, nil
|
||||
case "HEAD":
|
||||
return resp.StatusCode, nil
|
||||
|
||||
default:
|
||||
return nil, errors.New("unknown methods")
|
||||
}
|
||||
return nil, nil
|
||||
return nil, errors.New("unknown methods")
|
||||
}
|
||||
|
||||
func CreateIndexAndMappings(index, typ string, mappings []byte) (err error) {
|
||||
@@ -188,6 +213,7 @@ func CreateIndexAndMappings(index, typ string, mappings []byte) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建或更新索引
|
||||
func IndexOrUpdateDocument(index, typ string, id int32, doc []byte) (err error) {
|
||||
req, err := http.NewRequest("PUT", es.ParseURL("/%s/%s/%d", index, typ, id), bytes.NewReader(doc))
|
||||
if err != nil {
|
||||
@@ -214,6 +240,7 @@ type ESDeleteResult struct {
|
||||
} `json:"iterms"`
|
||||
}
|
||||
|
||||
// 删除文档
|
||||
func DeleteDocument(index, typ string, ids []string) error {
|
||||
var buff bytes.Buffer
|
||||
for _, id := range ids {
|
||||
@@ -248,6 +275,7 @@ func DeleteDocument(index, typ string, ids []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 查询结果
|
||||
type ESSearchResult struct {
|
||||
Took float32 `json:"took"`
|
||||
Hits struct {
|
||||
@@ -269,6 +297,7 @@ type ESSearchResult struct {
|
||||
} `json:"hits"`
|
||||
}
|
||||
|
||||
// DSL 语句查询文档
|
||||
func IndexQueryDSL(index, typ string, size, from int, dsl []byte) (*ESSearchResult, error) {
|
||||
req, err := http.NewRequest("POST", es.ParseURL("/%s/%s/_search?size=%d&from=%d", index, typ, size, from), bytes.NewReader(dsl))
|
||||
if err != nil {
|
||||
|
||||
167
front.go
167
front.go
@@ -20,13 +20,46 @@ import (
|
||||
func Filter() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 过滤黑名单
|
||||
BlackFilter(c)
|
||||
if BlackFilter(c) {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// 重定向
|
||||
if Redirect(c) {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// 用户cookie,用于统计
|
||||
UserCookie(c)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// 黑名单过滤
|
||||
func BlackFilter(c *gin.Context) bool {
|
||||
ip := c.ClientIP()
|
||||
if setting.BlackIP[ip] {
|
||||
c.String(http.StatusForbidden, "Your IP is blacklisted.")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 重定向
|
||||
func Redirect(c *gin.Context) bool {
|
||||
if setting.Conf.Mode.EnableHttps && c.Request.ProtoMajor == 1 {
|
||||
var port string
|
||||
if strings.Contains(c.Request.Host, ":") {
|
||||
port = fmt.Sprintf(":%d", setting.Conf.Mode.HttpsPort)
|
||||
}
|
||||
c.Redirect(http.StatusMovedPermanently, "https://"+setting.Conf.Mode.Domain+port+c.Request.RequestURI)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 用户识别
|
||||
func UserCookie(c *gin.Context) {
|
||||
cookie, err := c.Cookie("u")
|
||||
@@ -35,15 +68,6 @@ func UserCookie(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 黑名单过滤
|
||||
func BlackFilter(c *gin.Context) {
|
||||
ip := c.ClientIP()
|
||||
if setting.BlackIP[ip] {
|
||||
c.Abort()
|
||||
c.String(http.StatusForbidden, "Your IP is blacklisted.")
|
||||
}
|
||||
}
|
||||
|
||||
// 解析静态文件版本
|
||||
func StaticVersion(c *gin.Context) (version int) {
|
||||
cookie, err := c.Request.Cookie("v")
|
||||
@@ -55,7 +79,6 @@ func StaticVersion(c *gin.Context) (version int) {
|
||||
|
||||
func GetBase() gin.H {
|
||||
return gin.H{
|
||||
"Favicon": setting.Conf.Favicon,
|
||||
"BlogName": Ei.BlogName,
|
||||
"SubTitle": Ei.SubTitle,
|
||||
"Twitter": setting.Conf.Twitter,
|
||||
@@ -63,10 +86,12 @@ func GetBase() gin.H {
|
||||
"BTitle": Ei.BTitle,
|
||||
"BeiAn": Ei.BeiAn,
|
||||
"Domain": setting.Conf.Mode.Domain,
|
||||
"Kodo": setting.Conf.Kodo,
|
||||
"Qiniu": setting.Conf.Qiniu,
|
||||
"Disqus": setting.Conf.Disqus,
|
||||
}
|
||||
}
|
||||
|
||||
// not found
|
||||
func HandleNotFound(c *gin.Context) {
|
||||
h := GetBase()
|
||||
h["Version"] = StaticVersion(c)
|
||||
@@ -77,6 +102,7 @@ func HandleNotFound(c *gin.Context) {
|
||||
RenderHTMLFront(c, "notfound", h)
|
||||
}
|
||||
|
||||
// 首页
|
||||
func HandleHomePage(c *gin.Context) {
|
||||
h := GetBase()
|
||||
h["Version"] = StaticVersion(c)
|
||||
@@ -88,11 +114,12 @@ func HandleHomePage(c *gin.Context) {
|
||||
if err != nil || pn < 1 {
|
||||
pn = 1
|
||||
}
|
||||
h["Prev"], h["Next"], h["List"] = PageList(pn, setting.Conf.PageNum)
|
||||
h["Prev"], h["Next"], h["List"] = PageList(pn, setting.Conf.General.PageNum)
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLFront(c, "home", h)
|
||||
}
|
||||
|
||||
// 专题页
|
||||
func HandleSeriesPage(c *gin.Context) {
|
||||
h := GetBase()
|
||||
h["Version"] = StaticVersion(c)
|
||||
@@ -105,6 +132,7 @@ func HandleSeriesPage(c *gin.Context) {
|
||||
RenderHTMLFront(c, "series", h)
|
||||
}
|
||||
|
||||
// 归档页
|
||||
func HandleArchivesPage(c *gin.Context) {
|
||||
h := GetBase()
|
||||
h["Version"] = StaticVersion(c)
|
||||
@@ -117,13 +145,14 @@ func HandleArchivesPage(c *gin.Context) {
|
||||
RenderHTMLFront(c, "archives", h)
|
||||
}
|
||||
|
||||
// 文章
|
||||
func HandleArticlePage(c *gin.Context) {
|
||||
path := c.Param("slug")
|
||||
artc := Ei.MapArticles[path[0:strings.Index(path, ".")]]
|
||||
if artc == nil {
|
||||
if !strings.HasSuffix(path, ".html") || Ei.MapArticles[path[:len(path)-5]] == nil {
|
||||
HandleNotFound(c)
|
||||
return
|
||||
}
|
||||
artc := Ei.MapArticles[path[:len(path)-5]]
|
||||
h := GetBase()
|
||||
h["Version"] = StaticVersion(c)
|
||||
h["Title"] = artc.Title + " | " + Ei.BTitle
|
||||
@@ -154,6 +183,7 @@ func HandleArticlePage(c *gin.Context) {
|
||||
RenderHTMLFront(c, name, h)
|
||||
}
|
||||
|
||||
// 搜索页
|
||||
func HandleSearchPage(c *gin.Context) {
|
||||
h := GetBase()
|
||||
h["Version"] = StaticVersion(c)
|
||||
@@ -169,10 +199,12 @@ func HandleSearchPage(c *gin.Context) {
|
||||
start = 1
|
||||
}
|
||||
h["Word"] = q
|
||||
var result *ESSearchResult
|
||||
|
||||
vals := c.Request.URL.Query()
|
||||
result = Elasticsearch(q, setting.Conf.PageNum, start-1)
|
||||
if result != nil {
|
||||
result, err := Elasticsearch(q, setting.Conf.General.PageNum, start-1)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
} else {
|
||||
result.Took /= 1000
|
||||
for i, v := range result.Hits.Hits {
|
||||
if artc := Ei.MapArticles[result.Hits.Hits[i].Source.Slug]; len(v.Highlight.Content) == 0 && artc != nil {
|
||||
@@ -180,12 +212,12 @@ func HandleSearchPage(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
h["SearchResult"] = result
|
||||
if start-setting.Conf.PageNum > 0 {
|
||||
vals.Set("start", fmt.Sprint(start-setting.Conf.PageNum))
|
||||
if start-setting.Conf.General.PageNum > 0 {
|
||||
vals.Set("start", fmt.Sprint(start-setting.Conf.General.PageNum))
|
||||
h["Prev"] = vals.Encode()
|
||||
}
|
||||
if result.Hits.Total >= start+setting.Conf.PageNum {
|
||||
vals.Set("start", fmt.Sprint(start+setting.Conf.PageNum))
|
||||
if result.Hits.Total >= start+setting.Conf.General.PageNum {
|
||||
vals.Set("start", fmt.Sprint(start+setting.Conf.General.PageNum))
|
||||
h["Next"] = vals.Encode()
|
||||
}
|
||||
}
|
||||
@@ -196,6 +228,7 @@ func HandleSearchPage(c *gin.Context) {
|
||||
RenderHTMLFront(c, "search", h)
|
||||
}
|
||||
|
||||
// 评论页
|
||||
func HandleDisqusFrom(c *gin.Context) {
|
||||
params := strings.Split(c.Param("slug"), "|")
|
||||
if len(params) != 4 || params[1] == "" {
|
||||
@@ -207,6 +240,7 @@ func HandleDisqusFrom(c *gin.Context) {
|
||||
"Title": "发表评论 | " + Ei.BTitle,
|
||||
"ATitle": artc.Title,
|
||||
"Thread": params[1],
|
||||
"Slug": artc.Slug,
|
||||
}
|
||||
err := Tmpl.ExecuteTemplate(c.Writer, "disqus.html", data)
|
||||
if err != nil {
|
||||
@@ -215,22 +249,36 @@ func HandleDisqusFrom(c *gin.Context) {
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
}
|
||||
|
||||
// feed
|
||||
func HandleFeed(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "static/feed.xml")
|
||||
}
|
||||
|
||||
// opensearch
|
||||
func HandleOpenSearch(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "static/opensearch.xml")
|
||||
}
|
||||
|
||||
// robots
|
||||
func HandleRobots(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "static/robots.txt")
|
||||
}
|
||||
|
||||
// sitemap
|
||||
func HandleSitemap(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "static/sitemap.xml")
|
||||
}
|
||||
|
||||
// cross domain
|
||||
func HandleCrossDomain(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "static/crossdomain.xml")
|
||||
}
|
||||
|
||||
// favicon
|
||||
func HandleFavicon(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "static/favicon.ico")
|
||||
}
|
||||
|
||||
// 服务端推送谷歌统计
|
||||
func HandleBeacon(c *gin.Context) {
|
||||
ua := c.Request.UserAgent()
|
||||
@@ -245,12 +293,13 @@ func HandleBeacon(c *gin.Context) {
|
||||
vals.Set("dl", c.Request.Referer())
|
||||
vals.Set("uip", c.ClientIP())
|
||||
go func() {
|
||||
req, err := http.NewRequest("POST", "https://www.google-analytics.com/collect", strings.NewReader(vals.Encode()))
|
||||
req, err := http.NewRequest("POST", setting.Conf.Google.URL, strings.NewReader(vals.Encode()))
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
}
|
||||
req.Header.Set("User-Agent", ua)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
@@ -275,7 +324,7 @@ type DisqusComments struct {
|
||||
ErrMsg string `json:"errmsg"`
|
||||
Data struct {
|
||||
Next string `json:"next"`
|
||||
Total int `json:"total,omitempty"`
|
||||
Total int `json:"total"`
|
||||
Comments []commentsDetail `json:"comments"`
|
||||
Thread string `json:"thread"`
|
||||
} `json:"data"`
|
||||
@@ -287,17 +336,25 @@ type commentsDetail struct {
|
||||
Name string `json:"name"`
|
||||
Url string `json:"url"`
|
||||
Avatar string `json:"avatar"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
CreatedAtStr string `json:"createdAtStr"`
|
||||
Message string `json:"message"`
|
||||
IsDeleted bool `json:"isDeleted"`
|
||||
}
|
||||
|
||||
func HandleDisqus(c *gin.Context) {
|
||||
slug := c.Param("slug")
|
||||
cursor := c.Query("cursor")
|
||||
|
||||
dcs := DisqusComments{}
|
||||
postsList := PostsList(slug, cursor)
|
||||
if postsList != nil {
|
||||
if artc := Ei.MapArticles[slug]; artc != nil {
|
||||
dcs.Data.Thread = artc.Thread
|
||||
}
|
||||
postsList, err := PostsList(slug, cursor)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
dcs.ErrNo = FAIL
|
||||
dcs.ErrMsg = "系统错误"
|
||||
} else {
|
||||
dcs.ErrNo = postsList.Code
|
||||
if postsList.Cursor.HasNext {
|
||||
dcs.Data.Next = postsList.Cursor.Next
|
||||
@@ -314,33 +371,39 @@ func HandleDisqus(c *gin.Context) {
|
||||
Parent: v.Parent,
|
||||
Url: v.Author.ProfileUrl,
|
||||
Avatar: v.Author.Avatar.Cache,
|
||||
CreatedAt: v.CreatedAt,
|
||||
CreatedAtStr: ConvertStr(v.CreatedAt),
|
||||
Message: v.Message,
|
||||
IsDeleted: v.IsDeleted,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dcs.ErrNo = FAIL
|
||||
dcs.ErrMsg = "系统错误"
|
||||
}
|
||||
c.JSON(http.StatusOK, dcs)
|
||||
}
|
||||
|
||||
// [thread:[5279901489] parent:[] identifier:[post-troubleshooting-https] next:[] author_name:[你好] author_email:[chenqijing2@163.com] message:[fdsfdsf]]
|
||||
// 发表评论
|
||||
// [thread:[5279901489] parent:[] identifier:[post-troubleshooting-https]
|
||||
// next:[] author_name:[你好] author_email:[chenqijing2@163.com] message:[fdsfdsf]]
|
||||
type DisqusCreate struct {
|
||||
ErrNo int `json:"errno"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
Data commentsDetail `json:"data"`
|
||||
}
|
||||
|
||||
func HandleDisqusCreate(c *gin.Context) {
|
||||
rep := gin.H{"errno": SUCCESS, "errmsg": ""}
|
||||
defer c.JSON(http.StatusOK, rep)
|
||||
resp := &DisqusCreate{}
|
||||
defer c.JSON(http.StatusOK, resp)
|
||||
|
||||
msg := c.PostForm("message")
|
||||
email := c.PostForm("author_email")
|
||||
name := c.PostForm("author_name")
|
||||
thread := c.PostForm("thread")
|
||||
identifier := c.PostForm("identifier")
|
||||
if msg == "" || email == "" || name == "" || thread == "" || identifier == "" {
|
||||
rep["errno"] = FAIL
|
||||
rep["errmsg"] = "参数错误"
|
||||
resp.ErrNo = FAIL
|
||||
resp.ErrMsg = "参数错误"
|
||||
return
|
||||
}
|
||||
pc := &PostCreate{
|
||||
pc := &PostComment{
|
||||
Message: msg,
|
||||
Parent: c.PostForm("parent"),
|
||||
Thread: thread,
|
||||
@@ -350,16 +413,34 @@ func HandleDisqusCreate(c *gin.Context) {
|
||||
IpAddress: c.ClientIP(),
|
||||
}
|
||||
|
||||
id := PostComment(pc)
|
||||
if id == "" {
|
||||
rep["errno"] = FAIL
|
||||
rep["errmsg"] = "系统错误"
|
||||
postDetail, err := PostCreate(pc)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
resp.ErrNo = FAIL
|
||||
resp.ErrMsg = "系统错误"
|
||||
return
|
||||
}
|
||||
rep["errno"] = SUCCESS
|
||||
rep["data"] = gin.H{"id": id}
|
||||
err = PostApprove(postDetail.Response.Id)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
resp.ErrNo = FAIL
|
||||
resp.ErrMsg = "系统错误"
|
||||
return
|
||||
}
|
||||
resp.ErrNo = SUCCESS
|
||||
resp.Data = commentsDetail{
|
||||
Id: postDetail.Response.Id,
|
||||
Name: name,
|
||||
Parent: postDetail.Response.Parent,
|
||||
Url: postDetail.Response.Author.ProfileUrl,
|
||||
Avatar: postDetail.Response.Author.Avatar.Cache,
|
||||
CreatedAtStr: ConvertStr(postDetail.Response.CreatedAt),
|
||||
Message: postDetail.Response.Message,
|
||||
IsDeleted: postDetail.Response.IsDeleted,
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染页面
|
||||
func RenderHTMLFront(c *gin.Context, name string, data gin.H) {
|
||||
var buf bytes.Buffer
|
||||
err := Tmpl.ExecuteTemplate(&buf, name, data)
|
||||
|
||||
83
glide.lock
generated
83
glide.lock
generated
@@ -1,83 +0,0 @@
|
||||
hash: bd360fa297ed66950543990f9433cdcdf13c29dd99d9a01b49027e236b2cb9da
|
||||
updated: 2017-02-07T19:58:01.805533338+08:00
|
||||
imports:
|
||||
- name: github.com/boj/redistore
|
||||
version: fc113767cd6b051980f260d6dbe84b2740c46ab0
|
||||
- name: github.com/eiblog/blackfriday
|
||||
version: c0ec111761ae784fe31cc076f2fa0e2d2216d623
|
||||
- name: github.com/eiblog/utils
|
||||
version: ad2f63940c4f16d0dbfc3f4df59e8cb7af0f80ec
|
||||
subpackages:
|
||||
- logd
|
||||
- mgo
|
||||
- tmpl
|
||||
- uuid
|
||||
- name: github.com/garyburd/redigo
|
||||
version: 908534c8b97586a4597e3fa195875d2d26502b97
|
||||
subpackages:
|
||||
- internal
|
||||
- redis
|
||||
- name: github.com/gin-gonic/contrib
|
||||
version: 4d2dccc9a4541014fec054e483cc76609b97fb16
|
||||
subpackages:
|
||||
- sessions
|
||||
- name: github.com/gin-gonic/gin
|
||||
version: e2212d40c62a98b388a5eb48ecbdcf88534688ba
|
||||
subpackages:
|
||||
- binding
|
||||
- render
|
||||
- name: github.com/golang/protobuf
|
||||
version: 2402d76f3d41f928c7902a765dfc872356dd3aad
|
||||
subpackages:
|
||||
- proto
|
||||
- name: github.com/gorilla/context
|
||||
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
|
||||
- name: github.com/gorilla/securecookie
|
||||
version: fa5329f913702981df43dcb2a380bac429c810b5
|
||||
- name: github.com/gorilla/sessions
|
||||
version: 83c8db3bdc9be789e57e3756ffbcffd2d7d40176
|
||||
- name: github.com/manucorporat/sse
|
||||
version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d
|
||||
- name: github.com/mattn/go-isatty
|
||||
version: 30a891c33c7cde7b02a981314b4228ec99380cca
|
||||
- name: github.com/shurcooL/sanitized_anchor_name
|
||||
version: 1dba4b3954bc059efc3991ec364f9f9a35f597d2
|
||||
- name: golang.org/x/net
|
||||
version: f315505cf3349909cdf013ea56690da34e96a451
|
||||
subpackages:
|
||||
- context
|
||||
- name: golang.org/x/sys
|
||||
version: 7a6e5648d140666db5d920909e082ca00a87ba2c
|
||||
subpackages:
|
||||
- unix
|
||||
- name: gopkg.in/go-playground/validator.v8
|
||||
version: c193cecd124b5cc722d7ee5538e945bdb3348435
|
||||
- name: gopkg.in/mgo.v2
|
||||
version: 3f83fa5005286a7fe593b055f0d7771a7dce4655
|
||||
subpackages:
|
||||
- bson
|
||||
- internal/json
|
||||
- internal/sasl
|
||||
- internal/scram
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
|
||||
- name: qiniupkg.com/api.v7
|
||||
version: 7cfd4b639917bf924d8c1cd17a6d61175e809066
|
||||
subpackages:
|
||||
- api
|
||||
- auth/qbox
|
||||
- conf
|
||||
- kodo
|
||||
- kodocli
|
||||
- name: qiniupkg.com/x
|
||||
version: f512abcf45ab4e2ba0fd4784c57b53d495997d66
|
||||
subpackages:
|
||||
- bytes.v7
|
||||
- bytes.v7/seekable
|
||||
- ctype.v7
|
||||
- log.v7
|
||||
- reqid.v7
|
||||
- rpc.v7
|
||||
- url.v7
|
||||
- xlog.v7
|
||||
testImports: []
|
||||
25
glide.yaml
25
glide.yaml
@@ -1,25 +0,0 @@
|
||||
package: github.com/eiblog/eiblog
|
||||
import:
|
||||
- package: github.com/eiblog/blackfriday
|
||||
- package: github.com/eiblog/utils
|
||||
subpackages:
|
||||
- logd
|
||||
- mgo
|
||||
- tmpl
|
||||
- uuid
|
||||
- package: github.com/gin-gonic/contrib
|
||||
subpackages:
|
||||
- sessions
|
||||
- package: github.com/gin-gonic/gin
|
||||
version: ~1.1.4
|
||||
- package: gopkg.in/mgo.v2
|
||||
subpackages:
|
||||
- bson
|
||||
- package: gopkg.in/yaml.v2
|
||||
- package: qiniupkg.com/api.v7
|
||||
subpackages:
|
||||
- kodo
|
||||
- kodocli
|
||||
- package: qiniupkg.com/x
|
||||
subpackages:
|
||||
- url.v7
|
||||
33
go.mod
Normal file
33
go.mod
Normal file
@@ -0,0 +1,33 @@
|
||||
module github.com/eiblog/eiblog
|
||||
|
||||
require (
|
||||
github.com/boj/redistore v0.0.0-20180825063928-0920d8493e7f // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deepzz0/logd v0.0.0-20171206094927-f91dd8c6316f
|
||||
github.com/eiblog/blackfriday v0.0.0-20161010144836-c0ec111761ae
|
||||
github.com/eiblog/utils v0.0.0-20180209055746-a56f1b5e9c3c
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
|
||||
github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b
|
||||
github.com/gin-gonic/contrib v0.0.0-20180614032058-39cfb9727134
|
||||
github.com/gin-gonic/gin v1.3.0
|
||||
github.com/golang/protobuf v1.2.0 // indirect
|
||||
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b // indirect
|
||||
github.com/mattn/go-isatty v0.0.3 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v0.0.0-20180718012357-94122c33edd3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/qiniu/api.v7 v7.2.4+incompatible
|
||||
github.com/qiniu/x v7.0.8+incompatible // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/ugorji/go v1.1.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac // indirect
|
||||
golang.org/x/net v0.0.0-20180824152047-4bcd98cce591 // indirect
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
|
||||
golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87 // indirect
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
qiniupkg.com/x v7.0.8+incompatible // indirect
|
||||
)
|
||||
68
go.sum
Normal file
68
go.sum
Normal file
@@ -0,0 +1,68 @@
|
||||
github.com/boj/redistore v0.0.0-20180825063928-0920d8493e7f h1:rvqqJygTOcUSBlPi+3PjaBMlq0pvlpqoR+ZqbwxsVP4=
|
||||
github.com/boj/redistore v0.0.0-20180825063928-0920d8493e7f/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deepzz0/logd v0.0.0-20171206094927-f91dd8c6316f h1:hjWy8ptp0ggYgv/3A8dixSB9KTRgDcZH2D3Ap5hrwOs=
|
||||
github.com/deepzz0/logd v0.0.0-20171206094927-f91dd8c6316f/go.mod h1:8jMj6ab9czIU5udq3ovaK9/5sCIyQ1JWteFMn8w2QRI=
|
||||
github.com/eiblog/blackfriday v0.0.0-20161010144836-c0ec111761ae h1:V6YC640Gs5jEUYfCimyuXsTW5gzNcIEESG4MGmOJCtA=
|
||||
github.com/eiblog/blackfriday v0.0.0-20161010144836-c0ec111761ae/go.mod h1:HzHqTCGEAkSSzBM3shBvQHsHRQYUvjNOIC4mHipZ6tI=
|
||||
github.com/eiblog/utils v0.0.0-20180209055746-a56f1b5e9c3c h1:iTojaGfteYN44kmydHUSgTlie7/CBsOQGHiH5xaogCU=
|
||||
github.com/eiblog/utils v0.0.0-20180209055746-a56f1b5e9c3c/go.mod h1:mZHWnipRp41yw/rti2DgpbMiBE5i6ifqg7PKooEwRh4=
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8Jc6zMvyRz3PCQrTTCXnVRvEzyBcM890=
|
||||
github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY=
|
||||
github.com/gin-gonic/contrib v0.0.0-20180614032058-39cfb9727134 h1:xgqFZVwmmtWiuq5LUZ/wa34hJR2Dm9NZAH+Cj9a7Hu0=
|
||||
github.com/gin-gonic/contrib v0.0.0-20180614032058-39cfb9727134/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
|
||||
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
|
||||
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE=
|
||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b h1:X61dhFTE1Au92SvyF8HyAwdjWqiSdfBgFR7wTxC0+uU=
|
||||
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180718012357-94122c33edd3 h1:YFBuDro+e1UCqlJpDWGucQaO/UNhBX1GlS8Du0GNfPw=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180718012357-94122c33edd3/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/qiniu/api.v7 v7.2.4+incompatible h1:AtooPoOzz93zhThX7XN/H1jALug+Ent/JisGgtNzwAs=
|
||||
github.com/qiniu/api.v7 v7.2.4+incompatible/go.mod h1:V8/EzlTgLN6q0s0CJmg/I81ytsvldSF22F7h6MI02+c=
|
||||
github.com/qiniu/x v7.0.8+incompatible h1:P4LASsfwJY7SoZ13dwqBwGhZh7HKU8cdFVCUkmz0gZ8=
|
||||
github.com/qiniu/x v7.0.8+incompatible/go.mod h1:KpRKWYG/GaidPQVpoQ2Cvuvtts3gYnoo2PftgdmAiU4=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w=
|
||||
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg=
|
||||
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180824152047-4bcd98cce591 h1:4S2XUgvg3hUNTvxI307qkFPb9zKHG3Nf9TXFzX/DZZI=
|
||||
golang.org/x/net v0.0.0-20180824152047-4bcd98cce591/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87 h1:GqwDwfvIpC33dK9bA1fD+JiDUNsuAiQiEkpHqUKze4o=
|
||||
golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
qiniupkg.com/x v7.0.8+incompatible h1:Ek0ZVi5IyaWUAFkJbPRiqlh34xDM4uoKw7KqdpankvU=
|
||||
qiniupkg.com/x v7.0.8+incompatible/go.mod h1:6sLxR5IZ03vMaRAQAY/5MvzofeoBIjO4XE0Njv6V1ms=
|
||||
23
helper.go
23
helper.go
@@ -18,6 +18,7 @@ const (
|
||||
FAIL
|
||||
)
|
||||
|
||||
// 月份转换
|
||||
var monthToDays = map[time.Month]int{
|
||||
time.January: 31,
|
||||
time.February: 28,
|
||||
@@ -43,14 +44,17 @@ func EncryptPasswd(name, pass string) string {
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
func VerifyPasswd(origin, name, input string) bool {
|
||||
return origin == EncryptPasswd(name, input)
|
||||
}
|
||||
|
||||
// 随机 uuid
|
||||
func RandUUIDv4() string {
|
||||
return uuid.NewV4().String()
|
||||
}
|
||||
|
||||
// 读取目录
|
||||
func ReadDir(dir string, filter func(name string) bool) (files []string) {
|
||||
fis, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
@@ -69,16 +73,18 @@ func ReadDir(dir string, filter func(name string) bool) (files []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// 去掉 html tag
|
||||
func IgnoreHtmlTag(src string) string {
|
||||
//去除所有尖括号内的HTML代码
|
||||
re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
|
||||
// 去除所有尖括号内的HTML代码
|
||||
re, _ := regexp.Compile(`<[\S\s]+?>`)
|
||||
src = re.ReplaceAllString(src, "")
|
||||
|
||||
//去除换行符
|
||||
re, _ = regexp.Compile("\\s{2,}")
|
||||
// 去除换行符
|
||||
re, _ = regexp.Compile(`\s+`)
|
||||
return re.ReplaceAllString(src, "")
|
||||
}
|
||||
|
||||
// 获取第一张图片
|
||||
func PickFirstImage(html string) string {
|
||||
re, _ := regexp.Compile(`data-src="(.*?)"`)
|
||||
sli := re.FindAllStringSubmatch(html, 1)
|
||||
@@ -98,15 +104,16 @@ const (
|
||||
YEARS_AGO = "%d年前"
|
||||
)
|
||||
|
||||
// 时间转换为间隔
|
||||
func ConvertStr(str string) string {
|
||||
t, err := time.ParseInLocation("2006-01-02T15:04:05", str, time.UTC)
|
||||
if err != nil {
|
||||
logd.Error(err, str)
|
||||
return JUST_NOW
|
||||
}
|
||||
now := time.Now()
|
||||
now := time.Now().UTC()
|
||||
y1, m1, d1 := t.Date()
|
||||
y2, m2, d2 := now.UTC().Date()
|
||||
y2, m2, d2 := now.Date()
|
||||
h1, mi1, s1 := t.Clock()
|
||||
h2, mi2, s2 := now.Clock()
|
||||
if y := y2 - y1; y > 1 || (y == 1 && m2-m1 >= 0) {
|
||||
@@ -118,11 +125,12 @@ func ConvertStr(str string) string {
|
||||
} else if h := d*24 + h2 - h1; h > 1 || (h == 1 && mi2-mi1 >= 0) {
|
||||
return fmt.Sprintf(HOURS_AGO, h)
|
||||
} else if mi := h*60 + mi2 - mi1; mi > 1 || (mi == 1 && s2-s1 >= 0) {
|
||||
return fmt.Sprintf(MINUTES_AGO, m)
|
||||
return fmt.Sprintf(MINUTES_AGO, mi)
|
||||
}
|
||||
return JUST_NOW
|
||||
}
|
||||
|
||||
// 获取天数
|
||||
func dayIn(year int, m time.Month) int {
|
||||
if m == time.February && isLeap(year) {
|
||||
return 29
|
||||
@@ -130,6 +138,7 @@ func dayIn(year int, m time.Month) int {
|
||||
return monthToDays[m]
|
||||
}
|
||||
|
||||
// 是否是闰年
|
||||
func isLeap(year int) bool {
|
||||
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
||||
}
|
||||
|
||||
64
helper_test.go
Normal file
64
helper_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Package main provides ...
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReadDir(t *testing.T) {
|
||||
files := ReadDir("setting", func(name string) bool { return false })
|
||||
assert.Len(t, files, 2)
|
||||
}
|
||||
|
||||
func TestIgnoreHtmlTag(t *testing.T) {
|
||||
testStr := []string{
|
||||
"<script>hello</script>",
|
||||
}
|
||||
|
||||
expectStr := []string{
|
||||
"hello",
|
||||
}
|
||||
|
||||
for i, v := range testStr {
|
||||
assert.Equal(t, expectStr[i], IgnoreHtmlTag(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickFirstImage(t *testing.T) {
|
||||
testStr := []string{
|
||||
`<img width="480" height="310" alt="acme_aliyun_1" src="https://st.deepzz.com/blog/img/acme_aliyun_1.jpg">`,
|
||||
`<img width="480" height="310" alt="acme_aliyun_1" data-src="https://st.deepzz.com/acme_aliyun_1.jpg"><img width="480" height="310" alt="acme_aliyun_1" src="https://st.deepzz.com/blog/img/acme_aliyun_1.jpg">`,
|
||||
}
|
||||
|
||||
expectStr := []string{
|
||||
"",
|
||||
"https://st.deepzz.com/acme_aliyun_1.jpg",
|
||||
}
|
||||
|
||||
for i, v := range testStr {
|
||||
assert.Equal(t, expectStr[i], PickFirstImage(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovertStr(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
testStr := []string{
|
||||
now.Format("2006-01-02T15:04:05"),
|
||||
now.Add(-time.Second * 20).Format("2006-01-02T15:04:05"),
|
||||
now.Add(-time.Minute).Format("2006-01-02T15:04:05"),
|
||||
now.Add(-time.Minute * 2).Format("2006-01-02T15:04:05"),
|
||||
now.Add(-time.Minute * 20).Format("2006-01-02T15:04:05"),
|
||||
now.Add(-time.Hour).Format("2006-01-02T15:04:05"),
|
||||
now.Add(-time.Hour * 2).Format("2006-01-02T15:04:05"),
|
||||
now.Add(-time.Hour * 24).Format("2006-01-02T15:04:05"),
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
t.Log(now.Format("2006-01-02T15:04:05"))
|
||||
for _, v := range testStr {
|
||||
t.Log(v, ConvertStr(v))
|
||||
}
|
||||
}
|
||||
2
model.go
2
model.go
@@ -121,6 +121,8 @@ type Article struct {
|
||||
Excerpt string `bson:"-"`
|
||||
// 一句话描述,文章第一句
|
||||
Desc string `bson:"-"`
|
||||
// disqus thread
|
||||
Thread string `bson:"-"`
|
||||
}
|
||||
|
||||
type SortArticles []*Article
|
||||
|
||||
4
ping.go
4
ping.go
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -64,7 +65,7 @@ func (p *pingRPC) PingFunc(slug string) {
|
||||
if len(setting.Conf.PingRPCs) == 0 {
|
||||
return
|
||||
}
|
||||
p.Params.Param[1].Value = "https://" + setting.Conf.Mode.Domain + "/post/" + slug + ".html"
|
||||
p.Params.Param[1].Value = fmt.Sprintf("https://%s/post/%s.html", setting.Conf.Mode.Domain, slug)
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteString(xml.Header)
|
||||
enc := xml.NewEncoder(buf)
|
||||
@@ -105,6 +106,7 @@ func init() {
|
||||
Pings = append(Pings, pr)
|
||||
}
|
||||
|
||||
// ping
|
||||
func DoPings(slug string) {
|
||||
for _, p := range Pings {
|
||||
go p.PingFunc(slug)
|
||||
|
||||
92
qiniu.go
92
qiniu.go
@@ -7,94 +7,77 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
"qiniupkg.com/api.v7/kodo"
|
||||
"qiniupkg.com/api.v7/kodocli"
|
||||
url "qiniupkg.com/x/url.v7"
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var qiniu_cfg = &kodo.Config{
|
||||
AccessKey: setting.Conf.Kodo.AccessKey,
|
||||
SecretKey: setting.Conf.Kodo.SecretKey,
|
||||
Scheme: "https",
|
||||
}
|
||||
|
||||
type bucket struct {
|
||||
name string
|
||||
domain string
|
||||
accessKey string
|
||||
secretKey string
|
||||
}
|
||||
|
||||
type PutRet struct {
|
||||
Hash string `json:"hash"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// 进度条
|
||||
func onProgress(fsize, uploaded int64) {
|
||||
d := int(float64(uploaded) / float64(fsize) * 100)
|
||||
if fsize == uploaded {
|
||||
fmt.Printf("\rUpload completed! ")
|
||||
fmt.Printf("\rUpload completed! \n")
|
||||
} else {
|
||||
fmt.Printf("\r%02d%% uploaded ", int(d))
|
||||
}
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
func FileUpload(name string, size int64, data io.Reader) (string, error) {
|
||||
if setting.Conf.Kodo.AccessKey == "" || setting.Conf.Kodo.SecretKey == "" {
|
||||
if setting.Conf.Qiniu.AccessKey == "" || setting.Conf.Qiniu.SecretKey == "" {
|
||||
return "", errors.New("qiniu config error")
|
||||
}
|
||||
|
||||
// 创建一个client
|
||||
c := kodo.New(0, qiniu_cfg)
|
||||
|
||||
key := getKey(name)
|
||||
mac := qbox.NewMac(setting.Conf.Qiniu.AccessKey, setting.Conf.Qiniu.SecretKey)
|
||||
// 设置上传的策略
|
||||
policy := &kodo.PutPolicy{
|
||||
Scope: setting.Conf.Kodo.Name,
|
||||
putPolicy := &storage.PutPolicy{
|
||||
Scope: setting.Conf.Qiniu.Bucket,
|
||||
Expires: 3600,
|
||||
InsertOnly: 1,
|
||||
}
|
||||
// 上传token
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
|
||||
// 生成一个上传token
|
||||
token := c.MakeUptoken(policy)
|
||||
// 构建一个uploader
|
||||
zone := 0
|
||||
uploader := kodocli.NewUploader(zone, nil)
|
||||
|
||||
key := getKey(name)
|
||||
if key == "" {
|
||||
return "", errors.New("不支持的文件类型")
|
||||
// 上传配置
|
||||
cfg := &storage.Config{
|
||||
Zone: &storage.ZoneHuadong,
|
||||
UseHTTPS: true,
|
||||
}
|
||||
// uploader
|
||||
uploader := storage.NewFormUploader(cfg)
|
||||
ret := new(storage.PutRet)
|
||||
putExtra := &storage.PutExtra{}
|
||||
|
||||
var ret PutRet
|
||||
var extra = kodocli.PutExtra{OnProgress: onProgress}
|
||||
err := uploader.Put(nil, &ret, token, key, data, size, &extra)
|
||||
err := uploader.Put(nil, ret, upToken, key, data, size, putExtra)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
url := "https://" + setting.Conf.Kodo.Domain + "/" + url.Escape(key)
|
||||
url := "https://" + setting.Conf.Qiniu.Domain + "/" + key
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
func FileDelete(name string) error {
|
||||
// new一个Bucket管理对象
|
||||
c := kodo.New(0, qiniu_cfg)
|
||||
p := c.Bucket(setting.Conf.Kodo.Name)
|
||||
|
||||
key := getKey(name)
|
||||
if key == "" {
|
||||
return errors.New("不支持的文件类型")
|
||||
}
|
||||
|
||||
// 调用Delete方法删除文件
|
||||
err := p.Delete(nil, key)
|
||||
// 打印返回值以及出错信息
|
||||
mac := qbox.NewMac(setting.Conf.Qiniu.AccessKey, setting.Conf.Qiniu.SecretKey)
|
||||
// 上传配置
|
||||
cfg := &storage.Config{
|
||||
Zone: &storage.ZoneHuadong,
|
||||
UseHTTPS: true,
|
||||
}
|
||||
// manager
|
||||
bucketManager := storage.NewBucketManager(mac, cfg)
|
||||
// Delete
|
||||
err := bucketManager.Delete(setting.Conf.Qiniu.Bucket, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 修复路径
|
||||
func getKey(name string) string {
|
||||
ext := filepath.Ext(name)
|
||||
var key string
|
||||
@@ -103,9 +86,12 @@ func getKey(name string) string {
|
||||
key = "blog/img/" + name
|
||||
case ".mov", ".mp4":
|
||||
key = "blog/video/" + name
|
||||
case ".go", ".js", ".css", ".cpp", ".php", ".rb", ".java", ".py", ".sql", ".lua", ".html", ".sh", ".xml", ".cs":
|
||||
case ".go", ".js", ".css", ".cpp", ".php", ".rb",
|
||||
".java", ".py", ".sql", ".lua", ".html",
|
||||
".sh", ".xml", ".cs":
|
||||
key = "blog/code/" + name
|
||||
case ".txt", ".md", ".ini", ".yaml", ".yml", ".doc", ".ppt", ".pdf":
|
||||
case ".txt", ".md", ".ini", ".yaml", ".yml",
|
||||
".doc", ".ppt", ".pdf":
|
||||
key = "blog/document/" + name
|
||||
case ".zip", ".rar", ".tar", ".gz":
|
||||
key = "blog/archive/" + name
|
||||
|
||||
@@ -7,11 +7,13 @@ import (
|
||||
)
|
||||
|
||||
func TestUpload(t *testing.T) {
|
||||
path := "/Users/chen/Desktop/png-MicroService-by-StuQ.png"
|
||||
path := "qiniu.go"
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
info, _ := file.Stat()
|
||||
url, err := FileUpload(info.Name(), info.Size(), file)
|
||||
if err != nil {
|
||||
|
||||
53
router.go
53
router.go
@@ -2,13 +2,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
"github.com/eiblog/utils/logd"
|
||||
"github.com/eiblog/utils/tmpl"
|
||||
"github.com/gin-gonic/autotls"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -19,12 +21,19 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
// 运行模式
|
||||
if setting.Conf.RunMode == setting.PROD {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
logd.SetLevel(logd.Lerror)
|
||||
}
|
||||
|
||||
router = gin.Default()
|
||||
store := sessions.NewCookieStore([]byte("eiblog321"))
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
store := sessions.NewCookieStore(b)
|
||||
store.Options(sessions.Options{
|
||||
MaxAge: 86400 * 7,
|
||||
Path: "/",
|
||||
@@ -40,7 +49,7 @@ func init() {
|
||||
}
|
||||
return false
|
||||
})
|
||||
_, err := Tmpl.ParseFiles(files...)
|
||||
_, err = Tmpl.ParseFiles(files...)
|
||||
if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
@@ -62,6 +71,8 @@ func init() {
|
||||
router.GET("/opensearch.xml", HandleOpenSearch)
|
||||
router.GET("/sitemap.xml", HandleSitemap)
|
||||
router.GET("/robots.txt", HandleRobots)
|
||||
router.GET("/crossdomain.xml", HandleCrossDomain)
|
||||
router.GET("/favicon.ico", HandleFavicon)
|
||||
// 后台相关
|
||||
admin := router.Group("/admin")
|
||||
admin.GET("/login", HandleLogin)
|
||||
@@ -87,6 +98,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// 开始运行
|
||||
func Run() {
|
||||
var (
|
||||
endRunning = make(chan bool, 1)
|
||||
@@ -94,25 +106,38 @@ func Run() {
|
||||
)
|
||||
if setting.Conf.Mode.EnableHttp {
|
||||
go func() {
|
||||
logd.Infof("http server Running on %d\n", setting.Conf.Mode.HttpPort)
|
||||
logd.Printf("http server Running on %d\n", setting.Conf.Mode.HttpPort)
|
||||
err = router.Run(fmt.Sprintf(":%d", setting.Conf.Mode.HttpPort))
|
||||
if err != nil {
|
||||
logd.Info("ListenAndServe: ", err)
|
||||
logd.Error("ListenAndServe: ", err)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
endRunning <- true
|
||||
}
|
||||
}()
|
||||
}
|
||||
if setting.Conf.Mode.EnableHttps {
|
||||
go func() {
|
||||
logd.Infof("https server Running on %d\n", setting.Conf.Mode.HttpsPort)
|
||||
err = router.RunTLS(fmt.Sprintf(":%d", setting.Conf.Mode.HttpsPort), setting.Conf.Mode.CertFile, setting.Conf.Mode.KeyFile)
|
||||
if err != nil {
|
||||
logd.Info("ListenAndServe: ", err)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
endRunning <- true
|
||||
}
|
||||
}()
|
||||
if setting.Conf.Mode.AutoCert {
|
||||
go func() {
|
||||
logd.Print("https server Running on 443")
|
||||
err = autotls.Run(router, setting.Conf.Mode.Domain)
|
||||
if err != nil {
|
||||
logd.Error("ListenAndServe: ", err)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
endRunning <- true
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
go func() {
|
||||
logd.Printf("https server Running on %d\n", setting.Conf.Mode.HttpsPort)
|
||||
err = router.RunTLS(fmt.Sprintf(":%d", setting.Conf.Mode.HttpsPort),
|
||||
setting.Conf.Mode.CertFile, setting.Conf.Mode.KeyFile)
|
||||
if err != nil {
|
||||
logd.Error("ListenAndServe: ", err)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
endRunning <- true
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
<-endRunning
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DEV = "dev"
|
||||
PROD = "prod"
|
||||
DEV = "dev" // 该模式会输出 debug 等信息
|
||||
PROD = "prod" // 该模式用于生产环境
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -19,55 +19,69 @@ var (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
StaticVersion int // 当前静态文件版本
|
||||
RunMode string // 运行模式
|
||||
Trash int // 回收箱文章保留时间
|
||||
Clean int // 清理回收箱频率
|
||||
PageNum int // 前端每页文章数量
|
||||
PageSize int // 后台每页文章数量
|
||||
Length int // 自动截取预览长度
|
||||
Identifier string // 截取标示
|
||||
Description string // 文章描述前缀
|
||||
Favicon string // icon地址
|
||||
StartID int32 // 文章起始id
|
||||
SearchURL string // elasticsearch 地址
|
||||
Disqus struct { // 获取文章数量相关
|
||||
ShortName string
|
||||
PublicKey string
|
||||
PostsCount string
|
||||
PostsList string
|
||||
PostCreate string
|
||||
Interval int
|
||||
StaticVersion int // 当前静态文件版本
|
||||
FeedrURL string // superfeedr url
|
||||
HotWords []string // 热搜词
|
||||
PingRPCs []string // ping rpc 地址
|
||||
General struct {
|
||||
PageNum int // 前端每页文章数量
|
||||
PageSize int // 后台每页文章数量
|
||||
StartID int32 // 文章起始id
|
||||
DescPrefix string // 文章描述前缀
|
||||
Identifier string // 文章截取标示
|
||||
Length int // 文章自动截取预览长度
|
||||
Trash int // 回收箱文章保留时间
|
||||
Clean int // 清理回收箱频率
|
||||
}
|
||||
HotWords []string // 热搜词
|
||||
Google struct { // 谷歌统计
|
||||
Disqus struct { // 获取文章数量相关
|
||||
ShortName string
|
||||
PublicKey string
|
||||
AccessToken string
|
||||
PostsCount string
|
||||
PostsList string
|
||||
PostCreate string
|
||||
PostApprove string
|
||||
ThreadCreate string
|
||||
Embed string
|
||||
Interval int
|
||||
}
|
||||
Google struct { // 谷歌统计
|
||||
URL string
|
||||
Tid string
|
||||
V string
|
||||
T string
|
||||
}
|
||||
Kodo struct { // 七牛CDN
|
||||
Name string
|
||||
Qiniu struct { // 七牛CDN
|
||||
Bucket string
|
||||
Domain string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
}
|
||||
Mode RunMode // 运行模式
|
||||
Mode struct { // 运行模式
|
||||
EnableHttp bool
|
||||
HttpPort int
|
||||
EnableHttps bool
|
||||
HttpsPort int
|
||||
CertFile string
|
||||
KeyFile string
|
||||
AutoCert bool
|
||||
Domain string
|
||||
}
|
||||
Twitter struct { // twitter信息
|
||||
Card string
|
||||
Site string
|
||||
Image string
|
||||
Address string
|
||||
}
|
||||
FeedrURL string // superfeedr url
|
||||
PingRPCs []string // ping rpc 地址
|
||||
Account struct {
|
||||
Account struct { // account 账户
|
||||
Username string // *
|
||||
Password string // *
|
||||
Email string
|
||||
PhoneNumber string
|
||||
Address string
|
||||
}
|
||||
Blogger struct { // 初始化数据
|
||||
Blogger struct { // blog info 博客信息
|
||||
BlogName string
|
||||
SubTitle string
|
||||
BeiAn string
|
||||
@@ -76,16 +90,6 @@ type Config struct {
|
||||
}
|
||||
}
|
||||
|
||||
type RunMode struct {
|
||||
EnableHttp bool
|
||||
HttpPort int
|
||||
EnableHttps bool
|
||||
HttpsPort int
|
||||
CertFile string
|
||||
KeyFile string
|
||||
Domain string
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 初始化配置
|
||||
data, err := ioutil.ReadFile("conf/app.yml")
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
init()
|
||||
fmt.Printf("%v\n", *Conf)
|
||||
assert.NotNil(t, Conf)
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
|
||||
<cross-domain-policy>
|
||||
<allow-access-from domain="*" />
|
||||
<allow-http-request-headers-from domain="*" headers="*"/>
|
||||
</cross-domain-policy>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,6 +0,0 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://deepzz.com/sitemap.xml
|
||||
|
||||
User-agent: MJ12bot
|
||||
Disallow: /
|
||||
@@ -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">
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<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>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="row typecho-page-main">
|
||||
<div class="col-mb-12 col-tb-3">
|
||||
<p>
|
||||
<img class="profile-avatar" src="//{{$.Kodo.Domain}}/static/img/avatar.jpg" alt="{{.BlogName}}" />
|
||||
<img class="profile-avatar" src="//{{$.Qiniu.Domain}}/static/img/avatar.jpg" alt="{{.BlogName}}" />
|
||||
</p>
|
||||
<h2>{{.BlogName}}</h2>
|
||||
<p>{{.SubTitle}}</p>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -45,10 +45,11 @@
|
||||
<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><a class="balloon-button left size-50" href="#">{{len .Articles}}</a></td>
|
||||
</tr>
|
||||
|
||||
@@ -1,2 +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><meta name=robots content="noindex, nofollow, noarchive"><title>{{.Title}}</title><style type="text/css">*{margin:0;padding:0}html,body{height:100%}body{background:#fff;color:#2a2e2e;font-size:15px;font-family:"Helvetica Neue",arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}::selection,::-moz-selection,::-webkit-selection{background-color:#2479CC;color:#eee}h3{font-size:1.3em;line-height:1.5;margin:15px 30px;text-align:center}a{color:#2479CC;text-decoration:none}.card{margin:15px 25px;text-align:left}.submit input,.submit textarea{-webkit-appearance:none;border:1px solid #bbb;border-radius:1px;font-size:15px;height:20px;line-height:20px;margin-left:10px;padding:6px 8px}.submit span{position:relative;top:8px}.submit li{display:-webkit-box;margin:15px 0}.submit textarea{height:130px}.submit .line{-webkit-box-flex:1;display:block}.submit .btn-submit{-webkit-appearance:none;background:#12b0e6;border:none;border-radius:0;box-shadow:inset 0 -5px 20px rgba(0,0,0,.1);color:#fff;cursor:pointer;display:block;font-size:14px;line-height:1;padding:0.625em .5em;width:100%}.submit li.tips{color:#999;font-size:14px}</style></head><body><header><h3>对「{{.ATitle}}」发表评论</h3></header><div class=bd><div class="card submit"><form onsubmit="return false" id="create-post"><ul><li><span>昵称:</span><input class=line name=author_name required><li><span>邮箱:</span><input class=line name=author_email type=email required><li><span>内容:</span><textarea class="line" name="message" required></textarea><li><input type=hidden name=thread value="{{.Thread}}"><input type=hidden name=parent value=""><input type=hidden name=identifier value="post-troubleshooting-https"><input type=hidden name=next value=""><button class="btn-submit" type=submit>立即发表</button><li class=tips>注:通过本表单提交的数据,会原样转发给 Disqus,本站不做任何存储和记录。<li><a href="#close" onclick="window.close();void(0)">放弃评论</a></ul></form></div></div><footer></footer><script src="https://cdn.bootcss.com/jquery/3.1.1/jquery.min.js" ></script><script>!function(a){function e(){try{localStorage.author_name=$('[name="author_name"]').val(),localStorage.author_email=$('[name="author_email"]').val()}catch(a){}}function t(){$('[name="author_name"]').val(localStorage.author_name),$('[name="author_email"]').val(localStorage.author_email),["author_name","author_email","message"].some(function(a){var e=$('[name="'+a+'"]');return e.val()?void 0:e.focus()})}var o=!1;$("#create-post").on("submit",function(e){if(e.preventDefault(),!o){o=!0;var t=$(".tips");t.html("提交中..."),$.post("/disqus/create",$("#create-post").serialize()).then(function(e){if(o=!1,e.errno)t.html("提交失败:"+e.errmsg);else{$(".btn-submit").prop("disabled",!0),t.html('提交成功!本窗口 <i id="timer">5</i> 秒后自动关闭。');try{setTimeout(function(){a.opener.location.hash="comment-"+e.data.id,a.opener.location.reload(),a.close()},5e3)}catch(n){}setInterval(function(){var a=$("#timer");a.html(Math.max(0,parseInt(a.html()))-1)},1e3)}})}}),t(),$('[name="author_name"], [name="author_email"]').on("change",e)}(this);</script></body></html>
|
||||
<!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><meta name=robots content="noindex, nofollow, noarchive"><title>{{.Title}}</title><style type="text/css">*{margin:0;padding:0}html,body{height:100%}body{background:#fff;color:#2a2e2e;font-size:15px;font-family:"Helvetica Neue",arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}::selection,::-moz-selection,::-webkit-selection{background-color:#2479CC;color:#eee}h3{font-size:1.3em;line-height:1.5;margin:15px 30px;text-align:center}a{color:#2479CC;text-decoration:none}.card{margin:15px 25px;text-align:left}.submit input,.submit textarea{-webkit-appearance:none;border:1px solid #bbb;border-radius:1px;font-size:15px;height:20px;line-height:20px;margin-left:10px;padding:6px 8px}.submit span{position:relative;top:8px}.submit li{display:-webkit-box;display:-ms-flexbox;display:flex;margin:15px 0}.submit textarea{height:130px}.submit .line{-webkit-box-flex:1;display:block;-ms-flex:1;flex:1}.submit .btn-submit{-webkit-appearance:none;background:#12b0e6;border:none;border-radius:0;box-shadow:inset 0 -5px 20px rgba(0,0,0,.1);color:#fff;cursor:pointer;display:block;font-size:14px;line-height:1;padding:0.625em .5em;width:100%}.submit li.tips{color:#999;font-size:14px}</style></head><body><header><h3>对「{{.ATitle}}」发表评论</h3></header><div class=bd><div class="card submit"><form onsubmit="return false" id="create-post"><ul><li><span>昵称:</span><input class=line name=author_name required placeholder="昵称会被公开显示"><li><span>邮箱:</span><input class=line name=author_email type=email required placeholder="邮箱不会公开显示"><li><span>内容:</span><textarea class="line" name="message" required placeholder="请不要发表无意义的评论内容"></textarea><li><input type=hidden name=thread value="{{.Thread}}"><input type=hidden name=parent value=""><input type=hidden name=identifier value="post-{{.Slug}}"><input type=hidden name=next value=""><button class="btn-submit" type=submit>立即发表</button><li class=tips>注:通过本表单提交的数据,会原样转发给 Disqus,本站不做任何存储和记录。<li><a href="#close" onclick="window.close();void(0)">放弃评论</a></ul></form></div></div><footer></footer><script src="https://cdn.bootcss.com/jquery/3.1.1/jquery.min.js" ></script><script>!function(a){function e(){try{localStorage.author_name=$('[name="author_name"]').val(),localStorage.author_email=$('[name="author_email"]').val()}catch(a){}}function t(){$('[name="author_name"]').val(localStorage.author_name),$('[name="author_email"]').val(localStorage.author_email),["author_name","author_email","message"].some(function(a){var e=$('[name="'+a+'"]');return e.val()?void 0:e.focus()})}var o=!1;$("#create-post").on("submit",function(e){if(e.preventDefault(),!o){o=!0;var t=$(".tips");t.html("提交中..."),$.post("/disqus/create",$("#create-post").serialize()).then(function(e){o=!1,e.errno?t.html("提交失败:"+e.errmsg):($(".btn-submit").prop("disabled",!0),t.html("提交成功!本窗口即将关闭。"),setTimeout(function(){try{a.opener.location.hash="comment-"+e.data.id,a.opener.TotalThread.currentServer.insertItem(e.data),a.close()}catch(t){a.close()}},1e3))})}}),t(),$('[name="author_name"], [name="author_email"]').on("change",e)}(this)</script></body></html>
|
||||
|
||||
@@ -1 +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><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="//{{.Kodo.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></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}} - <a target="_blank" rel="nofollow designer" class="external beian" href="http://www.miitbeian.gov.cn/">{{.BeiAn}}</a><br>Powered by <a target=_blank href="//github.com/eiblog/eiblog">Eiblog</a> & <a target=_blank rel="nofollow designer" class=external href="//imququ.com">JerryQu</a></footer></div></div><input type=hidden id=CURRENT_PAGE value="{{.CurrentPage}}"><input type=hidden id=CDN_DOMAIN value="//{{.Kodo.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>
|
||||
<!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><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></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}} - <a target="_blank" rel="nofollow designer" class="external beian" href="http://www.miitbeian.gov.cn/">{{.BeiAn}}</a><br>Powered by <a target=_blank href="//github.com/eiblog/eiblog">Eiblog</a> & <a target=_blank rel="nofollow designer" class=external href="//imququ.com">JerryQu</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>
|
||||
|
||||
@@ -1 +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" value="{{.Word}}" 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}}
|
||||
{{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}}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
63
xml.go
63
xml.go
@@ -25,11 +25,14 @@ func init() {
|
||||
if err != nil {
|
||||
logd.Fatal(err)
|
||||
}
|
||||
doFeed()
|
||||
doSitemap()
|
||||
doOpensearch()
|
||||
go doFeed()
|
||||
go doSitemap()
|
||||
doRobots()
|
||||
doCrossdomain()
|
||||
}
|
||||
|
||||
// 定时更新 feed
|
||||
func doFeed() {
|
||||
tpl := tpls.Lookup("feedTpl.xml")
|
||||
if tpl == nil {
|
||||
@@ -47,7 +50,7 @@ func doFeed() {
|
||||
"Artcs": artcs,
|
||||
}
|
||||
|
||||
f, err := os.OpenFile("static/feed.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
|
||||
f, err := os.OpenFile("static/feed.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
@@ -61,6 +64,7 @@ func doFeed() {
|
||||
time.AfterFunc(time.Hour*4, doFeed)
|
||||
}
|
||||
|
||||
// 定时更新 sitemap
|
||||
func doSitemap() {
|
||||
tpl := tpls.Lookup("sitemapTpl.xml")
|
||||
if tpl == nil {
|
||||
@@ -68,7 +72,7 @@ func doSitemap() {
|
||||
return
|
||||
}
|
||||
params := map[string]interface{}{"Artcs": Ei.Articles, "Domain": setting.Conf.Mode.Domain}
|
||||
f, err := os.OpenFile("static/sitemap.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
|
||||
f, err := os.OpenFile("static/sitemap.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
@@ -79,9 +83,10 @@ func doSitemap() {
|
||||
logd.Error(err)
|
||||
return
|
||||
}
|
||||
time.AfterFunc(time.Hour*24, doFeed)
|
||||
time.AfterFunc(time.Hour*24, doSitemap)
|
||||
}
|
||||
|
||||
// 渲染 opensearch
|
||||
func doOpensearch() {
|
||||
tpl := tpls.Lookup("opensearchTpl.xml")
|
||||
if tpl == nil {
|
||||
@@ -93,7 +98,53 @@ func doOpensearch() {
|
||||
"SubTitle": Ei.SubTitle,
|
||||
"Domain": setting.Conf.Mode.Domain,
|
||||
}
|
||||
f, err := os.OpenFile("static/opensearch.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
|
||||
f, err := os.OpenFile("static/opensearch.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
err = tpl.Execute(f, params)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染 robots
|
||||
func doRobots() {
|
||||
tpl := tpls.Lookup("robotsTpl.xml")
|
||||
if tpl == nil {
|
||||
logd.Error("not found robotsTpl.")
|
||||
return
|
||||
}
|
||||
params := map[string]string{
|
||||
"Domain": setting.Conf.Mode.Domain,
|
||||
}
|
||||
f, err := os.OpenFile("static/robots.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
err = tpl.Execute(f, params)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染 cross domain
|
||||
func doCrossdomain() {
|
||||
tpl := tpls.Lookup("crossdomainTpl.xml")
|
||||
if tpl == nil {
|
||||
logd.Error("not found crossdomainTpl.")
|
||||
return
|
||||
}
|
||||
params := map[string]string{
|
||||
"Domain": setting.Conf.Mode.Domain,
|
||||
}
|
||||
f, err := os.OpenFile("static/crossdomain.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user