Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77ad657cea | ||
|
|
6be2733a21 |
208
.github/workflows/release.yml
vendored
@@ -1,169 +1,65 @@
|
||||
name: Release Image & Asset
|
||||
name: release image & asset
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
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:
|
||||
package:
|
||||
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: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
- 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: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Package tar archive
|
||||
run: scripts/dist_tar.sh ${{ steps.vars.outputs.tag }}
|
||||
- name: Docker tag
|
||||
id: vars
|
||||
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:10})
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
|
||||
- name: Upload tar artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-archives
|
||||
path: "*.tar.gz"
|
||||
retention-days: 7
|
||||
- name: Build and push eiblog
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./build/package/eiblog.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
deepzz0/eiblog:${{ steps.vars.outputs.tag }}
|
||||
deepzz0/eiblog:latest
|
||||
|
||||
# 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: Build and push backup
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./build/package/backup.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
deepzz0/backup:${{ steps.vars.outputs.tag }}
|
||||
deepzz0/backup:latest
|
||||
|
||||
- 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 }}
|
||||
- name: Package tar
|
||||
env:
|
||||
GOPROXY: https://goproxy.io,direct
|
||||
run: scripts/dist_tar.sh ${{ steps.vars.outputs.tag }}
|
||||
- name: Release push
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
files: |
|
||||
*.tar.gz
|
||||
|
||||
5
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
*.DS_Store
|
||||
*.tar.gz
|
||||
*.db
|
||||
backend
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
@@ -17,7 +18,5 @@
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
bin
|
||||
cmd/eiblog/etc/assets/*.xml
|
||||
cmd/eiblog/etc/assets/*.txt
|
||||
assets/*.*
|
||||
db.sqlite
|
||||
cmd/*/backend
|
||||
45
CHANGELOG.md
@@ -2,51 +2,6 @@
|
||||
|
||||
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.5](https://github.com/eiblog/eiblog/compare/v3.0.4...v3.0.5) (2025-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* admin login session ([c1d73f1](https://github.com/eiblog/eiblog/commit/c1d73f1a453af62d15c25a79c382d9cefd8a3d2e))
|
||||
* mongodb uri error ([33afbd3](https://github.com/eiblog/eiblog/commit/33afbd351d2b41f9edf36959908c3f183745d903))
|
||||
* RUN_MODE error ([eaeeaaa](https://github.com/eiblog/eiblog/commit/eaeeaaafb98a4aa0a42dba74b411b1d361faf1d5))
|
||||
|
||||
### [3.0.4](https://github.com/eiblog/eiblog/compare/v3.0.3...v3.0.4) (2025-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** golang version ([9f77fb7](https://github.com/eiblog/eiblog/commit/9f77fb7b3f283aacb9d9c2a0e2f79c8a6c412edb))
|
||||
|
||||
### [3.0.2](https://github.com/eiblog/eiblog/compare/v3.0.1...v3.0.2) (2025-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ci ([ccb5e45](https://github.com/eiblog/eiblog/commit/ccb5e4546e224182c949e72e9eae82fbbe1a02fe))
|
||||
|
||||
### [3.0.1](https://github.com/eiblog/eiblog/compare/v3.0.0...v3.0.1) (2025-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dist tar ([cb2ed7c](https://github.com/eiblog/eiblog/commit/cb2ed7cb8244dda8cbd8c5966c7ed02e177777e5))
|
||||
|
||||
## [3.0.0](https://github.com/eiblog/eiblog/compare/v2.2.17...v3.0.0) (2025-07-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support custom page ([b69248f](https://github.com/eiblog/eiblog/commit/b69248f6a4937f8157d2393bd44c6c55174ae3e7))
|
||||
* twofactor bind ([e5100fa](https://github.com/eiblog/eiblog/commit/e5100fa018e2955c3d649b70245e3a379658d7ba))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ci ([f4c70b4](https://github.com/eiblog/eiblog/commit/f4c70b46c19d8761c252b767c92016c195299c0c))
|
||||
* ci dist tar ([24bfe52](https://github.com/eiblog/eiblog/commit/24bfe528b28b995c3d5b2c13314c8849a036942b))
|
||||
* custom page support embed ([aee4194](https://github.com/eiblog/eiblog/commit/aee4194c71dd98a7a84da96810a060f716d1ea57))
|
||||
|
||||
### [2.2.16](https://github.com/eiblog/eiblog/compare/v2.2.15...v2.2.16) (2025-03-13)
|
||||
|
||||
|
||||
|
||||
4
Makefile
@@ -22,6 +22,10 @@ backup:
|
||||
dist:
|
||||
@scripts/dist_tar.sh $(tag)
|
||||
|
||||
# clean
|
||||
clean:
|
||||
@rm -rf bin && rm -f *.tar.gz && rm -f backend
|
||||
|
||||
# protoc
|
||||
protoc:
|
||||
@cd pkg/proto && make protoc
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# EiBlog [](LICENSE.md) [](https://github.com/eiblog/eiblog/releases)
|
||||
|
||||
> 博客项目结构参考模版:https://github.com/deepzz0/appdemo
|
||||
|
||||
用过其它博客系统,不喜欢,不够轻,不够快!这是我开发的第二款博客系统,也实在不想再在这件事情上过多纠结了。`EiBlog` 是一个比较稳定的博客系统,现已迭代至 `2.0` 版本,稳定性和维护你是不用担心的。
|
||||
用过其它博客系统,不喜欢,不够轻,不够快!这是我开发的第二款博客系统,也实在不想再在这件事情上过多纠结了。`EiBlog` 是一个比较稳定的博客系统,现已迭代至 `2.0` 版本,稳定性和维护你是不用担心的。[V3 版本](https://github.com/eiblog/eiblog/tree/v3) 正在积极开发中!
|
||||
|
||||
但它有着部署简单(上线复杂!)的特点,不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(简洁、轻快,安全),等你体验。
|
||||
|
||||
@@ -33,8 +31,6 @@ $ docker run --name eiblog \
|
||||
参考项目根目录下的 [docker-compose.yml](https://github.com/eiblog/eiblog/blob/v2/docker-compose.yml),修改相关配置:
|
||||
|
||||
```
|
||||
$ docker compose up -d
|
||||
或
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
|
||||
|
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 |
@@ -1,21 +1,21 @@
|
||||
FROM golang:1.23 AS builder
|
||||
FROM golang:1.20 AS builder
|
||||
|
||||
WORKDIR /eiblog
|
||||
COPY . .
|
||||
RUN scripts/run_build.sh backup
|
||||
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
|
||||
|
||||
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
|
||||
COPY --from=builder /eiblog/bin/backend /app/backend
|
||||
COPY conf /app/conf
|
||||
|
||||
EXPOSE 9001
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
FROM golang:1.23 AS builder
|
||||
FROM golang:1.20 AS builder
|
||||
|
||||
WORKDIR /eiblog
|
||||
COPY . .
|
||||
RUN scripts/run_build.sh eiblog
|
||||
RUN ./scripts/run_build.sh eiblog
|
||||
|
||||
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="deepzz.qi@gmail.com"
|
||||
|
||||
RUN apk add --update --no-cache tzdata ca-certificates
|
||||
|
||||
RUN apk add --update --no-cache tzdata
|
||||
COPY README.md /app/README.md
|
||||
COPY CHANGELOG.md /app/CHANGELOG.md
|
||||
COPY LICENSE /app/LICENSE
|
||||
|
||||
COPY --from=builder /eiblog/cmd/eiblog/backend /app/backend
|
||||
COPY cmd/eiblog/etc /app/etc
|
||||
COPY --from=builder /eiblog/bin/backend /app/backend
|
||||
COPY conf /app/conf
|
||||
COPY website /app/website
|
||||
COPY assets /app/assets
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
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"
|
||||
@@ -1,14 +0,0 @@
|
||||
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
|
||||
@@ -1,17 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package db
|
||||
|
||||
// Storage 备份恢复器
|
||||
type Storage interface {
|
||||
Backup(name string) (string, error)
|
||||
Restore(path string) error
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/backup/config"
|
||||
pdb "github.com/eiblog/eiblog/pkg/connector/db"
|
||||
)
|
||||
|
||||
// MongoStorage 备份恢复器
|
||||
type MongoStorage struct{}
|
||||
|
||||
// Backup 备份
|
||||
func (r MongoStorage) Backup(name string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
|
||||
// dump
|
||||
u, err := url.Parse(config.Conf.Database.Source)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
arg := fmt.Sprintf("mongodump -h %s -d eiblog -o /tmp", u.Host)
|
||||
cmd := exec.CommandContext(ctx, "sh", "-c", arg)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// tar
|
||||
arg = fmt.Sprintf("tar czf /tmp/%s -C /tmp eiblog", name)
|
||||
cmd = exec.CommandContext(ctx, "sh", "-c", arg)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "/tmp/" + name, nil
|
||||
}
|
||||
|
||||
// Restore 恢复
|
||||
func (r MongoStorage) Restore(path string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
|
||||
// drop database
|
||||
database, err := pdb.NewMDB(ctx, config.Conf.Database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = database.Drop(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// unarchive
|
||||
arg := fmt.Sprintf("tar xzf %s -C /tmp", path)
|
||||
cmd := exec.CommandContext(ctx, "sh", "-c", arg)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// restore
|
||||
u, err := url.Parse(config.Conf.Database.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
arg = fmt.Sprintf("mongorestore -h %s -d eiblog /tmp/eiblog", u.Host)
|
||||
cmd = exec.CommandContext(ctx, "sh", "-c", arg)
|
||||
return cmd.Run()
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package to
|
||||
|
||||
// BackupRestorer 备份存储接口
|
||||
type BackupRestorer interface {
|
||||
Upload(path string) error
|
||||
Download() (path string, err error)
|
||||
}
|
||||
@@ -3,20 +3,16 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"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/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/core/backup/ping"
|
||||
"github.com/eiblog/eiblog/pkg/core/backup/swag"
|
||||
"github.com/eiblog/eiblog/pkg/core/backup/timer"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// @title backup API
|
||||
// @version 1.0
|
||||
// @description This is a backup server.
|
||||
|
||||
var restore bool
|
||||
|
||||
func init() {
|
||||
@@ -24,15 +20,15 @@ func init() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
logrus.Info("Hi, it's App " + config.Conf.Name)
|
||||
|
||||
fmt.Println("Hi, it's App " + config.Conf.BackupApp.Name)
|
||||
flag.Parse()
|
||||
|
||||
endRun := make(chan error, 1)
|
||||
|
||||
runCommand(restore, endRun)
|
||||
|
||||
runHTTPServer(endRun)
|
||||
logrus.Fatal(<-endRun)
|
||||
fmt.Println(<-endRun)
|
||||
}
|
||||
|
||||
func runCommand(restore bool, endRun chan error) {
|
||||
@@ -42,7 +38,11 @@ func runCommand(restore bool, endRun chan error) {
|
||||
}
|
||||
|
||||
func runHTTPServer(endRun chan error) {
|
||||
if config.Conf.RunMode.IsReleaseMode() {
|
||||
if !config.Conf.BackupApp.EnableHTTP {
|
||||
return
|
||||
}
|
||||
|
||||
if config.Conf.RunMode == config.ModeProd {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
e := gin.Default()
|
||||
@@ -54,8 +54,9 @@ func runHTTPServer(endRun chan error) {
|
||||
ping.RegisterRoutes(e)
|
||||
|
||||
// start
|
||||
address := fmt.Sprintf(":%d", config.Conf.BackupApp.HTTPPort)
|
||||
go func() {
|
||||
endRun <- e.Run(config.Conf.Listen)
|
||||
endRun <- e.Run(address)
|
||||
}()
|
||||
logrus.Info("HTTP server running on: " + config.Conf.Listen)
|
||||
fmt.Println("HTTP server running on: " + address)
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,83 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"contact": {}
|
||||
},
|
||||
"paths": {}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
info:
|
||||
contact: {}
|
||||
paths: {}
|
||||
swagger: "2.0"
|
||||
@@ -1,59 +0,0 @@
|
||||
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 # *登录明文密码
|
||||
@@ -1,8 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Custom Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Custom Page</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,10 +0,0 @@
|
||||
{{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,69 +0,0 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterRoutes register routes
|
||||
func RegisterRoutes(e *gin.Engine) {
|
||||
e.GET("/rss.html", handleFeed())
|
||||
e.GET("/feed", handleFeed())
|
||||
e.GET("/opensearch.xml", handleOpensearch())
|
||||
e.GET("/sitemap.xml", handleSitemap())
|
||||
e.GET("/robots.txt", handleRobots())
|
||||
e.GET("/crossdomain.xml", handleCrossDomain())
|
||||
e.GET("/favicon.ico", handleFavicon())
|
||||
}
|
||||
|
||||
// handleFeed feed.xml
|
||||
func handleFeed() gin.HandlerFunc {
|
||||
path := filepath.Join(config.EtcDir, "assets", "feed.xml")
|
||||
return func(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, path)
|
||||
}
|
||||
}
|
||||
|
||||
// handleOpensearch opensearch.xml
|
||||
func handleOpensearch() gin.HandlerFunc {
|
||||
path := filepath.Join(config.EtcDir, "assets", "opensearch.xml")
|
||||
return func(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, path)
|
||||
}
|
||||
}
|
||||
|
||||
// handleRobots robotx.txt
|
||||
func handleRobots() gin.HandlerFunc {
|
||||
path := filepath.Join(config.EtcDir, "assets", "robots.txt")
|
||||
return func(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, path)
|
||||
}
|
||||
}
|
||||
|
||||
// handleSitemap sitemap.xml
|
||||
func handleSitemap() gin.HandlerFunc {
|
||||
path := filepath.Join(config.EtcDir, "assets", "sitemap.xml")
|
||||
return func(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, path)
|
||||
}
|
||||
}
|
||||
|
||||
// handleCrossDomain crossdomain.xml
|
||||
func handleCrossDomain() gin.HandlerFunc {
|
||||
path := filepath.Join(config.EtcDir, "assets", "crossdomain.xml")
|
||||
return func(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, path)
|
||||
}
|
||||
}
|
||||
|
||||
// handleFavicon favicon.ico
|
||||
func handleFavicon() gin.HandlerFunc {
|
||||
path := filepath.Join(config.EtcDir, "assets", "favicon.ico")
|
||||
return func(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, path)
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/config"
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/handler/internal/store"
|
||||
"github.com/eiblog/eiblog/pkg/third/disqus"
|
||||
"github.com/eiblog/eiblog/pkg/third/es"
|
||||
"github.com/eiblog/eiblog/pkg/third/pinger"
|
||||
"github.com/eiblog/eiblog/pkg/third/qiniu"
|
||||
"github.com/eiblog/eiblog/tools"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
XMLTemplate *template.Template // template/xml模板
|
||||
HTMLTemplate *template.Template // page/html | website/html模板
|
||||
|
||||
Store store.Store // 数据库存储
|
||||
Ei *Cache // 博客数据缓存
|
||||
TwoFactorSecret string // 缓存两步验证密钥
|
||||
|
||||
ESClient *es.ESClient // es 客户端
|
||||
DisqusClient *disqus.DisqusClient // disqus 客户端
|
||||
QiniuClient *qiniu.QiniuClient // qiniu客户端
|
||||
Pinger *pinger.Pinger // pinger 客户端
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
tools.TimeLocation, err = time.LoadLocation(config.Conf.General.Timezone)
|
||||
if err != nil {
|
||||
logrus.Fatal("init timezone: ", err)
|
||||
}
|
||||
// 模板解析初始化
|
||||
root := filepath.Join(config.EtcDir, "xml", "*.xml")
|
||||
XMLTemplate, err = template.New("eiblog").Funcs(tools.TplFuncMap).ParseGlob(root)
|
||||
if err != nil {
|
||||
logrus.Fatal("init xml template: ", err)
|
||||
}
|
||||
root = filepath.Join(config.EtcDir, "template")
|
||||
files := tools.ReadDirFiles(root, func(fi fs.DirEntry) bool {
|
||||
// should not read dir & .DS_Store
|
||||
return strings.HasPrefix(fi.Name(), ".")
|
||||
})
|
||||
root = filepath.Join(config.EtcDir, "page")
|
||||
pageFiles := tools.ReadDirFiles(root, func(fi fs.DirEntry) bool {
|
||||
return !strings.HasSuffix(fi.Name(), ".html")
|
||||
})
|
||||
files = append(files, pageFiles...)
|
||||
HTMLTemplate, err = template.New("eiblog").Funcs(tools.TplFuncMap).ParseFiles(files...)
|
||||
if err != nil {
|
||||
logrus.Fatal("init html template: ", err)
|
||||
}
|
||||
|
||||
// 数据库初始化
|
||||
logrus.Info("store drivers: ", store.Drivers())
|
||||
Store, err = store.NewStore(config.Conf.Database)
|
||||
if err != nil {
|
||||
logrus.Fatal("init store: ", err)
|
||||
}
|
||||
|
||||
Ei, err = NewCache()
|
||||
if err != nil {
|
||||
logrus.Fatal("init blog cache: ", err)
|
||||
}
|
||||
|
||||
if config.Conf.ESHost != "" {
|
||||
ESClient, err = es.NewESClient(config.Conf.ESHost)
|
||||
if err != nil {
|
||||
logrus.Fatal("init es client: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
DisqusClient, err = disqus.NewDisqusClient(config.Conf.Host, config.Conf.Disqus)
|
||||
if err != nil {
|
||||
logrus.Fatal("init disqus client: ", err)
|
||||
}
|
||||
|
||||
QiniuClient, err = qiniu.NewQiniuClient(config.Conf.Qiniu)
|
||||
if err != nil {
|
||||
logrus.Fatal("init qiniu client: ", err)
|
||||
}
|
||||
|
||||
Pinger, err = pinger.NewPinger(config.Conf.Host, config.Conf.FeedRPC)
|
||||
if err != nil {
|
||||
logrus.Fatal("init pinger: ", err)
|
||||
}
|
||||
|
||||
// 启动定时器
|
||||
go startTimer()
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/config"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func startTimer() {
|
||||
err := generateOpensearch()
|
||||
if err != nil {
|
||||
logrus.Error("startTimer.generateOpensearch: ", err)
|
||||
}
|
||||
err = generateRobots()
|
||||
if err != nil {
|
||||
logrus.Error("startTimer.generateRobots: ", err)
|
||||
}
|
||||
err = generateCrossdomain()
|
||||
if err != nil {
|
||||
logrus.Error("startTimer.generateCrossdomain: ", err)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
for now := range ticker.C {
|
||||
// generate feed & sitemap
|
||||
if now.Hour()%4 == 0 {
|
||||
err = generateFeed()
|
||||
if err != nil {
|
||||
logrus.Error("startTimer.generateFeed: ", err)
|
||||
}
|
||||
err = generateSitemap()
|
||||
if err != nil {
|
||||
logrus.Error("startTimer.generateSitemap: ", err)
|
||||
}
|
||||
}
|
||||
// clean expired articles
|
||||
exp := now.Add(-48 * time.Hour)
|
||||
err := Store.CleanArticles(context.Background(), exp)
|
||||
if err != nil {
|
||||
logrus.Error("startTimer.CleanArticles: ", err)
|
||||
}
|
||||
// fetch disqus count
|
||||
if now.Hour()%5 == 0 {
|
||||
err = DisqusClient.PostsCount(Ei.ArticlesMap)
|
||||
if err != nil {
|
||||
logrus.Error("startTimer.PostsCount: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generateFeed 定时刷新feed
|
||||
func generateFeed() error {
|
||||
tpl := XMLTemplate.Lookup("feedTpl.xml")
|
||||
if tpl == nil {
|
||||
return errors.New("not found: feedTpl.xml")
|
||||
}
|
||||
|
||||
_, _, articles := Ei.PageArticleFE(1, 20)
|
||||
params := map[string]interface{}{
|
||||
"Title": Ei.Blogger.BTitle,
|
||||
"SubTitle": Ei.Blogger.SubTitle,
|
||||
"Host": config.Conf.Host,
|
||||
"FeedrURL": config.Conf.FeedRPC.FeedrURL,
|
||||
"BuildDate": time.Now().Format(time.RFC1123Z),
|
||||
"Articles": articles,
|
||||
}
|
||||
path := filepath.Join(config.EtcDir, "assets", "feed.xml")
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return tpl.Execute(f, params)
|
||||
}
|
||||
|
||||
// generateSitemap 定时刷新sitemap
|
||||
func generateSitemap() error {
|
||||
tpl := XMLTemplate.Lookup("sitemapTpl.xml")
|
||||
if tpl == nil {
|
||||
return errors.New("not found: sitemapTpl.xml")
|
||||
}
|
||||
|
||||
params := map[string]interface{}{
|
||||
"Articles": Ei.Articles,
|
||||
"Host": config.Conf.Host,
|
||||
}
|
||||
path := filepath.Join(config.EtcDir, "assets", "sitemap.xml")
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return tpl.Execute(f, params)
|
||||
}
|
||||
|
||||
// generateOpensearch 生成opensearch.xml
|
||||
func generateOpensearch() error {
|
||||
tpl := XMLTemplate.Lookup("opensearchTpl.xml")
|
||||
if tpl == nil {
|
||||
return errors.New("not found: opensearchTpl.xml")
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"BTitle": Ei.Blogger.BTitle,
|
||||
"SubTitle": Ei.Blogger.SubTitle,
|
||||
"Host": config.Conf.Host,
|
||||
}
|
||||
path := filepath.Join(config.EtcDir, "assets", "opensearch.xml")
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return tpl.Execute(f, params)
|
||||
}
|
||||
|
||||
// generateRobots 生成robots.txt
|
||||
func generateRobots() error {
|
||||
tpl := XMLTemplate.Lookup("robotsTpl.xml")
|
||||
if tpl == nil {
|
||||
return errors.New("not found: robotsTpl.xml")
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"Host": config.Conf.Host,
|
||||
}
|
||||
path := filepath.Join(config.EtcDir, "assets", "robots.txt")
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return tpl.Execute(f, params)
|
||||
}
|
||||
|
||||
// generateCrossdomain 生成crossdomain.xml
|
||||
func generateCrossdomain() error {
|
||||
tpl := XMLTemplate.Lookup("crossdomainTpl.xml")
|
||||
if tpl == nil {
|
||||
return errors.New("not found: crossdomainTpl.xml")
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"Host": config.Conf.Host,
|
||||
}
|
||||
path := filepath.Join(config.EtcDir, "assets", "crossdomain.xml")
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return tpl.Execute(f, params)
|
||||
}
|
||||
@@ -2,69 +2,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/config"
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/handler/admin"
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/handler/file"
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/handler/pages"
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/handler/swag"
|
||||
"github.com/eiblog/eiblog/pkg/middleware"
|
||||
"github.com/eiblog/eiblog/tools"
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/core/eiblog"
|
||||
"github.com/eiblog/eiblog/pkg/core/eiblog/admin"
|
||||
"github.com/eiblog/eiblog/pkg/core/eiblog/file"
|
||||
"github.com/eiblog/eiblog/pkg/core/eiblog/page"
|
||||
"github.com/eiblog/eiblog/pkg/core/eiblog/swag"
|
||||
"github.com/eiblog/eiblog/pkg/mid"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logrus.Info("Hi, it's App " + config.Conf.Name)
|
||||
fmt.Println("Hi, it's App " + config.Conf.EiBlogApp.Name)
|
||||
|
||||
endRun := make(chan error, 1)
|
||||
|
||||
runHTTPServer(endRun)
|
||||
logrus.Fatal(<-endRun)
|
||||
fmt.Println(<-endRun)
|
||||
}
|
||||
|
||||
func runHTTPServer(endRun chan error) {
|
||||
if config.Conf.RunMode.IsReleaseMode() {
|
||||
if !config.Conf.EiBlogApp.EnableHTTP {
|
||||
return
|
||||
}
|
||||
|
||||
if config.Conf.RunMode == config.ModeProd {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
e := gin.Default()
|
||||
// middleware
|
||||
e.Use(middleware.UserMiddleware())
|
||||
e.Use(middleware.SessionMiddleware(
|
||||
middleware.SessionOpts{
|
||||
Name: "su",
|
||||
Secure: config.Conf.RunMode.IsReleaseMode(),
|
||||
Secret: tools.CryptoRand(16),
|
||||
}))
|
||||
e.Use(mid.UserMiddleware())
|
||||
e.Use(mid.SessionMiddleware(mid.SessionOpts{
|
||||
Name: "su",
|
||||
Secure: config.Conf.RunMode == config.ModeProd,
|
||||
Secret: []byte("ZGlzvcmUoMTAsICI="),
|
||||
}))
|
||||
|
||||
// swag
|
||||
swag.RegisterRoutes(e)
|
||||
|
||||
// static files, page
|
||||
e.Static("/static", filepath.Join(config.EtcDir, "assets"))
|
||||
|
||||
// custom pages
|
||||
pages.RegisterRoutesCustomPages(e)
|
||||
root := filepath.Join(config.WorkDir, "assets")
|
||||
e.Static("/static", root)
|
||||
|
||||
// static files
|
||||
file.RegisterRoutes(e)
|
||||
// frontend pages
|
||||
pages.RegisterRoutes(e)
|
||||
page.RegisterRoutes(e)
|
||||
// unauthz api
|
||||
admin.RegisterRoutes(e)
|
||||
|
||||
// admin router
|
||||
group := e.Group("/admin", middleware.AuthFilter)
|
||||
group := e.Group("/admin", eiblog.AuthFilter)
|
||||
{
|
||||
pages.RegisterRoutesAuthz(group)
|
||||
page.RegisterRoutesAuthz(group)
|
||||
admin.RegisterRoutesAuthz(group)
|
||||
}
|
||||
|
||||
// start
|
||||
address := fmt.Sprintf(":%d", config.Conf.EiBlogApp.HTTPPort)
|
||||
go func() {
|
||||
endRun <- e.Run(config.Conf.Listen)
|
||||
endRun <- e.Run(address)
|
||||
}()
|
||||
logrus.Info("HTTP server running on: " + config.Conf.Listen)
|
||||
fmt.Println("HTTP server running on: " + address)
|
||||
}
|
||||
|
||||
3
conf/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Configuration file templates or default configs.
|
||||
|
||||
Put your confd or consul-template template files here.
|
||||
66
conf/app.yml
Normal file
@@ -0,0 +1,66 @@
|
||||
appname: eiblog
|
||||
database:
|
||||
driver: sqlite
|
||||
source: ./db.sqlite
|
||||
eshost: # http://elasticsearch:9200
|
||||
eiblogapp:
|
||||
mode:
|
||||
name: cmd-eiblog
|
||||
enablehttp: true
|
||||
httpport: 9000
|
||||
host: example.com
|
||||
staticversion: 1 # 静态文件版本
|
||||
hotwords: # 热搜词
|
||||
- docker
|
||||
- mongodb
|
||||
- curl
|
||||
- dns
|
||||
general: # 常规配置
|
||||
pagenum: 10 # 首页展示文章数量
|
||||
pagesize: 20 # 管理界面
|
||||
startid: 11 # 起始ID,预留id不时之需, 不用管
|
||||
descprefix: "Desc:" # 文章描述前缀
|
||||
identifier: <!--more--> # 截取预览标识
|
||||
length: 400 # 自动截取预览, 字符数
|
||||
timezone: Asia/Shanghai # 时区
|
||||
disqus: # 评论相关
|
||||
shortname: xxxxxx
|
||||
publickey: wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
|
||||
accesstoken: 50023908f39f4607957e909b495326af
|
||||
google: # 谷歌分析
|
||||
url: https://www.google-analytics.com/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 # *登录明文密码
|
||||
backupapp:
|
||||
mode:
|
||||
name: cmd-backup
|
||||
enablehttp: true
|
||||
httpport: 9001
|
||||
backupto: qiniu # 备份到七牛云
|
||||
interval: 7d # 多久备份一次
|
||||
validity: 60 # 保存时长days
|
||||
qiniu: # 七牛OSS
|
||||
bucket: backup
|
||||
domain: st.deepzz.com
|
||||
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
|
||||
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
|
||||
@@ -1,6 +1,7 @@
|
||||
version: '3'
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:3.6
|
||||
image: mongo:3.2
|
||||
volumes:
|
||||
- ${PWD}/mgodb:/data/db
|
||||
restart: always
|
||||
@@ -9,11 +10,10 @@ services:
|
||||
volumes:
|
||||
- ${PWD}/esdata:/usr/share/elasticsearch/data
|
||||
restart: always
|
||||
|
||||
eiblog:
|
||||
image: deepzz0/eiblog:latest
|
||||
volumes:
|
||||
- ${PWD}/eiblog/etc:/app/etc
|
||||
- ${PWD}/conf:/app/conf
|
||||
extra_hosts:
|
||||
- "disqus.com:151.101.192.134"
|
||||
- "deepzz.disqus.com:151.101.192.134"
|
||||
@@ -25,12 +25,11 @@ services:
|
||||
ports:
|
||||
- 127.0.0.1:9000:9000
|
||||
restart: always
|
||||
|
||||
backup:
|
||||
image: deepzz0/backup:latest
|
||||
#command: ./backend --restore true
|
||||
volumes:
|
||||
- ${PWD}/backup/etc:/app/etc
|
||||
- ${PWD}/conf:/app/conf
|
||||
links:
|
||||
- mongodb
|
||||
restart: always
|
||||
102
go.mod
@@ -1,100 +1,24 @@
|
||||
module github.com/eiblog/eiblog
|
||||
|
||||
go 1.23.0
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/eiblog/blackfriday v0.0.0-20161010144836-c0ec111761ae
|
||||
github.com/gin-contrib/sessions v1.0.4
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gin-contrib/sessions v0.0.4
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/qiniu/go-sdk/v7 v7.11.0
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.4
|
||||
go.mongodb.org/mongo-driver v1.17.3
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
|
||||
github.com/swaggo/gin-swagger v1.3.3
|
||||
github.com/swaggo/swag v1.7.4
|
||||
go.mongodb.org/mongo-driver v1.5.4
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/clickhouse v0.7.0
|
||||
gorm.io/driver/mysql v1.6.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/driver/clickhouse v0.4.2
|
||||
gorm.io/driver/mysql v1.5.2
|
||||
gorm.io/driver/postgres v1.5.4
|
||||
gorm.io/driver/sqlite v1.5.4
|
||||
gorm.io/driver/sqlserver v1.5.2
|
||||
gorm.io/gorm v1.30.1
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/ClickHouse/ch-go v0.67.0 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.39.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.4.0 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.6.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/paulmach/orb v0.11.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
golang.org/x/arch v0.19.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gorm.io/gorm v1.25.5
|
||||
)
|
||||
|
||||
415
go.sum
@@ -1,5 +1,3 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
@@ -18,60 +16,71 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0 h1:HCc0+LpPfpCKs6LGGLAhwBARt9632unrVcI6i8s/8os=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/ClickHouse/ch-go v0.67.0 h1:18MQF6vZHj+4/hTRaK7JbS/TIzn4I55wC+QzO24uiqc=
|
||||
github.com/ClickHouse/ch-go v0.67.0/go.mod h1:2MSAeyVmgt+9a2k2SQPPG1b4qbTPzdGDpf1+bcHh+18=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.39.0 h1:spDlvQPW4d2EIOmzxeoRdeUPQ5j9zFryEx6L+XjfGoM=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.39.0/go.mod h1:m13KylpdcPzpIjznlfXp53IpdgZ7plTxOSCZnKphYZ8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.2.0 h1:dj00TDKY+xwuTJdbpspCSmTLFyWzRJerTHwaBxut1C0=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.2.0/go.mod h1:8f2XZUi7XoeU+uPIytSi1cvx8fmJxi7vIgqpvYTF1+o=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||
github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
|
||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/eiblog/blackfriday v0.0.0-20161010144836-c0ec111761ae h1:V6YC640Gs5jEUYfCimyuXsTW5gzNcIEESG4MGmOJCtA=
|
||||
github.com/eiblog/blackfriday v0.0.0-20161010144836-c0ec111761ae/go.mod h1:HzHqTCGEAkSSzBM3shBvQHsHRQYUvjNOIC4mHipZ6tI=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
||||
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
|
||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
||||
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k=
|
||||
github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0shBPy4jFc=
|
||||
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
||||
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
|
||||
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ=
|
||||
github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
@@ -83,15 +92,47 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
||||
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
||||
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
|
||||
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
|
||||
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
|
||||
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
|
||||
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
|
||||
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
|
||||
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
|
||||
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
|
||||
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
|
||||
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
|
||||
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
|
||||
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
|
||||
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
|
||||
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
|
||||
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
|
||||
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
|
||||
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
|
||||
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
|
||||
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
|
||||
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
@@ -99,40 +140,40 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
|
||||
@@ -141,21 +182,32 @@ github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
|
||||
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
|
||||
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
@@ -166,184 +218,231 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||
github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc=
|
||||
github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
|
||||
github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||
github.com/paulmach/orb v0.7.1 h1:Zha++Z5OX/l168sqHK3k4z18LDvr+YAO/VjK0ReQ9rU=
|
||||
github.com/paulmach/orb v0.7.1/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A=
|
||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||
github.com/qiniu/go-sdk/v7 v7.11.0 h1:Cdx/1E3ybv0OFKnkGwoDN/t6bCCntjrWhwWuRaqI3XQ=
|
||||
github.com/qiniu/go-sdk/v7 v7.11.0/go.mod h1:btsaOc8CA3hdVloULfFdDgDc+g4f3TDZEFsDY0BLE+w=
|
||||
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM=
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
|
||||
github.com/swaggo/gin-swagger v1.3.3 h1:XHyYmeNVFG5PbyWHG4jXtxOm2P4kiZapDCWsyDDiQ/I=
|
||||
github.com/swaggo/gin-swagger v1.3.3/go.mod h1:ymsZuGpbbu+S7ZoQ49QPpZoDBj6uqhb8WizgQPVgWl0=
|
||||
github.com/swaggo/swag v1.7.4 h1:up+ixy8yOqJKiFcuhMgkuYuF4xnevuhnFAXXF8OSfNg=
|
||||
github.com/swaggo/swag v1.7.4/go.mod h1:zD8h6h4SPv7t3l+4BKdRquqW1ASWjKZgT6Qv9z3kNqI=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
|
||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||
github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
|
||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
||||
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mongodb.org/mongo-driver v1.5.4 h1:NPIBF/lxEcKNfWwoCJRX8+dMVwecWf9q3qUJkuh75oM=
|
||||
go.mongodb.org/mongo-driver v1.5.4/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
|
||||
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
|
||||
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
|
||||
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
|
||||
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
@@ -351,24 +450,29 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -377,6 +481,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
@@ -385,17 +490,19 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/clickhouse v0.7.0 h1:BCrqvgONayvZRgtuA6hdya+eAW5P2QVagV3OlEp1vtA=
|
||||
gorm.io/driver/clickhouse v0.7.0/go.mod h1:TmNo0wcVTsD4BBObiRnCahUgHJHjBIwuRejHwYt3JRs=
|
||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/driver/clickhouse v0.4.2 h1:vt7WkXX0u5SeCDyR4w/Jz0ce25/tYqgoP3UUERgMZmY=
|
||||
gorm.io/driver/clickhouse v0.4.2/go.mod h1:va7QQfIQWmknHDAXyMvW/5y2OEIRfTc65G4aC13kCvQ=
|
||||
gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
|
||||
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
|
||||
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
||||
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/driver/sqlserver v1.5.2 h1:+o4RQ8w1ohPbADhFqDxeeZnSWjwOcBnxBckjTbcP4wk=
|
||||
gorm.io/driver/sqlserver v1.5.2/go.mod h1:gaKF0MO0cfTq9Q3/XhkowSw4g6nIwHPGAs4hzKCmvBo=
|
||||
gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.2-0.20230610234218-206613868439/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
||||
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
// Package cache provides ...
|
||||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -11,13 +11,20 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/config"
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/handler/internal/store"
|
||||
"github.com/eiblog/eiblog/pkg/cache/render"
|
||||
"github.com/eiblog/eiblog/pkg/cache/store"
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/internal"
|
||||
"github.com/eiblog/eiblog/pkg/model"
|
||||
"github.com/eiblog/eiblog/tools"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// Ei eiblog cache
|
||||
Ei *Cache
|
||||
|
||||
// PagesCh regenerate pages chan
|
||||
PagesCh = make(chan string, 2)
|
||||
// PageSeries the page series regenerate flag
|
||||
@@ -27,11 +34,45 @@ var (
|
||||
|
||||
// ArticleStartID article start id
|
||||
ArticleStartID = 11
|
||||
// TrashArticleExp trash article timeout
|
||||
TrashArticleExp = time.Duration(-48) * time.Hour
|
||||
)
|
||||
|
||||
func init() {
|
||||
// init timezone
|
||||
var err error
|
||||
tools.TimeLocation, err = time.LoadLocation(
|
||||
config.Conf.EiBlogApp.General.Timezone)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// init store
|
||||
logrus.Info("store drivers: ", store.Drivers())
|
||||
store, err := store.NewStore(config.Conf.Database.Driver,
|
||||
config.Conf.Database.Source)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Ei init
|
||||
Ei = &Cache{
|
||||
lock: sync.Mutex{},
|
||||
Store: store,
|
||||
TagArticles: make(map[string]model.SortedArticles),
|
||||
ArticlesMap: make(map[string]*model.Article),
|
||||
}
|
||||
err = Ei.loadOrInit()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go Ei.regeneratePages()
|
||||
go Ei.timerClean()
|
||||
go Ei.timerDisqus()
|
||||
}
|
||||
|
||||
// Cache 整站缓存
|
||||
type Cache struct {
|
||||
lock sync.Mutex
|
||||
store.Store
|
||||
|
||||
// load from db
|
||||
Blogger *model.Blogger
|
||||
@@ -47,30 +88,13 @@ type Cache struct {
|
||||
ArticlesMap map[string]*model.Article // slug:article
|
||||
}
|
||||
|
||||
// NewCache 缓存整个博客数据
|
||||
func NewCache() (*Cache, error) {
|
||||
// Ei init
|
||||
cache := &Cache{
|
||||
lock: sync.Mutex{},
|
||||
TagArticles: make(map[string]model.SortedArticles),
|
||||
ArticlesMap: make(map[string]*model.Article),
|
||||
}
|
||||
err := cache.loadOrInit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 异步渲染series,archive页面
|
||||
go cache.regeneratePages()
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
// AddArticle 添加文章
|
||||
func (cache *Cache) AddArticle(article *model.Article) error {
|
||||
cache.lock.Lock()
|
||||
defer cache.lock.Unlock()
|
||||
func (c *Cache) AddArticle(article *model.Article) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// store
|
||||
err := Store.InsertArticle(context.Background(), article, ArticleStartID)
|
||||
err := c.InsertArticle(context.Background(), article, ArticleStartID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -79,77 +103,77 @@ func (cache *Cache) AddArticle(article *model.Article) error {
|
||||
return nil
|
||||
}
|
||||
// 正式发布文章
|
||||
cache.refreshCache(article, false)
|
||||
c.refreshCache(article, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RepArticle 替换文章
|
||||
func (cache *Cache) RepArticle(oldArticle, newArticle *model.Article) {
|
||||
cache.lock.Lock()
|
||||
defer cache.lock.Unlock()
|
||||
func (c *Cache) RepArticle(oldArticle, newArticle *model.Article) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
cache.ArticlesMap[newArticle.Slug] = newArticle
|
||||
GenerateExcerptMarkdown(newArticle)
|
||||
c.ArticlesMap[newArticle.Slug] = newArticle
|
||||
render.GenerateExcerptMarkdown(newArticle)
|
||||
if newArticle.ID < ArticleStartID {
|
||||
return
|
||||
}
|
||||
if oldArticle != nil { // 移除旧文章
|
||||
cache.refreshCache(oldArticle, true)
|
||||
c.refreshCache(oldArticle, true)
|
||||
}
|
||||
cache.refreshCache(newArticle, false)
|
||||
c.refreshCache(newArticle, false)
|
||||
}
|
||||
|
||||
// DelArticle 删除文章
|
||||
func (cache *Cache) DelArticle(id int) error {
|
||||
cache.lock.Lock()
|
||||
defer cache.lock.Unlock()
|
||||
func (c *Cache) DelArticle(id int) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
article, _ := cache.FindArticleByID(id)
|
||||
article, _ := c.FindArticleByID(id)
|
||||
if article == nil {
|
||||
return nil
|
||||
}
|
||||
// set delete
|
||||
err := Store.UpdateArticle(context.Background(), id, map[string]interface{}{
|
||||
err := c.UpdateArticle(context.Background(), id, map[string]interface{}{
|
||||
"deleted_at": time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// drop from tags,series,archives
|
||||
cache.refreshCache(article, true)
|
||||
c.refreshCache(article, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSerie 添加专题
|
||||
func (cache *Cache) AddSerie(serie *model.Serie) error {
|
||||
cache.lock.Lock()
|
||||
defer cache.lock.Unlock()
|
||||
func (c *Cache) AddSerie(serie *model.Serie) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
err := Store.InsertSerie(context.Background(), serie)
|
||||
err := c.InsertSerie(context.Background(), serie)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache.Series = append(cache.Series, serie)
|
||||
c.Series = append(c.Series, serie)
|
||||
PagesCh <- PageSeries
|
||||
return nil
|
||||
}
|
||||
|
||||
// DelSerie 删除专题
|
||||
func (cache *Cache) DelSerie(id int) error {
|
||||
cache.lock.Lock()
|
||||
defer cache.lock.Unlock()
|
||||
func (c *Cache) DelSerie(id int) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
for i, serie := range cache.Series {
|
||||
for i, serie := range c.Series {
|
||||
if serie.ID == id {
|
||||
if len(serie.Articles) > 0 {
|
||||
return errors.New("请删除该专题下的所有文章")
|
||||
}
|
||||
err := Store.RemoveSerie(context.Background(), id)
|
||||
err := c.RemoveSerie(context.Background(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache.Series[i] = nil
|
||||
cache.Series = append(cache.Series[:i], cache.Series[i+1:]...)
|
||||
c.Series[i] = nil
|
||||
c.Series = append(c.Series[:i], c.Series[i+1:]...)
|
||||
PagesCh <- PageSeries
|
||||
break
|
||||
}
|
||||
@@ -158,12 +182,12 @@ func (cache *Cache) DelSerie(id int) error {
|
||||
}
|
||||
|
||||
// PageArticleFE 文章翻页
|
||||
func (cache *Cache) PageArticleFE(page int, pageSize int) (prev,
|
||||
func (c *Cache) PageArticleFE(page int, pageSize int) (prev,
|
||||
next int, articles []*model.Article) {
|
||||
|
||||
var l int
|
||||
for l = len(cache.Articles); l > 0; l-- {
|
||||
if cache.Articles[l-1].ID >= ArticleStartID {
|
||||
for l = len(c.Articles); l > 0; l-- {
|
||||
if c.Articles[l-1].ID >= ArticleStartID {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -188,12 +212,12 @@ func (cache *Cache) PageArticleFE(page int, pageSize int) (prev,
|
||||
if e > l {
|
||||
e = l
|
||||
}
|
||||
articles = cache.Articles[s:e]
|
||||
articles = c.Articles[s:e]
|
||||
return
|
||||
}
|
||||
|
||||
// PageArticleBE 后台文章分页
|
||||
func (cache *Cache) PageArticleBE(se int, kw string, draft, del bool, p,
|
||||
func (c *Cache) PageArticleBE(se int, kw string, draft, del bool, p,
|
||||
n int) ([]*model.Article, int) {
|
||||
|
||||
search := store.SearchArticles{
|
||||
@@ -214,7 +238,7 @@ func (cache *Cache) PageArticleBE(se int, kw string, draft, del bool, p,
|
||||
search.Fields[store.SearchArticleTitle] = kw
|
||||
}
|
||||
}
|
||||
articles, count, err := Store.LoadArticleList(context.Background(), search)
|
||||
articles, count, err := c.LoadArticleList(context.Background(), search)
|
||||
if err != nil {
|
||||
return nil, 0
|
||||
}
|
||||
@@ -226,8 +250,8 @@ func (cache *Cache) PageArticleBE(se int, kw string, draft, del bool, p,
|
||||
}
|
||||
|
||||
// FindArticleByID 通过ID查找文章
|
||||
func (cache *Cache) FindArticleByID(id int) (*model.Article, int) {
|
||||
for i, article := range cache.Articles {
|
||||
func (c *Cache) FindArticleByID(id int) (*model.Article, int) {
|
||||
for i, article := range c.Articles {
|
||||
if article.ID == id {
|
||||
return article, i
|
||||
}
|
||||
@@ -236,32 +260,32 @@ func (cache *Cache) FindArticleByID(id int) (*model.Article, int) {
|
||||
}
|
||||
|
||||
// refreshCache 刷新缓存
|
||||
func (cache *Cache) refreshCache(article *model.Article, del bool) {
|
||||
func (c *Cache) refreshCache(article *model.Article, del bool) {
|
||||
if del {
|
||||
_, idx := cache.FindArticleByID(article.ID)
|
||||
_, idx := c.FindArticleByID(article.ID)
|
||||
|
||||
delete(cache.ArticlesMap, article.Slug)
|
||||
cache.Articles = append(cache.Articles[:idx], cache.Articles[idx+1:]...)
|
||||
delete(c.ArticlesMap, article.Slug)
|
||||
c.Articles = append(c.Articles[:idx], c.Articles[idx+1:]...)
|
||||
// 从链表移除
|
||||
cache.recalcLinkedList(article, true)
|
||||
c.recalcLinkedList(article, true)
|
||||
// 从tag、serie、archive移除
|
||||
cache.redelArticle(article)
|
||||
c.redelArticle(article)
|
||||
return
|
||||
}
|
||||
// 添加文章
|
||||
defer GenerateExcerptMarkdown(article)
|
||||
defer render.GenerateExcerptMarkdown(article)
|
||||
|
||||
cache.ArticlesMap[article.Slug] = article
|
||||
cache.Articles = append([]*model.Article{article}, cache.Articles...)
|
||||
sort.Sort(cache.Articles)
|
||||
c.ArticlesMap[article.Slug] = article
|
||||
c.Articles = append([]*model.Article{article}, c.Articles...)
|
||||
sort.Sort(c.Articles)
|
||||
// 从链表添加
|
||||
cache.recalcLinkedList(article, false)
|
||||
c.recalcLinkedList(article, false)
|
||||
// 从tag、serie、archive添加
|
||||
cache.readdArticle(article, true)
|
||||
c.readdArticle(article, true)
|
||||
}
|
||||
|
||||
// recalcLinkedList 重算文章链表
|
||||
func (cache *Cache) recalcLinkedList(article *model.Article, del bool) {
|
||||
func (c *Cache) recalcLinkedList(article *model.Article, del bool) {
|
||||
// 删除操作
|
||||
if del {
|
||||
if article.Prev == nil && article.Next != nil {
|
||||
@@ -275,56 +299,56 @@ func (cache *Cache) recalcLinkedList(article *model.Article, del bool) {
|
||||
return
|
||||
}
|
||||
// 添加操作
|
||||
_, idx := cache.FindArticleByID(article.ID)
|
||||
if idx == 0 && cache.Articles[idx+1].ID >= ArticleStartID {
|
||||
article.Next = cache.Articles[idx+1]
|
||||
cache.Articles[idx+1].Prev = article
|
||||
} else if idx > 0 && cache.Articles[idx-1].ID >= ArticleStartID {
|
||||
article.Prev = cache.Articles[idx-1]
|
||||
if cache.Articles[idx-1].Next != nil {
|
||||
article.Next = cache.Articles[idx-1].Next
|
||||
cache.Articles[idx-1].Next.Prev = article
|
||||
_, idx := c.FindArticleByID(article.ID)
|
||||
if idx == 0 && c.Articles[idx+1].ID >= ArticleStartID {
|
||||
article.Next = c.Articles[idx+1]
|
||||
c.Articles[idx+1].Prev = article
|
||||
} else if idx > 0 && c.Articles[idx-1].ID >= ArticleStartID {
|
||||
article.Prev = c.Articles[idx-1]
|
||||
if c.Articles[idx-1].Next != nil {
|
||||
article.Next = c.Articles[idx-1].Next
|
||||
c.Articles[idx-1].Next.Prev = article
|
||||
}
|
||||
cache.Articles[idx-1].Next = article
|
||||
c.Articles[idx-1].Next = article
|
||||
}
|
||||
}
|
||||
|
||||
// readdArticle 添加文章到tag、series、archive
|
||||
func (cache *Cache) readdArticle(article *model.Article, needSort bool) {
|
||||
func (c *Cache) readdArticle(article *model.Article, needSort bool) {
|
||||
// tag
|
||||
for _, tag := range article.Tags {
|
||||
cache.TagArticles[tag] = append(cache.TagArticles[tag], article)
|
||||
c.TagArticles[tag] = append(c.TagArticles[tag], article)
|
||||
if needSort {
|
||||
sort.Sort(cache.TagArticles[tag])
|
||||
sort.Sort(c.TagArticles[tag])
|
||||
}
|
||||
}
|
||||
// series
|
||||
for i, serie := range cache.Series {
|
||||
for i, serie := range c.Series {
|
||||
if serie.ID != article.SerieID {
|
||||
continue
|
||||
}
|
||||
cache.Series[i].Articles = append(cache.Series[i].Articles, article)
|
||||
c.Series[i].Articles = append(c.Series[i].Articles, article)
|
||||
if needSort {
|
||||
sort.Sort(cache.Series[i].Articles)
|
||||
sort.Sort(c.Series[i].Articles)
|
||||
PagesCh <- PageSeries // 重建专题
|
||||
}
|
||||
}
|
||||
// archive
|
||||
y, m, _ := article.CreatedAt.Date()
|
||||
for i, archive := range cache.Archives {
|
||||
for i, archive := range c.Archives {
|
||||
ay, am, _ := archive.Time.Date()
|
||||
if y != ay || m != am {
|
||||
continue
|
||||
}
|
||||
cache.Archives[i].Articles = append(cache.Archives[i].Articles, article)
|
||||
c.Archives[i].Articles = append(c.Archives[i].Articles, article)
|
||||
if needSort {
|
||||
sort.Sort(cache.Archives[i].Articles)
|
||||
sort.Sort(c.Archives[i].Articles)
|
||||
PagesCh <- PageArchive // 重建归档
|
||||
}
|
||||
return
|
||||
}
|
||||
// 新建归档
|
||||
cache.Archives = append(cache.Archives, &model.Archive{
|
||||
c.Archives = append(c.Archives, &model.Archive{
|
||||
Time: article.CreatedAt,
|
||||
Articles: model.SortedArticles{article},
|
||||
})
|
||||
@@ -334,25 +358,25 @@ func (cache *Cache) readdArticle(article *model.Article, needSort bool) {
|
||||
}
|
||||
|
||||
// redelArticle 从tag、series、archive删除文章
|
||||
func (cache *Cache) redelArticle(article *model.Article) {
|
||||
func (c *Cache) redelArticle(article *model.Article) {
|
||||
// tag
|
||||
for _, tag := range article.Tags {
|
||||
for i, v := range cache.TagArticles[tag] {
|
||||
for i, v := range c.TagArticles[tag] {
|
||||
if v == article {
|
||||
cache.TagArticles[tag] = append(cache.TagArticles[tag][0:i], cache.TagArticles[tag][i+1:]...)
|
||||
if len(cache.TagArticles[tag]) == 0 {
|
||||
delete(cache.TagArticles, tag)
|
||||
c.TagArticles[tag] = append(c.TagArticles[tag][0:i], c.TagArticles[tag][i+1:]...)
|
||||
if len(c.TagArticles[tag]) == 0 {
|
||||
delete(c.TagArticles, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// serie
|
||||
for i, serie := range cache.Series {
|
||||
for i, serie := range c.Series {
|
||||
if serie.ID == article.SerieID {
|
||||
for j, v := range serie.Articles {
|
||||
if v == article {
|
||||
cache.Series[i].Articles = append(cache.Series[i].Articles[0:j],
|
||||
cache.Series[i].Articles[j+1:]...)
|
||||
c.Series[i].Articles = append(c.Series[i].Articles[0:j],
|
||||
c.Series[i].Articles[j+1:]...)
|
||||
PagesCh <- PageSeries
|
||||
break
|
||||
}
|
||||
@@ -360,15 +384,15 @@ func (cache *Cache) redelArticle(article *model.Article) {
|
||||
}
|
||||
}
|
||||
// archive
|
||||
for i, archive := range cache.Archives {
|
||||
for i, archive := range c.Archives {
|
||||
ay, am, _ := archive.Time.Date()
|
||||
if y, m, _ := article.CreatedAt.Date(); ay == y && am == m {
|
||||
for j, v := range archive.Articles {
|
||||
if v == article {
|
||||
cache.Archives[i].Articles = append(cache.Archives[i].Articles[0:j],
|
||||
cache.Archives[i].Articles[j+1:]...)
|
||||
if len(cache.Archives[i].Articles) == 0 {
|
||||
cache.Archives = append(cache.Archives[:i], cache.Archives[i+1:]...)
|
||||
c.Archives[i].Articles = append(c.Archives[i].Articles[0:j],
|
||||
c.Archives[i].Articles[j+1:]...)
|
||||
if len(c.Archives[i].Articles) == 0 {
|
||||
c.Archives = append(c.Archives[:i], c.Archives[i+1:]...)
|
||||
}
|
||||
PagesCh <- PageArchive
|
||||
break
|
||||
@@ -379,79 +403,81 @@ func (cache *Cache) redelArticle(article *model.Article) {
|
||||
}
|
||||
|
||||
// loadOrInit 读取数据或初始化
|
||||
func (cache *Cache) loadOrInit() error {
|
||||
func (c *Cache) loadOrInit() error {
|
||||
blogapp := config.Conf.EiBlogApp
|
||||
// blogger
|
||||
blogger := &model.Blogger{
|
||||
BlogName: strings.Title(config.Conf.Account.Username),
|
||||
BlogName: strings.Title(blogapp.Account.Username),
|
||||
SubTitle: "Rome was not built in one day.",
|
||||
BeiAn: "蜀ICP备xxxxxxxx号-1",
|
||||
BTitle: fmt.Sprintf("%s's Blog", strings.Title(config.Conf.Account.Username)),
|
||||
BTitle: fmt.Sprintf("%s's Blog", strings.Title(blogapp.Account.Username)),
|
||||
Copyright: `本站使用「<a href="//creativecommons.org/licenses/by/4.0/">署名 4.0 国际</a>」创作共享协议,转载请注明作者及原网址。`,
|
||||
}
|
||||
created, err := Store.LoadInsertBlogger(context.Background(), blogger)
|
||||
created, err := c.LoadInsertBlogger(context.Background(), blogger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache.Blogger = blogger
|
||||
c.Blogger = blogger
|
||||
if created { // init articles: about blogroll
|
||||
about := &model.Article{
|
||||
ID: 1, // 固定ID
|
||||
Author: config.Conf.Account.Username,
|
||||
Author: blogapp.Account.Username,
|
||||
Title: "关于",
|
||||
Slug: "about",
|
||||
CreatedAt: time.Time{}.AddDate(0, 0, 1),
|
||||
}
|
||||
err = Store.InsertArticle(context.Background(), about, ArticleStartID)
|
||||
err = c.InsertArticle(context.Background(), about, ArticleStartID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 推送到 disqus
|
||||
go DisqusClient.ThreadCreate(about, blogger.BTitle)
|
||||
go internal.ThreadCreate(about, blogger.BTitle)
|
||||
blogroll := &model.Article{
|
||||
ID: 2, // 固定ID
|
||||
Author: config.Conf.Account.Username,
|
||||
Author: blogapp.Account.Username,
|
||||
Title: "友情链接",
|
||||
Slug: "blogroll",
|
||||
CreatedAt: time.Time{}.AddDate(0, 0, 7),
|
||||
}
|
||||
err = Store.InsertArticle(context.Background(), blogroll, ArticleStartID)
|
||||
err = c.InsertArticle(context.Background(), blogroll, ArticleStartID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// account
|
||||
pwd := tools.EncryptPasswd(config.Conf.Account.Username, config.Conf.Account.Password)
|
||||
pwd := tools.EncryptPasswd(blogapp.Account.Username,
|
||||
blogapp.Account.Password)
|
||||
|
||||
account := &model.Account{
|
||||
Username: config.Conf.Account.Username,
|
||||
Username: blogapp.Account.Username,
|
||||
Password: pwd,
|
||||
}
|
||||
_, err = Store.LoadInsertAccount(context.Background(), account)
|
||||
_, err = c.LoadInsertAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache.Account = account
|
||||
c.Account = account
|
||||
// series
|
||||
series, err := Store.LoadAllSerie(context.Background())
|
||||
series, err := c.LoadAllSerie(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache.Series = series
|
||||
c.Series = series
|
||||
// all articles
|
||||
search := store.SearchArticles{
|
||||
Page: 1,
|
||||
Limit: 9999,
|
||||
Fields: map[string]interface{}{store.SearchArticleDraft: false},
|
||||
}
|
||||
articles, _, err := Store.LoadArticleList(context.Background(), search)
|
||||
articles, _, err := c.LoadArticleList(context.Background(), search)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, v := range articles {
|
||||
// 渲染页面
|
||||
GenerateExcerptMarkdown(v)
|
||||
render.GenerateExcerptMarkdown(v)
|
||||
|
||||
cache.ArticlesMap[v.Slug] = v
|
||||
c.ArticlesMap[v.Slug] = v
|
||||
// 分析文章
|
||||
if v.ID < ArticleStartID {
|
||||
continue
|
||||
@@ -463,9 +489,9 @@ func (cache *Cache) loadOrInit() error {
|
||||
articles[i+1].ID >= ArticleStartID {
|
||||
v.Next = articles[i+1]
|
||||
}
|
||||
cache.readdArticle(v, false)
|
||||
c.readdArticle(v, false)
|
||||
}
|
||||
cache.Articles = articles
|
||||
Ei.Articles = articles
|
||||
// 重建专题与归档
|
||||
PagesCh <- PageSeries
|
||||
PagesCh <- PageArchive
|
||||
@@ -473,15 +499,15 @@ func (cache *Cache) loadOrInit() error {
|
||||
}
|
||||
|
||||
// regeneratePages 重新生成series,archive页面
|
||||
func (cache *Cache) regeneratePages() {
|
||||
func (c *Cache) regeneratePages() {
|
||||
for {
|
||||
switch page := <-PagesCh; page {
|
||||
case PageSeries:
|
||||
sort.Sort(cache.Series)
|
||||
sort.Sort(c.Series)
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString(cache.Blogger.SeriesSay)
|
||||
buf.WriteString(c.Blogger.SeriesSay)
|
||||
buf.WriteString("\n\n")
|
||||
for _, series := range cache.Series {
|
||||
for _, series := range c.Series {
|
||||
buf.WriteString(fmt.Sprintf("### %s{#toc-%d}", series.Name, series.ID))
|
||||
buf.WriteByte('\n')
|
||||
buf.WriteString(series.Desc)
|
||||
@@ -494,16 +520,16 @@ func (cache *Cache) regeneratePages() {
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
cache.PageSeries = string(PageRender(buf.Bytes()))
|
||||
c.PageSeries = string(render.PageRender(buf.Bytes()))
|
||||
case PageArchive:
|
||||
sort.Sort(cache.Archives)
|
||||
sort.Sort(c.Archives)
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString(cache.Blogger.ArchivesSay + "\n")
|
||||
buf.WriteString(c.Blogger.ArchivesSay + "\n")
|
||||
var (
|
||||
currentYear string
|
||||
gt12Month = len(cache.Archives) > 12
|
||||
gt12Month = len(c.Archives) > 12
|
||||
)
|
||||
for _, archive := range cache.Archives {
|
||||
for _, archive := range c.Archives {
|
||||
t := archive.Time.In(tools.TimeLocation)
|
||||
if gt12Month {
|
||||
year := t.Format("2006 年")
|
||||
@@ -527,7 +553,32 @@ func (cache *Cache) regeneratePages() {
|
||||
}
|
||||
}
|
||||
}
|
||||
cache.PageArchives = string(PageRender(buf.Bytes()))
|
||||
c.PageArchives = string(render.PageRender(buf.Bytes()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// timerClean 定时清理文章
|
||||
func (c *Cache) timerClean() {
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
|
||||
for now := range ticker.C {
|
||||
exp := now.Add(TrashArticleExp)
|
||||
err := c.CleanArticles(context.Background(), exp)
|
||||
if err != nil {
|
||||
logrus.Error("cache.timerClean.CleanArticles: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// timerDisqus disqus定时操作
|
||||
func (c *Cache) timerDisqus() {
|
||||
ticker := time.NewTicker(5 * time.Hour)
|
||||
|
||||
for range ticker.C {
|
||||
err := internal.PostsCount(c.ArticlesMap)
|
||||
if err != nil {
|
||||
logrus.Error("cache.timerDisqus.PostsCount: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package internal
|
||||
// Package render provides ...
|
||||
package render
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/config"
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/model"
|
||||
"github.com/eiblog/eiblog/tools"
|
||||
|
||||
@@ -36,7 +37,7 @@ const (
|
||||
|
||||
var (
|
||||
// 渲染markdown操作和截取摘要操作
|
||||
regIdentifier = regexp.MustCompile(config.Conf.General.Identifier)
|
||||
regIdentifier = regexp.MustCompile(config.Conf.EiBlogApp.General.Identifier)
|
||||
// header
|
||||
regHeader = regexp.MustCompile("</nav></div>")
|
||||
)
|
||||
@@ -49,9 +50,11 @@ func PageRender(md []byte) []byte {
|
||||
|
||||
// GenerateExcerptMarkdown 生成预览和描述
|
||||
func GenerateExcerptMarkdown(article *model.Article) {
|
||||
if strings.HasPrefix(article.Content, config.Conf.General.DescPrefix) {
|
||||
blogapp := config.Conf.EiBlogApp
|
||||
|
||||
if strings.HasPrefix(article.Content, blogapp.General.DescPrefix) {
|
||||
index := strings.Index(article.Content, "\r\n")
|
||||
prefix := article.Content[len(config.Conf.General.DescPrefix):index]
|
||||
prefix := article.Content[len(blogapp.General.DescPrefix):index]
|
||||
|
||||
article.Desc = tools.IgnoreHTMLTag(prefix)
|
||||
article.Content = article.Content[index:]
|
||||
@@ -74,7 +77,7 @@ func GenerateExcerptMarkdown(article *model.Article) {
|
||||
return
|
||||
}
|
||||
uc := []rune(article.Content)
|
||||
length := config.Conf.General.Length
|
||||
length := blogapp.General.Length
|
||||
if len(uc) < length {
|
||||
length = len(uc)
|
||||
}
|
||||
@@ -6,18 +6,17 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/config"
|
||||
pdb "github.com/eiblog/eiblog/pkg/connector/db"
|
||||
"github.com/eiblog/eiblog/pkg/model"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||
)
|
||||
|
||||
// example:
|
||||
// driver: mongodb
|
||||
// source: mongodb://localhost:27017/eiblog
|
||||
// source: mongodb://localhost:27017
|
||||
|
||||
const (
|
||||
mongoDBName = "eiblog"
|
||||
@@ -32,39 +31,44 @@ const (
|
||||
)
|
||||
|
||||
type mongodb struct {
|
||||
*mongo.Database
|
||||
*mongo.Client
|
||||
}
|
||||
|
||||
// Init init mongodb client
|
||||
func (db *mongodb) Init(name, source string) (Store, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
database, err := pdb.NewMDB(ctx, config.Conf.Database)
|
||||
opts := options.Client().ApplyURI(source)
|
||||
client, err := mongo.Connect(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.Database = database
|
||||
err = client.Ping(ctx, readpref.Primary())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.Client = client
|
||||
// create index
|
||||
indexModel := mongo.IndexModel{
|
||||
Keys: bson.D{bson.E{Key: "username", Value: 1}},
|
||||
Options: options.Index().SetUnique(true).SetSparse(true),
|
||||
}
|
||||
db.Database.Collection(collectionAccount).
|
||||
db.Database(mongoDBName).Collection(collectionAccount).
|
||||
Indexes().
|
||||
CreateOne(context.Background(), indexModel)
|
||||
indexModel = mongo.IndexModel{
|
||||
Keys: bson.D{bson.E{Key: "slug", Value: 1}},
|
||||
Options: options.Index().SetUnique(true).SetSparse(true),
|
||||
}
|
||||
db.Database.Collection(collectionArticle).
|
||||
db.Database(mongoDBName).Collection(collectionArticle).
|
||||
Indexes().
|
||||
CreateOne(context.Background(), indexModel)
|
||||
indexModel = mongo.IndexModel{
|
||||
Keys: bson.D{bson.E{Key: "slug", Value: 1}},
|
||||
Options: options.Index().SetUnique(true).SetSparse(true),
|
||||
}
|
||||
db.Database.Collection(collectionSerie).
|
||||
db.Database(mongoDBName).Collection(collectionSerie).
|
||||
Indexes().
|
||||
CreateOne(context.Background(), indexModel)
|
||||
return db, nil
|
||||
@@ -74,7 +78,7 @@ func (db *mongodb) Init(name, source string) (Store, error) {
|
||||
func (db *mongodb) LoadInsertBlogger(ctx context.Context,
|
||||
blogger *model.Blogger) (created bool, err error) {
|
||||
|
||||
collection := db.Database.Collection(collectionBlogger)
|
||||
collection := db.Database(mongoDBName).Collection(collectionBlogger)
|
||||
|
||||
filter := bson.M{}
|
||||
result := collection.FindOne(ctx, filter)
|
||||
@@ -95,7 +99,7 @@ func (db *mongodb) LoadInsertBlogger(ctx context.Context,
|
||||
func (db *mongodb) UpdateBlogger(ctx context.Context,
|
||||
fields map[string]interface{}) error {
|
||||
|
||||
collection := db.Database.Collection(collectionBlogger)
|
||||
collection := db.Database(mongoDBName).Collection(collectionBlogger)
|
||||
|
||||
filter := bson.M{}
|
||||
params := bson.M{}
|
||||
@@ -111,7 +115,7 @@ func (db *mongodb) UpdateBlogger(ctx context.Context,
|
||||
func (db *mongodb) LoadInsertAccount(ctx context.Context,
|
||||
acct *model.Account) (created bool, err error) {
|
||||
|
||||
collection := db.Database.Collection(collectionAccount)
|
||||
collection := db.Database(mongoDBName).Collection(collectionAccount)
|
||||
|
||||
filter := bson.M{"username": acct.Username}
|
||||
result := collection.FindOne(ctx, filter)
|
||||
@@ -132,7 +136,7 @@ func (db *mongodb) LoadInsertAccount(ctx context.Context,
|
||||
func (db *mongodb) UpdateAccount(ctx context.Context, name string,
|
||||
fields map[string]interface{}) error {
|
||||
|
||||
collection := db.Database.Collection(collectionAccount)
|
||||
collection := db.Database(mongoDBName).Collection(collectionAccount)
|
||||
|
||||
filter := bson.M{"username": name}
|
||||
params := bson.M{}
|
||||
@@ -146,7 +150,7 @@ func (db *mongodb) UpdateAccount(ctx context.Context, name string,
|
||||
|
||||
// InsertSerie 创建专题
|
||||
func (db *mongodb) InsertSerie(ctx context.Context, serie *model.Serie) error {
|
||||
collection := db.Database.Collection(collectionSerie)
|
||||
collection := db.Database(mongoDBName).Collection(collectionSerie)
|
||||
|
||||
serie.ID = db.nextValue(ctx, counterNameSerie)
|
||||
_, err := collection.InsertOne(ctx, serie)
|
||||
@@ -155,7 +159,7 @@ func (db *mongodb) InsertSerie(ctx context.Context, serie *model.Serie) error {
|
||||
|
||||
// RemoveSerie 删除专题
|
||||
func (db *mongodb) RemoveSerie(ctx context.Context, id int) error {
|
||||
collection := db.Database.Collection(collectionSerie)
|
||||
collection := db.Database(mongoDBName).Collection(collectionSerie)
|
||||
|
||||
filter := bson.M{"id": id}
|
||||
_, err := collection.DeleteOne(ctx, filter)
|
||||
@@ -166,7 +170,7 @@ func (db *mongodb) RemoveSerie(ctx context.Context, id int) error {
|
||||
func (db *mongodb) UpdateSerie(ctx context.Context, id int,
|
||||
fields map[string]interface{}) error {
|
||||
|
||||
collection := db.Database.Collection(collectionSerie)
|
||||
collection := db.Database(mongoDBName).Collection(collectionSerie)
|
||||
|
||||
filter := bson.M{"id": id}
|
||||
params := bson.M{}
|
||||
@@ -180,7 +184,7 @@ func (db *mongodb) UpdateSerie(ctx context.Context, id int,
|
||||
|
||||
// LoadAllSerie 查询所有专题
|
||||
func (db *mongodb) LoadAllSerie(ctx context.Context) (model.SortedSeries, error) {
|
||||
collection := db.Database.Collection(collectionSerie)
|
||||
collection := db.Database(mongoDBName).Collection(collectionSerie)
|
||||
|
||||
opts := options.Find().SetSort(bson.M{"id": -1})
|
||||
filter := bson.M{}
|
||||
@@ -214,14 +218,14 @@ func (db *mongodb) InsertArticle(ctx context.Context, article *model.Article, st
|
||||
}
|
||||
}
|
||||
|
||||
collection := db.Database.Collection(collectionArticle)
|
||||
collection := db.Database(mongoDBName).Collection(collectionArticle)
|
||||
_, err := collection.InsertOne(ctx, article)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveArticle 硬删除文章
|
||||
func (db *mongodb) RemoveArticle(ctx context.Context, id int) error {
|
||||
collection := db.Database.Collection(collectionArticle)
|
||||
collection := db.Database(mongoDBName).Collection(collectionArticle)
|
||||
|
||||
filter := bson.M{"id": id}
|
||||
_, err := collection.DeleteOne(ctx, filter)
|
||||
@@ -230,7 +234,7 @@ func (db *mongodb) RemoveArticle(ctx context.Context, id int) error {
|
||||
|
||||
// CleanArticles 清理回收站文章
|
||||
func (db *mongodb) CleanArticles(ctx context.Context, exp time.Time) error {
|
||||
collection := db.Database.Collection(collectionArticle)
|
||||
collection := db.Database(mongoDBName).Collection(collectionArticle)
|
||||
|
||||
// 超过两天自动删除
|
||||
filter := bson.M{"deleted_at": bson.M{"$gt": time.Time{}, "$lt": exp}}
|
||||
@@ -242,7 +246,7 @@ func (db *mongodb) CleanArticles(ctx context.Context, exp time.Time) error {
|
||||
func (db *mongodb) UpdateArticle(ctx context.Context, id int,
|
||||
fields map[string]interface{}) error {
|
||||
|
||||
collection := db.Database.Collection(collectionArticle)
|
||||
collection := db.Database(mongoDBName).Collection(collectionArticle)
|
||||
|
||||
filter := bson.M{"id": id}
|
||||
params := bson.M{}
|
||||
@@ -256,7 +260,7 @@ func (db *mongodb) UpdateArticle(ctx context.Context, id int,
|
||||
|
||||
// LoadArticle 查找文章
|
||||
func (db *mongodb) LoadArticle(ctx context.Context, id int) (*model.Article, error) {
|
||||
collection := db.Database.Collection(collectionArticle)
|
||||
collection := db.Database(mongoDBName).Collection(collectionArticle)
|
||||
|
||||
filter := bson.M{"id": id}
|
||||
result := collection.FindOne(ctx, filter)
|
||||
@@ -272,7 +276,7 @@ func (db *mongodb) LoadArticle(ctx context.Context, id int) (*model.Article, err
|
||||
// LoadArticleList 获取文章列表
|
||||
func (db *mongodb) LoadArticleList(ctx context.Context, search SearchArticles) (
|
||||
model.SortedArticles, int, error) {
|
||||
collection := db.Database.Collection(collectionArticle)
|
||||
collection := db.Database(mongoDBName).Collection(collectionArticle)
|
||||
|
||||
filter := bson.M{}
|
||||
for k, v := range search.Fields {
|
||||
@@ -322,6 +326,11 @@ func (db *mongodb) LoadArticleList(ctx context.Context, search SearchArticles) (
|
||||
return articles, int(count), nil
|
||||
}
|
||||
|
||||
// DropDatabase drop eiblog database
|
||||
func (db *mongodb) DropDatabase(ctx context.Context) error {
|
||||
return db.Database(mongoDBName).Drop(ctx)
|
||||
}
|
||||
|
||||
// counter counter
|
||||
type counter struct {
|
||||
Name string
|
||||
@@ -330,7 +339,7 @@ type counter struct {
|
||||
|
||||
// nextValue counter value
|
||||
func (db *mongodb) nextValue(ctx context.Context, name string) int {
|
||||
collection := db.Database.Collection(collectionCounter)
|
||||
collection := db.Database(mongoDBName).Collection(collectionCounter)
|
||||
|
||||
opts := options.FindOneAndUpdate().SetUpsert(true).
|
||||
SetReturnDocument(options.After)
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/model"
|
||||
)
|
||||
|
||||
@@ -20,10 +19,7 @@ var (
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
store, err = NewStore(config.Database{
|
||||
Driver: "mongodb",
|
||||
Source: "mongodb://127.0.0.1:27017",
|
||||
})
|
||||
store, err = NewStore("mongodb", "mongodb://127.0.0.1:27017")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/model"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/driver/clickhouse"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
@@ -64,15 +63,12 @@ func (db *rdbms) Init(name, source string) (Store, error) {
|
||||
return nil, err
|
||||
}
|
||||
// auto migrate
|
||||
err = gormDB.AutoMigrate(
|
||||
gormDB.AutoMigrate(
|
||||
&model.Account{},
|
||||
&model.Blogger{},
|
||||
&model.Article{},
|
||||
&model.Serie{},
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Error("rdbms.AutoMigrate: ", err)
|
||||
}
|
||||
db.DB = gormDB
|
||||
return db, nil
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/model"
|
||||
)
|
||||
|
||||
@@ -65,6 +64,9 @@ type Store interface {
|
||||
LoadArticle(ctx context.Context, id int) (*model.Article, error)
|
||||
// LoadArticleList 查找文章列表
|
||||
LoadArticleList(ctx context.Context, search SearchArticles) (model.SortedArticles, int, error)
|
||||
|
||||
// 危险操作
|
||||
DropDatabase(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Driver 存储驱动
|
||||
@@ -100,14 +102,13 @@ func Drivers() []string {
|
||||
}
|
||||
|
||||
// NewStore 新建存储
|
||||
func NewStore(conf config.Database) (Store, error) {
|
||||
func NewStore(name string, source string) (Store, error) {
|
||||
storeMu.RLock()
|
||||
driver, ok := stores[conf.Driver]
|
||||
driver, ok := stores[name]
|
||||
storeMu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("store: unknown driver %q (forgotten import?)", conf.Driver)
|
||||
return nil, fmt.Errorf("store: unknown driver %q (forgotten import?)", name)
|
||||
}
|
||||
|
||||
return driver.Init(conf.Driver, conf.Source)
|
||||
return driver.Init(name, source)
|
||||
}
|
||||
@@ -4,48 +4,172 @@ package config
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// RunMode 列表
|
||||
const (
|
||||
RunModeLocal RunMode = "local" // 本地环境
|
||||
RunModeDev RunMode = "dev" // 开发环境
|
||||
RunModeProd RunMode = "prod" // 生产环境
|
||||
var (
|
||||
// Conf config instance
|
||||
Conf Config
|
||||
|
||||
// ModeDev run mode as development
|
||||
ModeDev = "dev"
|
||||
// ModeProd run mode as production
|
||||
ModeProd = "prod"
|
||||
// WorkDir workspace dir
|
||||
WorkDir string
|
||||
)
|
||||
|
||||
// RunMode 运行模式
|
||||
type RunMode string
|
||||
|
||||
// IsReleaseMode 是否
|
||||
func (mode RunMode) IsReleaseMode() bool {
|
||||
return mode == RunModeProd
|
||||
// Mode run mode
|
||||
type Mode struct {
|
||||
Name string `yaml:"name"`
|
||||
EnableHTTP bool `yaml:"enablehttp"`
|
||||
HTTPPort int `yaml:"httpport"`
|
||||
EnableGRPC bool `yaml:"enablegrpc"`
|
||||
GRPCPort int `yaml:"grpcport"`
|
||||
Host string `yaml:"host"`
|
||||
}
|
||||
|
||||
// IsRunMode 是否是runmode
|
||||
func (mode RunMode) IsRunMode() bool {
|
||||
return mode == RunModeDev || mode == RunModeProd || mode == RunModeLocal
|
||||
// Database sql database
|
||||
type Database struct {
|
||||
Driver string `yaml:"driver"`
|
||||
Source string `yaml:"source"`
|
||||
}
|
||||
|
||||
// WorkEtcPath walk etc dir
|
||||
func WorkEtcPath() (string, error) {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
wd, err := os.Getwd()
|
||||
// General common
|
||||
type General struct {
|
||||
PageNum int `yaml:"pagenum"` // 前台每页文章数量
|
||||
PageSize int `yaml:"pagesize"` // 后台每页文章数量
|
||||
StartID int `yaml:"startid"` // 文章启始ID
|
||||
DescPrefix string `yaml:"descprefix"` // 文章描述前缀
|
||||
Identifier string `yaml:"identifier"` // 文章截取标识
|
||||
Length int `yaml:"length"` // 文章预览长度
|
||||
Timezone string `yaml:"timezone"` // 时区
|
||||
}
|
||||
|
||||
// Disqus comments
|
||||
type Disqus struct {
|
||||
ShortName string `yaml:"shortname"`
|
||||
PublicKey string `yaml:"publickey"`
|
||||
AccessToken string `yaml:"accesstoken"`
|
||||
}
|
||||
|
||||
// Twitter card
|
||||
type Twitter struct {
|
||||
Card string `yaml:"card"`
|
||||
Site string `yaml:"site"`
|
||||
Image string `yaml:"image"`
|
||||
Address string `yaml:"address"`
|
||||
}
|
||||
|
||||
// Google analytics
|
||||
type Google struct {
|
||||
URL string `yaml:"url"`
|
||||
Tid string `yaml:"tid"`
|
||||
V string `yaml:"v"`
|
||||
AdSense string `yaml:"adsense"`
|
||||
}
|
||||
|
||||
// Qiniu oss
|
||||
type Qiniu struct {
|
||||
Bucket string `yaml:"bucket"`
|
||||
Domain string `yaml:"domain"`
|
||||
AccessKey string `yaml:"accesskey"`
|
||||
SecretKey string `yaml:"secretkey"`
|
||||
}
|
||||
|
||||
// FeedRPC feedr
|
||||
type FeedRPC struct {
|
||||
FeedrURL string `yaml:"feedrurl"`
|
||||
PingRPC []string `yaml:"pingrpc"`
|
||||
}
|
||||
|
||||
// Account info
|
||||
type Account struct {
|
||||
Username string `yaml:"username"` // *
|
||||
Password string `yaml:"password"` // *
|
||||
Email string `yaml:"email"`
|
||||
PhoneNumber string `yaml:"phonenumber"`
|
||||
Address string `yaml:"address"`
|
||||
}
|
||||
|
||||
// Blogger info
|
||||
type Blogger struct {
|
||||
BlogName string `yaml:"blogname"`
|
||||
SubTitle string `yaml:"subtitle"`
|
||||
BeiAn string `yaml:"beian"`
|
||||
BTitle string `yaml:"btitle"`
|
||||
Copyright string `yaml:"copyright"`
|
||||
}
|
||||
|
||||
// EiBlogApp config
|
||||
type EiBlogApp struct {
|
||||
Mode
|
||||
|
||||
StaticVersion int `yaml:"staticversion"`
|
||||
HotWords []string `yaml:"hotwords"`
|
||||
General General `yaml:"general"`
|
||||
Disqus Disqus `yaml:"disqus"`
|
||||
Google Google `yaml:"google"`
|
||||
Qiniu Qiniu `yaml:"qiniu"`
|
||||
Twitter Twitter `yaml:"twitter"`
|
||||
FeedRPC FeedRPC `yaml:"feedrpc"`
|
||||
Account Account `yaml:"account"`
|
||||
Blogger Blogger `yaml:"blogger"`
|
||||
}
|
||||
|
||||
// BackupApp config
|
||||
type BackupApp struct {
|
||||
Mode
|
||||
|
||||
BackupTo string `yaml:"backupto"`
|
||||
Interval string `yaml:"interval"` // circle backup, default: 7d
|
||||
Validity int `yaml:"validity"` // storage days, default: 60
|
||||
Qiniu Qiniu `yaml:"qiniu"` // qiniu config
|
||||
}
|
||||
|
||||
// Config app config
|
||||
type Config struct {
|
||||
RunMode string `yaml:"runmode"`
|
||||
AppName string `yaml:"appname"`
|
||||
Database Database `yaml:"database"`
|
||||
ESHost string `yaml:"eshost"`
|
||||
EiBlogApp EiBlogApp `yaml:"eiblogapp"`
|
||||
BackupApp BackupApp `yaml:"backupapp"`
|
||||
}
|
||||
|
||||
// load config file
|
||||
func init() {
|
||||
// compatibility linux and windows
|
||||
var err error
|
||||
WorkDir = workDir()
|
||||
path := filepath.Join(WorkDir, "conf", "app.yml")
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
panic(err)
|
||||
}
|
||||
// find etc path, try 3 times
|
||||
var etc string
|
||||
for gopath != wd && wd != "/" {
|
||||
etc = filepath.Join(wd, "etc")
|
||||
|
||||
_, err := os.Stat(etc)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
wd = filepath.Dir(wd)
|
||||
err = yaml.Unmarshal(data, &Conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// read run mode from env
|
||||
Conf.RunMode = ModeDev
|
||||
if runmode := os.Getenv("RUN_MODE"); runmode == ModeProd {
|
||||
Conf.RunMode = runmode
|
||||
}
|
||||
// read env
|
||||
readDBEnv()
|
||||
}
|
||||
|
||||
func readDBEnv() {
|
||||
key := strings.ToUpper(Conf.AppName) + "_DB_DRIVER"
|
||||
if d := os.Getenv(key); d != "" {
|
||||
Conf.Database.Driver = d
|
||||
}
|
||||
key = strings.ToUpper(Conf.AppName) + "_DB_SOURCE"
|
||||
if s := os.Getenv(key); s != "" {
|
||||
Conf.Database.Source = s
|
||||
}
|
||||
return etc, nil
|
||||
}
|
||||
|
||||
29
pkg/config/dev.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// +build !prod
|
||||
|
||||
// Package config provides ...
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// workDir recognize workspace dir
|
||||
var workDir = func() string {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for wd != "" {
|
||||
name := filepath.Join(wd, "conf")
|
||||
_, err := os.Stat(name)
|
||||
if err != nil {
|
||||
dir, _ := path.Split(wd)
|
||||
wd = path.Clean(dir)
|
||||
continue
|
||||
}
|
||||
return wd
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package config
|
||||
|
||||
// APIMode 应用配置
|
||||
type APIMode struct {
|
||||
// 运行模式,可根据不同模式做相关判断,如日志打印等
|
||||
RunMode RunMode
|
||||
|
||||
// 服务名称,如 eiblog, backup
|
||||
Name string
|
||||
// 监听地址,如 0.0.0.0:9000
|
||||
Listen string
|
||||
// 所属域名,如 deepzz.com
|
||||
Host string
|
||||
// 一般的应用都会有个密钥,可以用这个字段
|
||||
Secret string
|
||||
}
|
||||
|
||||
// Database 数据库配置
|
||||
type Database struct {
|
||||
// 数据库驱动,如 sqlite, mysql, postgres 等
|
||||
Driver string
|
||||
// 数据库连接字符串,如
|
||||
// sqlite:./db.sqlite
|
||||
// mysql:root:123456@tcp(127.0.0.1:3306)/eiblog?charset=utf8mb4&parseTime=True&loc=Local
|
||||
Source string
|
||||
}
|
||||
|
||||
// Disqus 评论配置
|
||||
type Disqus struct {
|
||||
// 短名称,如 deepzz
|
||||
ShortName string
|
||||
// 公共密钥 wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
|
||||
PublicKey string
|
||||
// 访问令牌, 需自行创建
|
||||
AccessToken string
|
||||
}
|
||||
|
||||
// Twitter 社交配置
|
||||
type Twitter struct {
|
||||
// 卡片, 可选 summary, summary_large_image, player
|
||||
Card string
|
||||
// id号, 如 deepzz02
|
||||
Site string
|
||||
// 图片, 如 st.deepzz.cn/static/img/avatar.jpg
|
||||
Image string
|
||||
// 地址, 如 twitter.com/deepzz02
|
||||
Address string
|
||||
}
|
||||
|
||||
// Google 分析配置
|
||||
type Google struct {
|
||||
// url, 如 https://www.google-analytics.com/g/collect
|
||||
URL string
|
||||
// tid, 如 G-S085VRC5PF
|
||||
Tid string
|
||||
// v, 如 "2"
|
||||
V string
|
||||
// 如果开启广发, 配置 <script async src="xxx" crossorigin="anonymous"></script>
|
||||
AdSense string
|
||||
}
|
||||
|
||||
// Qiniu 对象存储配置
|
||||
type Qiniu struct {
|
||||
// bucket, 如 eiblog
|
||||
Bucket string
|
||||
// domain, 如 st.deepzz.cn
|
||||
Domain string
|
||||
// accesskey, 如 1234567890
|
||||
AccessKey string
|
||||
// secretkey, 如 1234567890
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
// FeedRPC 订阅配置
|
||||
type FeedRPC struct {
|
||||
// feedrurl, 如 https://deepzz.superfeedr.com/
|
||||
FeedrURL string
|
||||
// pingrpc, 如 http://ping.baidu.com/ping/RPC2, http://rpc.pingomatic.com/
|
||||
PingRPC []string
|
||||
}
|
||||
|
||||
// General 博客通用配置
|
||||
type General struct {
|
||||
// 前台每页文章数量, 一般配置为 10
|
||||
PageNum int
|
||||
// 后台每页文章数量, 一般配置为 20
|
||||
PageSize int
|
||||
// 文章描述前缀, 一般配置为 Desc:
|
||||
DescPrefix string
|
||||
// 文章截取标识, 一般配置为 <!--more-->
|
||||
Identifier string
|
||||
// 文章预览长度, 一般配置为 400
|
||||
Length int
|
||||
// 时区, 一般配置为 Asia/Shanghai
|
||||
Timezone string
|
||||
// 是否启用两步验证
|
||||
TwoFactor bool
|
||||
}
|
||||
|
||||
// Account 账户配置
|
||||
type Account struct {
|
||||
// *必须配置, 后台登录用户名
|
||||
Username string
|
||||
// *必须配置, 后台登录密码。登录后请后台立即修改
|
||||
Password string
|
||||
}
|
||||
|
||||
// Blogger 博客配置, 无需配置,程序默认初始化,可在后台更改
|
||||
type Blogger struct {
|
||||
// 博客名称, 如 deepzz
|
||||
BlogName string
|
||||
// 格言, 如 Rome was not built in one day.
|
||||
SubTitle string
|
||||
// 备案号, 不填则不显示在网站底部, 如 蜀ICP备xxxxxxxx号-1
|
||||
BeiAn string
|
||||
// 标题, 如 deepzz's Blog
|
||||
BTitle string
|
||||
// 版权, 如 本站使用「<a href="//creativecommons.org/licenses/by/4.0/">署名 4.0 国际</a>」创作共享协议,转载请注明作者及原网址。
|
||||
Copyright string // 版权
|
||||
}
|
||||
|
||||
// CustomPage 自定义页面配置
|
||||
type CustomPage struct {
|
||||
// 页面名称, 如 独立作品
|
||||
Name string
|
||||
// 页面链接, 如 /works
|
||||
Path string
|
||||
// 是否显示在导航栏, 如 true
|
||||
ShowInNav bool
|
||||
// 是否嵌入, 如 true
|
||||
IsEmbed bool
|
||||
}
|
||||
7
pkg/config/pro.go
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build prod
|
||||
|
||||
// Package config provides ...
|
||||
package config
|
||||
|
||||
// workDir production use current dir
|
||||
var workDir = func() string { return "" }
|
||||
@@ -1,43 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||
)
|
||||
|
||||
// NewMDB new mongodb
|
||||
// https://docs.mongodb.com/manual/reference/connection-string/
|
||||
// mongodb://db0.example.com,db1.example.com,db2.example.com/dbname?replicaSet=myRepl&w=majority&wtimeoutMS=5000
|
||||
func NewMDB(ctx context.Context, opts config.Database) (*mongo.Database, error) {
|
||||
if opts.Driver != "mongodb" {
|
||||
return nil, errors.New("db: driver must be mongodb, but " + opts.Driver)
|
||||
}
|
||||
u, err := url.Parse(opts.Source)
|
||||
if err != nil {
|
||||
return nil, errors.New("db: " + err.Error())
|
||||
}
|
||||
database := u.Path
|
||||
if database == "" {
|
||||
return nil, errors.New("db: please specify a database")
|
||||
}
|
||||
database = database[1:]
|
||||
|
||||
// remove database
|
||||
u.Path = ""
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(u.String()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = client.Ping(ctx, readpref.Primary())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.Database(database), nil
|
||||
}
|
||||
8
pkg/core/backup/api.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Package backup provides ...
|
||||
package backup
|
||||
|
||||
// @title APP Demo API
|
||||
// @version 1.0
|
||||
// @description This is a sample server celler server.
|
||||
|
||||
// @BasePath /api
|
||||
80
pkg/core/backup/docs/docs.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
package docs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/swaggo/swag"
|
||||
)
|
||||
|
||||
var doc = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {}
|
||||
}`
|
||||
|
||||
type swaggerInfo struct {
|
||||
Version string
|
||||
Host string
|
||||
BasePath string
|
||||
Schemes []string
|
||||
Title string
|
||||
Description string
|
||||
}
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = swaggerInfo{
|
||||
Version: "1.0",
|
||||
Host: "",
|
||||
BasePath: "/api",
|
||||
Schemes: []string{},
|
||||
Title: "APP Demo API",
|
||||
Description: "This is a sample server celler server.",
|
||||
}
|
||||
|
||||
type s struct{}
|
||||
|
||||
func (s *s) ReadDoc() string {
|
||||
sInfo := SwaggerInfo
|
||||
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
|
||||
|
||||
t, err := template.New("swagger_info").Funcs(template.FuncMap{
|
||||
"marshal": func(v interface{}) string {
|
||||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
},
|
||||
"escape": func(v interface{}) string {
|
||||
// escape tabs
|
||||
str := strings.Replace(v.(string), "\t", "\\t", -1)
|
||||
// replace " with \", and if that results in \\", replace that with \\\"
|
||||
str = strings.Replace(str, "\"", "\\\"", -1)
|
||||
return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
|
||||
},
|
||||
}).Parse(doc)
|
||||
if err != nil {
|
||||
return doc
|
||||
}
|
||||
|
||||
var tpl bytes.Buffer
|
||||
if err := t.Execute(&tpl, sInfo); err != nil {
|
||||
return doc
|
||||
}
|
||||
|
||||
return tpl.String()
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(swag.Name, &s{})
|
||||
}
|
||||
11
pkg/core/backup/docs/swagger.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "This is a sample server celler server.",
|
||||
"title": "APP Demo API",
|
||||
"contact": {},
|
||||
"version": "1.0"
|
||||
},
|
||||
"basePath": "/api",
|
||||
"paths": {}
|
||||
}
|
||||
8
pkg/core/backup/docs/swagger.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
basePath: /api
|
||||
info:
|
||||
contact: {}
|
||||
description: This is a sample server celler server.
|
||||
title: APP Demo API
|
||||
version: "1.0"
|
||||
paths: {}
|
||||
swagger: "2.0"
|
||||
@@ -1,3 +1,4 @@
|
||||
// Package ping provides ...
|
||||
package ping
|
||||
|
||||
import (
|
||||
@@ -12,13 +13,6 @@ func RegisterRoutes(group gin.IRoutes) {
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// Package swag provides ...
|
||||
package swag
|
||||
|
||||
import (
|
||||
_ "github.com/eiblog/eiblog/cmd/eiblog/docs" // docs
|
||||
_ "github.com/eiblog/eiblog/pkg/core/backup/docs" // docs
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
145
pkg/core/backup/timer/qiniu/qiniu.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// Package qiniu provides ...
|
||||
package qiniu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/cache/store"
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/internal"
|
||||
)
|
||||
|
||||
// Storage qiniu storage
|
||||
type Storage struct{}
|
||||
|
||||
// BackupData implements timer.Storage
|
||||
func (s Storage) BackupData(now time.Time) error {
|
||||
switch config.Conf.Database.Driver {
|
||||
case "mongodb":
|
||||
return backupFromMongoDB(now)
|
||||
default:
|
||||
return errors.New("unsupported source backup to qiniu: " +
|
||||
config.Conf.Database.Driver)
|
||||
}
|
||||
}
|
||||
|
||||
// RestoreData implements timer.Storage
|
||||
func (s Storage) RestoreData() error {
|
||||
switch config.Conf.Database.Driver {
|
||||
case "mongodb":
|
||||
return restoreToMongoDB()
|
||||
default:
|
||||
return errors.New("unsupported source restore from qiniu: " +
|
||||
config.Conf.Database.Driver)
|
||||
}
|
||||
}
|
||||
|
||||
func backupFromMongoDB(now time.Time) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*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
|
||||
name := fmt.Sprintf("eiblog-%s.tar.gz", now.Format("2006-01-02"))
|
||||
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
|
||||
}
|
||||
|
||||
// upload file
|
||||
f, err := os.Open("/tmp/" + name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploadParams := internal.UploadParams{
|
||||
Name: filepath.Join("blog", name), // blog/eiblog-xx.tar.gz
|
||||
Size: s.Size(),
|
||||
Data: f,
|
||||
NoCompletePath: true,
|
||||
|
||||
Conf: config.Conf.BackupApp.Qiniu,
|
||||
}
|
||||
_, err = internal.QiniuUpload(uploadParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// after days delete
|
||||
deleteParams := internal.DeleteParams{
|
||||
Name: filepath.Join("blog", name), // blog/eiblog-xx.tar.gz
|
||||
Days: config.Conf.BackupApp.Validity,
|
||||
NoCompletePath: true,
|
||||
|
||||
Conf: config.Conf.BackupApp.Qiniu,
|
||||
}
|
||||
return internal.QiniuDelete(deleteParams)
|
||||
}
|
||||
|
||||
func restoreToMongoDB() error {
|
||||
// backup file
|
||||
params := internal.ContentParams{
|
||||
Prefix: "blog/",
|
||||
|
||||
Conf: config.Conf.BackupApp.Qiniu,
|
||||
}
|
||||
raw, err := internal.QiniuContent(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile("/tmp/eiblog.tar.gz", os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = f.Write(raw)
|
||||
defer f.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*20)
|
||||
defer cancel()
|
||||
// drop database
|
||||
store, err := store.NewStore(config.Conf.Database.Driver,
|
||||
config.Conf.Database.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = store.DropDatabase(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// unarchive
|
||||
arg := fmt.Sprintf("tar xzf /tmp/eiblog.tar.gz -C /tmp")
|
||||
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()
|
||||
}
|
||||
74
pkg/core/backup/timer/timer.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Package timer provides ...
|
||||
package timer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/core/backup/timer/qiniu"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Start to backup with ticker
|
||||
func Start(restore bool) (err error) {
|
||||
var storage Storage
|
||||
// backup instance
|
||||
switch config.Conf.BackupApp.BackupTo {
|
||||
case "qiniu":
|
||||
storage = qiniu.Storage{}
|
||||
|
||||
default:
|
||||
return errors.New("timer: unknown backup to driver: " +
|
||||
config.Conf.BackupApp.BackupTo)
|
||||
}
|
||||
if restore {
|
||||
err = storage.RestoreData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Info("timer: RestoreData success")
|
||||
}
|
||||
// parse duration
|
||||
interval, err := ParseDuration(config.Conf.BackupApp.Interval)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := time.NewTicker(interval)
|
||||
for now := range t.C {
|
||||
err = storage.BackupData(now)
|
||||
if err != nil {
|
||||
logrus.Error("timer: Start.BackupData: ", 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)
|
||||
}
|
||||
|
||||
// Storage backup backend
|
||||
type Storage interface {
|
||||
BackupData(now time.Time) error
|
||||
RestoreData() error
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// Package admin provides ...
|
||||
package admin
|
||||
|
||||
import (
|
||||
@@ -9,13 +10,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/config"
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/handler/internal"
|
||||
"github.com/eiblog/eiblog/pkg/middleware"
|
||||
"github.com/eiblog/eiblog/pkg/cache"
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/core/eiblog"
|
||||
"github.com/eiblog/eiblog/pkg/internal"
|
||||
"github.com/eiblog/eiblog/pkg/model"
|
||||
"github.com/eiblog/eiblog/pkg/third/qiniu"
|
||||
"github.com/eiblog/eiblog/tools"
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -48,74 +48,36 @@ func RegisterRoutesAuthz(group gin.IRoutes) {
|
||||
group.POST("/api/trash-recover", handleAPITrashRecover)
|
||||
group.POST("/api/file-upload", handleAPIQiniuUpload)
|
||||
group.POST("/api/file-delete", handleAPIQiniuDelete)
|
||||
group.POST("/api/twofactor", handleAPITwoFactor)
|
||||
}
|
||||
|
||||
// handleAcctLogin 登录接口
|
||||
func handleAcctLogin(c *gin.Context) {
|
||||
user := c.PostForm("user")
|
||||
pwd := c.PostForm("password")
|
||||
code := c.PostForm("code")
|
||||
// code := c.PostForm("code") // 二次验证
|
||||
if user == "" || pwd == "" {
|
||||
logrus.Warnf("参数错误: %s %s", user, pwd)
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
if internal.Ei.Account.Username != user ||
|
||||
internal.Ei.Account.Password != tools.EncryptPasswd(user, pwd) {
|
||||
if cache.Ei.Account.Username != user ||
|
||||
cache.Ei.Account.Password != tools.EncryptPasswd(user, pwd) {
|
||||
logrus.Warnf("账号或密码错误 %s, %s", user, pwd)
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
// 两步验证
|
||||
if config.Conf.General.TwoFactor &&
|
||||
internal.Ei.Account.TwoFactorSecret != "" {
|
||||
valid := totp.Validate(code, internal.Ei.Account.TwoFactorSecret)
|
||||
if !valid {
|
||||
logrus.Warnf("两步验证: %s", code)
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
}
|
||||
// 登录成功
|
||||
middleware.SetLogin(c, user)
|
||||
eiblog.SetLogin(c, user)
|
||||
|
||||
internal.Ei.Account.LoginIP = c.ClientIP()
|
||||
internal.Ei.Account.LoginAt = time.Now()
|
||||
internal.Store.UpdateAccount(context.Background(), user, map[string]interface{}{
|
||||
"login_ip": internal.Ei.Account.LoginIP,
|
||||
"login_at": internal.Ei.Account.LoginAt,
|
||||
cache.Ei.Account.LoginIP = c.ClientIP()
|
||||
cache.Ei.Account.LoginAt = time.Now()
|
||||
cache.Ei.UpdateAccount(context.Background(), user, map[string]interface{}{
|
||||
"login_ip": cache.Ei.Account.LoginIP,
|
||||
"login_at": cache.Ei.Account.LoginAt,
|
||||
})
|
||||
c.Redirect(http.StatusFound, "/admin/profile")
|
||||
}
|
||||
|
||||
// handleAPITwoFactor 两步验证
|
||||
func handleAPITwoFactor(c *gin.Context) {
|
||||
code := c.PostForm("code")
|
||||
if code == "" {
|
||||
responseNotice(c, NoticeNotice, "验证码不能为空", "")
|
||||
return
|
||||
}
|
||||
valid := totp.Validate(code, internal.TwoFactorSecret)
|
||||
if !valid {
|
||||
responseNotice(c, NoticeNotice, "验证码错误", "")
|
||||
return
|
||||
}
|
||||
err := internal.Store.UpdateAccount(context.Background(), internal.Ei.Account.Username,
|
||||
map[string]interface{}{
|
||||
"two_factor_secret": internal.TwoFactorSecret,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Error("handleAPITwoFactor.UpdateAccount: ", err)
|
||||
responseNotice(c, NoticeNotice, err.Error(), "")
|
||||
return
|
||||
}
|
||||
internal.Ei.Account.TwoFactorSecret = internal.TwoFactorSecret
|
||||
internal.TwoFactorSecret = ""
|
||||
c.Request.Header.Set("Referer", "/admin/profile")
|
||||
responseNotice(c, NoticeSuccess, "绑定成功", "")
|
||||
}
|
||||
|
||||
// handleAPIBlogger 更新博客信息
|
||||
func handleAPIBlogger(c *gin.Context) {
|
||||
bn := c.PostForm("blogName")
|
||||
@@ -129,7 +91,7 @@ func handleAPIBlogger(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err := internal.Store.UpdateBlogger(context.Background(), map[string]interface{}{
|
||||
err := cache.Ei.UpdateBlogger(context.Background(), map[string]interface{}{
|
||||
"blog_name": bn,
|
||||
"b_title": bt,
|
||||
"bei_an": ba,
|
||||
@@ -142,14 +104,14 @@ func handleAPIBlogger(c *gin.Context) {
|
||||
responseNotice(c, NoticeNotice, err.Error(), "")
|
||||
return
|
||||
}
|
||||
internal.Ei.Blogger.BlogName = bn
|
||||
internal.Ei.Blogger.BTitle = bt
|
||||
internal.Ei.Blogger.BeiAn = ba
|
||||
internal.Ei.Blogger.SubTitle = st
|
||||
internal.Ei.Blogger.SeriesSay = ss
|
||||
internal.Ei.Blogger.ArchivesSay = as
|
||||
internal.PagesCh <- internal.PageSeries
|
||||
internal.PagesCh <- internal.PageArchive
|
||||
cache.Ei.Blogger.BlogName = bn
|
||||
cache.Ei.Blogger.BTitle = bt
|
||||
cache.Ei.Blogger.BeiAn = ba
|
||||
cache.Ei.Blogger.SubTitle = st
|
||||
cache.Ei.Blogger.SeriesSay = ss
|
||||
cache.Ei.Blogger.ArchivesSay = as
|
||||
cache.PagesCh <- cache.PageSeries
|
||||
cache.PagesCh <- cache.PageArchive
|
||||
responseNotice(c, NoticeSuccess, "更新成功", "")
|
||||
}
|
||||
|
||||
@@ -164,7 +126,7 @@ func handleAPIAccount(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err := internal.Store.UpdateAccount(context.Background(), internal.Ei.Account.Username,
|
||||
err := cache.Ei.UpdateAccount(context.Background(), cache.Ei.Account.Username,
|
||||
map[string]interface{}{
|
||||
"email": e,
|
||||
"phone_n": pn,
|
||||
@@ -175,9 +137,9 @@ func handleAPIAccount(c *gin.Context) {
|
||||
responseNotice(c, NoticeNotice, err.Error(), "")
|
||||
return
|
||||
}
|
||||
internal.Ei.Account.Email = e
|
||||
internal.Ei.Account.PhoneN = pn
|
||||
internal.Ei.Account.Address = ad
|
||||
cache.Ei.Account.Email = e
|
||||
cache.Ei.Account.PhoneN = pn
|
||||
cache.Ei.Account.Address = ad
|
||||
responseNotice(c, NoticeSuccess, "更新成功", "")
|
||||
}
|
||||
|
||||
@@ -194,13 +156,13 @@ func handleAPIPassword(c *gin.Context) {
|
||||
responseNotice(c, NoticeNotice, "密码格式错误", "")
|
||||
return
|
||||
}
|
||||
if internal.Ei.Account.Password != tools.EncryptPasswd(internal.Ei.Account.Username, od) {
|
||||
if cache.Ei.Account.Password != tools.EncryptPasswd(cache.Ei.Account.Username, od) {
|
||||
responseNotice(c, NoticeNotice, "原始密码不正确", "")
|
||||
return
|
||||
}
|
||||
newPwd := tools.EncryptPasswd(internal.Ei.Account.Username, nw)
|
||||
newPwd := tools.EncryptPasswd(cache.Ei.Account.Username, nw)
|
||||
|
||||
err := internal.Store.UpdateAccount(context.Background(), internal.Ei.Account.Username,
|
||||
err := cache.Ei.UpdateAccount(context.Background(), cache.Ei.Account.Username,
|
||||
map[string]interface{}{
|
||||
"password": newPwd,
|
||||
})
|
||||
@@ -209,7 +171,7 @@ func handleAPIPassword(c *gin.Context) {
|
||||
responseNotice(c, NoticeNotice, err.Error(), "")
|
||||
return
|
||||
}
|
||||
internal.Ei.Account.Password = newPwd
|
||||
cache.Ei.Account.Password = newPwd
|
||||
responseNotice(c, NoticeSuccess, "更新成功", "")
|
||||
}
|
||||
|
||||
@@ -221,7 +183,7 @@ func handleDraftDelete(c *gin.Context) {
|
||||
responseNotice(c, NoticeNotice, "参数错误", "")
|
||||
return
|
||||
}
|
||||
err = internal.Store.RemoveArticle(context.Background(), id)
|
||||
err = cache.Ei.RemoveArticle(context.Background(), id)
|
||||
if err != nil {
|
||||
logrus.Error("handleDraftDelete.RemoveArticle: ", err)
|
||||
responseNotice(c, NoticeNotice, "删除失败", "")
|
||||
@@ -236,11 +198,11 @@ func handleAPIPostDelete(c *gin.Context) {
|
||||
var ids []int
|
||||
for _, v := range c.PostFormArray("cid[]") {
|
||||
id, err := strconv.Atoi(v)
|
||||
if err != nil || id < internal.ArticleStartID {
|
||||
if err != nil || id < config.Conf.EiBlogApp.General.StartID {
|
||||
responseNotice(c, NoticeNotice, "参数错误", "")
|
||||
return
|
||||
}
|
||||
err = internal.Ei.DelArticle(id)
|
||||
err = cache.Ei.DelArticle(id)
|
||||
if err != nil {
|
||||
logrus.Error("handleAPIPostDelete.DelArticle: ", err)
|
||||
|
||||
@@ -250,14 +212,12 @@ func handleAPIPostDelete(c *gin.Context) {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
// elasticsearch
|
||||
if internal.ESClient != nil {
|
||||
err := internal.ESClient.ElasticDelIndex(ids)
|
||||
if err != nil {
|
||||
logrus.Error("handleAPIPostDelete.ElasticDelIndex: ", err)
|
||||
}
|
||||
err := internal.ElasticDelIndex(ids)
|
||||
if err != nil {
|
||||
logrus.Error("handleAPIPostDelete.ElasticDelIndex: ", err)
|
||||
}
|
||||
// TODO disqus delete
|
||||
responseNotice(c, NoticeSuccess, "删除成功,已移入到回收箱", "")
|
||||
responseNotice(c, NoticeSuccess, "删除成功", "")
|
||||
}
|
||||
|
||||
// handleAPIPostCreate 创建文章
|
||||
@@ -311,7 +271,7 @@ func handleAPIPostCreate(c *gin.Context) {
|
||||
Content: text,
|
||||
Slug: slug,
|
||||
IsDraft: do != "publish",
|
||||
Author: internal.Ei.Account.Username,
|
||||
Author: cache.Ei.Account.Username,
|
||||
SerieID: serieid,
|
||||
Tags: tags,
|
||||
CreatedAt: date,
|
||||
@@ -319,7 +279,7 @@ func handleAPIPostCreate(c *gin.Context) {
|
||||
cid, err = strconv.Atoi(c.PostForm("cid"))
|
||||
// 新文章
|
||||
if err != nil || cid < 1 {
|
||||
err = internal.Ei.AddArticle(article)
|
||||
err = cache.Ei.AddArticle(article)
|
||||
if err != nil {
|
||||
logrus.Error("handleAPIPostCreate.AddArticle: ", err)
|
||||
return
|
||||
@@ -329,22 +289,20 @@ func handleAPIPostCreate(c *gin.Context) {
|
||||
|
||||
if !article.IsDraft {
|
||||
// disqus
|
||||
internal.DisqusClient.ThreadCreate(article, internal.Ei.Blogger.BTitle)
|
||||
internal.ThreadCreate(article, cache.Ei.Blogger.BTitle)
|
||||
// 异步执行,快
|
||||
go func() {
|
||||
// elastic
|
||||
if internal.ESClient != nil {
|
||||
internal.ESClient.ElasticAddIndex(article)
|
||||
}
|
||||
internal.ElasticAddIndex(article)
|
||||
// rss
|
||||
internal.Pinger.PingFunc(internal.Ei.Blogger.BTitle, slug)
|
||||
internal.PingFunc(cache.Ei.Blogger.BTitle, slug)
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
// 旧文章
|
||||
article.ID = cid
|
||||
artc, _ := internal.Ei.FindArticleByID(article.ID) // cache
|
||||
artc, _ := cache.Ei.FindArticleByID(article.ID) // cache
|
||||
if artc != nil {
|
||||
article.IsDraft = false
|
||||
article.Count = artc.Count
|
||||
@@ -354,7 +312,7 @@ func handleAPIPostCreate(c *gin.Context) {
|
||||
article.UpdatedAt = time.Now()
|
||||
}
|
||||
// 数据库更新
|
||||
err = internal.Store.UpdateArticle(context.Background(), article.ID, map[string]interface{}{
|
||||
err = cache.Ei.UpdateArticle(context.Background(), article.ID, map[string]interface{}{
|
||||
"title": article.Title,
|
||||
"content": article.Content,
|
||||
"serie_id": article.SerieID,
|
||||
@@ -368,19 +326,17 @@ func handleAPIPostCreate(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if !article.IsDraft {
|
||||
internal.Ei.RepArticle(artc, article)
|
||||
cache.Ei.RepArticle(artc, article)
|
||||
// disqus
|
||||
if artc == nil {
|
||||
internal.DisqusClient.ThreadCreate(article, internal.Ei.Blogger.BTitle)
|
||||
internal.ThreadCreate(article, cache.Ei.Blogger.BTitle)
|
||||
}
|
||||
// 异步执行,快
|
||||
go func() {
|
||||
// elastic
|
||||
if internal.ESClient != nil {
|
||||
internal.ESClient.ElasticAddIndex(article)
|
||||
}
|
||||
internal.ElasticAddIndex(article)
|
||||
// rss
|
||||
internal.Pinger.PingFunc(internal.Ei.Blogger.BTitle, slug)
|
||||
internal.PingFunc(cache.Ei.Blogger.BTitle, slug)
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -393,7 +349,7 @@ func handleAPISerieDelete(c *gin.Context) {
|
||||
responseNotice(c, NoticeNotice, err.Error(), "")
|
||||
return
|
||||
}
|
||||
err = internal.Ei.DelSerie(id)
|
||||
err = cache.Ei.DelSerie(id)
|
||||
if err != nil {
|
||||
responseNotice(c, NoticeNotice, err.Error(), "")
|
||||
return
|
||||
@@ -420,7 +376,7 @@ func handleAPISerieCreate(c *gin.Context) {
|
||||
mid, err := strconv.Atoi(c.PostForm("mid"))
|
||||
if err == nil && mid > 0 {
|
||||
var serie *model.Serie
|
||||
for _, v := range internal.Ei.Series {
|
||||
for _, v := range cache.Ei.Series {
|
||||
if v.ID == mid {
|
||||
serie = v
|
||||
break
|
||||
@@ -430,7 +386,7 @@ func handleAPISerieCreate(c *gin.Context) {
|
||||
responseNotice(c, NoticeNotice, "专题不存在", "")
|
||||
return
|
||||
}
|
||||
err = internal.Store.UpdateSerie(context.Background(), mid, map[string]interface{}{
|
||||
err = cache.Ei.UpdateSerie(context.Background(), mid, map[string]interface{}{
|
||||
"slug": slug,
|
||||
"name": name,
|
||||
"desc": desc,
|
||||
@@ -443,9 +399,9 @@ func handleAPISerieCreate(c *gin.Context) {
|
||||
serie.Slug = slug
|
||||
serie.Name = name
|
||||
serie.Desc = desc
|
||||
internal.PagesCh <- internal.PageSeries
|
||||
cache.PagesCh <- cache.PageSeries
|
||||
} else {
|
||||
err = internal.Ei.AddSerie(&model.Serie{
|
||||
err = cache.Ei.AddSerie(&model.Serie{
|
||||
Slug: slug,
|
||||
Name: name,
|
||||
Desc: desc,
|
||||
@@ -468,7 +424,7 @@ func handleAPITrashDelete(c *gin.Context) {
|
||||
responseNotice(c, NoticeNotice, "参数错误", "")
|
||||
return
|
||||
}
|
||||
err = internal.Store.RemoveArticle(context.Background(), id)
|
||||
err = cache.Ei.RemoveArticle(context.Background(), id)
|
||||
if err != nil {
|
||||
responseNotice(c, NoticeNotice, err.Error(), "")
|
||||
return
|
||||
@@ -486,7 +442,7 @@ func handleAPITrashRecover(c *gin.Context) {
|
||||
return
|
||||
|
||||
}
|
||||
err = internal.Store.UpdateArticle(context.Background(), id, map[string]interface{}{
|
||||
err = cache.Ei.UpdateArticle(context.Background(), id, map[string]interface{}{
|
||||
"deleted_at": time.Time{},
|
||||
"is_draft": true,
|
||||
})
|
||||
@@ -517,12 +473,14 @@ func handleAPIQiniuUpload(c *gin.Context) {
|
||||
}
|
||||
filename := strings.ToLower(header.Filename)
|
||||
|
||||
params := qiniu.UploadParams{
|
||||
params := internal.UploadParams{
|
||||
Name: filename,
|
||||
Size: s.Size(),
|
||||
Data: file,
|
||||
|
||||
Conf: config.Conf.EiBlogApp.Qiniu,
|
||||
}
|
||||
url, err := internal.QiniuClient.Upload(params)
|
||||
url, err := internal.QiniuUpload(params)
|
||||
if err != nil {
|
||||
logrus.Error("handleAPIQiniuUpload.QiniuUpload: ", err)
|
||||
c.String(http.StatusBadRequest, err.Error())
|
||||
@@ -547,10 +505,12 @@ func handleAPIQiniuDelete(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
params := qiniu.DeleteParams{
|
||||
params := internal.DeleteParams{
|
||||
Name: name,
|
||||
|
||||
Conf: config.Conf.EiBlogApp.Qiniu,
|
||||
}
|
||||
err := internal.QiniuClient.Delete(params)
|
||||
err := internal.QiniuDelete(params)
|
||||
if err != nil {
|
||||
logrus.Error("handleAPIQiniuDelete.QiniuDelete: ", err)
|
||||
}
|
||||
@@ -1,50 +1,18 @@
|
||||
package middleware
|
||||
// Package eiblog provides ...
|
||||
package eiblog
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// SessionOpts 设置选项
|
||||
type SessionOpts struct {
|
||||
Name string
|
||||
Secure bool // required
|
||||
Secret []byte // required
|
||||
// redis store
|
||||
RedisAddr string
|
||||
RedisPwd string
|
||||
}
|
||||
// @title APP Demo API
|
||||
// @version 1.0
|
||||
// @description This is a sample server celler server.
|
||||
|
||||
// SessionMiddleware session中间件
|
||||
func SessionMiddleware(opts SessionOpts) gin.HandlerFunc {
|
||||
store := cookie.NewStore(opts.Secret)
|
||||
store.Options(sessions.Options{
|
||||
MaxAge: 86400 * 30,
|
||||
Path: "/",
|
||||
Secure: opts.Secure,
|
||||
HttpOnly: true,
|
||||
})
|
||||
name := "SESSIONID"
|
||||
if opts.Name != "" {
|
||||
name = opts.Name
|
||||
}
|
||||
return sessions.Sessions(name, store)
|
||||
}
|
||||
|
||||
// UserMiddleware 用户cookie标记
|
||||
func UserMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
cookie, err := c.Cookie("u")
|
||||
if err != nil || cookie == "" {
|
||||
u1 := uuid.New().String()
|
||||
c.SetCookie("u", u1, 86400*730, "/", "", true, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
// @BasePath /api
|
||||
|
||||
// AuthFilter auth filter
|
||||
func AuthFilter(c *gin.Context) {
|
||||
80
pkg/core/eiblog/docs/docs.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
package docs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/swaggo/swag"
|
||||
)
|
||||
|
||||
var doc = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {}
|
||||
}`
|
||||
|
||||
type swaggerInfo struct {
|
||||
Version string
|
||||
Host string
|
||||
BasePath string
|
||||
Schemes []string
|
||||
Title string
|
||||
Description string
|
||||
}
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = swaggerInfo{
|
||||
Version: "1.0",
|
||||
Host: "",
|
||||
BasePath: "/api",
|
||||
Schemes: []string{},
|
||||
Title: "APP Demo API",
|
||||
Description: "This is a sample server celler server.",
|
||||
}
|
||||
|
||||
type s struct{}
|
||||
|
||||
func (s *s) ReadDoc() string {
|
||||
sInfo := SwaggerInfo
|
||||
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
|
||||
|
||||
t, err := template.New("swagger_info").Funcs(template.FuncMap{
|
||||
"marshal": func(v interface{}) string {
|
||||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
},
|
||||
"escape": func(v interface{}) string {
|
||||
// escape tabs
|
||||
str := strings.Replace(v.(string), "\t", "\\t", -1)
|
||||
// replace " with \", and if that results in \\", replace that with \\\"
|
||||
str = strings.Replace(str, "\"", "\\\"", -1)
|
||||
return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
|
||||
},
|
||||
}).Parse(doc)
|
||||
if err != nil {
|
||||
return doc
|
||||
}
|
||||
|
||||
var tpl bytes.Buffer
|
||||
if err := t.Execute(&tpl, sInfo); err != nil {
|
||||
return doc
|
||||
}
|
||||
|
||||
return tpl.String()
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(swag.Name, &s{})
|
||||
}
|
||||
11
pkg/core/eiblog/docs/swagger.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "This is a sample server celler server.",
|
||||
"title": "APP Demo API",
|
||||
"contact": {},
|
||||
"version": "1.0"
|
||||
},
|
||||
"basePath": "/api",
|
||||
"paths": {}
|
||||
}
|
||||
8
pkg/core/eiblog/docs/swagger.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
basePath: /api
|
||||
info:
|
||||
contact: {}
|
||||
description: This is a sample server celler server.
|
||||
title: APP Demo API
|
||||
version: "1.0"
|
||||
paths: {}
|
||||
swagger: "2.0"
|
||||
49
pkg/core/eiblog/file/file.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Package file provides ...
|
||||
package file
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterRoutes register routes
|
||||
func RegisterRoutes(e *gin.Engine) {
|
||||
e.GET("/rss.html", handleFeed)
|
||||
e.GET("/feed", handleFeed)
|
||||
e.GET("/opensearch.xml", handleOpensearch)
|
||||
e.GET("/sitemap.xml", handleSitemap)
|
||||
e.GET("/robots.txt", handleRobots)
|
||||
e.GET("/crossdomain.xml", handleCrossDomain)
|
||||
e.GET("/favicon.ico", handleFavicon)
|
||||
}
|
||||
|
||||
// handleFeed feed.xml
|
||||
func handleFeed(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "assets/feed.xml")
|
||||
}
|
||||
|
||||
// handleOpensearch opensearch.xml
|
||||
func handleOpensearch(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "assets/opensearch.xml")
|
||||
}
|
||||
|
||||
// handleRobots robotx.txt
|
||||
func handleRobots(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "assets/robots.txt")
|
||||
}
|
||||
|
||||
// handleSitemap sitemap.xml
|
||||
func handleSitemap(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "assets/sitemap.xml")
|
||||
}
|
||||
|
||||
// handleCrossDomain crossdomain.xml
|
||||
func handleCrossDomain(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "assets/crossdomain.xml")
|
||||
}
|
||||
|
||||
// handleFavicon favicon.ico
|
||||
func handleFavicon(c *gin.Context) {
|
||||
http.ServeFile(c.Writer, c.Request, "assets/favicon.ico")
|
||||
}
|
||||
164
pkg/core/eiblog/file/timer.go
Normal file
@@ -0,0 +1,164 @@
|
||||
// Package file provides ...
|
||||
package file
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/cache"
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/tools"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var xmlTmpl *template.Template
|
||||
|
||||
func init() {
|
||||
root := filepath.Join(config.WorkDir, "website", "template", "*.xml")
|
||||
|
||||
var err error
|
||||
xmlTmpl, err = template.New("").Funcs(template.FuncMap{
|
||||
"dateformat": tools.DateFormat,
|
||||
"imgtonormal": tools.ImgToNormal,
|
||||
}).ParseGlob(root)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
generateOpensearch()
|
||||
generateRobots()
|
||||
generateCrossdomain()
|
||||
go timerFeed()
|
||||
go timerSitemap()
|
||||
}
|
||||
|
||||
// timerFeed 定时刷新feed
|
||||
func timerFeed() {
|
||||
tpl := xmlTmpl.Lookup("feedTpl.xml")
|
||||
if tpl == nil {
|
||||
logrus.Info("file: not found: feedTpl.xml")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
_, _, articles := cache.Ei.PageArticleFE(1, 20)
|
||||
params := map[string]interface{}{
|
||||
"Title": cache.Ei.Blogger.BTitle,
|
||||
"SubTitle": cache.Ei.Blogger.SubTitle,
|
||||
"Host": config.Conf.EiBlogApp.Host,
|
||||
"FeedrURL": config.Conf.EiBlogApp.FeedRPC.FeedrURL,
|
||||
"BuildDate": now.Format(time.RFC1123Z),
|
||||
"Articles": articles,
|
||||
}
|
||||
f, err := os.OpenFile("assets/feed.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
logrus.Error("file: timerFeed.OpenFile: ", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
err = tpl.Execute(f, params)
|
||||
if err != nil {
|
||||
logrus.Error("file: timerFeed.Execute: ", err)
|
||||
return
|
||||
}
|
||||
time.AfterFunc(time.Hour*4, timerFeed)
|
||||
}
|
||||
|
||||
// timerSitemap 定时刷新sitemap
|
||||
func timerSitemap() {
|
||||
tpl := xmlTmpl.Lookup("sitemapTpl.xml")
|
||||
if tpl == nil {
|
||||
logrus.Info("file: not found: sitemapTpl.xml")
|
||||
return
|
||||
}
|
||||
|
||||
params := map[string]interface{}{
|
||||
"Articles": cache.Ei.Articles,
|
||||
"Host": config.Conf.EiBlogApp.Host,
|
||||
}
|
||||
f, err := os.OpenFile("assets/sitemap.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
logrus.Error("file: timerSitemap.OpenFile: ", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
err = tpl.Execute(f, params)
|
||||
if err != nil {
|
||||
logrus.Error("file: timerSitemap.Execute: ", err)
|
||||
return
|
||||
}
|
||||
time.AfterFunc(time.Hour*24, timerSitemap)
|
||||
}
|
||||
|
||||
// generateOpensearch 生成opensearch.xml
|
||||
func generateOpensearch() {
|
||||
tpl := xmlTmpl.Lookup("opensearchTpl.xml")
|
||||
if tpl == nil {
|
||||
logrus.Info("file: not found: opensearchTpl.xml")
|
||||
return
|
||||
}
|
||||
params := map[string]string{
|
||||
"BTitle": cache.Ei.Blogger.BTitle,
|
||||
"SubTitle": cache.Ei.Blogger.SubTitle,
|
||||
"Host": config.Conf.EiBlogApp.Host,
|
||||
}
|
||||
f, err := os.OpenFile("assets/opensearch.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
logrus.Error("file: generateOpensearch.OpenFile: ", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
err = tpl.Execute(f, params)
|
||||
if err != nil {
|
||||
logrus.Error("file: generateOpensearch.Execute: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// generateRobots 生成robots.txt
|
||||
func generateRobots() {
|
||||
tpl := xmlTmpl.Lookup("robotsTpl.xml")
|
||||
if tpl == nil {
|
||||
logrus.Info("file: not found: robotsTpl.xml")
|
||||
return
|
||||
}
|
||||
params := map[string]string{
|
||||
"Host": config.Conf.EiBlogApp.Host,
|
||||
}
|
||||
f, err := os.OpenFile("assets/robots.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
logrus.Error("file: generateRobots.OpenFile: ", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
err = tpl.Execute(f, params)
|
||||
if err != nil {
|
||||
logrus.Error("file: generateRobots.Execute: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// generateCrossdomain 生成crossdomain.xml
|
||||
func generateCrossdomain() {
|
||||
tpl := xmlTmpl.Lookup("crossdomainTpl.xml")
|
||||
if tpl == nil {
|
||||
logrus.Info("file: not found: crossdomainTpl.xml")
|
||||
return
|
||||
}
|
||||
params := map[string]string{
|
||||
"Host": config.Conf.EiBlogApp.Host,
|
||||
}
|
||||
f, err := os.OpenFile("assets/crossdomain.xml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
logrus.Error("file: generateCrossdomain.OpenFile: ", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
err = tpl.Execute(f, params)
|
||||
if err != nil {
|
||||
logrus.Error("file: generateCrossdomain.Execute: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,29 @@
|
||||
package pages
|
||||
// Package page provides ...
|
||||
package page
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"image/png"
|
||||
htemplate "html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/config"
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/handler/internal"
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/handler/internal/store"
|
||||
"github.com/eiblog/eiblog/pkg/middleware"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/eiblog/eiblog/pkg/cache"
|
||||
"github.com/eiblog/eiblog/pkg/cache/store"
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/core/eiblog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// baseBEParams 基础参数
|
||||
func baseBEParams(_ *gin.Context) gin.H {
|
||||
func baseBEParams(c *gin.Context) gin.H {
|
||||
return gin.H{
|
||||
"Author": internal.Ei.Account.Username,
|
||||
"Qiniu": config.Conf.Qiniu,
|
||||
"Author": cache.Ei.Account.Username,
|
||||
"Qiniu": config.Conf.EiBlogApp.Qiniu,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,51 +31,22 @@ func baseBEParams(_ *gin.Context) gin.H {
|
||||
func handleLoginPage(c *gin.Context) {
|
||||
logout := c.Query("logout")
|
||||
if logout == "true" {
|
||||
middleware.SetLogout(c)
|
||||
} else if middleware.IsLogined(c) {
|
||||
eiblog.SetLogout(c)
|
||||
} else if eiblog.IsLogined(c) {
|
||||
c.Redirect(http.StatusFound, "/admin/profile")
|
||||
return
|
||||
}
|
||||
params := gin.H{
|
||||
"BTitle": internal.Ei.Blogger.BTitle,
|
||||
"TwoFactor": config.Conf.General.TwoFactor &&
|
||||
internal.Ei.Account.TwoFactorSecret != "",
|
||||
}
|
||||
params := gin.H{"BTitle": cache.Ei.Blogger.BTitle}
|
||||
renderHTMLAdminLayout(c, "login.html", params)
|
||||
}
|
||||
|
||||
// handleAdminProfile 个人配置
|
||||
func handleAdminProfile(c *gin.Context) {
|
||||
params := baseBEParams(c)
|
||||
params["Title"] = "个人配置 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "个人配置 | " + cache.Ei.Blogger.BTitle
|
||||
params["Path"] = c.Request.URL.Path
|
||||
params["Console"] = true
|
||||
params["Ei"] = internal.Ei
|
||||
if c.Query("unbind") == "true" {
|
||||
internal.Ei.Account.TwoFactorSecret = ""
|
||||
_ = internal.Store.UpdateAccount(context.Background(), internal.Ei.Account.Username,
|
||||
map[string]interface{}{
|
||||
"two_factor_secret": "",
|
||||
})
|
||||
}
|
||||
if config.Conf.General.TwoFactor &&
|
||||
internal.Ei.Account.TwoFactorSecret == "" {
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: config.Conf.Host,
|
||||
AccountName: internal.Ei.Account.Username,
|
||||
})
|
||||
internal.TwoFactorSecret = key.Secret()
|
||||
if err == nil {
|
||||
var buf bytes.Buffer
|
||||
img, err := key.Image(200, 200)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
png.Encode(&buf, img)
|
||||
b64 := base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||
params["TwoFactorSecret"] = "data:image/png;base64," + b64
|
||||
}
|
||||
}
|
||||
params["Ei"] = cache.Ei
|
||||
renderHTMLAdminLayout(c, "admin-profile", params)
|
||||
}
|
||||
|
||||
@@ -92,20 +61,20 @@ func handleAdminPost(c *gin.Context) {
|
||||
params := baseBEParams(c)
|
||||
id, err := strconv.Atoi(c.Query("cid"))
|
||||
if err == nil && id > 0 {
|
||||
article, _ := internal.Store.LoadArticle(context.Background(), id)
|
||||
article, _ := cache.Ei.LoadArticle(context.Background(), id)
|
||||
if article != nil {
|
||||
params["Title"] = "编辑文章 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "编辑文章 | " + cache.Ei.Blogger.BTitle
|
||||
params["Edit"] = article
|
||||
}
|
||||
}
|
||||
if params["Title"] == nil {
|
||||
params["Title"] = "撰写文章 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "撰写文章 | " + cache.Ei.Blogger.BTitle
|
||||
}
|
||||
params["Path"] = c.Request.URL.Path
|
||||
params["Domain"] = config.Conf.Host
|
||||
params["Series"] = internal.Ei.Series
|
||||
params["Domain"] = config.Conf.EiBlogApp.Host
|
||||
params["Series"] = cache.Ei.Series
|
||||
var tags []T
|
||||
for tag := range internal.Ei.TagArticles {
|
||||
for tag := range cache.Ei.TagArticles {
|
||||
tags = append(tags, T{tag, tag})
|
||||
}
|
||||
str, _ := json.Marshal(tags)
|
||||
@@ -128,15 +97,15 @@ func handleAdminPosts(c *gin.Context) {
|
||||
vals := c.Request.URL.Query()
|
||||
|
||||
params := baseBEParams(c)
|
||||
params["Title"] = "文章管理 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "文章管理 | " + cache.Ei.Blogger.BTitle
|
||||
params["Manage"] = true
|
||||
params["Path"] = c.Request.URL.Path
|
||||
params["Series"] = internal.Ei.Series
|
||||
params["Series"] = cache.Ei.Series
|
||||
params["Serie"] = se
|
||||
params["KW"] = kw
|
||||
var max int
|
||||
params["List"], max = internal.Ei.PageArticleBE(se, kw, false, false,
|
||||
pg, config.Conf.General.PageSize)
|
||||
params["List"], max = cache.Ei.PageArticleBE(se, kw, false, false,
|
||||
pg, config.Conf.EiBlogApp.General.PageSize)
|
||||
if pg < max {
|
||||
vals.Set("page", fmt.Sprint(pg+1))
|
||||
params["Next"] = vals.Encode()
|
||||
@@ -157,10 +126,10 @@ func handleAdminPosts(c *gin.Context) {
|
||||
// handleAdminSeries 专题列表
|
||||
func handleAdminSeries(c *gin.Context) {
|
||||
params := baseBEParams(c)
|
||||
params["Title"] = "专题管理 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "专题管理 | " + cache.Ei.Blogger.BTitle
|
||||
params["Manage"] = true
|
||||
params["Path"] = c.Request.URL.Path
|
||||
params["List"] = internal.Ei.Series
|
||||
params["List"] = cache.Ei.Series
|
||||
renderHTMLAdminLayout(c, "admin-series", params)
|
||||
}
|
||||
|
||||
@@ -169,11 +138,11 @@ func handleAdminSerie(c *gin.Context) {
|
||||
params := baseBEParams(c)
|
||||
|
||||
id, err := strconv.Atoi(c.Query("mid"))
|
||||
params["Title"] = "新增专题 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "新增专题 | " + cache.Ei.Blogger.BTitle
|
||||
if err == nil && id > 0 {
|
||||
for _, v := range internal.Ei.Series {
|
||||
for _, v := range cache.Ei.Series {
|
||||
if v.ID == id {
|
||||
params["Title"] = "编辑专题 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "编辑专题 | " + cache.Ei.Blogger.BTitle
|
||||
params["Edit"] = v
|
||||
break
|
||||
}
|
||||
@@ -187,10 +156,10 @@ func handleAdminSerie(c *gin.Context) {
|
||||
// handleAdminTags 标签列表
|
||||
func handleAdminTags(c *gin.Context) {
|
||||
params := baseBEParams(c)
|
||||
params["Title"] = "标签管理 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "标签管理 | " + cache.Ei.Blogger.BTitle
|
||||
params["Manage"] = true
|
||||
params["Path"] = c.Request.URL.Path
|
||||
params["List"] = internal.Ei.TagArticles
|
||||
params["List"] = cache.Ei.TagArticles
|
||||
renderHTMLAdminLayout(c, "admin-tags", params)
|
||||
}
|
||||
|
||||
@@ -201,7 +170,7 @@ func handleDraftDelete(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||||
return
|
||||
}
|
||||
err = internal.Store.RemoveArticle(context.Background(), id)
|
||||
err = cache.Ei.RemoveArticle(context.Background(), id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "删除错误"})
|
||||
return
|
||||
@@ -213,7 +182,7 @@ func handleDraftDelete(c *gin.Context) {
|
||||
func handleAdminDraft(c *gin.Context) {
|
||||
params := baseBEParams(c)
|
||||
|
||||
params["Title"] = "草稿箱 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "草稿箱 | " + cache.Ei.Blogger.BTitle
|
||||
params["Manage"] = true
|
||||
params["Path"] = c.Request.URL.Path
|
||||
var err error
|
||||
@@ -222,7 +191,7 @@ func handleAdminDraft(c *gin.Context) {
|
||||
Limit: 9999,
|
||||
Fields: map[string]interface{}{store.SearchArticleDraft: true},
|
||||
}
|
||||
params["List"], _, err = internal.Store.LoadArticleList(context.Background(), search)
|
||||
params["List"], _, err = cache.Ei.LoadArticleList(context.Background(), search)
|
||||
if err != nil {
|
||||
logrus.Error("handleDraft.LoadDraftArticles: ", err)
|
||||
c.Status(http.StatusBadRequest)
|
||||
@@ -235,7 +204,7 @@ func handleAdminDraft(c *gin.Context) {
|
||||
// handleAdminTrash 回收箱页
|
||||
func handleAdminTrash(c *gin.Context) {
|
||||
params := baseBEParams(c)
|
||||
params["Title"] = "回收箱 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "回收箱 | " + cache.Ei.Blogger.BTitle
|
||||
params["Manage"] = true
|
||||
params["Path"] = c.Request.URL.Path
|
||||
var err error
|
||||
@@ -244,7 +213,7 @@ func handleAdminTrash(c *gin.Context) {
|
||||
Limit: 9999,
|
||||
Fields: map[string]interface{}{store.SearchArticleTrash: true},
|
||||
}
|
||||
params["List"], _, err = internal.Store.LoadArticleList(context.Background(), search)
|
||||
params["List"], _, err = cache.Ei.LoadArticleList(context.Background(), search)
|
||||
if err != nil {
|
||||
logrus.Error("handleTrash.LoadArticleList: ", err)
|
||||
}
|
||||
@@ -254,7 +223,7 @@ func handleAdminTrash(c *gin.Context) {
|
||||
// handleAdminGeneral 基本设置
|
||||
func handleAdminGeneral(c *gin.Context) {
|
||||
params := baseBEParams(c)
|
||||
params["Title"] = "基本设置 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "基本设置 | " + cache.Ei.Blogger.BTitle
|
||||
params["Setting"] = true
|
||||
params["Path"] = c.Request.URL.Path
|
||||
renderHTMLAdminLayout(c, "admin-general", params)
|
||||
@@ -263,7 +232,7 @@ func handleAdminGeneral(c *gin.Context) {
|
||||
// handleAdminDiscussion 阅读设置
|
||||
func handleAdminDiscussion(c *gin.Context) {
|
||||
params := baseBEParams(c)
|
||||
params["Title"] = "阅读设置 | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = "阅读设置 | " + cache.Ei.Blogger.BTitle
|
||||
params["Setting"] = true
|
||||
params["Path"] = c.Request.URL.Path
|
||||
renderHTMLAdminLayout(c, "admin-discussion", params)
|
||||
@@ -274,19 +243,19 @@ func renderHTMLAdminLayout(c *gin.Context, name string, data gin.H) {
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
// special page
|
||||
if name == "login.html" {
|
||||
err := internal.HTMLTemplate.ExecuteTemplate(c.Writer, name, data)
|
||||
err := htmlTmpl.ExecuteTemplate(c.Writer, name, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
err := internal.HTMLTemplate.ExecuteTemplate(&buf, name, data)
|
||||
err := htmlTmpl.ExecuteTemplate(&buf, name, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data["LayoutContent"] = template.HTML(buf.String())
|
||||
err = internal.HTMLTemplate.ExecuteTemplate(c.Writer, "adminLayout.html", data)
|
||||
data["LayoutContent"] = htemplate.HTML(buf.String())
|
||||
err = htmlTmpl.ExecuteTemplate(c.Writer, "adminLayout.html", data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package pages
|
||||
// Package page provides ...
|
||||
package page
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
htemplate "html/template"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
@@ -12,10 +13,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/config"
|
||||
"github.com/eiblog/eiblog/cmd/eiblog/handler/internal"
|
||||
pconfig "github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/third/disqus"
|
||||
"github.com/eiblog/eiblog/pkg/cache"
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/internal"
|
||||
"github.com/eiblog/eiblog/tools"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -28,22 +28,21 @@ func baseFEParams(c *gin.Context) gin.H {
|
||||
|
||||
cookie, err := c.Request.Cookie("v")
|
||||
if err != nil || cookie.Value !=
|
||||
fmt.Sprint(config.Conf.StaticVersion) {
|
||||
version = config.Conf.StaticVersion
|
||||
fmt.Sprint(config.Conf.EiBlogApp.StaticVersion) {
|
||||
version = config.Conf.EiBlogApp.StaticVersion
|
||||
}
|
||||
return gin.H{
|
||||
"BlogName": internal.Ei.Blogger.BlogName,
|
||||
"SubTitle": internal.Ei.Blogger.SubTitle,
|
||||
"BTitle": internal.Ei.Blogger.BTitle,
|
||||
"BeiAn": internal.Ei.Blogger.BeiAn,
|
||||
"Domain": config.Conf.Host,
|
||||
"BlogName": cache.Ei.Blogger.BlogName,
|
||||
"SubTitle": cache.Ei.Blogger.SubTitle,
|
||||
"BTitle": cache.Ei.Blogger.BTitle,
|
||||
"BeiAn": cache.Ei.Blogger.BeiAn,
|
||||
"Domain": config.Conf.EiBlogApp.Host,
|
||||
"CopyYear": time.Now().Year(),
|
||||
"Twitter": config.Conf.Twitter,
|
||||
"Qiniu": config.Conf.Qiniu,
|
||||
"Disqus": config.Conf.Disqus,
|
||||
"AdSense": config.Conf.Google.AdSense,
|
||||
"Twitter": config.Conf.EiBlogApp.Twitter,
|
||||
"Qiniu": config.Conf.EiBlogApp.Qiniu,
|
||||
"Disqus": config.Conf.EiBlogApp.Disqus,
|
||||
"AdSense": config.Conf.EiBlogApp.Google.AdSense,
|
||||
"Version": version,
|
||||
"Pages": config.Conf.Pages,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,16 +59,16 @@ func handleNotFound(c *gin.Context) {
|
||||
// handleHomePage 首页
|
||||
func handleHomePage(c *gin.Context) {
|
||||
params := baseFEParams(c)
|
||||
params["Title"] = internal.Ei.Blogger.BTitle + " | " + internal.Ei.Blogger.SubTitle
|
||||
params["Description"] = "博客首页," + internal.Ei.Blogger.SubTitle
|
||||
params["Title"] = cache.Ei.Blogger.BTitle + " | " + cache.Ei.Blogger.SubTitle
|
||||
params["Description"] = "博客首页," + cache.Ei.Blogger.SubTitle
|
||||
params["Path"] = c.Request.URL.Path
|
||||
params["CurrentPage"] = "blog-home"
|
||||
pn, err := strconv.Atoi(c.Query("pn"))
|
||||
if err != nil || pn < 1 {
|
||||
pn = 1
|
||||
}
|
||||
params["Prev"], params["Next"], params["List"] = internal.Ei.PageArticleFE(pn,
|
||||
config.Conf.General.PageNum)
|
||||
params["Prev"], params["Next"], params["List"] = cache.Ei.PageArticleFE(pn,
|
||||
config.Conf.EiBlogApp.General.PageNum)
|
||||
|
||||
renderHTMLHomeLayout(c, "home", params)
|
||||
}
|
||||
@@ -77,13 +76,13 @@ func handleHomePage(c *gin.Context) {
|
||||
// handleArticlePage 文章页
|
||||
func handleArticlePage(c *gin.Context) {
|
||||
slug := c.Param("slug")
|
||||
if !strings.HasSuffix(slug, ".html") || internal.Ei.ArticlesMap[slug[:len(slug)-5]] == nil {
|
||||
if !strings.HasSuffix(slug, ".html") || cache.Ei.ArticlesMap[slug[:len(slug)-5]] == nil {
|
||||
handleNotFound(c)
|
||||
return
|
||||
}
|
||||
article := internal.Ei.ArticlesMap[slug[:len(slug)-5]]
|
||||
article := cache.Ei.ArticlesMap[slug[:len(slug)-5]]
|
||||
params := baseFEParams(c)
|
||||
params["Title"] = article.Title + " | " + internal.Ei.Blogger.BTitle
|
||||
params["Title"] = article.Title + " | " + cache.Ei.Blogger.BTitle
|
||||
params["Path"] = c.Request.URL.Path
|
||||
params["CurrentPage"] = "post-" + article.Slug
|
||||
params["Article"] = article
|
||||
@@ -92,21 +91,21 @@ func handleArticlePage(c *gin.Context) {
|
||||
switch slug {
|
||||
case "blogroll.html":
|
||||
name = "blogroll"
|
||||
params["Description"] = "友情连接," + internal.Ei.Blogger.SubTitle
|
||||
params["Description"] = "友情连接," + cache.Ei.Blogger.SubTitle
|
||||
case "about.html":
|
||||
name = "about"
|
||||
params["Description"] = "关于作者," + internal.Ei.Blogger.SubTitle
|
||||
params["Description"] = "关于作者," + cache.Ei.Blogger.SubTitle
|
||||
default:
|
||||
params["Description"] = article.Desc + "," + internal.Ei.Blogger.SubTitle
|
||||
params["Description"] = article.Desc + "," + cache.Ei.Blogger.SubTitle
|
||||
name = "article"
|
||||
params["Copyright"] = internal.Ei.Blogger.Copyright
|
||||
params["Copyright"] = cache.Ei.Blogger.Copyright
|
||||
if !article.UpdatedAt.IsZero() {
|
||||
params["Days"] = int(time.Since(article.UpdatedAt).Hours()) / 24
|
||||
params["Days"] = int(time.Now().Sub(article.UpdatedAt).Hours()) / 24
|
||||
} else {
|
||||
params["Days"] = int(time.Since(article.CreatedAt).Hours()) / 24
|
||||
params["Days"] = int(time.Now().Sub(article.CreatedAt).Hours()) / 24
|
||||
}
|
||||
if article.SerieID > 0 {
|
||||
for _, series := range internal.Ei.Series {
|
||||
for _, series := range cache.Ei.Series {
|
||||
if series.ID == article.SerieID {
|
||||
params["Serie"] = series
|
||||
}
|
||||
@@ -119,65 +118,65 @@ func handleArticlePage(c *gin.Context) {
|
||||
// handleSeriesPage 专题页
|
||||
func handleSeriesPage(c *gin.Context) {
|
||||
params := baseFEParams(c)
|
||||
params["Title"] = "专题 | " + internal.Ei.Blogger.BTitle
|
||||
params["Description"] = "专题列表," + internal.Ei.Blogger.SubTitle
|
||||
params["Title"] = "专题 | " + cache.Ei.Blogger.BTitle
|
||||
params["Description"] = "专题列表," + cache.Ei.Blogger.SubTitle
|
||||
params["Path"] = c.Request.URL.Path
|
||||
params["CurrentPage"] = "series"
|
||||
params["Article"] = internal.Ei.PageSeries
|
||||
params["Article"] = cache.Ei.PageSeries
|
||||
renderHTMLHomeLayout(c, "series", params)
|
||||
}
|
||||
|
||||
// handleArchivePage 归档页
|
||||
func handleArchivePage(c *gin.Context) {
|
||||
params := baseFEParams(c)
|
||||
params["Title"] = "归档 | " + internal.Ei.Blogger.BTitle
|
||||
params["Description"] = "博客归档," + internal.Ei.Blogger.SubTitle
|
||||
params["Title"] = "归档 | " + cache.Ei.Blogger.BTitle
|
||||
params["Description"] = "博客归档," + cache.Ei.Blogger.SubTitle
|
||||
params["Path"] = c.Request.URL.Path
|
||||
params["CurrentPage"] = "archives"
|
||||
params["Article"] = internal.Ei.PageArchives
|
||||
params["Article"] = cache.Ei.PageArchives
|
||||
renderHTMLHomeLayout(c, "archives", params)
|
||||
}
|
||||
|
||||
// handleSearchPage 搜索页
|
||||
func handleSearchPage(c *gin.Context) {
|
||||
params := baseFEParams(c)
|
||||
params["Title"] = "站内搜索 | " + internal.Ei.Blogger.BTitle
|
||||
params["Description"] = "站内搜索," + internal.Ei.Blogger.SubTitle
|
||||
params["Title"] = "站内搜索 | " + cache.Ei.Blogger.BTitle
|
||||
params["Description"] = "站内搜索," + cache.Ei.Blogger.SubTitle
|
||||
params["Path"] = ""
|
||||
params["CurrentPage"] = "search-post"
|
||||
q := strings.TrimSpace(c.Query("q"))
|
||||
params["Word"] = q
|
||||
|
||||
if q != "" && internal.ESClient != nil {
|
||||
q := strings.TrimSpace(c.Query("q"))
|
||||
if q != "" {
|
||||
start, err := strconv.Atoi(c.Query("start"))
|
||||
if start < 1 || err != nil {
|
||||
start = 1
|
||||
}
|
||||
params["Word"] = q
|
||||
|
||||
vals := c.Request.URL.Query()
|
||||
result, err := internal.ESClient.ElasticSearch(q, config.Conf.General.PageNum, start-1)
|
||||
result, err := internal.ElasticSearch(q, config.Conf.EiBlogApp.General.PageNum, start-1)
|
||||
if err != nil {
|
||||
logrus.Error("HandleSearchPage.ElasticSearch: ", err)
|
||||
} else {
|
||||
result.Took /= 1000
|
||||
for i, v := range result.Hits.Hits {
|
||||
article := internal.Ei.ArticlesMap[v.Source.Slug]
|
||||
article := cache.Ei.ArticlesMap[v.Source.Slug]
|
||||
if len(v.Highlight.Content) == 0 && article != nil {
|
||||
result.Hits.Hits[i].Highlight.Content = []string{article.Excerpt}
|
||||
}
|
||||
}
|
||||
params["SearchResult"] = result
|
||||
if num := start - config.Conf.General.PageNum; num > 0 {
|
||||
if num := start - config.Conf.EiBlogApp.General.PageNum; num > 0 {
|
||||
vals.Set("start", fmt.Sprint(num))
|
||||
params["Prev"] = vals.Encode()
|
||||
}
|
||||
if num := start + config.Conf.General.PageNum; result.Hits.Total >= num {
|
||||
if num := start + config.Conf.EiBlogApp.General.PageNum; result.Hits.Total >= num {
|
||||
vals.Set("start", fmt.Sprint(num))
|
||||
params["Next"] = vals.Encode()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params["HotWords"] = config.Conf.HotWords
|
||||
params["HotWords"] = config.Conf.EiBlogApp.HotWords
|
||||
}
|
||||
renderHTMLHomeLayout(c, "search", params)
|
||||
}
|
||||
@@ -201,11 +200,11 @@ func handleDisqusList(c *gin.Context) {
|
||||
|
||||
slug := c.Param("slug")
|
||||
cursor := c.Query("cursor")
|
||||
artc := internal.Ei.ArticlesMap[slug]
|
||||
artc := cache.Ei.ArticlesMap[slug]
|
||||
if artc != nil {
|
||||
dcs.Data.Thread = artc.Thread
|
||||
}
|
||||
postsList, err := internal.DisqusClient.PostsList(artc, cursor)
|
||||
postsList, err := internal.PostsList(artc, cursor)
|
||||
if err != nil {
|
||||
logrus.Error("hadnleDisqusList.PostsList: ", err)
|
||||
dcs.ErrNo = 0
|
||||
@@ -237,10 +236,10 @@ func handleDisqusList(c *gin.Context) {
|
||||
if artc != nil && artc.Thread == "" {
|
||||
if dcs.Data.Thread != "" {
|
||||
artc.Thread = dcs.Data.Thread
|
||||
} else if internal.DisqusClient.ThreadDetails(artc) == nil {
|
||||
} else if internal.ThreadDetails(artc) == nil {
|
||||
dcs.Data.Thread = artc.Thread
|
||||
}
|
||||
internal.Store.UpdateArticle(context.Background(), artc.ID,
|
||||
cache.Ei.UpdateArticle(context.Background(), artc.ID,
|
||||
map[string]interface{}{
|
||||
"thread": artc.Thread,
|
||||
})
|
||||
@@ -254,9 +253,9 @@ func handleDisqusPage(c *gin.Context) {
|
||||
c.String(http.StatusOK, "出错啦。。。")
|
||||
return
|
||||
}
|
||||
article := internal.Ei.ArticlesMap[array[0]]
|
||||
article := cache.Ei.ArticlesMap[array[0]]
|
||||
params := gin.H{
|
||||
"Title": "发表评论 | " + internal.Ei.Blogger.BTitle,
|
||||
"Title": "发表评论 | " + cache.Ei.Blogger.BTitle,
|
||||
"ATitle": article.Title,
|
||||
"Thread": array[1],
|
||||
"Slug": article.Slug,
|
||||
@@ -301,7 +300,7 @@ func handleDisqusCreate(c *gin.Context) {
|
||||
}
|
||||
logrus.Infof("email: %s comments: %s", email, thread)
|
||||
|
||||
comment := disqus.PostComment{
|
||||
comment := internal.PostComment{
|
||||
Message: msg,
|
||||
Parent: c.PostForm("parent"),
|
||||
Thread: thread,
|
||||
@@ -310,14 +309,14 @@ func handleDisqusCreate(c *gin.Context) {
|
||||
Identifier: identifier,
|
||||
IPAddress: c.ClientIP(),
|
||||
}
|
||||
postDetail, err := internal.DisqusClient.PostCreate(&comment)
|
||||
postDetail, err := internal.PostCreate(&comment)
|
||||
if err != nil {
|
||||
logrus.Error("handleDisqusCreate.PostCreate: ", err)
|
||||
resp.ErrNo = 1
|
||||
resp.ErrMsg = "提交评论失败,请重试"
|
||||
return
|
||||
}
|
||||
err = internal.DisqusClient.PostApprove(postDetail.Response.ID)
|
||||
err = internal.PostApprove(postDetail.Response.ID)
|
||||
if err != nil {
|
||||
logrus.Error("handleDisqusCreate.PostApprove: ", err)
|
||||
resp.ErrNo = 1
|
||||
@@ -342,8 +341,8 @@ func handleBeaconPage(c *gin.Context) {
|
||||
ua := c.Request.UserAgent()
|
||||
|
||||
vals := c.Request.URL.Query()
|
||||
vals.Set("v", config.Conf.Google.V)
|
||||
vals.Set("tid", config.Conf.Google.Tid)
|
||||
vals.Set("v", config.Conf.EiBlogApp.Google.V)
|
||||
vals.Set("tid", config.Conf.EiBlogApp.Google.Tid)
|
||||
cookie, _ := c.Cookie("u")
|
||||
vals.Set("cid", cookie)
|
||||
|
||||
@@ -355,7 +354,7 @@ func handleBeaconPage(c *gin.Context) {
|
||||
vals.Set("_p", fmt.Sprint(201226219+rand.Intn(499999999))) // random page load hash
|
||||
vals.Set("_ee", "1") // external event
|
||||
go func() {
|
||||
url := config.Conf.Google.URL + "?" + vals.Encode()
|
||||
url := config.Conf.EiBlogApp.Google.URL + "?" + vals.Encode()
|
||||
req, err := http.NewRequest("POST", url, nil)
|
||||
if err != nil {
|
||||
logrus.Error("HandleBeaconPage.NewRequest: ", err)
|
||||
@@ -383,61 +382,24 @@ func handleBeaconPage(c *gin.Context) {
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// handleCustomPage 自定义页面
|
||||
func handleCustomPage(c *gin.Context) {
|
||||
path := c.Request.URL.Path
|
||||
if !strings.HasSuffix(path, ".html") {
|
||||
handleNotFound(c)
|
||||
return
|
||||
}
|
||||
// find config
|
||||
var page *pconfig.CustomPage
|
||||
for _, p := range config.Conf.Pages {
|
||||
if p.Path == path {
|
||||
page = &p
|
||||
break
|
||||
}
|
||||
}
|
||||
if page == nil {
|
||||
handleNotFound(c)
|
||||
return
|
||||
}
|
||||
var params gin.H
|
||||
if page.IsEmbed {
|
||||
params = baseFEParams(c)
|
||||
params["Path"] = c.Request.URL.Path
|
||||
params["Title"] = page.Name + " | " + internal.Ei.Blogger.SubTitle
|
||||
}
|
||||
// serve custom page
|
||||
name := c.Param("path")
|
||||
renderHTMLHomeLayout(c, name, params)
|
||||
}
|
||||
|
||||
// renderHTMLHomeLayout homelayout html
|
||||
func renderHTMLHomeLayout(c *gin.Context, name string, data gin.H) {
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
// special page
|
||||
switch {
|
||||
case name == "disqus.html":
|
||||
err := internal.HTMLTemplate.ExecuteTemplate(c.Writer, name, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
case data == nil:
|
||||
err := internal.HTMLTemplate.ExecuteTemplate(c.Writer, name, nil)
|
||||
if name == "disqus.html" {
|
||||
err := htmlTmpl.ExecuteTemplate(c.Writer, name, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
err := internal.HTMLTemplate.ExecuteTemplate(&buf, name, data)
|
||||
err := htmlTmpl.ExecuteTemplate(&buf, name, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data["LayoutContent"] = template.HTML(buf.String())
|
||||
err = internal.HTMLTemplate.ExecuteTemplate(c.Writer, "homeLayout.html", data)
|
||||
data["LayoutContent"] = htemplate.HTML(buf.String())
|
||||
err = htmlTmpl.ExecuteTemplate(c.Writer, "homeLayout.html", data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -1,12 +1,38 @@
|
||||
package pages
|
||||
// Package page provides ...
|
||||
package page
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/tools"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterRoutesCustomPages 注册自定义页面
|
||||
func RegisterRoutesCustomPages(e *gin.Engine) {
|
||||
e.GET("/page/:path", handleCustomPage)
|
||||
// htmlTmpl html template cache
|
||||
var htmlTmpl *template.Template
|
||||
|
||||
func init() {
|
||||
htmlTmpl = template.New("eiblog").Funcs(tools.TplFuncMap)
|
||||
root := filepath.Join(config.WorkDir, "website")
|
||||
files := tools.ReadDirFiles(root, func(fi fs.DirEntry) bool {
|
||||
name := fi.Name()
|
||||
if name == ".DS_Store" {
|
||||
return true
|
||||
}
|
||||
// should not read template dir
|
||||
if fi.IsDir() && name == "template" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
_, err := htmlTmpl.ParseFiles(files...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes register routes
|
||||
@@ -1,7 +1,8 @@
|
||||
// Package swag provides ...
|
||||
package swag
|
||||
|
||||
import (
|
||||
_ "github.com/eiblog/eiblog/cmd/eiblog/docs" // docs
|
||||
_ "github.com/eiblog/eiblog/pkg/core/eiblog/docs" // docs
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
10
pkg/internal/CHANGELOG.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# 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.
|
||||
|
||||
### [2.2.17](https://github.com/eiblog/eiblog/compare/v2.2.16...v2.2.17) (2025-04-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* backup file auto delete ([0fe849a](https://github.com/eiblog/eiblog/commit/0fe849ae67de36f2d249e3306ac7d098bc057070))
|
||||
@@ -1,4 +1,5 @@
|
||||
package disqus
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -11,7 +12,6 @@ import (
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/model"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// disqus api
|
||||
@@ -26,19 +26,13 @@ const (
|
||||
disqusAPIKey = "E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F"
|
||||
)
|
||||
|
||||
// DisqusClient disqus client
|
||||
type DisqusClient struct {
|
||||
Host string
|
||||
|
||||
Conf config.Disqus
|
||||
}
|
||||
|
||||
// NewDisqusClient new disqus client
|
||||
func NewDisqusClient(host string, conf config.Disqus) (*DisqusClient, error) {
|
||||
if conf.ShortName == "" || conf.PublicKey == "" || conf.AccessToken == "" {
|
||||
return nil, errors.New("disqus: config incompleted")
|
||||
func checkDisqusConfig() error {
|
||||
if config.Conf.EiBlogApp.Disqus.ShortName != "" &&
|
||||
config.Conf.EiBlogApp.Disqus.PublicKey != "" &&
|
||||
config.Conf.EiBlogApp.Disqus.AccessToken != "" {
|
||||
return nil
|
||||
}
|
||||
return &DisqusClient{Host: host, Conf: conf}, nil
|
||||
return errors.New("disqus: config incompleted")
|
||||
}
|
||||
|
||||
// postsCountResp 评论数量响应
|
||||
@@ -52,10 +46,14 @@ type postsCountResp struct {
|
||||
}
|
||||
|
||||
// PostsCount 获取文章评论数量
|
||||
func (cli *DisqusClient) PostsCount(articles map[string]*model.Article) error {
|
||||
func PostsCount(articles map[string]*model.Article) error {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", cli.Conf.PublicKey)
|
||||
vals.Set("forum", cli.Conf.ShortName)
|
||||
vals.Set("api_key", config.Conf.EiBlogApp.Disqus.PublicKey)
|
||||
vals.Set("forum", config.Conf.EiBlogApp.Disqus.ShortName)
|
||||
// batch get
|
||||
var count, index int
|
||||
for _, article := range articles {
|
||||
@@ -67,7 +65,7 @@ func (cli *DisqusClient) PostsCount(articles map[string]*model.Article) error {
|
||||
continue
|
||||
}
|
||||
count = 0
|
||||
resp, err := http.DefaultClient.Get(apiPostsCount + "?" + vals.Encode())
|
||||
resp, err := httpGet(apiPostsCount + "?" + vals.Encode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -129,16 +127,20 @@ type postDetail struct {
|
||||
}
|
||||
|
||||
// PostsList 评论列表
|
||||
func (cli *DisqusClient) PostsList(article *model.Article, cursor string) (*PostsListResp, error) {
|
||||
func PostsList(article *model.Article, cursor string) (*PostsListResp, error) {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", disqusAPIKey)
|
||||
vals.Set("forum", cli.Conf.ShortName)
|
||||
vals.Set("forum", config.Conf.EiBlogApp.Disqus.ShortName)
|
||||
vals.Set("thread:ident", "post-"+article.Slug)
|
||||
vals.Set("cursor", cursor)
|
||||
vals.Set("order", "popular")
|
||||
vals.Set("limit", "50")
|
||||
|
||||
resp, err := http.DefaultClient.Get(apiPostsList + "?" + vals.Encode())
|
||||
resp, err := httpGet(apiPostsList + "?" + vals.Encode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -179,11 +181,10 @@ type PostCreateResp struct {
|
||||
}
|
||||
|
||||
// PostCreate 评论文章
|
||||
func (cli *DisqusClient) PostCreate(pc *PostComment) (*PostCreateResp, error) {
|
||||
if cli == nil {
|
||||
return nil, errors.New("disqus client is nil, please init")
|
||||
func PostCreate(pc *PostComment) (*PostCreateResp, error) {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", disqusAPIKey)
|
||||
vals.Set("message", pc.Message)
|
||||
@@ -193,14 +194,8 @@ func (cli *DisqusClient) PostCreate(pc *PostComment) (*PostCreateResp, error) {
|
||||
vals.Set("author_name", pc.AuthorName)
|
||||
// vals.Set("state", "approved")
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, apiPostCreate, strings.NewReader(vals.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Referer", "https://disqus.com")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
header := http.Header{"Referer": {"https://disqus.com"}}
|
||||
resp, err := httpPostHeader(apiPostCreate, vals, header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -230,24 +225,18 @@ type approvedResp struct {
|
||||
}
|
||||
|
||||
// PostApprove 批准评论
|
||||
func (cli *DisqusClient) PostApprove(post string) error {
|
||||
if cli == nil {
|
||||
logrus.Warnf("disqus client is nil, please init")
|
||||
return nil
|
||||
func PostApprove(post string) error {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", disqusAPIKey)
|
||||
vals.Set("access_token", cli.Conf.AccessToken)
|
||||
vals.Set("api_key", config.Conf.EiBlogApp.Disqus.PublicKey)
|
||||
vals.Set("access_token", config.Conf.EiBlogApp.Disqus.AccessToken)
|
||||
vals.Set("post", post)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, apiPostApprove, strings.NewReader(vals.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Referer", "https://disqus.com")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
header := http.Header{"Referer": {"https://disqus.com"}}
|
||||
resp, err := httpPostHeader(apiPostApprove, vals, header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -275,32 +264,26 @@ type threadCreateResp struct {
|
||||
}
|
||||
|
||||
// ThreadCreate 创建thread
|
||||
func (cli *DisqusClient) ThreadCreate(article *model.Article, btitle string) error {
|
||||
if cli == nil {
|
||||
return errors.New("disqus client is nil, please init")
|
||||
func ThreadCreate(article *model.Article, btitle string) error {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", disqusAPIKey)
|
||||
vals.Set("access_token", cli.Conf.AccessToken)
|
||||
vals.Set("forum", cli.Conf.ShortName)
|
||||
vals.Set("api_key", config.Conf.EiBlogApp.Disqus.PublicKey)
|
||||
vals.Set("access_token", config.Conf.EiBlogApp.Disqus.AccessToken)
|
||||
vals.Set("forum", config.Conf.EiBlogApp.Disqus.ShortName)
|
||||
vals.Set("title", article.Title+" | "+btitle)
|
||||
vals.Set("identifier", "post-"+article.Slug)
|
||||
|
||||
urlPath := fmt.Sprintf("https://%s/post/%s.html", cli.Host, article.Slug)
|
||||
urlPath := fmt.Sprintf("https://%s/post/%s.html", config.Conf.EiBlogApp.Host, article.Slug)
|
||||
vals.Set("url", urlPath)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, apiThreadCreate, strings.NewReader(vals.Encode()))
|
||||
resp, err := httpPost(apiThreadCreate, vals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Referer", "https://disqus.com")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -328,18 +311,18 @@ type threadDetailsResp struct {
|
||||
}
|
||||
|
||||
// ThreadDetails thread详细
|
||||
func (cli *DisqusClient) ThreadDetails(article *model.Article) error {
|
||||
if cli == nil {
|
||||
return errors.New("disqus client is nil, please init")
|
||||
func ThreadDetails(article *model.Article) error {
|
||||
if err := checkDisqusConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("api_key", disqusAPIKey)
|
||||
vals.Set("access_token", cli.Conf.AccessToken)
|
||||
vals.Set("forum", cli.Conf.ShortName)
|
||||
vals.Set("api_key", config.Conf.EiBlogApp.Disqus.PublicKey)
|
||||
vals.Set("access_token", config.Conf.EiBlogApp.Disqus.AccessToken)
|
||||
vals.Set("forum", config.Conf.EiBlogApp.Disqus.ShortName)
|
||||
vals.Set("thread:ident", "post-"+article.Slug)
|
||||
|
||||
resp, err := http.DefaultClient.Get(apiThreadDetails + "?" + vals.Encode())
|
||||
resp, err := httpGet(apiThreadDetails + "?" + vals.Encode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
package es
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eiblog/eiblog/pkg/config"
|
||||
"github.com/eiblog/eiblog/pkg/model"
|
||||
"github.com/eiblog/eiblog/tools"
|
||||
|
||||
@@ -27,26 +29,30 @@ const (
|
||||
ElasticType = "article"
|
||||
)
|
||||
|
||||
// ESClient es client
|
||||
type ESClient struct {
|
||||
Host string
|
||||
func init() {
|
||||
if checkESConfig() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mappings := fmt.Sprintf(`{"mappings":{"%s":{"properties":{"content":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"},"date":{"index":"not_analyzed","type":"date"},"slug":{"type":"string"},"tag":{"index":"not_analyzed","type":"string"},"title":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"}}}}}`, "article")
|
||||
err := createIndexAndMappings(ElasticIndex, ElasticType, []byte(mappings))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// NewESClient new es client
|
||||
func NewESClient(host string) (*ESClient, error) {
|
||||
if host == "" {
|
||||
return nil, errors.New("es: elasticsearch host is empty")
|
||||
func checkESConfig() error {
|
||||
if config.Conf.ESHost == "" {
|
||||
return errors.New("es: elasticsearch not config")
|
||||
}
|
||||
es := &ESClient{Host: host}
|
||||
err := es.createIndexAndMappings(ElasticIndex, ElasticType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// ElasticSearch 搜索文章
|
||||
func (cli *ESClient) ElasticSearch(query string, size, from int) (*SearchIndexResult, error) {
|
||||
func ElasticSearch(query string, size, from int) (*SearchIndexResult, error) {
|
||||
if err := checkESConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 分析查询
|
||||
var (
|
||||
regTerm = regexp.MustCompile(`(tag|slug|date):`)
|
||||
@@ -94,13 +100,17 @@ func (cli *ESClient) ElasticSearch(query string, size, from int) (*SearchIndexRe
|
||||
// 判断是否为空,判断搜索方式
|
||||
dsl := fmt.Sprintf("{"+SearchFilter+"}", strings.Join(filter, ","))
|
||||
if kw != "" {
|
||||
dsl = strings.ReplaceAll(strings.ReplaceAll(`{"highlight":{"fields":{"content":{},"title":{}},"post_tags":["\u003c/b\u003e"],"pre_tags":["\u003cb\u003e"]},"query":{"dis_max":{"queries":[{"match":{"title":{"boost":4,"minimum_should_match":"50%","query":"$1"}}},{"match":{"content":{"boost":4,"minimum_should_match":"75%","query":"$1"}}},{"match":{"tag":{"boost":2,"minimum_should_match":"100%","query":"$1"}}},{"match":{"slug":{"boost":1,"minimum_should_match":"100%","query":"$1"}}}],"tie_breaker":0.3}},$2}`, "$1", kw), "$2", fmt.Sprintf(SearchFilter, strings.Join(filter, ",")))
|
||||
dsl = strings.Replace(strings.Replace(`{"highlight":{"fields":{"content":{},"title":{}},"post_tags":["\u003c/b\u003e"],"pre_tags":["\u003cb\u003e"]},"query":{"dis_max":{"queries":[{"match":{"title":{"boost":4,"minimum_should_match":"50%","query":"$1"}}},{"match":{"content":{"boost":4,"minimum_should_match":"75%","query":"$1"}}},{"match":{"tag":{"boost":2,"minimum_should_match":"100%","query":"$1"}}},{"match":{"slug":{"boost":1,"minimum_should_match":"100%","query":"$1"}}}],"tie_breaker":0.3}},$2}`, "$1", kw, -1), "$2", fmt.Sprintf(SearchFilter, strings.Join(filter, ",")), -1)
|
||||
}
|
||||
return cli.indexQueryDSL(ElasticIndex, ElasticType, size, from, []byte(dsl))
|
||||
return indexQueryDSL(ElasticIndex, ElasticType, size, from, []byte(dsl))
|
||||
}
|
||||
|
||||
// ElasticAddIndex 添加或更新索引
|
||||
func (cli *ESClient) ElasticAddIndex(article *model.Article) error {
|
||||
func ElasticAddIndex(article *model.Article) error {
|
||||
if err := checkESConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
img := tools.PickFirstImage(article.Content)
|
||||
mapping := map[string]interface{}{
|
||||
"title": article.Title,
|
||||
@@ -111,16 +121,20 @@ func (cli *ESClient) ElasticAddIndex(article *model.Article) error {
|
||||
"date": article.CreatedAt,
|
||||
}
|
||||
data, _ := json.Marshal(mapping)
|
||||
return cli.indexOrUpdateDocument(ElasticIndex, ElasticType, article.ID, data)
|
||||
return indexOrUpdateDocument(ElasticIndex, ElasticType, article.ID, data)
|
||||
}
|
||||
|
||||
// ElasticDelIndex 删除索引
|
||||
func (cli *ESClient) ElasticDelIndex(ids []int) error {
|
||||
func ElasticDelIndex(ids []int) error {
|
||||
if err := checkESConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var target []string
|
||||
for _, id := range ids {
|
||||
target = append(target, fmt.Sprint(id))
|
||||
}
|
||||
return cli.deleteIndexDocument(ElasticIndex, ElasticType, target)
|
||||
return deleteIndexDocument(ElasticIndex, ElasticType, target)
|
||||
}
|
||||
|
||||
// indicesCreateResult 索引创建结果
|
||||
@@ -129,11 +143,9 @@ type indicesCreateResult struct {
|
||||
}
|
||||
|
||||
// createIndexAndMappings 创建索引和映射关系
|
||||
func (cli *ESClient) createIndexAndMappings(index, typ string) error {
|
||||
mappings := fmt.Sprintf(`{"mappings":{"%s":{"properties":{"content":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"},"date":{"index":"not_analyzed","type":"date"},"slug":{"type":"string"},"tag":{"index":"not_analyzed","type":"string"},"title":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"}}}}}`, typ)
|
||||
|
||||
rawurl := fmt.Sprintf("%s/%s/%s", cli.Host, index, typ)
|
||||
resp, err := http.DefaultClient.Head(rawurl)
|
||||
func createIndexAndMappings(index, typ string, mappings []byte) error {
|
||||
rawurl := fmt.Sprintf("%s/%s/%s", config.Conf.ESHost, index, typ)
|
||||
resp, err := httpHead(rawurl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,13 +154,8 @@ func (cli *ESClient) createIndexAndMappings(index, typ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawurl = fmt.Sprintf("%s/%s", cli.Host, index)
|
||||
req, err := http.NewRequest(http.MethodPut, rawurl, bytes.NewReader([]byte(mappings)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
rawurl = fmt.Sprintf("%s/%s", config.Conf.ESHost, index)
|
||||
resp, err = httpPut(rawurl, mappings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -169,15 +176,9 @@ func (cli *ESClient) createIndexAndMappings(index, typ string) error {
|
||||
}
|
||||
|
||||
// indexOrUpdateDocument 创建或更新索引
|
||||
func (cli *ESClient) indexOrUpdateDocument(index, typ string, id int, doc []byte) (err error) {
|
||||
rawurl := fmt.Sprintf("%s/%s/%s/%d", cli.Host, index, typ, id)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, rawurl, bytes.NewReader(doc))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
func indexOrUpdateDocument(index, typ string, id int, doc []byte) (err error) {
|
||||
rawurl := fmt.Sprintf("%s/%s/%s/%d", config.Conf.ESHost, index, typ, id)
|
||||
resp, err := httpPut(rawurl, doc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -204,7 +205,7 @@ type deleteIndexResult struct {
|
||||
}
|
||||
|
||||
// deleteIndexDocument 删除文档
|
||||
func (cli *ESClient) deleteIndexDocument(index, typ string, ids []string) error {
|
||||
func deleteIndexDocument(index, typ string, ids []string) error {
|
||||
buf := bytes.Buffer{}
|
||||
for _, id := range ids {
|
||||
dd := deleteIndexReq{Index: index, Type: typ, ID: id}
|
||||
@@ -213,13 +214,8 @@ func (cli *ESClient) deleteIndexDocument(index, typ string, ids []string) error
|
||||
buf.Write(b)
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
rawurl := fmt.Sprintf("%s/_bulk", cli.Host)
|
||||
req, err := http.NewRequest(http.MethodPost, rawurl, bytes.NewReader(buf.Bytes()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
rawurl := fmt.Sprintf("%s/_bulk", config.Conf.ESHost)
|
||||
resp, err := httpPost(rawurl, buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -268,10 +264,10 @@ type SearchIndexResult struct {
|
||||
}
|
||||
|
||||
// indexQueryDSL 语句查询文档
|
||||
func (c *ESClient) indexQueryDSL(index, typ string, size, from int, dsl []byte) (*SearchIndexResult, error) {
|
||||
rawurl := fmt.Sprintf("%s/%s/%s/_search?size=%d&from=%d", c.Host,
|
||||
func indexQueryDSL(index, typ string, size, from int, dsl []byte) (*SearchIndexResult, error) {
|
||||
rawurl := fmt.Sprintf("%s/%s/%s/_search?size=%d&from=%d", config.Conf.ESHost,
|
||||
index, typ, size, from)
|
||||
resp, err := http.Post(rawurl, "application/json", bytes.NewReader(dsl))
|
||||
resp, err := httpPost(rawurl, dsl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
139
pkg/internal/http.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func newRequest(method, rawurl string, data interface{}) (*http.Request, error) {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
originHost := u.Host
|
||||
// 获取主机IP
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
addrErr := err.(*net.AddrError)
|
||||
if addrErr.Err != "missing port in address" {
|
||||
return nil, err
|
||||
}
|
||||
// set default value
|
||||
host = originHost
|
||||
switch u.Scheme {
|
||||
case "http":
|
||||
port = "80"
|
||||
case "https":
|
||||
port = "443"
|
||||
}
|
||||
}
|
||||
ips, err := net.LookupHost(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("http: not found ip(%s)", u.Host)
|
||||
}
|
||||
host = net.JoinHostPort(ips[0], port)
|
||||
u.Host = host
|
||||
// 创建HTTP Request
|
||||
var req *http.Request
|
||||
switch raw := data.(type) {
|
||||
case url.Values:
|
||||
req, err = http.NewRequest(method, u.String(),
|
||||
strings.NewReader(raw.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
case []byte:
|
||||
req, err = http.NewRequest(method, u.String(),
|
||||
bytes.NewReader(raw))
|
||||
case nil:
|
||||
req, err = http.NewRequest(method, u.String(), nil)
|
||||
default:
|
||||
return nil, fmt.Errorf("http: unsupported data type: %T", data)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 设置Host
|
||||
req.Host = originHost
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// httpHead HTTP HEAD请求
|
||||
func httpHead(rawurl string) (*http.Response, error) {
|
||||
req, err := newRequest(http.MethodHead, rawurl, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
// httpGet HTTP GET请求
|
||||
func httpGet(rawurl string) (*http.Response, error) {
|
||||
req, err := newRequest(http.MethodGet, rawurl, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 发起请求
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
// httpPost HTTP POST请求, 自动识别是否是form
|
||||
func httpPost(rawurl string, data interface{}) (*http.Response, error) {
|
||||
req, err := newRequest(http.MethodPost, rawurl, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 发起请求
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
// httpPostHeader HTTP POST请求,自定义Header
|
||||
func httpPostHeader(rawurl string, data interface{},
|
||||
header http.Header) (*http.Response, error) {
|
||||
|
||||
req, err := newRequest(http.MethodPost, rawurl, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set header
|
||||
req.Header = header
|
||||
// 发起请求
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
// httpPut HTTP PUT请求
|
||||
func httpPut(rawurl string, data interface{}) (*http.Response, error) {
|
||||
req, err := newRequest(http.MethodPut, rawurl, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 发起请求
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
package pinger
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -13,50 +14,29 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Pinger pinger
|
||||
type Pinger struct {
|
||||
Host string
|
||||
|
||||
Conf config.FeedRPC
|
||||
}
|
||||
|
||||
// NewPinger new pinger
|
||||
func NewPinger(host string, conf config.FeedRPC) (*Pinger, error) {
|
||||
if conf.FeedrURL == "" {
|
||||
return nil, fmt.Errorf("feedr url is empty")
|
||||
}
|
||||
return &Pinger{Host: host, Conf: conf}, nil
|
||||
}
|
||||
|
||||
// PingFunc ping blog article to SE
|
||||
func (p *Pinger) PingFunc(btitle, slug string) {
|
||||
err := p.feedrPingFunc(btitle, slug)
|
||||
if err != nil {
|
||||
logrus.Error("pinger: PingFunc feedr: ", err)
|
||||
}
|
||||
err = p.rpcPingFunc(btitle, slug)
|
||||
if err != nil {
|
||||
logrus.Error("pinger: PingFunc: rpc: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
// feedrPingFunc http://<your-hub-name>.superfeedr.com/
|
||||
func (p *Pinger) feedrPingFunc(btitle, slug string) error {
|
||||
var feedrPingFunc = func(btitle, slug string) error {
|
||||
feedrHost := config.Conf.EiBlogApp.FeedRPC.FeedrURL
|
||||
if feedrHost == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("hub.mode", "publish")
|
||||
vals.Add("hub.url", fmt.Sprintf("https://%s/post/%s.html", p.Host, slug))
|
||||
resp, err := http.DefaultClient.PostForm(p.Conf.FeedrURL, vals)
|
||||
vals.Add("hub.url", fmt.Sprintf("https://%s/post/%s.html",
|
||||
config.Conf.BackupApp.Host, slug))
|
||||
resp, err := httpPost(feedrHost, vals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 204 {
|
||||
return fmt.Errorf("pinger: status code: %d, %s", resp.StatusCode, string(data))
|
||||
return fmt.Errorf("pinger: status code: %d, %s",
|
||||
resp.StatusCode, string(data))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -81,13 +61,16 @@ type rpcValue struct {
|
||||
}
|
||||
|
||||
// rpcPingFunc ping rpc
|
||||
func (p *Pinger) rpcPingFunc(btitle, slug string) error {
|
||||
var rpcPingFunc = func(btitle, slug string) error {
|
||||
if len(config.Conf.EiBlogApp.FeedRPC.PingRPC) == 0 {
|
||||
return nil
|
||||
}
|
||||
param := rpcPingParam{MethodName: "weblogUpdates.extendedPing"}
|
||||
param.Params.Param = [4]rpcValue{
|
||||
0: {Value: btitle},
|
||||
1: {Value: "https://" + p.Host},
|
||||
2: {Value: fmt.Sprintf("https://%s/post/%s.html", p.Host, slug)},
|
||||
3: {Value: "https://" + p.Host + "/rss.html"},
|
||||
0: rpcValue{Value: btitle},
|
||||
1: rpcValue{Value: "https://" + config.Conf.EiBlogApp.Host},
|
||||
2: rpcValue{Value: fmt.Sprintf("https://%s/post/%s.html", config.Conf.EiBlogApp.Host, slug)},
|
||||
3: rpcValue{Value: "https://" + config.Conf.EiBlogApp.Host + "/rss.html"},
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString(xml.Header)
|
||||
@@ -98,15 +81,13 @@ func (p *Pinger) rpcPingFunc(btitle, slug string) error {
|
||||
data := buf.Bytes()
|
||||
header := http.Header{}
|
||||
header.Set("Content-Type", "text/xml")
|
||||
|
||||
for _, addr := range p.Conf.PingRPC {
|
||||
resp, err := http.DefaultClient.Post(addr, "text/xml", bytes.NewReader(data))
|
||||
for _, addr := range config.Conf.EiBlogApp.FeedRPC.PingRPC {
|
||||
resp, err := httpPostHeader(addr, data, header)
|
||||
if err != nil {
|
||||
logrus.Error("rpcPingFunc.httpPostHeader: ", err)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logrus.Error("rpcPingFunc.ReadAll: ", err)
|
||||
@@ -118,3 +99,15 @@ func (p *Pinger) rpcPingFunc(btitle, slug string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PingFunc ping blog article to SE
|
||||
func PingFunc(btitle, slug string) {
|
||||
err := feedrPingFunc(btitle, slug)
|
||||
if err != nil {
|
||||
logrus.Error("pinger: PingFunc feedr: ", err)
|
||||
}
|
||||
err = rpcPingFunc(btitle, slug)
|
||||
if err != nil {
|
||||
logrus.Error("pinger: PingFunc: rpc: ", err)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
package qiniu
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -14,49 +15,39 @@ import (
|
||||
"github.com/qiniu/go-sdk/v7/storage"
|
||||
)
|
||||
|
||||
// QiniuClient qiniu client
|
||||
type QiniuClient struct {
|
||||
Conf config.Qiniu
|
||||
}
|
||||
|
||||
// NewQiniuClient new qiniu client
|
||||
func NewQiniuClient(conf config.Qiniu) (*QiniuClient, error) {
|
||||
if conf.AccessKey == "" ||
|
||||
conf.SecretKey == "" ||
|
||||
conf.Bucket == "" ||
|
||||
conf.Domain == "" {
|
||||
return nil, errors.New("qiniu config error")
|
||||
}
|
||||
return &QiniuClient{Conf: conf}, nil
|
||||
}
|
||||
|
||||
// UploadParams upload params
|
||||
type UploadParams struct {
|
||||
Name string
|
||||
Size int64
|
||||
Data io.Reader
|
||||
NoCompletePath bool
|
||||
|
||||
Conf config.Qiniu
|
||||
}
|
||||
|
||||
// Upload 上传文件
|
||||
func (cli *QiniuClient) Upload(params UploadParams) (string, error) {
|
||||
// QiniuUpload 上传文件
|
||||
func QiniuUpload(params UploadParams) (string, error) {
|
||||
if params.Conf.AccessKey == "" ||
|
||||
params.Conf.SecretKey == "" {
|
||||
return "", errors.New("qiniu config error")
|
||||
}
|
||||
key := params.Name
|
||||
if !params.NoCompletePath {
|
||||
key = filepath.Base(params.Name)
|
||||
}
|
||||
|
||||
mac := qbox.NewMac(cli.Conf.AccessKey,
|
||||
cli.Conf.SecretKey)
|
||||
mac := qbox.NewMac(params.Conf.AccessKey,
|
||||
params.Conf.SecretKey)
|
||||
// 设置上传策略
|
||||
putPolicy := &storage.PutPolicy{
|
||||
Scope: cli.Conf.Bucket,
|
||||
Scope: params.Conf.Bucket,
|
||||
Expires: 3600,
|
||||
InsertOnly: 1,
|
||||
}
|
||||
// 上传token
|
||||
uploadToken := putPolicy.UploadToken(mac)
|
||||
// 上传配置
|
||||
region, err := storage.GetRegion(cli.Conf.AccessKey, cli.Conf.Bucket)
|
||||
region, err := storage.GetRegion(params.Conf.AccessKey, params.Conf.Bucket)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -74,7 +65,7 @@ func (cli *QiniuClient) Upload(params UploadParams) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
url := "https://" + cli.Conf.Domain + "/" + key
|
||||
url := "https://" + params.Conf.Domain + "/" + key
|
||||
return url, nil
|
||||
}
|
||||
|
||||
@@ -83,19 +74,21 @@ type DeleteParams struct {
|
||||
Name string
|
||||
Days int
|
||||
NoCompletePath bool
|
||||
|
||||
Conf config.Qiniu
|
||||
}
|
||||
|
||||
// QiniuDelete 删除文件
|
||||
func (cli *QiniuClient) Delete(params DeleteParams) error {
|
||||
func QiniuDelete(params DeleteParams) error {
|
||||
key := params.Name
|
||||
if !params.NoCompletePath {
|
||||
key = completeQiniuKey(params.Name)
|
||||
}
|
||||
|
||||
mac := qbox.NewMac(cli.Conf.AccessKey,
|
||||
cli.Conf.SecretKey)
|
||||
mac := qbox.NewMac(params.Conf.AccessKey,
|
||||
params.Conf.SecretKey)
|
||||
// 上传配置
|
||||
region, err := storage.GetRegion(cli.Conf.AccessKey, cli.Conf.Bucket)
|
||||
region, err := storage.GetRegion(params.Conf.AccessKey, params.Conf.Bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -107,22 +100,24 @@ func (cli *QiniuClient) Delete(params DeleteParams) error {
|
||||
bucketManager := storage.NewBucketManager(mac, cfg)
|
||||
// Delete
|
||||
if params.Days > 0 {
|
||||
return bucketManager.DeleteAfterDays(cli.Conf.Bucket, key, params.Days)
|
||||
return bucketManager.DeleteAfterDays(params.Conf.Bucket, key, params.Days)
|
||||
}
|
||||
return bucketManager.Delete(cli.Conf.Bucket, key)
|
||||
return bucketManager.Delete(params.Conf.Bucket, key)
|
||||
}
|
||||
|
||||
// ContentParams list params
|
||||
type ContentParams struct {
|
||||
Prefix string
|
||||
|
||||
Conf config.Qiniu
|
||||
}
|
||||
|
||||
// Content 获取文件内容
|
||||
func (cli *QiniuClient) Content(params ContentParams) ([]byte, error) {
|
||||
mac := qbox.NewMac(cli.Conf.AccessKey,
|
||||
cli.Conf.SecretKey)
|
||||
// QiniuContent 获取文件列表
|
||||
func QiniuContent(params ContentParams) ([]byte, error) {
|
||||
mac := qbox.NewMac(params.Conf.AccessKey,
|
||||
params.Conf.SecretKey)
|
||||
// region
|
||||
region, err := storage.GetRegion(cli.Conf.AccessKey, cli.Conf.Bucket)
|
||||
region, err := storage.GetRegion(params.Conf.AccessKey, params.Conf.Bucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,7 +128,7 @@ func (cli *QiniuClient) Content(params ContentParams) ([]byte, error) {
|
||||
// manager
|
||||
bucketManager := storage.NewBucketManager(mac, cfg)
|
||||
// list file
|
||||
files, _, _, _, err := bucketManager.ListFiles(cli.Conf.Bucket, params.Prefix, "", "", 1)
|
||||
files, _, _, _, err := bucketManager.ListFiles(params.Conf.Bucket, params.Prefix, "", "", 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -141,7 +136,7 @@ func (cli *QiniuClient) Content(params ContentParams) ([]byte, error) {
|
||||
return nil, errors.New("no file")
|
||||
}
|
||||
deadline := time.Now().Add(time.Second * 60).Unix()
|
||||
url := storage.MakePrivateURLv2(mac, "https://"+cli.Conf.Domain, files[0].Key, deadline)
|
||||
url := storage.MakePrivateURLv2(mac, "https://"+params.Conf.Domain, files[0].Key, deadline)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -156,16 +151,27 @@ func completeQiniuKey(name string) string {
|
||||
ext := filepath.Ext(name)
|
||||
|
||||
switch ext {
|
||||
case ".bmp", ".png", ".jpg", ".gif", ".ico", ".jpeg":
|
||||
case ".bmp", ".png", ".jpg",
|
||||
".gif", ".ico", ".jpeg":
|
||||
|
||||
name = "blog/img/" + name
|
||||
case ".mov", ".mp4":
|
||||
name = "blog/video/" + name
|
||||
case ".go", ".js", ".css", ".cpp", ".php", ".rb", ".java",
|
||||
".py", ".sql", ".lua", ".html", ".sh", ".xml", ".cs":
|
||||
case ".go", ".js", ".css",
|
||||
".cpp", ".php", ".rb",
|
||||
".java", ".py", ".sql",
|
||||
".lua", ".html", ".sh",
|
||||
".xml", ".cs":
|
||||
|
||||
name = "blog/code/" + name
|
||||
case ".txt", ".md", ".ini", ".yaml", ".yml", ".doc", ".ppt", ".pdf":
|
||||
case ".txt", ".md", ".ini",
|
||||
".yaml", ".yml", ".doc",
|
||||
".ppt", ".pdf":
|
||||
|
||||
name = "blog/document/" + name
|
||||
case ".zip", ".rar", ".tar", ".gz":
|
||||
case ".zip", ".rar", ".tar",
|
||||
".gz":
|
||||
|
||||
name = "blog/archive/" + name
|
||||
default:
|
||||
name = "blog/other/" + name
|
||||
@@ -1,4 +1,5 @@
|
||||
package qiniu
|
||||
// Package internal provides ...
|
||||
package internal
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -9,16 +10,6 @@ import (
|
||||
)
|
||||
|
||||
func TestQiniuUpload(t *testing.T) {
|
||||
cli, err := NewQiniuClient(config.Qiniu{
|
||||
AccessKey: os.Getenv("QINIU_ACCESSKEY"),
|
||||
SecretKey: os.Getenv("QINIU_SECRETKEY"),
|
||||
Bucket: os.Getenv("QINIU_BUCKET"),
|
||||
Domain: os.Getenv("QINIU_DOMAIN"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("NewQiniuClient error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
f, _ := os.Open("qiniu_test.go")
|
||||
fi, _ := f.Stat()
|
||||
@@ -36,11 +27,17 @@ func TestQiniuUpload(t *testing.T) {
|
||||
Name: "test-" + time.Now().Format("200601021504059999") + ".go",
|
||||
Size: fi.Size(),
|
||||
Data: f,
|
||||
Conf: config.Qiniu{
|
||||
AccessKey: os.Getenv("QINIU_ACCESSKEY"),
|
||||
SecretKey: os.Getenv("QINIU_SECRETKEY"),
|
||||
Bucket: os.Getenv("QINIU_BUCKET"),
|
||||
Domain: "bu.st.deepzz.com",
|
||||
},
|
||||
}}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := cli.Upload(tt.args.params)
|
||||
got, err := QiniuUpload(tt.args.params)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("QiniuUpload() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
@@ -51,21 +48,15 @@ func TestQiniuUpload(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQiniuContent(t *testing.T) {
|
||||
cli, err := NewQiniuClient(config.Qiniu{
|
||||
AccessKey: os.Getenv("QINIU_ACCESSKEY"),
|
||||
SecretKey: os.Getenv("QINIU_SECRETKEY"),
|
||||
Bucket: os.Getenv("QINIU_BUCKET"),
|
||||
Domain: os.Getenv("QINIU_DOMAIN"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("NewQiniuClient error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
params := ContentParams{
|
||||
Prefix: "blog/",
|
||||
Conf: config.Qiniu{
|
||||
AccessKey: os.Getenv("QINIU_ACCESSKEY"),
|
||||
SecretKey: os.Getenv("QINIU_SECRETKEY"),
|
||||
Bucket: os.Getenv("QINIU_BUCKET"),
|
||||
Domain: "bu.st.deepzz.com",
|
||||
},
|
||||
}
|
||||
_, err = cli.Content(params)
|
||||
_, err := QiniuContent(params)
|
||||
if err != nil {
|
||||
t.Errorf("QiniuList error = %v", err)
|
||||
}
|
||||
55
pkg/mid/language.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Package mid provides ...
|
||||
package mid
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// LangOpts 语言选项
|
||||
type LangOpts struct {
|
||||
CookieName string
|
||||
Default string
|
||||
Supported []string
|
||||
}
|
||||
|
||||
// isExist language
|
||||
func (opts LangOpts) isExist(l string) bool {
|
||||
for _, v := range opts.Supported {
|
||||
if v == l {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LangMiddleware set language
|
||||
func LangMiddleware(opts LangOpts) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
lang, err := c.Cookie(opts.CookieName)
|
||||
// found cookie
|
||||
if err == nil {
|
||||
c.Set(opts.CookieName, lang)
|
||||
return
|
||||
}
|
||||
// set cookie
|
||||
al := strings.ToLower(c.GetHeader("Accept-Language"))
|
||||
if al != "" {
|
||||
// choose default if not supported
|
||||
lang = opts.Default
|
||||
|
||||
langs := strings.Split(al, ",")
|
||||
for _, v := range langs {
|
||||
if opts.isExist(v) {
|
||||
lang = v
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lang = opts.Default
|
||||
}
|
||||
c.SetCookie(opts.CookieName, lang, 86400*365, "/", "", false, false)
|
||||
c.Set(opts.CookieName, lang)
|
||||
}
|
||||
}
|
||||
34
pkg/mid/session.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Package mid provides ...
|
||||
package mid
|
||||
|
||||
import (
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SessionOpts 设置选项
|
||||
type SessionOpts struct {
|
||||
Name string
|
||||
Secure bool // required
|
||||
Secret []byte // required
|
||||
// redis store
|
||||
RedisAddr string
|
||||
RedisPwd string
|
||||
}
|
||||
|
||||
// SessionMiddleware session中间件
|
||||
func SessionMiddleware(opts SessionOpts) gin.HandlerFunc {
|
||||
store := cookie.NewStore(opts.Secret)
|
||||
store.Options(sessions.Options{
|
||||
MaxAge: 86400 * 30,
|
||||
Path: "/",
|
||||
Secure: opts.Secure,
|
||||
HttpOnly: true,
|
||||
})
|
||||
name := "SESSIONID"
|
||||
if opts.Name != "" {
|
||||
name = opts.Name
|
||||
}
|
||||
return sessions.Sessions(name, store)
|
||||
}
|
||||
18
pkg/mid/u.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Package mid provides ...
|
||||
package mid
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
// UserMiddleware 用户cookie标记
|
||||
func UserMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
cookie, err := c.Cookie("u")
|
||||
if err != nil || cookie == "" {
|
||||
u1 := uuid.NewV4().String()
|
||||
c.SetCookie("u", u1, 86400*730, "/", "", true, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,11 @@ import "time"
|
||||
|
||||
// Account 博客账户
|
||||
type Account struct {
|
||||
Username string `gorm:"column:username;primaryKey" bson:"username"` // 用户名
|
||||
Password string `gorm:"column:password;not null" bson:"password"` // 密码
|
||||
Email string `gorm:"column:email;not null" bson:"email"` // 邮件地址
|
||||
PhoneN string `gorm:"column:phone_n;not null" bson:"phone_n"` // 手机号
|
||||
Address string `gorm:"column:address;not null" bson:"address"` // 地址信息
|
||||
TwoFactorSecret string `gorm:"column:two_factor_secret" bson:"two_factor_secret"` // 两步验证密钥
|
||||
Username string `gorm:"column:username;primaryKey" bson:"username"` // 用户名
|
||||
Password string `gorm:"column:password;not null" bson:"password"` // 密码
|
||||
Email string `gorm:"column:email;not null" bson:"email"` // 邮件地址
|
||||
PhoneN string `gorm:"column:phone_n;not null" bson:"phone_n"` // 手机号
|
||||
Address string `gorm:"column:address;not null" bson:"address"` // 地址信息
|
||||
|
||||
LogoutAt time.Time `gorm:"column:logout_at;not null" bson:"logout_at"` // 登出时间
|
||||
LoginIP string `gorm:"column:login_ip;not null" bson:"login_ip"` // 最近登录IP
|
||||
|
||||
@@ -5,21 +5,17 @@ set -e
|
||||
_tag="$1"
|
||||
_arch=$(go env GOARCH)
|
||||
|
||||
for file in cmd/*; do
|
||||
# Skip if not a directory
|
||||
if [ ! -d "$file" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
for file in pkg/core/*; do
|
||||
app="$(basename $file)";
|
||||
# tar platform
|
||||
for os in linux darwin windows; do
|
||||
_target="$app-$_tag.$os-$_arch.tar.gz"
|
||||
GOOS=$os GOARCH=$_arch scripts/run_build.sh $app
|
||||
|
||||
# Create tar with flattened structure using -C parameter
|
||||
tar czf "$_target" \
|
||||
CHANGELOG.md LICENSE README.md \
|
||||
-C "./cmd/$app" etc backend
|
||||
GOOS=$os GOARCH=$_arch \
|
||||
go build -tags prod -ldflags '-extldflags "-static"' -o backend "./cmd/$app"
|
||||
if [ "$app" = "eiblog" ]; then
|
||||
tar czf $_target conf website assets backend
|
||||
else
|
||||
tar czf $_target conf backend
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
cd cmd/$1 && go run main.go
|
||||
go run cmd/$1/main.go
|
||||
|
||||