Compare commits
356 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd7981b4f9 | ||
|
|
9f77fb7b3f | ||
|
|
f201499945 | ||
|
|
d7736abb25 | ||
|
|
3c4fa6d08a | ||
|
|
ccb5e4546e | ||
|
|
6ce6411da0 | ||
|
|
cb2ed7cb82 | ||
|
|
ea566d1650 | ||
|
|
88dccca295 | ||
|
|
cb11a2b8db | ||
|
|
ed03a264f0 | ||
|
|
aee4194c71 | ||
|
|
b69248f6a4 | ||
|
|
24bfe528b2 | ||
|
|
23e35dcefa | ||
|
|
f4c70b46c1 | ||
|
|
e5100fa018 | ||
|
|
91e1731909 | ||
|
|
4abe528742 | ||
|
|
be0280ac56 | ||
|
|
a0b41d08bd | ||
|
|
8fcabd5e15 | ||
|
|
0a410f09f3 | ||
|
|
0fe849ae67 | ||
|
|
c06a32a268 | ||
|
|
79fccb958c | ||
|
|
8e2679e49f | ||
|
|
52fe7303f3 | ||
|
|
616248d33f | ||
|
|
6e1965a764 | ||
|
|
27bc610a31 | ||
|
|
b53fc91ce7 | ||
|
|
720387ecd5 | ||
|
|
88f23bd1a0 | ||
|
|
6a2d720d36 | ||
|
|
95e55ee13c | ||
|
|
ca293a4933 | ||
|
|
c82d73ca34 | ||
|
|
433064de00 | ||
|
|
9d71ca8198 | ||
|
|
7c938bf024 | ||
|
|
65fcc69efa | ||
|
|
d06bab72a5 | ||
|
|
95900eec1a | ||
|
|
dfae78891d | ||
|
|
c515e33e2c | ||
|
|
5b12dd625b | ||
|
|
9b918caccd | ||
|
|
2be53379e1 | ||
|
|
944e27c58a | ||
|
|
92baf970bc | ||
|
|
64a754167a | ||
|
|
af2a20c34a | ||
|
|
f28d0e77e0 | ||
|
|
9a1b4db61a | ||
|
|
c9d04aded3 | ||
|
|
ee51f678cb | ||
|
|
434c1bf168 | ||
|
|
d2de603957 | ||
|
|
e7fdf6b1db | ||
|
|
305ae0ee70 | ||
|
|
a5749fdab6 | ||
|
|
391f19e2a9 | ||
|
|
2c6c5bb977 | ||
|
|
860a85e209 | ||
|
|
2a8d9b3bbd | ||
|
|
1509a68cda | ||
|
|
7f26503247 | ||
|
|
235145ed46 | ||
|
|
20e89d6a76 | ||
|
|
d86ab71ad9 | ||
|
|
4600ed5094 | ||
|
|
5fcadd1c81 | ||
|
|
42b106d582 | ||
|
|
bb06be36fe | ||
|
|
cb3fb2d2e7 | ||
|
|
779a23cb75 | ||
|
|
e2fa96cd62 | ||
|
|
db26fb51e5 | ||
|
|
994be5d508 | ||
|
|
a5c3d33565 | ||
|
|
1ffc58eccf | ||
|
|
b6ad4e8949 | ||
|
|
41704917db | ||
|
|
f6ba716f55 | ||
|
|
b2e6c168c5 | ||
|
|
eca741896f | ||
|
|
17792e5a7e | ||
|
|
04289c633e | ||
|
|
3a5eb6fccc | ||
|
|
f6d8656c83 | ||
|
|
4690d5123b | ||
|
|
a9e8e39d34 | ||
|
|
c51055a0db | ||
|
|
445b188517 | ||
|
|
4bfff2e5e9 | ||
|
|
aa91997c0c | ||
|
|
3b2a6689be | ||
|
|
4c46be3f03 | ||
|
|
da47e9880f | ||
|
|
4f92e0d619 | ||
|
|
3a8f7d120b | ||
|
|
cf0a897ad0 | ||
|
|
418b604946 | ||
|
|
b93c320987 | ||
|
|
b24f7c0666 | ||
|
|
efe80fbc6b | ||
|
|
65b89bcae5 | ||
|
|
289b8145bc | ||
|
|
bf0ad811ff | ||
|
|
db00a9b527 | ||
|
|
38aa704198 | ||
|
|
9c58447e3b | ||
|
|
34fc5f368c | ||
|
|
daa561e67e | ||
|
|
364d293660 | ||
|
|
5170844623 | ||
|
|
7047a3599d | ||
|
|
d0c830c1e6 | ||
|
|
bf699e4957 | ||
|
|
c315737871 | ||
|
|
d844c0e8f8 | ||
|
|
f6cb55c00f | ||
|
|
a5292027c0 | ||
|
|
00cf0b5c9f | ||
|
|
6805afa759 | ||
|
|
4f6f85a85a | ||
|
|
dc3e6659b5 | ||
|
|
ca74d13598 | ||
|
|
82fba0ddb4 | ||
|
|
2b6bae1f74 | ||
|
|
6387412776 | ||
|
|
cfaa25e123 | ||
|
|
6ff6acdbda | ||
|
|
b3b3be448f | ||
|
|
78f5bfc1ce | ||
|
|
4b9eb1ff4d | ||
|
|
7d04b8f5c0 | ||
|
|
d000090e30 | ||
|
|
56e396f252 | ||
|
|
f6662fa4c5 | ||
|
|
a570c783f3 | ||
|
|
e2046d0d39 | ||
|
|
cdbe082764 | ||
|
|
3a86c6a65c | ||
|
|
c1c9e6025a | ||
|
|
a15791a792 | ||
|
|
ea87f4b2e6 | ||
|
|
a0653cf67f | ||
|
|
397d47bc00 | ||
|
|
00d9df4406 | ||
|
|
2b277bd901 | ||
|
|
c18f3b7da9 | ||
|
|
79ac024312 | ||
|
|
1e4c0afc19 | ||
|
|
055d7d2164 | ||
|
|
ed7c510744 | ||
|
|
488c0f04fe | ||
|
|
a99ed9504b | ||
|
|
6cde9323b1 | ||
|
|
2b226977ab | ||
|
|
aa076e5e3c | ||
|
|
26635790e1 | ||
|
|
569a1fe2a1 | ||
|
|
182eaff085 | ||
|
|
90e7082322 | ||
|
|
e71e42571f | ||
|
|
3527e11b04 | ||
|
|
a407bdfe72 | ||
|
|
05aa254e70 | ||
|
|
4ec3065f9d | ||
|
|
7bec238f9c | ||
|
|
605787958d | ||
|
|
c06f02622a | ||
|
|
f431bf5adc | ||
|
|
7922043bc6 | ||
|
|
2fbc5fa024 | ||
|
|
9c341f88d2 | ||
|
|
0ae5e6c9fc | ||
|
|
3e2b746319 | ||
|
|
2cd72e190e | ||
|
|
1c955769f4 | ||
|
|
a2769f0913 | ||
|
|
bf3d45a404 | ||
|
|
4b92636079 | ||
|
|
753aaa4ace | ||
|
|
43d7a26e19 | ||
|
|
872d0b1987 | ||
|
|
897b05d071 | ||
|
|
c5a3bd6eab | ||
|
|
beea4f1746 | ||
|
|
9f563f0ae9 | ||
|
|
4749b1e681 | ||
|
|
a39e3aac3b | ||
|
|
63b55b2df8 | ||
|
|
cb091532d5 | ||
|
|
6b74e1d208 | ||
|
|
1815cea2cd | ||
|
|
c3fdbfcb78 | ||
|
|
990e6abbd8 | ||
|
|
bb40570053 | ||
|
|
e2df642a46 | ||
|
|
68e01cdf1f | ||
|
|
bd69c62254 | ||
|
|
970c6068d5 | ||
|
|
27a1f600de | ||
|
|
edf0fbac51 | ||
|
|
98ca570a36 | ||
|
|
9a526f97f8 | ||
|
|
33a29f5e57 | ||
|
|
30ebf76eda | ||
|
|
f5c0bcdb99 | ||
|
|
4b53da3801 | ||
|
|
1bdfb6abea | ||
|
|
33f47d8f3a | ||
|
|
cbd0cfaaf5 | ||
|
|
080c992a92 | ||
|
|
371b2326ea | ||
|
|
2720d11b23 | ||
|
|
b7751d7b9e | ||
|
|
24d81db8be | ||
|
|
010137ebf5 | ||
|
|
c6a2439c54 | ||
|
|
1d54ff3ac5 | ||
|
|
63a4d69209 | ||
|
|
b35d7de58a | ||
|
|
77ea01b7c1 | ||
|
|
5f608b638d | ||
|
|
52da8abceb | ||
|
|
f016b28cb6 | ||
|
|
01b7643ca5 | ||
|
|
375d43761b | ||
|
|
f3e9727947 | ||
|
|
911aa963c7 | ||
|
|
fb66b6871e | ||
|
|
5ae76f243e | ||
|
|
051b034e51 | ||
|
|
27439ecc71 | ||
|
|
d02c838447 | ||
|
|
d17acf5325 | ||
|
|
b278ca377f | ||
|
|
93131441e4 | ||
|
|
ddcc6c2d2e | ||
|
|
ef63ae9598 | ||
|
|
2ed9db5c7b | ||
|
|
06a12bc6f9 | ||
|
|
6524b45751 | ||
|
|
ceb9e2690b | ||
|
|
405fbaf24f | ||
|
|
3245c0e0d3 | ||
|
|
badc62e3f0 | ||
|
|
a5561f257b | ||
|
|
eb37b83ebd | ||
|
|
b2fab703fc | ||
|
|
37deb390d9 | ||
|
|
6fa5088352 | ||
|
|
e023a33786 | ||
|
|
6f818c4b5d | ||
|
|
9ad22fb2d9 | ||
|
|
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 | ||
|
|
d622a8397f | ||
|
|
c75619785d | ||
|
|
c014c6450b | ||
|
|
d8879f8d32 | ||
|
|
9bb0905aab | ||
|
|
c39844ca63 |
@@ -1,17 +1,12 @@
|
||||
.git
|
||||
vendor
|
||||
setting
|
||||
conf
|
||||
static
|
||||
views
|
||||
!static/tzdata
|
||||
Dockerfile
|
||||
glide.yaml
|
||||
glide.lock
|
||||
*.yml
|
||||
*.md
|
||||
*.go
|
||||
*.sh
|
||||
*.DS_Store
|
||||
# Ignore all files and dirs
|
||||
|
||||
|
||||
# Unignore files or dirs
|
||||
.github
|
||||
bin
|
||||
docs
|
||||
.gitignore
|
||||
.dockerignore
|
||||
db.sqlite
|
||||
docker-compose.yml
|
||||
eiblog.conf
|
||||
Makefile
|
||||
|
||||
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: eiblog
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
169
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
name: Release Image & Asset
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
id-token: write # for SLSA provenance
|
||||
attestations: write # for attestations
|
||||
|
||||
env:
|
||||
REGISTRY: docker.io
|
||||
GOPROXY: https://goproxy.io,direct
|
||||
|
||||
jobs:
|
||||
# Job 1: 打包源码 tar 文件
|
||||
package-source:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag: ${{ steps.vars.outputs.tag }}
|
||||
sha: ${{ steps.vars.outputs.sha }}
|
||||
date: ${{ steps.vars.outputs.date }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Extract metadata
|
||||
id: vars
|
||||
run: |
|
||||
echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
echo "sha=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
|
||||
echo "date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Package tar archive
|
||||
run: scripts/dist_tar.sh ${{ steps.vars.outputs.tag }}
|
||||
|
||||
- name: Upload tar artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-archives
|
||||
path: "*.tar.gz"
|
||||
retention-days: 7
|
||||
|
||||
# Job 2: 构建并推送 Docker 镜像
|
||||
build-images:
|
||||
runs-on: ubuntu-latest
|
||||
needs: package-source
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
app: [eiblog, backup]
|
||||
include:
|
||||
- app: eiblog
|
||||
port: 9000
|
||||
- app: backup
|
||||
port: 9001
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/deepzz0/${{ matrix.app }}
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
labels: |
|
||||
org.opencontainers.image.title=${{ matrix.app }}
|
||||
org.opencontainers.image.description=eiblog ${{ matrix.app }} service
|
||||
org.opencontainers.image.vendor=deepzz
|
||||
org.opencontainers.image.revision=${{ needs.package-source.outputs.sha }}
|
||||
org.opencontainers.image.created=${{ needs.package-source.outputs.date }}
|
||||
|
||||
- name: Build and push ${{ matrix.app }} image
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./build/package/${{ matrix.app }}/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha,scope=${{ matrix.app }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.app }}
|
||||
provenance: true
|
||||
sbom: true
|
||||
|
||||
- name: Generate SLSA attestation
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-name: ${{ env.REGISTRY }}/deepzz0/${{ matrix.app }}
|
||||
subject-digest: ${{ steps.build.outputs.digest }}
|
||||
|
||||
# Job 3: 创建 GitHub Release
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [package-source, build-images]
|
||||
steps:
|
||||
- name: Download tar artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-archives
|
||||
|
||||
- name: Create release summary
|
||||
run: |
|
||||
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
|
||||
# 🎉 Release ${{ needs.package-source.outputs.tag }} Created!
|
||||
|
||||
## 📦 Docker Images
|
||||
- **eiblog**: `deepzz0/eiblog:${{ needs.package-source.outputs.tag }}`
|
||||
- **backup**: `deepzz0/backup:${{ needs.package-source.outputs.tag }}`
|
||||
|
||||
## 🏗️ Build Info
|
||||
- **Tag**: ${{ needs.package-source.outputs.tag }}
|
||||
- **Commit**: ${{ needs.package-source.outputs.sha }}
|
||||
- **Built**: ${{ needs.package-source.outputs.date }}
|
||||
- **Platforms**: linux/amd64, linux/arm64, linux/arm/v7
|
||||
|
||||
## 🔐 Security
|
||||
- ✅ SLSA Build Provenance
|
||||
- ✅ SBOM (Software Bill of Materials)
|
||||
- ✅ Container Signing
|
||||
EOF
|
||||
|
||||
- name: Create GitHub release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: "*.tar.gz"
|
||||
generate_release_notes: true
|
||||
name: Release ${{ needs.package-source.outputs.tag }}
|
||||
body: |
|
||||
## Docker Images
|
||||
```bash
|
||||
docker pull deepzz0/eiblog:${{ needs.package-source.outputs.tag }}
|
||||
docker pull deepzz0/backup:${{ needs.package-source.outputs.tag }}
|
||||
```
|
||||
|
||||
## Multi-Architecture Support
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/arm/v7
|
||||
|
||||
Built with commit ${{ needs.package-source.outputs.sha }} on ${{ needs.package-source.outputs.date }}
|
||||
29
.gitignore
vendored
@@ -1,10 +1,23 @@
|
||||
*.DS_Store
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
vendor
|
||||
vendor/**
|
||||
conf/ssl/domain.*
|
||||
eiblog
|
||||
static/feed.xml
|
||||
static/opensearch.xml
|
||||
static/sitemap.xml
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.DS_Store
|
||||
*.tar.gz
|
||||
*.db
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
bin
|
||||
cmd/eiblog/etc/assets/*.xml
|
||||
cmd/eiblog/etc/assets/*.txt
|
||||
db.sqlite
|
||||
cmd/*/backend
|
||||
44
.travis.yml
@@ -1,44 +0,0 @@
|
||||
sudo: required # 超级权限
|
||||
|
||||
dist: trusty # 在ubuntu:trusty
|
||||
|
||||
language: go # 声明构建语言环境
|
||||
|
||||
go: # 只构建最新版本
|
||||
- tip
|
||||
|
||||
services: # docker环境
|
||||
- docker
|
||||
|
||||
# branches: # 限定项目分支
|
||||
# only:
|
||||
# - master
|
||||
|
||||
before_install:
|
||||
- curl https://glide.sh/get | sh # 安装glide包管理
|
||||
|
||||
script:
|
||||
- glide up
|
||||
- GOOS=linux GOARCH=amd64 go build # 编译版本
|
||||
- docker build -t registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog . # 构建镜像
|
||||
|
||||
after_success:
|
||||
- if [ "$TRAVIS_BRANCH" =~ ^v[0-9](\.[0-9])+.*$ ]; then
|
||||
docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" registry.cn-hangzhou.aliyuncs.com;
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog;
|
||||
fi
|
||||
|
||||
before_deploy:
|
||||
- ./dist.sh
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: AGW05sQuQfjy77+JprSV+ohti/VVgFuh7UOTV0+hwxqsOVXSoIQz/ZPOlHWPP1iiSiGGEalspm+UtKRvADcDfllUaEwo7kebfFeMx4X//qxFxQSQ5LJYx7qxsTDpuQ4CF8zifCtND3ynnUAdx0P6FFkxE/67kN2n4CrhIxYCUb8gNPzDDRuS0ZNBC4zzNldJo/vtatbvc2btuFfwKoClYf+xPLy5luLqDvKF+hdjJ8NuZl8BWkWxXE+kk8fW4iUn2IV0qtLRZ3FQUyAF2CumzxqZfViX+rYTXsfbabYY5nYG6opT4mUEF58T4X3uRV0e3Q6Fe73nmLh9cyAoQl1BTSJ1XiyV4eJJWcKEMY7DqJ646lzoUT449YwvTK57klcfBbShpcjFf2alVdEbr9jbEXrCkuWKnssO9VfufhYF6t9h22c79evpexpIbsoncPD+b+n712MzufREtUF4kpUdkIir5n9CgQl/l7S+fV+n+gME+mcA44K7iPXkC80UfxJiw83QizT39OQhExq6SPIwrbt2vlAkBpSLMUS9iAHtTJYUsmH1SsmrxGK3WromKysWeTRJbcAJls2k6V313sn4TuYBWiHTUfsUBhv+objDFA2TsfO+g0g1JsdfZb5EsKrqNvs/2ta1xlzdE0+/TLG/YNKIOPkHnXswAM3DZm3zEss=
|
||||
skip_cleanup: true
|
||||
file: "*.tar.gz"
|
||||
file_glob: true
|
||||
on:
|
||||
tags: true
|
||||
repo: eiblog/eiblog
|
||||
all_branches: true
|
||||
279
CHANGELOG.md
@@ -1,12 +1,271 @@
|
||||
# Eiblog Changelog
|
||||
# Changelog
|
||||
|
||||
## v1.0.0 (2016-01-09)
|
||||
首次发布版本
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
* 全站`HTTPS`设计,安全、极速。
|
||||
* `Elasticsearch`博客搜索系统。
|
||||
* 开源`Typecho`完整博客后台。
|
||||
* 全功能`Markdown`编辑器。
|
||||
* 异步`Google analysts`分析统计。
|
||||
* `Disqus`评论系统。
|
||||
* 后台直接对接七牛`CDN`。
|
||||
### [3.0.4](https://github.com/eiblog/eiblog/compare/v3.0.3...v3.0.4) (2025-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** golang version ([9f77fb7](https://github.com/eiblog/eiblog/commit/9f77fb7b3f283aacb9d9c2a0e2f79c8a6c412edb))
|
||||
|
||||
### [3.0.2](https://github.com/eiblog/eiblog/compare/v3.0.1...v3.0.2) (2025-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ci ([ccb5e45](https://github.com/eiblog/eiblog/commit/ccb5e4546e224182c949e72e9eae82fbbe1a02fe))
|
||||
|
||||
### [3.0.1](https://github.com/eiblog/eiblog/compare/v3.0.0...v3.0.1) (2025-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dist tar ([cb2ed7c](https://github.com/eiblog/eiblog/commit/cb2ed7cb8244dda8cbd8c5966c7ed02e177777e5))
|
||||
|
||||
## [3.0.0](https://github.com/eiblog/eiblog/compare/v2.2.17...v3.0.0) (2025-07-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support custom page ([b69248f](https://github.com/eiblog/eiblog/commit/b69248f6a4937f8157d2393bd44c6c55174ae3e7))
|
||||
* twofactor bind ([e5100fa](https://github.com/eiblog/eiblog/commit/e5100fa018e2955c3d649b70245e3a379658d7ba))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ci ([f4c70b4](https://github.com/eiblog/eiblog/commit/f4c70b46c19d8761c252b767c92016c195299c0c))
|
||||
* ci dist tar ([24bfe52](https://github.com/eiblog/eiblog/commit/24bfe528b28b995c3d5b2c13314c8849a036942b))
|
||||
* custom page support embed ([aee4194](https://github.com/eiblog/eiblog/commit/aee4194c71dd98a7a84da96810a060f716d1ea57))
|
||||
|
||||
### [2.2.16](https://github.com/eiblog/eiblog/compare/v2.2.15...v2.2.16) (2025-03-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* empty "beian" display issue ([79fccb9](https://github.com/eiblog/eiblog/commit/79fccb958c3eb8e21615389359c1d613c5fb079b))
|
||||
|
||||
### [2.2.15](https://github.com/eiblog/eiblog/compare/v2.2.14...v2.2.15) (2024-12-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* disqus list posts ([52fe730](https://github.com/eiblog/eiblog/commit/52fe7303f3345421c0f2e2989a6c174d5b1a689e))
|
||||
* disqus thread not store ([616248d](https://github.com/eiblog/eiblog/commit/616248d33fdf44dbc3aed41e92adae001a4f5577))
|
||||
|
||||
### [2.2.14](https://github.com/eiblog/eiblog/compare/v2.2.13...v2.2.14) (2024-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 1. bei_an cannot update error ([b53fc91](https://github.com/eiblog/eiblog/commit/b53fc91ce7b67c3811c232ad6236898f84bc391b)), closes [#43](https://github.com/eiblog/eiblog/issues/43) [#44](https://github.com/eiblog/eiblog/issues/44)
|
||||
* **serie:** update serie did not rerender ([88f23bd](https://github.com/eiblog/eiblog/commit/88f23bd1a0d6183d6de484cb79303506f0506d15))
|
||||
|
||||
### [2.2.13](https://github.com/eiblog/eiblog/compare/v2.2.12...v2.2.13) (2024-01-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* load more comments ([95e55ee](https://github.com/eiblog/eiblog/commit/95e55ee13c1af13e2bb2149ccbe60013c93e4e69))
|
||||
|
||||
### [2.2.12](https://github.com/eiblog/eiblog/compare/v2.2.11...v2.2.12) (2024-01-02)
|
||||
|
||||
### [2.2.11](https://github.com/eiblog/eiblog/compare/v2.2.10...v2.2.11) (2024-01-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **disqus:** fix returned posts list not have parent post ([9d71ca8](https://github.com/eiblog/eiblog/commit/9d71ca81988bfc614d13fcb02079f0dba9ef43cc))
|
||||
|
||||
### [2.2.10](https://github.com/eiblog/eiblog/compare/v2.2.9...v2.2.10) (2023-12-22)
|
||||
|
||||
### [2.2.9](https://github.com/eiblog/eiblog/compare/v2.2.8...v2.2.9) (2023-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* google analytics not work, supported ga4 measurement protocol ([9b918ca](https://github.com/eiblog/eiblog/commit/9b918caccd9fd7faff7d253693e075ccd0150c17))
|
||||
|
||||
### [2.2.8](https://github.com/eiblog/eiblog/compare/v2.2.7...v2.2.8) (2023-07-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backup:** restore db and tests ([f28d0e7](https://github.com/eiblog/eiblog/commit/f28d0e77e06dd435dc13a1867f18a011e34b8f53))
|
||||
|
||||
### [2.2.7](https://github.com/eiblog/eiblog/compare/v2.2.6...v2.2.7) (2023-07-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backup:** 数据恢复错误 ([ee51f67](https://github.com/eiblog/eiblog/commit/ee51f678cbf93332fedccf927704e78a6063289c))
|
||||
|
||||
### [2.2.6](https://github.com/eiblog/eiblog/compare/v2.2.5...v2.2.6) (2023-06-19)
|
||||
|
||||
### [2.2.5](https://github.com/eiblog/eiblog/compare/v2.2.4...v2.2.5) (2023-05-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backup:** libresolv.so.2: No such file or directory ([2c6c5bb](https://github.com/eiblog/eiblog/commit/2c6c5bb97708683bed8c889a7a1076f1f88ee5a8))
|
||||
|
||||
### [2.2.4](https://github.com/eiblog/eiblog/compare/v2.2.3...v2.2.4) (2023-05-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** fix .dockerignore ([1509a68](https://github.com/eiblog/eiblog/commit/1509a68cda74ace6b4060b5015f20143303ca36f))
|
||||
|
||||
### [2.2.3](https://github.com/eiblog/eiblog/compare/v2.2.2...v2.2.3) (2023-05-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** script file name ([20e89d6](https://github.com/eiblog/eiblog/commit/20e89d6a76f01aee60b98f8ae43a2c65b4fb3904))
|
||||
|
||||
### [2.2.2](https://github.com/eiblog/eiblog/compare/v2.2.1...v2.2.2) (2023-05-25)
|
||||
|
||||
### [2.2.1](https://github.com/eiblog/eiblog/compare/v2.2.0...v2.2.1) (2023-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* try to fix backup symbol not found ([bb06be3](https://github.com/eiblog/eiblog/commit/bb06be36fe016e753ca56aa2321ce7e39bffe3e0))
|
||||
|
||||
## [2.2.0](https://github.com/eiblog/eiblog/compare/v2.1.18...v2.2.0) (2023-05-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **backup:** add restore flag ([779a23c](https://github.com/eiblog/eiblog/commit/779a23cb75ab5059826370d08b754569a4af4aea))
|
||||
|
||||
### [2.1.18](https://github.com/eiblog/eiblog/compare/v2.1.17...v2.1.18) (2023-01-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backup:** can not execute ([f6ba716](https://github.com/eiblog/eiblog/commit/f6ba716f554cfb638752875c4842e4ffb1b7e9a6))
|
||||
* disqus api using http post ([b2e6c16](https://github.com/eiblog/eiblog/commit/b2e6c168c5f63b29cf5c2884e04dd99caa677bc0))
|
||||
|
||||
### [2.1.17](https://github.com/eiblog/eiblog/compare/v2.1.16...v2.1.17) (2023-01-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 1. template read panic ([f6d8656](https://github.com/eiblog/eiblog/commit/f6d8656c83591584581383643d109611d7ed2caa))
|
||||
* **disqus:** failed to commit disqus comments ([a9e8e39](https://github.com/eiblog/eiblog/commit/a9e8e39d342488ec46175997f3df9ab109f2fecf))
|
||||
* fist comment of disqus error ([17792e5](https://github.com/eiblog/eiblog/commit/17792e5a7edb7e84623d9307555e7983ba306565))
|
||||
|
||||
### [2.1.16](https://github.com/eiblog/eiblog/compare/v2.1.15...v2.1.16) (2022-11-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backup:** error path in compressed file ([aa91997](https://github.com/eiblog/eiblog/commit/aa91997c0caca27e9979692879f8877765dabd9d))
|
||||
* rss image path incorrect: data-src -> src ([4bfff2e](https://github.com/eiblog/eiblog/commit/4bfff2e5e9b0efb4112a5f2f6bc55eebcef1c6eb))
|
||||
|
||||
### [2.1.15](https://github.com/eiblog/eiblog/compare/v2.1.14...v2.1.15) (2022-09-28)
|
||||
|
||||
### [2.1.14](https://github.com/eiblog/eiblog/compare/v2.1.13...v2.1.14) (2022-09-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cgo and sqlite build in alpine image closed [#28](https://github.com/eiblog/eiblog/issues/28) ([b93c320](https://github.com/eiblog/eiblog/commit/b93c320987a936db6e5ca50c547022de9ab9a0f1))
|
||||
|
||||
### [2.1.13](https://github.com/eiblog/eiblog/compare/v2.1.12...v2.1.13) (2022-09-27)
|
||||
|
||||
### [2.1.12](https://github.com/eiblog/eiblog/compare/v2.1.11...v2.1.12) (2022-08-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#35](https://github.com/eiblog/eiblog/issues/35) no pubDate added for feed generation ([289b814](https://github.com/eiblog/eiblog/commit/289b8145bcdabd0060c9a0d5f40f5df69d3882d3))
|
||||
|
||||
### [2.1.11](https://github.com/eiblog/eiblog/compare/v2.1.10...v2.1.11) (2022-07-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backup:** configuration error ([38aa704](https://github.com/eiblog/eiblog/commit/38aa704198070d3e1436b230b40b1deb3e94c5ac))
|
||||
|
||||
### [2.1.10](https://github.com/eiblog/eiblog/compare/v2.1.9...v2.1.10) (2022-04-22)
|
||||
|
||||
### [2.1.9](https://github.com/eiblog/eiblog/compare/v2.1.8...v2.1.9) (2022-02-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **eiblog:** comments title error ([5170844](https://github.com/eiblog/eiblog/commit/517084462336ce01c3f79099c1e54297979f5877))
|
||||
|
||||
### [2.1.8](https://github.com/eiblog/eiblog/compare/v2.1.7...v2.1.8) (2021-11-18)
|
||||
|
||||
### [2.1.7](https://github.com/eiblog/eiblog/compare/v2.1.6...v2.1.7) (2021-11-13)
|
||||
|
||||
### [2.1.6](https://github.com/eiblog/eiblog/compare/v2.1.5...v2.1.6) (2021-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backup:** backup to qiniu, app.yml->validity change to int ([00cf0b5](https://github.com/eiblog/eiblog/commit/00cf0b5c9f787f3f45f1747b7cb1742c417c6dd6))
|
||||
|
||||
### [2.1.5](https://github.com/eiblog/eiblog/compare/v2.1.4...v2.1.5) (2021-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **release:** golang env ([4f6f85a](https://github.com/eiblog/eiblog/commit/4f6f85a85ae56849c49e91d76bbbce1790f16e29))
|
||||
|
||||
### [2.1.4](https://github.com/eiblog/eiblog/compare/v2.1.3...v2.1.4) (2021-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **release:** github action runner ([82fba0d](https://github.com/eiblog/eiblog/commit/82fba0ddb47c1f66cb659f775c120c08dd2b4447))
|
||||
|
||||
### [2.1.4](https://github.com/eiblog/eiblog/compare/v2.1.3...v2.1.4) (2021-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **release:** github action runner ([82fba0d](https://github.com/eiblog/eiblog/commit/82fba0ddb47c1f66cb659f775c120c08dd2b4447))
|
||||
|
||||
### [2.1.3](https://github.com/eiblog/eiblog/compare/v2.1.2...v2.1.3) (2021-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auto_save:** fix auto save return empty id ([cfaa25e](https://github.com/eiblog/eiblog/commit/cfaa25e1239aba580e0718d40f1a2bf31158b217))
|
||||
* backup app judge db driver ([78f5bfc](https://github.com/eiblog/eiblog/commit/78f5bfc1ce017bf77a7442f40963a706e608df51))
|
||||
* **pwd:** fix init pwd ([7d04b8f](https://github.com/eiblog/eiblog/commit/7d04b8f5c0846bcd0c7e07d0fc3704a535f6360a))
|
||||
|
||||
### [2.1.2](https://github.com/eiblog/eiblog/compare/v2.1.1...v2.1.2) (2021-07-27)
|
||||
|
||||
### [2.1.1](https://github.com/eiblog/eiblog/compare/v2.1.0...v2.1.1) (2021-07-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* sqlite error ([e2046d0](https://github.com/eiblog/eiblog/commit/e2046d0d39d9914473fe7b8fae3b18246ed133ce))
|
||||
|
||||
### [2.0.6](https://github.com/eiblog/eiblog/compare/v2.0.5...v2.0.6) (2021-05-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* workdir loop ([2b277bd](https://github.com/eiblog/eiblog/commit/2b277bd90188d53b90fddd0f6a8edad00f888f53))
|
||||
* workdir path error ([c18f3b7](https://github.com/eiblog/eiblog/commit/c18f3b7da96e3181b40867a88f9c8cad042d2f44))
|
||||
|
||||
## [1.1.0](https://github.com/deepzz0/appdemo/compare/v1.0.0...v1.1.0) (2020-12-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **docker:** make build, build docker image ([3ac2b8b](https://github.com/deepzz0/appdemo/commit/3ac2b8b2efadf024dfcf58e7ef8341b1a89cf1b1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* config path fixed [#1](https://github.com/deepzz0/appdemo/issues/1) ([4343eb4](https://github.com/deepzz0/appdemo/commit/4343eb44e8fffc6825be57393e024c75c4f68b7b))
|
||||
|
||||
## 1.0.0 (2020-10-31)
|
||||
|
||||
11
Dockerfile
@@ -1,11 +0,0 @@
|
||||
FROM alpine
|
||||
MAINTAINER deepzz <deepzz.qi@gmail.com>
|
||||
|
||||
RUN apk update
|
||||
RUN apk add ca-certificates
|
||||
ADD static/tzdata/Shanghai /etc/localtime
|
||||
|
||||
COPY . /eiblog
|
||||
EXPOSE 9000
|
||||
WORKDIR /eiblog
|
||||
ENTRYPOINT ["./eiblog"]
|
||||
3
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Manuel Martínez-Almeida
|
||||
Copyright (c) 2020-NOW deepzz0 <deepzz.qi@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
27
Makefile
Normal file
@@ -0,0 +1,27 @@
|
||||
.PHONY: demo build swag
|
||||
|
||||
tag=`git describe --abbrev=0 --tags`
|
||||
|
||||
swag:
|
||||
@scripts/swag_init.sh
|
||||
|
||||
_app:
|
||||
@scripts/new_app.sh
|
||||
|
||||
# below you should write
|
||||
|
||||
# run eiblog app
|
||||
eiblog:
|
||||
@scripts/run_app.sh eiblog
|
||||
|
||||
# run backup app
|
||||
backup:
|
||||
@scripts/run_app.sh backup
|
||||
|
||||
# dist tar
|
||||
dist:
|
||||
@scripts/dist_tar.sh $(tag)
|
||||
|
||||
# protoc
|
||||
protoc:
|
||||
@cd pkg/proto && make protoc
|
||||
370
README.md
@@ -1,308 +1,106 @@
|
||||
# EiBlog [](https://travis-ci.org/eiblog/eiblog)
|
||||
# EiBlog [](LICENSE.md) [](https://github.com/eiblog/eiblog/releases)
|
||||
|
||||
> 系统根据[https://imququ.com](https://imququ.com)一系列文章和方向进行搭建,期间获得了QuQu的很大帮助,在此表示感谢。
|
||||
> 博客项目结构参考模版:https://github.com/deepzz0/appdemo
|
||||
|
||||
用过其它博客系统,不喜欢,不够轻,不够快!自己做过共两款博客系统,完美主义的我(毕竟处女座)也实在是不想再在这件事情上过多纠结了。`Eiblog`应该是一个比较稳定的博客系统,且是博主以后使用的博客系统,稳定性和维护你是不用担心的,唯独该系统部署过程太过复杂,并且不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(明显快,安全),等你体验。
|
||||
用过其它博客系统,不喜欢,不够轻,不够快!这是我开发的第二款博客系统,也实在不想再在这件事情上过多纠结了。`EiBlog` 是一个比较稳定的博客系统,现已迭代至 `2.0` 版本,稳定性和维护你是不用担心的。
|
||||
|
||||
<!--more-->
|
||||
但它有着部署简单(上线复杂!)的特点,不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(简洁、轻快,安全),等你体验。
|
||||
|
||||
### 介绍
|
||||
整个博客系统涉及到模块如下:
|
||||
Docker镜像地址:
|
||||
|
||||
* `MongoDB`,博客采用 mongodb 作为存储数据库。
|
||||
* `Elasticsearch`,采用`elasticsearch`作为博客的站内搜索,尽管占用内存稍高。
|
||||
* `Disqus`,作为博客评论系统,国内大部分被墙,故实现两种评论方式。
|
||||
* `Nginx`,作为反向代理服务器,并做相关`http header`和证书的设置。
|
||||
* `Google Analytics`,作为博客系统的数据分析统计工具。
|
||||
* `七牛 CDN`,作为博客系统的静态文件存储,博文的图片附件什么上传至这里。
|
||||
* 博客服务:[deepzz0/eiblog](https://hub.docker.com/r/deepzz0/eiblog)
|
||||
* 博客搜索:[deepzz0/elasticsearch](https://hub.docker.com/r/deepzz0/elasticsearch)
|
||||
* 数据备份:[deepzz0/backup](https://hub.docker.com/r/deepzz0/backup)
|
||||
|
||||
相关技术有:
|
||||
### 快速体验
|
||||
|
||||
* `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、下载压缩包,到 [这里](https://github.com/eiblog/eiblog/releases) 下载 eiblog(非backup) 相应系统压缩包,然后解压缩。
|
||||
|
||||
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`功能,搜索更加自然。
|
||||
2、启动服务:`./backend`
|
||||
|
||||
当然,在信息安全方面也没少下功夫,虽然我们只是一个小小的博客系统。
|
||||
|
||||
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+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
|
||||
|
||||
相关图片展示:
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
> `注`:图片1,图片2是博客界面,图片3是后台界面,图片4是性能展示。
|
||||
|
||||
好了,说了那么多,吹了那么多,我们实际来动手搭建一个`Eiblog`吧。
|
||||
|
||||
### 安装
|
||||
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
|
||||
$ go get https://github.com/eiblog/eiblog
|
||||
```
|
||||
进行源码编译二进制文件运行。
|
||||
|
||||
3、如果你对`docker`技术也有研究的话,你也可以通过`docker`来安装:
|
||||
``` sh
|
||||
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
||||
**Docker**
|
||||
|
||||
```
|
||||
镜像内部只提供了`eiblog`的二进制文件,因为其它内容定制化的需求过高。所以需要将`conf`、`static`、`views`目录映射出来,后面会具体说到。
|
||||
|
||||
### 本地测试
|
||||
在我们下载好可执行程序之后,我们可以开始本地测试的工作了。
|
||||
|
||||
本地测试需要搭建两个服务`mongodb`和`elasticsearch2.4.1`(可选,搜索服务不可用)。
|
||||
|
||||
`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 \
|
||||
$ docker run --name eiblog \
|
||||
-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
|
||||
deepzz0/eiblog:latest
|
||||
```
|
||||
|
||||
然后,执行:
|
||||
``` sh
|
||||
$ cd ~
|
||||
**Compose**
|
||||
|
||||
参考项目根目录下的 [docker-compose.yml](https://github.com/eiblog/eiblog/blob/v2/docker-compose.yml),修改相关配置:
|
||||
|
||||
```
|
||||
$ docker compose up -d
|
||||
或
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
等待些许时间,成功运行。
|
||||
然后访问 `localhost:9000` 就可以了,后台地址 `localhost:9000/admin/login`,默认账户密码 `deepzz/deepzz`。
|
||||
|
||||
> 默认情况下未开启博客搜索 `elasticsearch`,需要的话需要启动 es 服务并修改配置 `app.yml`。
|
||||
|
||||
### 成功搭建者博客
|
||||
**数据库支持**
|
||||
|
||||
* [https://razeencheng.com/](https://razeencheng.com/) - Razeen's Blog
|
||||
| 类型(driver) | 地址(source)示例 |
|
||||
| -------------- | ------------------------------------------------------------ |
|
||||
| mongodb | mongodb://localhost:27017 |
|
||||
| mysql | user:password@tcp(localhost:3306)/eiblog?charset=utf8mb4&parseTime=True&loc=Local |
|
||||
| postgres | host=localhost port=5432 user=user password=password dbname=eiblog sslmode=disable |
|
||||
| sqlite | /path/eiblog.db |
|
||||
| sqlserver | sqlserver://user:password@localhost:9930?database=eiblog |
|
||||
| clickhouse | tcp://localhost:9000?database=eiblog&username=user&password=password&read_timeout=10&write_timeout=20 |
|
||||
|
||||
### 功能特性
|
||||
|
||||
本着博客本质用来分享知识的特点,`EiBlog` 不会有较强的定制功能(包括主题,CDN支持等),仅保持常用简单页面与功能:
|
||||
|
||||
```
|
||||
首页、专题、归档、友链、关于、搜索
|
||||
```
|
||||
|
||||
功能说明:
|
||||
|
||||
* 博客归档,利用时间线帮助我们将归纳博文,内容少于一年按月归档,大于则按年归档。
|
||||
* 博客专题,有时候博文是同一系列,专题能够帮助我们很好归纳博文,对阅读者是非常友好的。
|
||||
* 标签系统,每篇博文都可以打上不同标签,使得在归档和专题不满足的情况下自定义归档,这块辅助搜索简直完美。
|
||||
* 搜索系统,依托ElasticSearch实现的站内搜索,速度与效率并存,再加上google opensearch,搜索只流畅。
|
||||
* 管理后台,内嵌全功能 `Typecho` 后台系统,全功能 `Markdown` 编辑器让你感觉什么是简洁清爽。
|
||||
* 谷歌统计,由于google api的速度问题,从而实现了后端API异步统计,使得博客页面加载飞速。
|
||||
* Disqus评论,国内评论系统不友好,因此选择disqus,又由于众所周知原因国内不能用,实现另类disqus评论方式。
|
||||
* 多存储后端,支持mongodb、mysql、postgres、sqlite等存储后端。
|
||||
* 七牛CDN,支持在 `Markdown` 编辑器直接上传附件,让你只考虑编辑内容,解放思想。
|
||||
* 自动备份,支持多存储后端的备份功能,备份数据保存到七牛CDN上。
|
||||
|
||||
当然,为了让整个系统加载速度更快,还做了更多优化措施:
|
||||
|
||||
* 文章评论数量(不重要)通过后端跑定时任务获取,所以有时评论数量是不对的,这样减少了 API 调用。
|
||||
* 整站内容全部内存缓存,`mardown` 文档全部转换为 html 进行缓存,减少了转换过程。
|
||||
* `.js`、`.css` 等静态文件浏览器本地存储,小图片 base64 内置到 css 中,二次访问不会产生网络带来的延迟,加速访问。通过版本控制更新。
|
||||
* 最佳实践 nginx 配置,可以查看 `eiblog.conf`,开启压缩缩小传输量,服务器传输证书链、开启 `Session Resumption`、`Session Ticket`、`OCSP Stapling `等加速证书握手,再次提高速度。
|
||||
|
||||
### 博客页面
|
||||
|
||||
可以容易的看到 [ssllabs](https://www.ssllabs.com/ssltest/analyze.html?d=deepzz.com&latest) 评分`A+`,[myssl](https://myssl.com/deepzz.com) 评分`A+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
|
||||
|
||||

|
||||

|
||||

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

|
||||
|
||||
### 授权许可
|
||||
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/eiblog/eiblog/blob/master/LICENSE) 文件中。
|
||||
|
||||
如果你的博客使用`Eiblog`搭建,你可以在[这里](https://github.com/eiblog/eiblog/issues/1)提交网址。
|
||||
|
||||
463
api.go
@@ -1,463 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
"github.com/eiblog/utils/logd"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
const (
|
||||
NOTICE_SUCCESS = "success"
|
||||
NOTICE_NOTICE = "notice"
|
||||
NOTICE_ERROR = "error"
|
||||
)
|
||||
|
||||
var APIs = make(map[string]func(c *gin.Context))
|
||||
|
||||
func init() {
|
||||
// 更新账号信息
|
||||
APIs["account"] = apiAccount
|
||||
// 更新博客信息
|
||||
APIs["blog"] = apiBlog
|
||||
// 更新密码
|
||||
APIs["password"] = apiPassword
|
||||
// 删除文章
|
||||
APIs["post-delete"] = apiPostDelete
|
||||
// 添加文章
|
||||
APIs["post-add"] = apiPostAdd
|
||||
// 删除专题
|
||||
APIs["serie-delete"] = apiSerieDelete
|
||||
// 添加专题
|
||||
APIs["serie-add"] = apiSerieAdd
|
||||
// 专题排序
|
||||
APIs["serie-sort"] = apiSerieSort
|
||||
// 删除草稿箱
|
||||
APIs["draft-delete"] = apiDraftDelete
|
||||
// 删除回收箱
|
||||
APIs["trash-delete"] = apiTrashDelete
|
||||
// 恢复回收箱
|
||||
APIs["trash-recover"] = apiTrashRecover
|
||||
// 上传文件
|
||||
APIs["file-upload"] = apiFileUpload
|
||||
// 删除文件
|
||||
APIs["file-delete"] = apiFileDelete
|
||||
}
|
||||
|
||||
func apiAccount(c *gin.Context) {
|
||||
e := c.PostForm("email")
|
||||
pn := c.PostForm("phoneNumber")
|
||||
ad := c.PostForm("address")
|
||||
logd.Debug(e, pn, ad)
|
||||
if (e != "" && !CheckEmail(e)) || (pn != "" && !CheckSMS(pn)) {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
return
|
||||
}
|
||||
err := UpdateAccountField(bson.M{"$set": bson.M{"email": e, "phonen": pn, "address": ad}})
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
Ei.Email = e
|
||||
Ei.PhoneN = pn
|
||||
Ei.Address = ad
|
||||
responseNotice(c, NOTICE_SUCCESS, "更新成功", "")
|
||||
}
|
||||
|
||||
func apiBlog(c *gin.Context) {
|
||||
bn := c.PostForm("blogName")
|
||||
bt := c.PostForm("bTitle")
|
||||
ba := c.PostForm("beiAn")
|
||||
st := c.PostForm("subTitle")
|
||||
ss := c.PostForm("seriessay")
|
||||
as := c.PostForm("archivessay")
|
||||
if bn == "" || bt == "" {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
return
|
||||
}
|
||||
err := UpdateAccountField(bson.M{"$set": bson.M{"blogger.blogname": bn, "blogger.btitle": bt, "blogger.beian": ba, "blogger.subtitle": st, "blogger.seriessay": ss, "blogger.archivessay": as}})
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
Ei.BlogName = bn
|
||||
Ei.BTitle = bt
|
||||
Ei.BeiAn = ba
|
||||
Ei.SubTitle = st
|
||||
Ei.SeriesSay = ss
|
||||
Ei.ArchivesSay = as
|
||||
Ei.CH <- SERIES_MD
|
||||
Ei.CH <- ARCHIVE_MD
|
||||
responseNotice(c, NOTICE_SUCCESS, "更新成功", "")
|
||||
}
|
||||
|
||||
func apiPassword(c *gin.Context) {
|
||||
logd.Debug(c.Request.PostForm.Encode())
|
||||
od := c.PostForm("old")
|
||||
nw := c.PostForm("new")
|
||||
cf := c.PostForm("confirm")
|
||||
if nw != cf {
|
||||
responseNotice(c, NOTICE_NOTICE, "两次密码输入不一致", "")
|
||||
return
|
||||
}
|
||||
if !CheckPwd(nw) {
|
||||
responseNotice(c, NOTICE_NOTICE, "密码格式错误", "")
|
||||
return
|
||||
}
|
||||
if !VerifyPasswd(Ei.Password, Ei.Username, od) {
|
||||
responseNotice(c, NOTICE_NOTICE, "原始密码不正确", "")
|
||||
return
|
||||
}
|
||||
newPwd := EncryptPasswd(Ei.Username, nw)
|
||||
err := UpdateAccountField(bson.M{"$set": bson.M{"password": newPwd}})
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
Ei.Password = newPwd
|
||||
responseNotice(c, NOTICE_SUCCESS, "更新成功", "")
|
||||
}
|
||||
|
||||
func apiPostDelete(c *gin.Context) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
|
||||
}()
|
||||
err = c.Request.ParseForm()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ids []int32
|
||||
var i int
|
||||
for _, v := range c.Request.PostForm["cid[]"] {
|
||||
i, err = strconv.Atoi(v)
|
||||
if err != nil || i < 1 {
|
||||
err = errors.New("参数错误")
|
||||
return
|
||||
}
|
||||
ids = append(ids, int32(i))
|
||||
}
|
||||
err = DelArticles(ids...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// elasticsearch 删除索引
|
||||
err = ElasticDelIndex(ids)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func apiPostAdd(c *gin.Context) {
|
||||
var err error
|
||||
var do string
|
||||
var cid int
|
||||
defer func() {
|
||||
switch do {
|
||||
case "auto": // 自动保存
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"fail": FAIL, "time": time.Now().Format("15:04:05 PM"), "cid": cid})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": SUCCESS, "time": time.Now().Format("15:04:05 PM"), "cid": cid})
|
||||
case "save": // 保存草稿
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusFound, "/admin/manage-draft")
|
||||
case "publish": // 发布
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusFound, "/admin/manage-posts")
|
||||
}
|
||||
}()
|
||||
do = c.PostForm("do") // auto or save or publish
|
||||
slug := c.PostForm("slug")
|
||||
title := c.PostForm("title")
|
||||
text := c.PostForm("text")
|
||||
date := c.PostForm("date")
|
||||
serie := c.PostForm("serie")
|
||||
tag := c.PostForm("tags")
|
||||
update := c.PostForm("update")
|
||||
if title == "" || text == "" || slug == "" {
|
||||
err = errors.New("参数错误")
|
||||
return
|
||||
}
|
||||
var tags []string
|
||||
if tag != "" {
|
||||
tags = strings.Split(tag, ",")
|
||||
}
|
||||
serieid := CheckSerieID(serie)
|
||||
artc := &Article{
|
||||
Title: title,
|
||||
Content: text,
|
||||
Slug: slug,
|
||||
CreateTime: CheckDate(date),
|
||||
IsDraft: do != "publish",
|
||||
Author: Ei.Username,
|
||||
SerieID: serieid,
|
||||
Tags: tags,
|
||||
}
|
||||
cid, err = strconv.Atoi(c.PostForm("cid"))
|
||||
if err != nil || cid < 1 {
|
||||
err = AddArticle(artc)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
}
|
||||
cid = int(artc.ID)
|
||||
if !artc.IsDraft {
|
||||
ElasticIndex(artc)
|
||||
DoPings(slug)
|
||||
}
|
||||
return
|
||||
}
|
||||
artc.ID = int32(cid)
|
||||
i, a := GetArticle(artc.ID)
|
||||
if a != nil {
|
||||
artc.IsDraft = false
|
||||
artc.Count = a.Count
|
||||
artc.UpdateTime = a.UpdateTime
|
||||
Ei.Articles = append(Ei.Articles[0:i], Ei.Articles[i+1:]...)
|
||||
DelFromLinkedList(a)
|
||||
ManageTagsArticle(a, false, DELETE)
|
||||
ManageSeriesArticle(a, false, DELETE)
|
||||
ManageArchivesArticle(a, false, DELETE)
|
||||
delete(Ei.MapArticles, a.Slug)
|
||||
a = nil
|
||||
}
|
||||
if CheckBool(update) {
|
||||
artc.UpdateTime = time.Now()
|
||||
}
|
||||
err = UpdateArticle(bson.M{"id": artc.ID}, artc)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
return
|
||||
}
|
||||
if !artc.IsDraft {
|
||||
Ei.MapArticles[artc.Slug] = artc
|
||||
Ei.Articles = append(Ei.Articles, artc)
|
||||
sort.Sort(Ei.Articles)
|
||||
GenerateExcerptAndRender(artc)
|
||||
// elasticsearch 索引
|
||||
ElasticIndex(artc)
|
||||
DoPings(slug)
|
||||
if artc.ID >= setting.Conf.StartID {
|
||||
ManageTagsArticle(artc, true, ADD)
|
||||
ManageSeriesArticle(artc, true, ADD)
|
||||
ManageArchivesArticle(artc, true, ADD)
|
||||
AddToLinkedList(artc.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func apiSerieDelete(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
// 只能逐一删除
|
||||
for _, v := range c.Request.PostForm["mid[]"] {
|
||||
id, err := strconv.Atoi(v)
|
||||
if err != nil || id < 1 {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
err = DelSerie(int32(id))
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
}
|
||||
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
|
||||
}
|
||||
|
||||
func apiSerieAdd(c *gin.Context) {
|
||||
name := c.PostForm("name")
|
||||
slug := c.PostForm("slug")
|
||||
desc := c.PostForm("description")
|
||||
if name == "" || slug == "" || desc == "" {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
return
|
||||
}
|
||||
mid, err := strconv.Atoi(c.PostForm("mid"))
|
||||
if err == nil && mid > 0 {
|
||||
serie := QuerySerie(int32(mid))
|
||||
if serie == nil {
|
||||
responseNotice(c, NOTICE_NOTICE, "专题不存在", "")
|
||||
return
|
||||
}
|
||||
serie.Name = name
|
||||
serie.Slug = slug
|
||||
serie.Desc = desc
|
||||
serie.ID = int32(mid)
|
||||
err = UpdateSerie(serie)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = AddSerie(name, slug, desc)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
}
|
||||
responseNotice(c, NOTICE_SUCCESS, "操作成功", "")
|
||||
}
|
||||
|
||||
// 暂未启用
|
||||
func apiSerieSort(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
v := c.Request.PostForm["mid[]"]
|
||||
logd.Debug(v)
|
||||
}
|
||||
|
||||
func apiDraftDelete(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
for _, v := range c.Request.PostForm["mid[]"] {
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil || i < 1 {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
return
|
||||
}
|
||||
err = RemoveArticle(int32(i))
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
}
|
||||
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
|
||||
}
|
||||
|
||||
func apiTrashDelete(c *gin.Context) {
|
||||
logd.Debug(c.PostForm("key"))
|
||||
logd.Debug(c.Request.PostForm)
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
for _, v := range c.Request.PostForm["mid[]"] {
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil || i < 1 {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
return
|
||||
}
|
||||
err = RemoveArticle(int32(i))
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
}
|
||||
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
|
||||
}
|
||||
|
||||
func apiTrashRecover(c *gin.Context) {
|
||||
logd.Debug(c.PostForm("key"))
|
||||
logd.Debug(c.Request.PostForm)
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
for _, v := range c.Request.PostForm["mid[]"] {
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil || i < 1 {
|
||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||
return
|
||||
|
||||
}
|
||||
err = RecoverArticle(int32(i))
|
||||
if err != nil {
|
||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||
return
|
||||
}
|
||||
}
|
||||
responseNotice(c, NOTICE_SUCCESS, "恢复成功", "")
|
||||
}
|
||||
|
||||
func apiFileUpload(c *gin.Context) {
|
||||
type Size interface {
|
||||
Size() int64
|
||||
}
|
||||
file, header, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
c.String(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
s, ok := file.(Size)
|
||||
if !ok {
|
||||
logd.Error("assert failed")
|
||||
c.String(http.StatusBadRequest, "false")
|
||||
return
|
||||
}
|
||||
filename := strings.ToLower(header.Filename)
|
||||
url, err := FileUpload(filename, s.Size(), file)
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
c.String(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
typ := c.Request.Header.Get("Content-Type")
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"title": filename,
|
||||
"isImage": typ[:5] == "image",
|
||||
"url": url,
|
||||
"bytes": fmt.Sprintf("%dkb", s.Size()/1000),
|
||||
})
|
||||
}
|
||||
|
||||
func apiFileDelete(c *gin.Context) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
}
|
||||
c.String(http.StatusOK, "删掉了吗?鬼知道。。。")
|
||||
}()
|
||||
name := c.PostForm("title")
|
||||
if name == "" {
|
||||
err = errors.New("参数错误")
|
||||
return
|
||||
}
|
||||
err = FileDelete(name)
|
||||
}
|
||||
|
||||
func responseNotice(c *gin.Context, typ, content, hl string) {
|
||||
if hl != "" {
|
||||
c.SetCookie("notice_highlight", hl, 86400, "/", "", true, false)
|
||||
}
|
||||
c.SetCookie("notice_type", typ, 86400, "/", "", true, false)
|
||||
c.SetCookie("notice", fmt.Sprintf("[\"%s\"]", content), 86400, "/", "", true, false)
|
||||
c.Redirect(http.StatusFound, c.Request.Referer())
|
||||
}
|
||||
1
assets/README.md
Normal file
@@ -0,0 +1 @@
|
||||
Other assets to go along with your repository (images, logos, etc).
|
||||
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
299
back.go
@@ -1,299 +0,0 @@
|
||||
// Package main provides ...
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
"github.com/eiblog/utils/logd"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
func isLogin(c *gin.Context) bool {
|
||||
session := sessions.Default(c)
|
||||
v := session.Get("username")
|
||||
if v == nil || v.(string) != Ei.Username {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func AuthFilter() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if !isLogin(c) {
|
||||
c.Abort()
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// 登录界面
|
||||
func HandleLogin(c *gin.Context) {
|
||||
logout := c.Query("logout")
|
||||
if logout == "true" {
|
||||
session := sessions.Default(c)
|
||||
session.Delete("username")
|
||||
session.Save()
|
||||
} else if isLogin(c) {
|
||||
c.Redirect(http.StatusFound, "/admin/profile")
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "login.html", gin.H{"BTitle": Ei.BTitle})
|
||||
}
|
||||
|
||||
func HandleLoginPost(c *gin.Context) {
|
||||
user := c.PostForm("user")
|
||||
pwd := c.PostForm("password")
|
||||
// code := c.PostForm("code") // 二次验证
|
||||
if user == "" || pwd == "" {
|
||||
logd.Print("参数错误", user, pwd)
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
if Ei.Username != user || !VerifyPasswd(Ei.Password, user, pwd) {
|
||||
logd.Printf("账号或密码错误 %s, %s", user, pwd)
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
session := sessions.Default(c)
|
||||
session.Set("username", user)
|
||||
session.Save()
|
||||
Ei.LoginIP = c.ClientIP()
|
||||
Ei.LoginTime = time.Now()
|
||||
UpdateAccountField(bson.M{"$set": bson.M{"loginip": Ei.LoginIP, "logintime": Ei.LoginTime}})
|
||||
c.Redirect(http.StatusFound, "/admin/profile")
|
||||
}
|
||||
|
||||
func GetBack() gin.H {
|
||||
return gin.H{"Author": Ei.Username, "Kodo": setting.Conf.Kodo}
|
||||
}
|
||||
|
||||
// 个人配置
|
||||
func HandleProfile(c *gin.Context) {
|
||||
h := GetBack()
|
||||
h["Console"] = true
|
||||
h["Path"] = c.Request.URL.Path
|
||||
h["Title"] = "个人配置 | " + Ei.BTitle
|
||||
h["Account"] = Ei
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "admin-profile", h)
|
||||
}
|
||||
|
||||
// 写文章==>Write
|
||||
type T struct {
|
||||
ID string `json:"id"`
|
||||
Tags string `json:"tags"`
|
||||
}
|
||||
|
||||
func HandlePost(c *gin.Context) {
|
||||
h := GetBack()
|
||||
id, err := strconv.Atoi(c.Query("cid"))
|
||||
if err == nil && id > 0 {
|
||||
artc := QueryArticle(int32(id))
|
||||
if artc != nil {
|
||||
h["Title"] = "编辑文章 | " + Ei.BTitle
|
||||
h["Edit"] = artc
|
||||
}
|
||||
}
|
||||
if h["Title"] == nil {
|
||||
h["Title"] = "撰写文章 | " + Ei.BTitle
|
||||
}
|
||||
h["Path"] = c.Request.URL.Path
|
||||
h["Domain"] = setting.Conf.Mode.Domain
|
||||
h["Series"] = Ei.Series
|
||||
var tags []T
|
||||
for tag, _ := range Ei.Tags {
|
||||
tags = append(tags, T{tag, tag})
|
||||
}
|
||||
h["Tags"] = tags
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "admin-post", h)
|
||||
}
|
||||
|
||||
func HandleDraftDelete(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Query("cid"))
|
||||
if err != nil || id < 1 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||||
return
|
||||
}
|
||||
if err = RemoveArticle(int32(id)); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "删除错误"})
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusFound, "/admin/write-post")
|
||||
}
|
||||
|
||||
// 文章管理==>Manage
|
||||
func HandlePosts(c *gin.Context) {
|
||||
kw := c.Query("keywords")
|
||||
tmp := c.Query("serie")
|
||||
se, err := strconv.Atoi(tmp)
|
||||
if err != nil || se < 1 {
|
||||
se = 0
|
||||
}
|
||||
pg, err := strconv.Atoi(c.Query("page"))
|
||||
if err != nil || pg < 1 {
|
||||
pg = 1
|
||||
}
|
||||
vals := c.Request.URL.Query()
|
||||
h := GetBack()
|
||||
h["Manage"] = true
|
||||
h["Path"] = c.Request.URL.Path
|
||||
h["Title"] = "文章管理 | " + Ei.BTitle
|
||||
h["Series"] = Ei.Series
|
||||
h["Serie"] = se
|
||||
h["KW"] = kw
|
||||
var max int
|
||||
max, h["List"] = PageListBack(se, kw, false, false, pg, setting.Conf.PageSize)
|
||||
if pg < max {
|
||||
vals.Set("page", fmt.Sprint(pg+1))
|
||||
h["Next"] = vals.Encode()
|
||||
}
|
||||
if pg > 1 {
|
||||
vals.Set("page", fmt.Sprint(pg-1))
|
||||
h["Prev"] = vals.Encode()
|
||||
}
|
||||
h["PP"] = make(map[int]string, max)
|
||||
for i := 0; i < max; i++ {
|
||||
vals.Set("page", fmt.Sprint(i+1))
|
||||
h["PP"].(map[int]string)[i+1] = vals.Encode()
|
||||
}
|
||||
h["Cur"] = pg
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "admin-posts", h)
|
||||
}
|
||||
|
||||
// 专题列表
|
||||
func HandleSeries(c *gin.Context) {
|
||||
h := GetBack()
|
||||
h["Manage"] = true
|
||||
h["Path"] = c.Request.URL.Path
|
||||
h["Title"] = "专题管理 | " + Ei.BTitle
|
||||
h["List"] = Ei.Series
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "admin-series", h)
|
||||
}
|
||||
|
||||
func HandleSerie(c *gin.Context) {
|
||||
h := GetBack()
|
||||
id, err := strconv.Atoi(c.Query("mid"))
|
||||
if serie := QuerySerie(int32(id)); err == nil && id > 0 && serie != nil {
|
||||
h["Title"] = "编辑专题 | " + Ei.BTitle
|
||||
h["Edit"] = serie
|
||||
} else {
|
||||
h["Title"] = "新增专题 | " + Ei.BTitle
|
||||
}
|
||||
h["Manage"] = true
|
||||
h["Path"] = c.Request.URL.Path
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "admin-serie", h)
|
||||
}
|
||||
|
||||
// 标签列表
|
||||
func HandleTags(c *gin.Context) {
|
||||
h := GetBack()
|
||||
h["Manage"] = true
|
||||
h["Path"] = c.Request.URL.Path
|
||||
h["Title"] = "标签管理 | " + Ei.BTitle
|
||||
h["List"] = Ei.Tags
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "admin-tags", h)
|
||||
}
|
||||
|
||||
// 草稿箱
|
||||
func HandleDraft(c *gin.Context) {
|
||||
h := GetBack()
|
||||
h["Manage"] = true
|
||||
h["Path"] = c.Request.URL.Path
|
||||
h["Title"] = "草稿箱 | " + Ei.BTitle
|
||||
var err error
|
||||
h["List"], err = LoadDraft()
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
c.Status(http.StatusBadRequest)
|
||||
} else {
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
RenderHTMLBack(c, "admin-draft", h)
|
||||
}
|
||||
|
||||
// 回收箱
|
||||
func HandleTrash(c *gin.Context) {
|
||||
h := GetBack()
|
||||
h["Manage"] = true
|
||||
h["Path"] = c.Request.URL.Path
|
||||
h["Title"] = "回收箱 | " + Ei.BTitle
|
||||
var err error
|
||||
h["List"], err = LoadTrash()
|
||||
if err != nil {
|
||||
logd.Error(err)
|
||||
c.HTML(http.StatusBadRequest, "backLayout.html", h)
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "admin-trash", h)
|
||||
}
|
||||
|
||||
// 基本设置==>Setting
|
||||
func HandleGeneral(c *gin.Context) {
|
||||
h := GetBack()
|
||||
h["Setting"] = true
|
||||
h["Path"] = c.Request.URL.Path
|
||||
h["Title"] = "基本设置 | " + Ei.BTitle
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "admin-general", h)
|
||||
}
|
||||
|
||||
// 阅读设置
|
||||
func HandleDiscussion(c *gin.Context) {
|
||||
h := GetBack()
|
||||
h["Setting"] = true
|
||||
h["Path"] = c.Request.URL.Path
|
||||
h["Title"] = "阅读设置 | " + Ei.BTitle
|
||||
c.Status(http.StatusOK)
|
||||
RenderHTMLBack(c, "admin-discussion", h)
|
||||
}
|
||||
|
||||
// api
|
||||
func HandleAPI(c *gin.Context) {
|
||||
action := c.Param("action")
|
||||
logd.Debug("action=======>", action)
|
||||
api := APIs[action]
|
||||
if api == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Invalid API Request"})
|
||||
return
|
||||
}
|
||||
api(c)
|
||||
}
|
||||
|
||||
func RenderHTMLBack(c *gin.Context, name string, data gin.H) {
|
||||
if name == "login.html" {
|
||||
err := Tmpl.ExecuteTemplate(c.Writer, name, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
return
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err := Tmpl.ExecuteTemplate(&buf, name, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data["LayoutContent"] = template.HTML(buf.String())
|
||||
err = Tmpl.ExecuteTemplate(c.Writer, "backLayout.html", data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
}
|
||||
5
build/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Packaging and Continuous Integration.
|
||||
|
||||
Put your cloud (AMI), container (Docker), OS (deb, rpm, pkg) package configurations and scripts in the `/build/package` directory.
|
||||
|
||||
Put your CI (travis, circle, drone) configurations and scripts in the `/build/ci` directory. Note that some of the CI tools (e.g., Travis CI) are very picky about the location of their config files. Try putting the config files in the `/build/ci` directory linking them to the location where the CI tools expect them (when possible).
|
||||
23
build/package/backup/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM golang:1.23 AS builder
|
||||
|
||||
WORKDIR /eiblog
|
||||
COPY . .
|
||||
RUN scripts/run_build.sh backup
|
||||
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="deepzz.qi@gmail.com"
|
||||
|
||||
RUN apk add --update --no-cache tzdata ca-certificates \
|
||||
mongodb-tools libc6-compat gcompat
|
||||
|
||||
COPY README.md /app/README.md
|
||||
COPY CHANGELOG.md /app/CHANGELOG.md
|
||||
COPY LICENSE /app/LICENSE
|
||||
|
||||
COPY --from=builder /eiblog/cmd/backup/backend /app/backend
|
||||
COPY cmd/backup/etc /app/etc
|
||||
|
||||
EXPOSE 9001
|
||||
|
||||
WORKDIR /app
|
||||
CMD ["./backend"]
|
||||
22
build/package/eiblog/Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM golang:1.23 AS builder
|
||||
|
||||
WORKDIR /eiblog
|
||||
COPY . .
|
||||
RUN scripts/run_build.sh eiblog
|
||||
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="deepzz.qi@gmail.com"
|
||||
|
||||
RUN apk add --update --no-cache tzdata ca-certificates
|
||||
|
||||
COPY README.md /app/README.md
|
||||
COPY CHANGELOG.md /app/CHANGELOG.md
|
||||
COPY LICENSE /app/LICENSE
|
||||
|
||||
COPY --from=builder /eiblog/cmd/eiblog/backend /app/backend
|
||||
COPY cmd/eiblog/etc /app/etc
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
WORKDIR /app
|
||||
CMD ["./backend"]
|
||||
@@ -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
|
||||
44
check.go
@@ -1,44 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CheckEmail(e string) bool {
|
||||
reg := regexp.MustCompile(`^(\w)+([\.\-]\w+)*@(\w)+((\.\w+)+)$`)
|
||||
return reg.MatchString(e)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func CheckSMS(sms string) bool {
|
||||
reg := regexp.MustCompile(`^\+\d+$`)
|
||||
return reg.MatchString(sms)
|
||||
}
|
||||
|
||||
func CheckPwd(pwd string) bool {
|
||||
return len(pwd) > 5 && len(pwd) < 19
|
||||
}
|
||||
|
||||
func CheckDate(date string) time.Time {
|
||||
if t, err := time.ParseInLocation("2006-01-02 15:04", date, time.Local); err == nil {
|
||||
return t
|
||||
}
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func CheckSerieID(sid string) int32 {
|
||||
if id, err := strconv.Atoi(sid); err == nil {
|
||||
return int32(id)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func CheckBool(str string) bool {
|
||||
return str == "true" || str == "1"
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckEmail(t *testing.T) {
|
||||
e := "xx@email.com"
|
||||
e1 := "xxxxemail.com"
|
||||
e2 := "xxx#email.com"
|
||||
|
||||
t.Log(CheckEmail(e))
|
||||
t.Log(CheckEmail(e1))
|
||||
t.Log(CheckEmail(e2))
|
||||
}
|
||||
|
||||
func TestCheckDomain(t *testing.T) {
|
||||
d := "123.com"
|
||||
d1 := "http://123.com"
|
||||
d2 := "https://123.com"
|
||||
d3 := "123#.com"
|
||||
d4 := "123.coooom"
|
||||
|
||||
t.Log(CheckDomain(d))
|
||||
t.Log(CheckDomain(d1))
|
||||
t.Log(CheckDomain(d2))
|
||||
t.Log(CheckDomain(d3))
|
||||
t.Log(CheckDomain(d4))
|
||||
}
|
||||
7
cmd/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Main applications for this project.
|
||||
|
||||
The directory name for each application should match the name of the executable you want to have (e.g., `/cmd/myapp`).
|
||||
|
||||
Don't put a lot of code in the application directory. If you think the code can be imported and used in other projects, then it should live in the `/pkg` directory. If the code is not reusable or if you don't want others to reuse it, put that code in the `/internal` directory. You'll be surprised what others will do, so be explicit about your intentions!
|
||||
|
||||
It's common to have a small `main` function that imports and invokes the code from the `/internal` and `/pkg` directories and nothing else.
|
||||
67
cmd/backup/config/config.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config config
|
||||
type Config struct {
|
||||
config.APIMode
|
||||
|
||||
Database config.Database // 数据库配置
|
||||
BackupTo string // 备份到, default: qiniu
|
||||
Interval string // 备份周期, default: 7d
|
||||
Validity int // 备份保留时间, default: 60
|
||||
Qiniu config.Qiniu // 七牛OSS配置
|
||||
}
|
||||
|
||||
// Conf 配置
|
||||
var Conf Config
|
||||
|
||||
// load config file
|
||||
func init() {
|
||||
// run mode
|
||||
mode := config.RunMode(os.Getenv("RUN_MODE"))
|
||||
if !mode.IsRunMode() {
|
||||
panic("config: unsupported env RUN_MODE: " + mode)
|
||||
}
|
||||
logrus.Infof("Run mode:%s", mode)
|
||||
|
||||
// 加载配置文件
|
||||
etc, err := config.WorkEtcPath()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
path := filepath.Join(etc, "app.yml")
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = yaml.Unmarshal(data, &Conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
Conf.RunMode = mode
|
||||
|
||||
// read env
|
||||
readDatabaseEnv()
|
||||
}
|
||||
|
||||
func readDatabaseEnv() {
|
||||
key := strings.ToUpper(Conf.Name) + "_DB_DRIVER"
|
||||
if d := os.Getenv(key); d != "" {
|
||||
Conf.Database.Driver = d
|
||||
}
|
||||
key = strings.ToUpper(Conf.Name) + "_DB_SOURCE"
|
||||
if s := os.Getenv(key); s != "" {
|
||||
Conf.Database.Source = s
|
||||
}
|
||||
}
|
||||
60
cmd/backup/docs/docs.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
|
||||
const docTemplate = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/ping": {
|
||||
"get": {
|
||||
"description": "ping",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"ping"
|
||||
],
|
||||
"summary": "ping",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "it's ok",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "1.0",
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
Title: "backup API",
|
||||
Description: "This is a backup server.",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
RightDelim: "}}",
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}
|
||||
34
cmd/backup/docs/swagger.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "This is a backup server.",
|
||||
"title": "backup API",
|
||||
"contact": {},
|
||||
"version": "1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/ping": {
|
||||
"get": {
|
||||
"description": "ping",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"ping"
|
||||
],
|
||||
"summary": "ping",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "it's ok",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
cmd/backup/docs/swagger.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
info:
|
||||
contact: {}
|
||||
description: This is a backup server.
|
||||
title: backup API
|
||||
version: "1.0"
|
||||
paths:
|
||||
/ping:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: ping
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: it's ok
|
||||
schema:
|
||||
type: string
|
||||
summary: ping
|
||||
tags:
|
||||
- ping
|
||||
swagger: "2.0"
|
||||
14
cmd/backup/etc/app.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
apimode:
|
||||
name: cmd-backup
|
||||
listen: 0.0.0.0:9000
|
||||
database: # 数据库配置
|
||||
driver: mongodb
|
||||
source: mongodb://localhost:27017
|
||||
backupto: qiniu # 备份到, default: qiniu
|
||||
interval: 7d # 备份周期, default: 7d
|
||||
validity: 60 # 备份保留时间, default: 60
|
||||
qiniu: # 七牛OSS
|
||||
bucket: eiblog
|
||||
domain: st.deepzz.com
|
||||
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
|
||||
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
|
||||
17
cmd/backup/handler/internal/internal.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/eiblog/eiblog/cmd/backup/config"
|
||||
"github.com/eiblog/eiblog/pkg/third/qiniu"
|
||||
)
|
||||
|
||||
// QiniuClient 七牛客户端
|
||||
var QiniuClient *qiniu.QiniuClient
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
QiniuClient, err = qiniu.NewQiniuClient(config.Conf.Qiniu)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
24
cmd/backup/handler/ping/ping.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package ping
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterRoutes register routes
|
||||
func RegisterRoutes(group gin.IRoutes) {
|
||||
group.GET("/ping", handlePing)
|
||||
}
|
||||
|
||||
// handlePing ping
|
||||
// @Summary ping
|
||||
// @Description ping
|
||||
// @Tags ping
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {string} string "it's ok"
|
||||
// @Router /ping [get]
|
||||
func handlePing(c *gin.Context) {
|
||||
c.String(http.StatusOK, "it's ok")
|
||||
}
|
||||
14
cmd/backup/handler/swag/swag.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package swag
|
||||
|
||||
import (
|
||||
_ "github.com/eiblog/eiblog/cmd/eiblog/docs" // docs
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
// RegisterRoutes register routes
|
||||
func RegisterRoutes(group gin.IRoutes) {
|
||||
group.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
}
|
||||
7
cmd/backup/handler/timer/db/db.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package db
|
||||
|
||||
// Storage 备份恢复器
|
||||
type Storage interface {
|
||||
Backup(name string) (string, error)
|
||||
Restore(path string) error
|
||||
}
|
||||
72
cmd/backup/handler/timer/db/mgodb.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/backup/config"
|
||||
"github.com/eiblog/eiblog/pkg/connector/db"
|
||||
)
|
||||
|
||||
// MongoStorage 备份恢复器
|
||||
type MongoStorage struct{}
|
||||
|
||||
// Backup 备份
|
||||
func (r MongoStorage) Backup(name string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
|
||||
// dump
|
||||
u, err := url.Parse(config.Conf.Database.Source)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
arg := fmt.Sprintf("mongodump -h %s -d eiblog -o /tmp", u.Host)
|
||||
cmd := exec.CommandContext(ctx, "sh", "-c", arg)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// tar
|
||||
arg = fmt.Sprintf("tar czf /tmp/%s -C /tmp eiblog", name)
|
||||
cmd = exec.CommandContext(ctx, "sh", "-c", arg)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "/tmp/" + name, nil
|
||||
}
|
||||
|
||||
// Restore 恢复
|
||||
func (r MongoStorage) Restore(path string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
|
||||
// drop database
|
||||
mdb, err := db.NewMDB(config.Conf.Database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = mdb.Drop(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// unarchive
|
||||
arg := fmt.Sprintf("tar xzf %s -C /tmp", path)
|
||||
cmd := exec.CommandContext(ctx, "sh", "-c", arg)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// restore
|
||||
u, err := url.Parse(config.Conf.Database.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
arg = fmt.Sprintf("mongorestore -h %s -d eiblog /tmp/eiblog", u.Host)
|
||||
cmd = exec.CommandContext(ctx, "sh", "-c", arg)
|
||||
return cmd.Run()
|
||||
}
|
||||
95
cmd/backup/handler/timer/timer.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package timer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/backup/config"
|
||||
"github.com/eiblog/eiblog/cmd/backup/handler/timer/db"
|
||||
"github.com/eiblog/eiblog/cmd/backup/handler/timer/to"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Start to backup with ticker
|
||||
func Start(restore bool) (err error) {
|
||||
var (
|
||||
storage db.Storage
|
||||
backupTo to.BackupRestorer
|
||||
)
|
||||
|
||||
// backup from
|
||||
switch config.Conf.Database.Driver {
|
||||
case "mongodb":
|
||||
storage = db.MongoStorage{}
|
||||
|
||||
default:
|
||||
return errors.New("timer: unknown backup from driver: " +
|
||||
config.Conf.Database.Driver)
|
||||
}
|
||||
|
||||
// backup to
|
||||
switch config.Conf.BackupTo {
|
||||
case "qiniu":
|
||||
backupTo = to.QiniuBackupRestorer{}
|
||||
|
||||
default:
|
||||
return errors.New("timer: unknown backup to driver: " +
|
||||
config.Conf.BackupTo)
|
||||
}
|
||||
|
||||
// restore
|
||||
if restore {
|
||||
path, err := backupTo.Download()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = storage.Restore(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Info("timer: Restore success")
|
||||
}
|
||||
|
||||
// backup
|
||||
interval, err := ParseDuration(config.Conf.Interval)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := time.NewTicker(interval)
|
||||
for now := range t.C {
|
||||
name := fmt.Sprintf("eiblog-%s.tar.gz", now.Format("2006-01-02"))
|
||||
path, err := storage.Backup(name)
|
||||
if err != nil {
|
||||
logrus.Error("timer: Start.Backup: ", now.Format(time.RFC3339), err)
|
||||
continue
|
||||
}
|
||||
err = backupTo.Upload(path)
|
||||
if err != nil {
|
||||
logrus.Error("timer: Start.Backup: ", now.Format(time.RFC3339), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseDuration parse string to duration
|
||||
func ParseDuration(d string) (time.Duration, error) {
|
||||
if len(d) == 0 {
|
||||
return 0, errors.New("timer: incorrect duration input")
|
||||
}
|
||||
|
||||
length := len(d)
|
||||
switch d[length-1] {
|
||||
case 's', 'm', 'h':
|
||||
return time.ParseDuration(d)
|
||||
case 'd':
|
||||
di, err := strconv.Atoi(d[:length-1])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.Duration(di) * time.Hour * 24, nil
|
||||
}
|
||||
return 0, errors.New("timer: unsupported duration:" + d)
|
||||
}
|
||||
66
cmd/backup/handler/timer/to/qiniu.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package to
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/backup/config"
|
||||
"github.com/eiblog/eiblog/cmd/backup/handler/internal"
|
||||
"github.com/eiblog/eiblog/pkg/third/qiniu"
|
||||
)
|
||||
|
||||
// QiniuBackupRestorer qiniu backup restorer
|
||||
type QiniuBackupRestorer struct{}
|
||||
|
||||
// Upload implements timer.BackupRestorer
|
||||
func (s QiniuBackupRestorer) Upload(path string) error {
|
||||
name := filepath.Base(path)
|
||||
|
||||
// upload file
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploadParams := qiniu.UploadParams{
|
||||
Name: filepath.Join("blog", name), // blog/eiblog-xx.tar.gz
|
||||
Size: fi.Size(),
|
||||
Data: f,
|
||||
NoCompletePath: true,
|
||||
}
|
||||
_, err = internal.QiniuClient.Upload(uploadParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// after days delete
|
||||
deleteParams := qiniu.DeleteParams{
|
||||
Name: filepath.Join("blog", name), // blog/eiblog-xx.tar.gz
|
||||
Days: config.Conf.Validity,
|
||||
NoCompletePath: true,
|
||||
}
|
||||
return internal.QiniuClient.Delete(deleteParams)
|
||||
}
|
||||
|
||||
// Download implements timer.BackupRestorer
|
||||
func (s QiniuBackupRestorer) Download() (string, error) {
|
||||
// backup file
|
||||
params := qiniu.ContentParams{
|
||||
Prefix: "blog/",
|
||||
}
|
||||
raw, err := internal.QiniuClient.Content(params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
path := filepath.Join("/tmp", "eiblog.tar.gz")
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, _ = f.Write(raw)
|
||||
defer f.Close()
|
||||
return path, nil
|
||||
}
|
||||
7
cmd/backup/handler/timer/to/to.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package to
|
||||
|
||||
// BackupRestorer 备份存储接口
|
||||
type BackupRestorer interface {
|
||||
Upload(path string) error
|
||||
Download() (path string, err error)
|
||||
}
|
||||
61
cmd/backup/main.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Package main provides ...
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/backup/config"
|
||||
"github.com/eiblog/eiblog/cmd/backup/handler/ping"
|
||||
"github.com/eiblog/eiblog/cmd/backup/handler/swag"
|
||||
"github.com/eiblog/eiblog/cmd/backup/handler/timer"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// @title backup API
|
||||
// @version 1.0
|
||||
// @description This is a backup server.
|
||||
|
||||
var restore bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&restore, "restore", false, "restore data into mongodb")
|
||||
}
|
||||
|
||||
func main() {
|
||||
logrus.Info("Hi, it's App " + config.Conf.Name)
|
||||
|
||||
flag.Parse()
|
||||
endRun := make(chan error, 1)
|
||||
|
||||
runCommand(restore, endRun)
|
||||
|
||||
runHTTPServer(endRun)
|
||||
logrus.Fatal(<-endRun)
|
||||
}
|
||||
|
||||
func runCommand(restore bool, endRun chan error) {
|
||||
go func() {
|
||||
endRun <- timer.Start(restore)
|
||||
}()
|
||||
}
|
||||
|
||||
func runHTTPServer(endRun chan error) {
|
||||
if config.Conf.RunMode.IsReleaseMode() {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
e := gin.Default()
|
||||
|
||||
// swag
|
||||
swag.RegisterRoutes(e)
|
||||
|
||||
// route
|
||||
ping.RegisterRoutes(e)
|
||||
|
||||
// start
|
||||
go func() {
|
||||
endRun <- e.Run(config.Conf.Listen)
|
||||
}()
|
||||
logrus.Info("HTTP server running on: " + config.Conf.Listen)
|
||||
}
|
||||
5
cmd/eiblog/CHANGELOG.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [3.0.3](https://github.com/eiblog/eiblog/compare/v3.0.2...v3.0.3) (2025-07-25)
|
||||
83
cmd/eiblog/config/config.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
// Conf 配置
|
||||
Conf Config
|
||||
// EtcDir 工作目录
|
||||
EtcDir string
|
||||
)
|
||||
|
||||
// Config config
|
||||
type Config struct {
|
||||
config.APIMode
|
||||
// 静态资源版本, 每次更改了 js/css 都需要提高该值
|
||||
StaticVersion int
|
||||
// 数据库配置
|
||||
Database config.Database
|
||||
// 热词, 手动指定, 用于搜索
|
||||
HotWords []string
|
||||
// Elasticsearch 配置
|
||||
ESHost string
|
||||
|
||||
General config.General
|
||||
Disqus config.Disqus
|
||||
Google config.Google
|
||||
Qiniu config.Qiniu
|
||||
Twitter config.Twitter
|
||||
FeedRPC config.FeedRPC
|
||||
Account config.Account
|
||||
Pages []config.CustomPage
|
||||
}
|
||||
|
||||
// init 初始化配置
|
||||
func init() {
|
||||
// run mode
|
||||
mode := config.RunMode(os.Getenv("RUN_MODE"))
|
||||
if !mode.IsRunMode() {
|
||||
panic("config: unsupported env RUN_MODE: " + mode)
|
||||
}
|
||||
logrus.Infof("Run mode:%s", mode)
|
||||
|
||||
// 加载配置文件
|
||||
var err error
|
||||
EtcDir, err = config.WorkEtcPath()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
path := filepath.Join(EtcDir, "app.yml")
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = yaml.Unmarshal(data, &Conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
Conf.RunMode = mode
|
||||
|
||||
// 读取环境变量配置
|
||||
readDatabaseEnv()
|
||||
}
|
||||
|
||||
func readDatabaseEnv() {
|
||||
key := strings.ToUpper(Conf.Name) + "_DB_DRIVER"
|
||||
if d := os.Getenv(key); d != "" {
|
||||
Conf.Database.Driver = d
|
||||
}
|
||||
key = strings.ToUpper(Conf.Name) + "_DB_SOURCE"
|
||||
if s := os.Getenv(key); s != "" {
|
||||
Conf.Database.Source = s
|
||||
}
|
||||
}
|
||||
36
cmd/eiblog/docs/docs.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
|
||||
const docTemplate = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "",
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
Title: "",
|
||||
Description: "",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
RightDelim: "}}",
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}
|
||||
7
cmd/eiblog/docs/swagger.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"contact": {}
|
||||
},
|
||||
"paths": {}
|
||||
}
|
||||
4
cmd/eiblog/docs/swagger.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
info:
|
||||
contact: {}
|
||||
paths: {}
|
||||
swagger: "2.0"
|
||||
59
cmd/eiblog/etc/app.yml
Normal file
@@ -0,0 +1,59 @@
|
||||
apimode:
|
||||
name: cmd-eiblog
|
||||
listen: 0.0.0.0:9000
|
||||
host: example.com
|
||||
database: # 数据库配置
|
||||
driver: sqlite
|
||||
source: ./db.sqlite
|
||||
hotwords: # 热搜词
|
||||
- docker
|
||||
- mongodb
|
||||
- curl
|
||||
- dns
|
||||
staticversion: 1
|
||||
eshost: # http://elasticsearch:9200
|
||||
pages:
|
||||
- name: 自定义页面
|
||||
path: /page/custom.html
|
||||
showinnav: true
|
||||
isembed: false
|
||||
- name: 自定义页面2
|
||||
path: /page/custom2.html
|
||||
showinnav: true
|
||||
isembed: true
|
||||
general: # 常规配置
|
||||
pagenum: 10 # 首页展示文章数量
|
||||
pagesize: 20 # 管理界面
|
||||
descprefix: "Desc:" # 文章描述前缀
|
||||
identifier: <!--more--> # 截取预览标识
|
||||
length: 400 # 自动截取预览, 字符数
|
||||
timezone: Asia/Shanghai # 时区
|
||||
twofactor: true # 是否启用两步验证
|
||||
disqus: # 评论相关
|
||||
shortname: xxxxxx
|
||||
publickey: wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
|
||||
accesstoken: 50023908f39f4607957e909b495326af
|
||||
google: # 谷歌分析
|
||||
url: https://www.google-analytics.com/g/collect
|
||||
tid: G-xxxxxxxxxx
|
||||
v: "2"
|
||||
adsense: <script async src="https://pagead2.googlesyndication.com/xxx" crossorigin="anonymous"></script>
|
||||
qiniu: # 七牛OSS
|
||||
bucket: eiblog
|
||||
domain: st.deepzz.com
|
||||
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
|
||||
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
|
||||
twitter: # twitter card
|
||||
card: summary
|
||||
site: deepzz02
|
||||
image: st.deepzz.com/static/img/avatar.jpg
|
||||
address: twitter.com/deepzz02
|
||||
feedrpc: # rss ping
|
||||
feedrurl: https://deepzz.superfeedr.com/
|
||||
pingrpc:
|
||||
- http://ping.baidu.com/ping/RPC2
|
||||
- http://rpc.pingomatic.com/
|
||||
# 数据初始化操作,可到博客后台修改
|
||||
account:
|
||||
username: deepzz # *后台登录用户名
|
||||
password: deepzz # *登录明文密码
|
||||
|
Before Width: | Height: | Size: 847 B After Width: | Height: | Size: 847 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
8
cmd/eiblog/etc/page/custom.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Custom Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Custom Page</h1>
|
||||
</body>
|
||||
</html>
|
||||
10
cmd/eiblog/etc/page/custom2.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{{define "custom2.html"}}
|
||||
<div id=content class=inner>
|
||||
<article class="post" itemscope itemtype="http://schema.org/Article">
|
||||
<h1 class=title itemprop=headline>Custom Page</h1>
|
||||
<div class="entry-content" itemprop=articleBody>
|
||||
<p>Custom Page</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
{{end}}
|
||||
1
cmd/eiblog/etc/template/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This is the place to put your project's website data if you are not using GitHub pages.
|
||||
@@ -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">
|
||||
@@ -50,8 +50,8 @@
|
||||
<td><a href="/admin/write-post?cid={{.ID}}">{{.Title}}</a></td>
|
||||
<td>{{.Author}}</td>
|
||||
<td>{{if gt .SerieID 0}}专题ID:{{.SerieID}}{{else}}--{{end}}</td>
|
||||
<td>{{dateformat .CreateTime "2006/01/02 15:04"}}</td>
|
||||
<td>{{dateformat .UpdateTime "2006/01/02 15:04"}}</td>
|
||||
<td>{{dateformat .CreatedAt "2006/01/02 15:04"}}</td>
|
||||
<td>{{dateformat .UpdatedAt "2006/01/02 15:04"}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
@@ -22,10 +22,10 @@
|
||||
<label for=password class="sr-only">密码</label>
|
||||
<input type=password id=password name=password class="text-l w-100" placeholder="密码">
|
||||
</p>
|
||||
<!-- <p>
|
||||
{{if .TwoFactor}}<p>
|
||||
<label for=code class="sr-only">两步验证</label>
|
||||
<input type=text id=code name=code class="text-l w-100" placeholder="两步验证">
|
||||
</p> -->
|
||||
</p>{{end}}
|
||||
<p class=submit>
|
||||
<button type=submit class="btn btn-l w-100 primary">登录</button>
|
||||
</p>
|
||||
@@ -35,7 +35,7 @@
|
||||
<section class="typecho-post-option" role="application">
|
||||
<label for="date" class="typecho-label">发布日期</label>
|
||||
<p>
|
||||
<input class="typecho-date w-100" type="text" name="date" id="date" {{with .Edit}}value="{{dateformat .CreateTime "2006-01-02 15:04"}}"{{end}} />
|
||||
<input class="typecho-date w-100" type="text" name="date" id="date" {{with .Edit}}value="{{dateformat .CreatedAt "2006-01-02 15:04"}}"{{end}} />
|
||||
</p>
|
||||
</section>
|
||||
<section class="typecho-post-option category-option">
|
||||
@@ -265,7 +265,6 @@ $(document).ready(function() {
|
||||
|
||||
function autoSaveListener() {
|
||||
setInterval(function() {
|
||||
idInput.val(cid);
|
||||
var data = form.serialize();
|
||||
|
||||
if (savedData != data && !locked) {
|
||||
@@ -275,7 +274,7 @@ $(document).ready(function() {
|
||||
$.post(formAction, data + '&do=auto', function(o) {
|
||||
savedData = data;
|
||||
lastSaveTime = o.time;
|
||||
cid = o.cid;
|
||||
idInput.val(o.cid);
|
||||
autoSave.text('已保存' + ' (' + o.time + ')').effect('highlight', 1000);
|
||||
locked = false;
|
||||
}, 'json');
|
||||
@@ -64,12 +64,12 @@
|
||||
<td><a href="/post/{{.Slug}}.html#comments" class="balloon-button size-1">{{.Count}}</a></td>
|
||||
<td>
|
||||
<a href="/admin/write-post?cid={{.ID}}">{{.Title}}</a>
|
||||
<a target="_black" href="/post/{{.Slug}}.html" title="浏览 {{.Title}}"><i class="i-exlink"></i></a>
|
||||
<a target="_blank" href="/post/{{.Slug}}.html" title="浏览 {{.Title}}"><i class="i-exlink"></i></a>
|
||||
</td>
|
||||
<td>{{.Author}}</td>
|
||||
<td>{{if gt .SerieID 0}}专题ID:{{.SerieID}}{{else}}--{{end}}</td>
|
||||
<td>{{dateformat .CreateTime "06/01/02 15:04"}}</td>
|
||||
<td>{{dateformat .UpdateTime "06/01/02 15:04"}}</td>
|
||||
<td>{{dateformat .CreatedAt "06/01/02 15:04"}}</td>
|
||||
<td>{{dateformat .UpdatedAt "06/01/02 15:04"}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
@@ -3,15 +3,41 @@
|
||||
<div class="typecho-page-title">
|
||||
<h2>个人设置</h2>
|
||||
</div>
|
||||
{{with .Account}}
|
||||
{{with .Ei}}
|
||||
<div class="row typecho-page-main">
|
||||
<div class="col-mb-12 col-tb-3">
|
||||
<p>
|
||||
<img class="profile-avatar" src="//{{$.Kodo.Domain}}/static/img/avatar.jpg" alt="{{.BlogName}}" />
|
||||
<img class="profile-avatar" src="//{{$.Qiniu.Domain}}/static/img/avatar.png" alt="{{.Blogger.BlogName}}" />
|
||||
</p>
|
||||
<h2>{{.BlogName}}</h2>
|
||||
<p>{{.SubTitle}}</p>
|
||||
<p>最后登录: {{dateformat .LoginTime "2006/01/02 15:04"}}</p>
|
||||
<h2>{{.Blogger.BlogName}}</h2>
|
||||
<p>{{.Blogger.SubTitle}}</p>
|
||||
<p>最后登录: {{dateformat .Account.LoginAt "2006/01/02 15:04"}}</p>
|
||||
{{if .Account.TwoFactorSecret}}
|
||||
<strong>2FA 已绑定 <a class="unbind-2fa" href="/admin/profile?unbind=true">解绑</a></strong>
|
||||
{{end}}
|
||||
{{if $.TwoFactorSecret}}
|
||||
<strong>2FA 绑定</strong>
|
||||
<form action="/admin/api/twofactor" method="post" enctype="application/x-www-form-urlencoded">
|
||||
<ul class="typecho-option">
|
||||
<li>
|
||||
<img src="{{$.TwoFactorSecret}}" alt="2fa code" style="width:180px;height:180px;">
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="typecho-option">
|
||||
<li>
|
||||
<input id="2fa-0-1" name="code" type="text" class="text" />
|
||||
<p class="description">
|
||||
输入 2FA 验证码, 用于确认绑定.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="typecho-option typecho-option-submit">
|
||||
<li>
|
||||
<button type="submit" class="btn primary">
|
||||
确认绑定</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="col-mb-12 col-tb-6 col-tb-offset-1 typecho-content-panel" role="form">
|
||||
<section>
|
||||
@@ -21,7 +47,7 @@
|
||||
<li>
|
||||
<label class="typecho-label" for="info-0-1">
|
||||
个人邮箱</label>
|
||||
<input id="info-0-1" name="email" type="text" class="text" value="{{.Email}}" />
|
||||
<input id="info-0-1" name="email" type="text" class="text" value="{{.Account.Email}}" />
|
||||
<p class="description">
|
||||
用于发送告警邮件及其它通知, 建议填写, 如: example@163.com.</p>
|
||||
</li>
|
||||
@@ -30,7 +56,7 @@
|
||||
<li>
|
||||
<label class="typecho-label" for="info-0-2">
|
||||
移动电话</label>
|
||||
<input id="info-0-2" name="phoneNumber" type="text" class="text" value="{{.PhoneN}}" />
|
||||
<input id="info-0-2" name="phoneNumber" type="text" class="text" value="{{.Account.PhoneN}}" />
|
||||
<p class="description">
|
||||
选择填写, 如: +8615123456789.</p>
|
||||
</li>
|
||||
@@ -39,7 +65,7 @@
|
||||
<li>
|
||||
<label class="typecho-label" for="info-0-3">
|
||||
家庭住址</label>
|
||||
<input id="info-0-3" name="address" type="text" class="text" value="{{.Address}}" />
|
||||
<input id="info-0-3" name="address" type="text" class="text" value="{{.Account.Address}}" />
|
||||
<p class="description">
|
||||
选择填写, 如: xx省xx市xx区(县)xxxx小区xxx号.</p>
|
||||
</li>
|
||||
@@ -60,7 +86,7 @@
|
||||
<li>
|
||||
<label class="typecho-label" for="blog-0-1">
|
||||
博客昵称 *</label>
|
||||
<input id="blog-0-1" name="blogName" type="text" class="text" value="{{.BlogName}}" />
|
||||
<input id="blog-0-1" name="blogName" type="text" class="text" value="{{.Blogger.BlogName}}" />
|
||||
<p class="description">
|
||||
用户昵称可以与用户名不同, 用于前台显示.
|
||||
<br />如果你将此项留空, 将默认使用登录用户名.</p>
|
||||
@@ -68,45 +94,45 @@
|
||||
</ul>
|
||||
<ul class="typecho-option">
|
||||
<li>
|
||||
<label class="typecho-label" for="blog-0-4">
|
||||
<label class="typecho-label" for="blog-0-2">
|
||||
标题显示 *</label>
|
||||
<input id="blog-0-4" name="bTitle" type="text" class="text" value="{{.BTitle}}" />
|
||||
<input id="blog-0-2" name="bTitle" type="text" class="text" value="{{.Blogger.BTitle}}" />
|
||||
<p class="description">
|
||||
用于所有页面的title组成, 如: Deepzz's Blog</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="typecho-option">
|
||||
<li>
|
||||
<label class="typecho-label" for="blog-0-2">
|
||||
<label class="typecho-label" for="blog-0-3">
|
||||
个人格言</label>
|
||||
<input id="blog-0-2" name="subTitle" type="text" class="text" value="{{.SubTitle}}" />
|
||||
<input id="blog-0-3" name="subTitle" type="text" class="text" value="{{.Blogger.SubTitle}}" />
|
||||
<p class="description">
|
||||
简介或格言, 如: 生活百般滋味, 人生需要笑对.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="typecho-option">
|
||||
<li>
|
||||
<label class="typecho-label" for="blog-0-3">
|
||||
<label class="typecho-label" for="blog-0-4">
|
||||
备案号</label>
|
||||
<input id="blog-0-3" name="beiAn" type="text" class="text" value="{{.BeiAn}}" />
|
||||
<input id="blog-0-4" name="beiAn" type="text" class="text" value="{{.Blogger.BeiAn}}" />
|
||||
<p class="description">
|
||||
用于底部显示, 不添加则不显示, 如: 蜀 ICP 备 16021362 号</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="typecho-option">
|
||||
<li>
|
||||
<label class="typecho-label" for="blog-0-4">
|
||||
<label class="typecho-label" for="blog-0-5">
|
||||
专题前说</label>
|
||||
<textarea id="blog-0-4" name="seriessay">{{.SeriesSay}}</textarea>
|
||||
<textarea id="blog-0-5" name="seriessay">{{.Blogger.SeriesSay}}</textarea>
|
||||
<p class="description">
|
||||
此文字用于专题前述, 会在专题最前方显示.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="typecho-option">
|
||||
<li>
|
||||
<label class="typecho-label" for="blog-0-5">
|
||||
<label class="typecho-label" for="blog-0-6">
|
||||
归档前说</label>
|
||||
<textarea id="blog-0-5" name="archivessay">{{.ArchivesSay}}</textarea>
|
||||
<textarea id="blog-0-6" name="archivessay">{{.Blogger.ArchivesSay}}</textarea>
|
||||
<p class="description">
|
||||
此文字用于归档前述, 会在归档最前方显示.</p>
|
||||
</li>
|
||||
@@ -123,7 +149,7 @@
|
||||
<section id="change-password">
|
||||
<h3>密码修改</h3>
|
||||
<form action="/admin/api/password" method="post" enctype="application/x-www-form-urlencoded">
|
||||
<ul class="typecho-option" id="typecho-option-item-password-10">
|
||||
<ul class="typecho-option" id="typecho-option-item-oldpwd-11">
|
||||
<li>
|
||||
<label class="typecho-label" for="password-0-11">
|
||||
原始密码</label>
|
||||
@@ -132,26 +158,26 @@
|
||||
该账户旧密码.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="typecho-option" id="typecho-option-item-password-10">
|
||||
<ul class="typecho-option" id="typecho-option-item-newpwd-12">
|
||||
<li>
|
||||
<label class="typecho-label" for="password-0-11">
|
||||
<label class="typecho-label" for="password-0-12">
|
||||
用户密码</label>
|
||||
<input id="password-0-11" name="new" type="password" class="w-60" />
|
||||
<input id="password-0-12" name="new" type="password" class="w-60" />
|
||||
<p class="description">
|
||||
为此用户分配一个密码.
|
||||
<br />建议使用特殊字符与字母、数字的混编样式,以增加系统安全性.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="typecho-option" id="typecho-option-item-confirm-11">
|
||||
<ul class="typecho-option" id="typecho-option-item-confirm-13">
|
||||
<li>
|
||||
<label class="typecho-label" for="confirm-0-12">
|
||||
<label class="typecho-label" for="confirm-0-13">
|
||||
用户密码确认</label>
|
||||
<input id="confirm-0-12" name="confirm" type="password" class="w-60" />
|
||||
<input id="confirm-0-13" name="confirm" type="password" class="w-60" />
|
||||
<p class="description">
|
||||
请确认你的密码, 与上面输入的密码保持一致.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="typecho-option typecho-option-submit" id="typecho-option-item-submit-13">
|
||||
<ul class="typecho-option typecho-option-submit" id="typecho-option-item-submit-14">
|
||||
<li>
|
||||
<button type="submit" class="btn primary">
|
||||
更新密码</button>
|
||||
@@ -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,11 +45,12 @@
|
||||
<td>
|
||||
<input type="checkbox" value="{{.ID}}" name="mid[]" />
|
||||
</td>
|
||||
<td><a href="/admin/add-serie?mid={{.ID}}">{{.ID}}</a>
|
||||
<a href="/series.html#toc-{{.ID}}" title="浏览 {{.Name}}"><i class="i-exlink"></i></a>
|
||||
<td>{{.ID}}</td>
|
||||
<td>
|
||||
<a href="/admin/add-serie?mid={{.ID}}">{{.Name}}</a>
|
||||
<a target="_blank" href="/series.html#toc-{{.ID}}" title="浏览 {{.Name}}"><i class="i-exlink"></i></a>
|
||||
</td>
|
||||
<td>{{.Name}}</td>
|
||||
<td>{{dateformat .CreateTime "2006/01/02 15:04"}}</td>
|
||||
<td>{{dateformat .CreatedAt "2006/01/02 15:04"}}</td>
|
||||
<td><a class="balloon-button left size-50" href="#">{{len .Articles}}</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
@@ -51,8 +51,8 @@
|
||||
<td>{{.Title}}</td>
|
||||
<td>{{.Author}}</td>
|
||||
<td>{{if gt .SerieID 0}}专题ID:{{.SerieID}}{{else}}--{{end}}</td>
|
||||
<td>{{dateformat .CreateTime "2006/01/02 15:04"}}</td>
|
||||
<td>{{dateformat .DeleteTime "2006/01/02 15:04"}}</td>
|
||||
<td>{{dateformat .CreatedAt "2006/01/02 15:04"}}</td>
|
||||
<td>{{dateformat .DeletedAt "2006/01/02 15:04"}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
1
cmd/eiblog/etc/template/article.html
Normal file
@@ -0,0 +1 @@
|
||||
{{define "article"}}<div id=content class=inner>{{with .Article}}<article class="post post-{{.ID}}" itemscope itemtype="http://schema.org/Article"><div class=meta><div class=date><time itemprop=datePublished content="{{dateformat .CreatedAt "2006-01-02T15:04:05Z07:00"}}">{{dateformat .CreatedAt "Jan 02, 2006"}}</time></div><div class="date-modified"><time itemprop=dateModified content="{{dateformat .UpdatedAt "2006-01-02T15:04:05Z07:00"}}">{{dateformat .UpdatedAt "Jan 02, 2006"}}</time></div><div class=comment><a href="#comments">{{.Count}} Comments</a></div></div><h1 class=title itemprop=headline>{{.Title}}</h1><div class="entry-content" itemprop=articleBody>{{str2html .Header}}{{str2html .Content}}<p>本文链接:<a rel="bookmark" title="Permalink to {{.Title}}" href="//{{$.Domain}}/post/{{.Slug}}.html" itemprop="url">https://{{$.Domain}}/post/{{.Slug}}.html</a>,<a href="//{{$.Domain}}/post/{{.Slug}}.html#comments">参与评论 »</a></p><p>--<acronym title="End of File">EOF</acronym>--</p><p class="post-info">发表于 <span class="date">{{dateformat .CreatedAt "2006-01-02 15:04:05"}}</span>{{if gt (.Tags|len) 0}},并被添加「{{range $index, $elem := .Tags}}{{if gt $index 0}}、{{end}}<a href="/search.html?q=tag:{{$elem}}">{{$elem}}</a>{{end}}」标签{{end}}{{if .UpdatedAt|isnotzero}},最后修改于 <span class="date">{{dateformat .UpdatedAt "2006-01-02 15:04:05"}}</span>{{end}}。</p>{{with $.Copyright}}<p class="copyright-info">{{str2html $.Copyright}}<a href="//{{$.Domain}}/post/about.html#toc_1">更多说明 »</a></p>{{end}}{{if gt $.Days 100}}<p class="expire-tips">提醒:本文最后更新于 {{$.Days}} 天前,文中所描述的信息可能已发生改变,请谨慎使用。</p>{{end}}{{with $.Serie}}<div class="entry-series"><h3>专题「{{.Name}}」的其它文章 <a href="/series.html#toc-{{.ID}}" title="更多">»</a></h3><ul>{{range .Articles}}{{if ne .ID $.Article.ID}}<li><a href="/post/{{.Slug}}.html">{{.Title}}</a> <span class="date">({{dateformat .CreatedAt "Jan 02, 2006"}})</span></li>{{end}}{{end}}</ul></div>{{end}}</div></article><nav class="page-navi">{{with .Prev}}<a href="/post/{{.Slug}}.html" class=prev>« {{.Title}}</a>{{end}}{{with .Next}}<a href="/post/{{.Slug}}.html" class=next>{{.Title}} »</a>{{end}}</nav><section id=comments><h1 class=title>Comments</h1><div class=total_thread data-identifier="post-{{.Slug}}" data-url="https://{{$.Domain}}/post/{{.Slug}}.html"></div></section>{{end}}</div>{{end}}
|
||||
1
cmd/eiblog/etc/template/blogroll.html
Normal file
@@ -0,0 +1 @@
|
||||
{{define "blogroll"}}<div id=content class=inner>{{with .Article}}<article class="post post-{{.ID}}" itemscope itemtype="http://schema.org/Article"><h1 class=title itemprop=headline>{{.Title}}</h1><div class="entry-content" itemprop=articleBody><style>.links.ssl li{position:relative;padding-left:22px}.links.ssl li::before{content:'';display:block;position:absolute;top:6px;left:0;width:16px;height:16px;background-size:16px 16px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAD/0lEQVR4Ab2XA5hjOxTHO9batrdce8yrtW2b9dgq1jae1vbueOpn2zbzcoqP06luJ993epXk/0vOOUnK8abkq6Ttb1r2UY8+PJzx4L1DZY8/PrzrxYqySYqsXeEcf5Z+I9r3WlI0MjfzVoKx4Gnaj4XP0pDd/sl/kvq1/HJsxXSxcHP7Hs1bsS4+fSt3RXkt9ZVaz6DyehqpDTTSWI3503bF7+sopDEyqOBxqnks02Mia+IzxTyp1jwZlVSRVlHpS3F3qfWDV49I7TZmFNGdP4rqHj1LIhDn3Es2qfU0Kq0hAfLncVN7pvksTq0fNH+fxSZeVkN+kbSk/0xnddt1jYyanyGSaoz031BfpaO+5U7qyPVavAe3VdeSavIzq3gt+RUe0Qg3Z2wFnol/wSXSl+NuRzQPCfIKYGnOCDn4FHf2H71+8CJP2q7fN3Y/xIrWxKCYWX0SPBZv2SkyPOd+qlFVTyH5lfjaiGYhwZ607y1o07e0mvxBraPRhiMTD3qecsPbDsDB9CtE93ylUMLxouy+EHNLjdsrriXoIluGBrrdMDAoMHjslB7RkGoQ1ROm95wRGBQQFBwSGOqu4fqBC7OGlWuNDKwTH3Qf3KpLUHBgSKPCe8/u5G0+Lri343S0Hk/7e0UVxH9gslfi3ttxepJu59loo7sG9TNvJX5RXEn8hwH+FF+KfQ2/N6zbxz11+9Gl1g0C3Hm/5GB+VRyOeArSDvIZ7mEWIJg8NlU9beurGveFDe7zq+LRa9/cndsgwPU3C09kPYq2Lq+Y/C/xC7FPdpyZdAuT3/HW8EzcVF5NMJRUEdZ+oX/TF7eXOgM4BhWAfFX5aA2HpdK2a1SE4mpCJZ4JB8DiRgEg8meJBWs5LBY8E+fBBW4DzJEJN3s12u5RUQ29x+645HeAntzW3fIfp9bMkQv2NDlAL17rbtn3kusdy+88pXB3UwGAeA8Qh82n8Gka2mue3HQANvEkPYhDmmnNDMK74Va/uKBF27DQlWWjFH1Fbfs4F+dv9UsQtu0a2XrrsQmnD745FSmvJ1RNmNFrEr7Wldc7F2cVIGFxP/rw29MQPoxal2ks/BdcnYuzDBASFhQ0cw9/J5wNi+ynYbjC8ywxf4sTXfaDEIvZIJ4TIA4j3wTvmwrAAbH70NvTfnFn5H5Lw1FEt+Fw9RvAbIlgA6ub0ZnoC24DwL8ffJwSswmAT0Q3SmtcAxyHCvgYhnIfpnyGITJnywQ78Oay21ubLRVsx2cLbXEF8Xvh87TGAW6+U3gq8+FElP80GRU+T0HlOhKp9L5bWT2B148Ua79Zjyciy9dOTkTnKwqogidp76mqZnyjqpzxNdumrp75bfa9lNqK1272d2j+D+LY/CDPYPo9AAAAAElFTkSuQmCC)}.links li .more{color:#999;font-size:14px}</style>{{str2html .Content}}<p>注:为了节约本站用户的宝贵时间,长期无法访问的链接会被移除!另本站只有友情链接,不接受链接交换。</p></div></article>{{end}}</div>{{end}}
|
||||
1
cmd/eiblog/etc/template/disqus.html
Normal file
@@ -0,0 +1 @@
|
||||
<!Doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta content="width=device-width,minimum-scale=1.0" name=viewport><meta name="format-detection" content="telephone=no"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name=referrer content=always><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
cmd/eiblog/etc/template/home.html
Normal file
@@ -0,0 +1 @@
|
||||
{{define "home"}}<div id="content" class="inner">{{range .List}}<article class="post post-list"><div class="meta"><div class="date"><time>{{dateformat .CreatedAt "Jan 02, 2006"}}</time></div><div class="comment"><a href="/post/{{.Slug}}.html#comments">{{.Count}} Comments</a></div></div><h1 class="title"><a href="/post/{{.Slug}}.html">{{.Title}}</a></h1><div class="entry-content"><p>{{str2html .Excerpt}}[...]</p><p><a href="/post/{{.Slug}}.html" class="more-link">继续阅读 »</a></p></div></article>{{end}}<nav class="page-navi">{{if gt .Prev 0}}<a href="?pn={{.Prev}}" class="prev">« 上一页</a>{{end}}{{if gt .Next 0}}<a href="?pn={{.Next}}" class="next">下一页 »</a>{{end}}<div class="center"><a href="/archives.html">博客归档</a></div></nav></div>{{end}}
|
||||
1
cmd/eiblog/etc/template/homeLayout.html
Normal file
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><html lang="zh-cn"><head><meta charset="utf-8"><meta content="width=device-width,minimum-scale=1.0" name="viewport"><meta name="format-detection" content="telephone=no"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="referrer" content="always"><title>{{.Title}}</title>{{.AdSense}}<script>!function(n,t){function e(){o("nls",1)}function c(){t.documentElement.style.display="none",u(),location.reload()}function r(n){var t="";try{t=f[n]||"",t.length<99&&c()}catch(e){u()}return t}function i(n,t){try{f[n]=t,t!==f[n]&&u()}catch(e){u()}}function o(n,e){var c=999;e||(c=-1),c=new Date(+new Date+864e5*c).toGMTString();var r=n+"="+e+";path=/;secure;expires="+c;t.cookie=r}function a(n){var e=t.getElementById(n).innerHTML;i(n,e)}function l(e,c){var i=r(e),o=t.createElement(c?"script":"style");return n.execScript&&c?n.execScript(i):(o.innerHTML=i,void t.head.appendChild(o))}function u(){o("v",0)}var f,h=function(){},d=n.L={h:h,l:h,c:h};try{f=localStorage,d.h=a,d.l=l,d.c=o}catch(p){e()}}(this,document);</script>{{if .Version}}<script>L.c('v', {{.Version}})</script>{{end}}<link rel="apple-touch-icon" href="//{{.Qiniu.Domain}}/static/img/favicon.ico"><link rel="search" type="application/opensearchdescription+xml" href="//{{.Domain}}/opensearch.xml" title="{{.BTitle}}">{{if .Version}}<style id="blog_css">{{template "blog_css" .}}</style><script>L.h('blog_css')</script>{{else}}<script>L.l("blog_css")</script>{{end}}<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="//{{.Domain}}/rss.html"><meta name="description" content="{{.Description}}"><meta name="twitter:card" content={{.Twitter.Card}}><meta name="twitter:site" content="@{{.Twitter.Site}}"><meta name="twitter:title" content="{{.Title}}"><meta name="twitter:description" content="{{.Description}}"><meta name="twitter:image" content="https://{{.Twitter.Image}}"></head><body><div class="container"><div class="left-col"><div class="intrude-less"><header id="header" class="inner"><div class="profilepic"><a href="/"></a></div><h1><a href="/">{{.BlogName}}</a></h1><p class="subtitle">{{.SubTitle}}</p><nav id="main-nav"><ul><li {{if eq .Path "/"}}class="on" {{end}}><a href="/"><span>首页</span></a></li><li {{if eq .Path "/series.html"}}class="on" {{end}}><a href="/series.html"><span>专题</span></a></li><li {{if eq .Path "/archives.html"}}class="on" {{end}}><a href="/archives.html"><span>归档</span></a></li><li {{if eq .Path "/post/blogroll.html"}}class="on" {{end}}><a href="/post/blogroll.html"><span>友链</span></a></li><li {{if eq .Path "/post/about.html"}}class="on" {{end}}><a href="/post/about.html"><span>关于</span></a></li>{{range $index, $element := .Pages}}{{if .ShowInNav}}<li {{if eq $.Path .Path}}class="on" {{end}}><a href="{{.Path}}" {{if not .IsEmbed}}target="_blank"{{end}}><span>{{.Name}}</span></a></li>{{end}}{{end}}</ul></nav><nav id="sub-nav"><div class="social"><a target="_blank" class="twitter external" rel="nofollow" href="//{{.Twitter.Address}}" title="Twitter" aria-label="Twitter">Twitter</a><a target="_blank" class="rss" href="//{{.Domain}}/rss.html" title="RSS 订阅" aria-label="RSS 订阅">RSS</a><a class="search" href="/search.html" title="站内搜索" aria-label="站内搜索">Search</a></div></nav></header></div></div><div class="mid-col"><div class="mid-col-container">{{.LayoutContent}}</div><footer id=footer class=inner>© {{.CopyYear}} - {{.BTitle}}{{if .BeiAn}} - <a target="_blank" rel="nofollow designer" class="external beian" href="https://beian.miit.gov.cn">{{.BeiAn}}</a>{{end}}<br>Powered by <a target=_blank href="//github.com/eiblog/eiblog">Eiblog</a> & <a target=_blank rel="nofollow designer" class=external href="//github.com/deepzz0">Deepzz</a></footer></div></div><input type=hidden id=CURRENT_PAGE value="{{.CurrentPage}}"><input type=hidden id=CDN_DOMAIN value="//{{.Qiniu.Domain}}">{{if .Version}}<script id="ana_js">{{template "ana_js"}}</script><script>L.h('ana_js')</script>{{else}}<script>L.l('ana_js', 1)</script>{{end}}{{if .Version}}<script id="jq_js">{{template "jq_js"}}</script><script>L.h('jq_js')</script>{{else}}<script>L.l('jq_js', 1)</script>{{end}}{{if .Version}}<script id="hl_js">{{template "hl_js"}}</script><script>L.h('hl_js')</script>{{else}}<script>L.l('hl_js', 1)</script>{{end}}{{if .Version}}<script id="blog_js">{{template "blog_js" .}}</script><script>L.h('blog_js')</script>{{else}}<script>L.l('blog_js', 1)</script>{{end}}</body></html>
|
||||
1
cmd/eiblog/etc/template/search.html
Normal file
@@ -0,0 +1 @@
|
||||
{{define "search"}}<div id="content" class="inner"><article class="post post-search"><h1 class="title">站内搜索</h1><div class="entry-content"><div id="search"><form action="/search.html"><div class="wrapper"><input maxlength="80" placeholder="请输入关键字..." id="keyword" name="q" {{if .Word}}value="{{.Word}}"{{end}} type="search" required></div><input class="submit" type="submit" value="搜索"></form></div><div id="searchResult">{{if .Word}}{{with .SearchResult}}{{if gt (.Hits.Hits|len) 0}}<div class="info">本次搜索共找到结果 {{.Hits.Total}} 条 (用时 {{.Took}} 秒)</div>{{range .Hits.Hits}}<div class="item"><div class="title"><a href="/post/{{.Source.Slug}}.html">{{if .Highlight.Title}}{{str2html (join .Highlight.Title "")}}{{else}}{{.Source.Title}}{{end}}</a></div><div class="desc">{{if .Source.Img}}<div class="img"><img data-src="{{.Source.Img}}?imageView2/1/w/216/h/162"></div>{{end}}<div class="summary"><span class="date">{{dateformat .Source.Date "2006-01-02"}}</span> ... {{str2html (join .Highlight.Content "...")}} ...</div></div></div>{{end}}{{else}}<div class="no-result">没有找到任何结果,请更换查询词试试~</div><div class="item"><div class="title">或者试试 Google 站内搜索:<a target="_blank" href="//www.google.com/#q=site:{{$.Domain}} {{$.Word}}">site:{{$.Domain}} {{$.Word}}</a></div></div>{{end}}{{end}}{{else}}<div class="hot-words">热搜词:{{range .HotWords}}<a href="?q={{.}}">{{.}}</a>{{end}}</div><div class="intro"><p>支持的搜索格式:</p><ol><li>输入关键词全文搜索:<a href="?q=Let's Encrypt">Let's Encrypt</a>;</li><li>指定时间段搜索:<a href="?q=date:2016">date:2016</a>、<a href="?q=date:2016-10">date:2016-10</a>;</li><li>指定标签搜索:<a href="?q=tag:github">tag:github</a>、<a href="?q=tag:HTTPS">tag:HTTPS</a>;</li><li>组合搜索:<a href="?q=date:2016 tag:docker">date:2016 tag:docker</a>;</li></ol></div>{{end}}</div></div></article>{{if or .Prev .Next}}<nav class="page-navi">{{with .Prev}}<a href="?{{html .}}" class="prev">« 上一页</a>{{end}}{{with .Next}}<a href="?{{html .}}" class="next">下一页 »</a>{{end}}</nav>{{end}}</div>{{end}}
|
||||
3
cmd/eiblog/etc/template/st_ana.js
Normal file
@@ -0,0 +1,3 @@
|
||||
{{define "ana_js"}}
|
||||
!function(e,n,o){var t=e.screen,a=encodeURIComponent,r=["dt="+a(n.title),"dr="+a(n.referrer),"ul="+(o.language||o.browserLanguage).toLowerCase(),"sd="+t.colorDepth+"-bit","sr="+t.width+"x"+t.height,"_="+ +new Date],i="?"+r.join("&");e.__beacon_img=new Image,e.__beacon_img.src="/beacon.html"+i}(window,document,navigator,location);
|
||||
{{end}}
|
||||
3
cmd/eiblog/etc/template/st_blog.css
Normal file
3
cmd/eiblog/etc/template/st_blog.js
Normal file
4
cmd/eiblog/etc/xml/crossdomainTpl.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<cross-domain-policy>
|
||||
<allow-access-from domain="*.{{.Host}}" />
|
||||
</cross-domain-policy>
|
||||
24
cmd/eiblog/etc/xml/feedTpl.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>{{.Title}}</title>
|
||||
<link>https://{{.Host}}</link>
|
||||
<description>{{.SubTitle}}</description>
|
||||
<atom:link href="https://{{.Host}}/rss.html" rel="self" />
|
||||
<atom:link href="{{.FeedrURL}}" rel="hub" />
|
||||
<language>zh-CN</language>
|
||||
<lastBuildDate>{{.BuildDate}}</lastBuildDate>
|
||||
{{range .Articles}}
|
||||
<item>
|
||||
<title>{{.Title}}</title>
|
||||
<link>https://{{$.Host}}/post/{{.Slug}}.html</link>
|
||||
<comments>https://{{$.Host}}/post/{{.Slug}}.html#comments</comments>
|
||||
<guid>https://{{$.Host}}/post/{{.Slug}}.html</guid>
|
||||
<description>
|
||||
<![CDATA[{{imgtonormal .Content}}<p>本文链接:<a href="https://{{$.Host}}/post/{{.Slug}}.html">https://{{$.Host}}/post/{{.Slug}}.html</a>,<a href="https://{{$.Host}}/post/{{.Slug}}.html#comments">参与评论 »</a></p>]]>
|
||||
</description>
|
||||
<pubDate>{{dateformat .CreatedAt "Mon, 02 Jan 2006 15:04:05 -0700"}}</pubDate>
|
||||
</item>
|
||||
{{end}}
|
||||
</channel>
|
||||
</rss>
|
||||
9
cmd/eiblog/etc/xml/opensearchTpl.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
|
||||
xmlns:moz="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>{{.BTitle}}</ShortName>
|
||||
<Description>{{.SubTitle}}</Description>
|
||||
<InputEncoding>UTF-8</InputEncoding>
|
||||
<Url type="text/html" method="get" template="https://{{.Host}}/search.html?q={searchTerms}" />
|
||||
<moz:SearchForm>https://{{.Host}}/search.html</moz:SearchForm>
|
||||
</OpenSearchDescription>
|
||||