Compare commits

..

No commits in common. "master" and "20240219" have entirely different histories.

19 changed files with 471 additions and 602 deletions

View File

@ -1,3 +1,3 @@
MYURLS_PORT=8080 MYURLS_PORT=8002
MYURLS_DOMAIN=example.com MYURLS_DOMAIN=example.com
MYURLS_PROTO=https MYURLS_TTL=180

View File

@ -2,18 +2,17 @@ name: Github CI
on: on:
push: push:
branches: branches: [master]
- '*'
jobs: jobs:
linux_amd64_build: linux_amd64_build:
name: Linux amd64 build name: Linux amd64 build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Set up Go 1.22 - name: Set up Go 1.21
uses: actions/setup-go@v5 uses: actions/setup-go@v4
with: with:
go-version: '^1.22.1' go-version: '^1.21.6'
id: go id: go
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@master uses: actions/checkout@master
@ -32,10 +31,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# runs-on: self-hosted # runs-on: self-hosted
steps: steps:
- name: Set up Go 1.22 - name: Set up Go 1.21
uses: actions/setup-go@v5 uses: actions/setup-go@v4
with: with:
go-version: '^1.22.1' go-version: '^1.21.6'
id: go id: go
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@master uses: actions/checkout@master
@ -53,10 +52,10 @@ jobs:
# name: Darwin amd64 build # name: Darwin amd64 build
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:
# - name: Set up Go 1.22 # - name: Set up Go 1.21
# uses: actions/setup-go@v5 # uses: actions/setup-go@v4
# with: # with:
# go-version: '^1.22.1' # go-version: '^1.21.6'
# id: go # id: go
# - name: Check out code into the Go module directory # - name: Check out code into the Go module directory
# uses: actions/checkout@master # uses: actions/checkout@master
@ -74,10 +73,10 @@ jobs:
name: Windows x64 build name: Windows x64 build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Set up Go 1.22 - name: Set up Go 1.21
uses: actions/setup-go@v5 uses: actions/setup-go@v4
with: with:
go-version: '^1.22.1' go-version: '^1.21.6'
id: go id: go
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@master uses: actions/checkout@master

2
.gitignore vendored
View File

@ -2,8 +2,6 @@
build/ build/
logs/ logs/
data/ data/
*.log
.env .env
dist/ dist/
MyUrls

View File

@ -1,14 +1,14 @@
FROM golang:1.22-alpine AS build FROM golang:1.21-alpine AS build
RUN apk update && apk add upx
WORKDIR /app WORKDIR /app
COPY . . COPY main.go go.mod go.sum .
RUN go mod tidy
# RUN go env -w GOPROXY=https://mirrors.cloud.tencent.com/go/,direct RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o myurls main.go \
RUN go mod download && upx myurls
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o myurls
FROM scratch FROM scratch
WORKDIR /app WORKDIR /app
COPY --from=build /app/myurls ./ COPY --from=build /app/myurls ./
COPY public/* ./public/ COPY public/* ./public/
EXPOSE 8080 EXPOSE 8002
ENTRYPOINT ["/app/myurls"] ENTRYPOINT ["/app/myurls"]

View File

@ -5,40 +5,41 @@ BINARY_DARWIN_ARM64="build/myurls-darwin-arm64"
BINARY_WINDOWS="build/myurls-windows-x64" BINARY_WINDOWS="build/myurls-windows-x64"
BINARY_ARM64="build/myurls-linux-arm64" BINARY_ARM64="build/myurls-linux-arm64"
GOFILES="main.go"
VERSION=1.0.0 VERSION=1.0.0
BUILD=`date +%FT%T%z` BUILD=`date +%FT%T%z`
default: default:
@echo ${BINARY_DEFAULT} @echo ${BINARY_DEFAULT}
@CGO_ENABLED=0 go build -ldflags="-s -w" -o ${BINARY_DEFAULT} @CGO_ENABLED=0 go build -ldflags="-s -w" -o ${BINARY_DEFAULT} ${GOFILES}
all: all:
@echo ${BINARY_LINUX} @echo ${BINARY_LINUX}
@CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_LINUX} @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_LINUX} ${GOFILES}
# @echo ${BINARY_DARWIN} # @echo ${BINARY_DARWIN}
# @CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_DARWIN} # @CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_DARWIN} ${GOFILES}
# @echo ${BINARY_DARWIN_ARM64} # @echo ${BINARY_DARWIN_ARM64}
# @CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o ${BINARY_DARWIN_ARM64} # @CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o ${BINARY_DARWIN_ARM64} ${GOFILES}
@echo ${BINARY_WINDOWS} @echo ${BINARY_WINDOWS}
@CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_WINDOWS} @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_WINDOWS} ${GOFILES}
@echo ${BINARY_ARM64} @echo ${BINARY_ARM64}
@CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o ${BINARY_ARM64} @CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o ${BINARY_ARM64} ${GOFILES}
linux: linux:
@echo ${BINARY_LINUX} @echo ${BINARY_LINUX}
@CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_LINUX} @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_LINUX} ${GOFILES}
darwin: darwin:
@echo ${BINARY_DARWIN} @echo ${BINARY_DARWIN}
@CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_DARWIN} @CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_DARWIN} ${GOFILES}
windows: windows:
@echo ${BINARY_WINDOWS} @echo ${BINARY_WINDOWS}
@CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_WINDOWS} @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o ${BINARY_WINDOWS} ${GOFILES}
aarch64: aarch64:
@echo ${BINARY_ARM64} @echo ${BINARY_ARM64}
@CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o ${BINARY_ARM64} @CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o ${BINARY_ARM64} ${GOFILES}
install: install:
@go mod tidy @go mod tidy

View File

@ -1,11 +1,15 @@
# MyUrls # MyUrls
基于 Go 1.22 与 Redis 实现的本地短链接服务,用于缩短 URL 与短链接还原。 基于 Go 1.21 与 Redis 实现的本地短链接服务,用于缩短 URL 与短链接还原。
## Table of Contents ## Table of Contents
- [Dependencies](#dependencies) - [Dependencies](#dependencies)
- [Docker](#docker) - [Docker](#docker)
- [Deploy Online](#deploy-online)
- [Deploy on Railway](#deploy-on-railway)
- [部署](#部署)
- [添加域名](#添加域名)
- [Install](#install) - [Install](#install)
- [Usage](#usage) - [Usage](#usage)
- [日志清理](#日志清理) - [日志清理](#日志清理)
@ -31,7 +35,7 @@ sudo apt-get install redis-server -y
现在你可以无需安装其他服务,使用 docker 或 [docker-compose](https://docs.docker.com/compose/install/) 部署本项目。注:请自行修改 .env 中参数。 现在你可以无需安装其他服务,使用 docker 或 [docker-compose](https://docs.docker.com/compose/install/) 部署本项目。注:请自行修改 .env 中参数。
``` ```
docker run -d --restart always --name myurls careywong/myurls:latest -domain example.com -port 8002 -conn 127.0.0.1:6379 -password '' docker run -d --restart always --name myurls careywong/myurls:latest -domain example.com -port 8002 -conn 127.0.0.1:6379 -passwd '' -ttl 90
``` ```
```shell script ```shell script
@ -42,6 +46,25 @@ cp .env.example .env
docker-compose up -d docker-compose up -d
``` ```
## Deploy Online
### Deploy on Railway
#### 部署
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Fpzcn%2FMyurls-Railway&plugins=redis&envs=ENV_DOMAIN%2CENV_TTL%2CPORT&ENV_DOMAINDesc=Your+domain.&ENV_TTLDesc=Short+link+validity+period+%28day%29&PORTDesc=DO+NOT+CHANGE&ENV_TTLDefault=180&PORTDefault=80)
通过上方链接一键部署到Railway并填入以下参数
参数说明:
- `DOMAIN` - 短链接域名必填项不需要添加https:// (如 abc.com)
- `TTL` - 短链接有效期,单位(天)默认180天 (default 180)
- `PORT` - 端口保持80请勿修改
#### 添加域名
在Cloudflare中添加域名并配置SSL/TLS为完全及以上并在Railway中接入该域名参考[官方文档](https://docs.railway.app/deploy/exposing-your-app#lets-encrypt-ssl-certificates)。
## Install ## Install
@ -54,7 +77,7 @@ make install
生成可执行文件,目录位于 build/ 。默认当前平台,其他平台请参照 Makefile 或执行对应 go build 命令。 生成可执行文件,目录位于 build/ 。默认当前平台,其他平台请参照 Makefile 或执行对应 go build 命令。
```shell script ```shell script
make bash release.sh
``` ```
## Usage ## Usage
@ -62,18 +85,17 @@ make
前往 [Actions](https://github.com/CareyWang/MyUrls/actions/workflows/go.yml) 下载对应平台可执行文件。 前往 [Actions](https://github.com/CareyWang/MyUrls/actions/workflows/go.yml) 下载对应平台可执行文件。
```shell script ```shell script
Usage of ./MyUrls: Usage:
-conn string -conn string
address of the redis server (default "localhost:6379") Redis连接格式: host:port (default "127.0.0.1:6379")
-domain string -domain string
domain of the server (default "localhost:8080") 短链接域名,必填项
-h display help -passwd string
-password string Redis连接密码
password of the redis server -port int
-port string 服务端口 (default 8002)
port to run the server on (default "8080") -ttl int
-proto string 短链接有效期,单位(天)默认180天。 (default 180)
protocol of the server (default "https")
``` ```
建议配合 [pm2](https://pm2.keymetrics.io/) 开启守护进程。 建议配合 [pm2](https://pm2.keymetrics.io/) 开启守护进程。

View File

@ -1,13 +0,0 @@
package main
type Response struct {
Code int `json:"Code"`
Msg string `json:"Message"`
Data any `json:"Data"`
}
// Response codes
const ResponseCodeSuccess = 0 // Success
const ResponseCodeSuccessLegacy = 1 // Success
const ResponseCodeParamsCheckError = 1001 // Parameter check error
const ResponseCodeServerError = 1002 // Server error

View File

@ -6,15 +6,15 @@ services:
restart: always restart: always
env_file: .env env_file: .env
ports: ports:
- "${MYURLS_PORT}:${MYURLS_PORT}" - "${MYURLS_PORT}:8002"
volumes: volumes:
- ./data/myurls/logs:/app/logs - ./data/myurls/logs:/app/logs
depends_on: depends_on:
- myurls-redis - myurls-redis
entrypoint: ["/app/myurls", "-domain", "${MYURLS_DOMAIN}", "-conn", myurls-redis:6379] entrypoint: ["/app/myurls", "-domain", "${MYURLS_DOMAIN}", "-conn", myurls-redis:6379, "-ttl", "${MYURLS_TTL}"]
myurls-redis: myurls-redis:
image: "redis:7" image: "redis:6"
container_name: myurls-redis container_name: myurls-redis
restart: always restart: always
volumes: volumes:

31
go.mod
View File

@ -1,46 +1,39 @@
module github.com/CareyWang/MyUrls module github.com/CareyWang/MyUrls
go 1.22 go 1.20
require ( require (
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/redis/go-redis/v9 v9.5.1 github.com/gomodule/redigo v1.8.9
github.com/stretchr/testify v1.9.0 github.com/sirupsen/logrus v1.9.3
go.uber.org/zap v1.27.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
require ( require (
github.com/bytedance/sonic v1.11.3 // indirect github.com/bytedance/sonic v1.10.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.19.0 // indirect github.com/go-playground/validator/v10 v10.17.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/kr/pretty v0.3.0 // indirect github.com/kr/pretty v0.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.7.0 // indirect golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.18.0 // indirect
golang.org/x/net v0.22.0 // indirect golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

66
go.sum
View File

@ -1,13 +1,7 @@
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
@ -19,8 +13,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@ -28,23 +20,23 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 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/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 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=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 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/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 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/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 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/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@ -54,8 +46,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.3.0 h1:jX8FDLfW4ThVXctBNZ+3cIWnCSnrACDV73r76dy0aQQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.3.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 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-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -63,60 +55,50 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 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/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,112 +0,0 @@
package main
import (
"encoding/base64"
"time"
"github.com/gin-gonic/gin"
)
const defaultTTL = time.Hour * 24 * 365 // 默认过期时间1年
const defaultRenewTime = time.Hour * 48 // 默认续命时间2天
const defaultShortKeyLength = 7 // 默认短链接长度7位
// ShortToLongHandler gets the long URL from a short URL
func ShortToLongHandler() gin.HandlerFunc {
return func(c *gin.Context) {
resp := Response{}
shortKey := c.Param("shortKey")
longURL := ShortToLong(c, shortKey)
if longURL == "" {
resp.Code = ResponseCodeServerError
resp.Msg = "failed to get long URL, please check the short URL if exists or expired"
c.JSON(404, resp)
return
}
// todo
// check whether need renew expiration time
// only renew once per day
// if err := Renew(c, shortKey, defaultRenewTime); err != nil {
// logger.Warn("failed to renew short URL: ", err.Error())
// }
c.Redirect(301, longURL)
}
}
type LongToShortParams struct {
LongUrl string `form:"longUrl" json:"longUrl" binding:"required"`
ShortKey string `form:"shortKey" json:"shortKey" binding:"omitempty"`
}
// LongToShortHandler creates a short URL from a long URL
func LongToShortHandler() gin.HandlerFunc {
return func(c *gin.Context) {
resp := Response{}
// check parameters
req := LongToShortParams{}
if err := c.ShouldBind(&req); err != nil {
resp.Code = ResponseCodeParamsCheckError
resp.Msg = "invalid parameters"
logger.Warn("invalid parameters: ", err.Error())
c.JSON(200, resp)
return
}
// 兼容以前的实现,这里如果是 base64 编码的字符串,进行解码
_longUrl, err := base64.StdEncoding.DecodeString(req.LongUrl)
if err == nil {
req.LongUrl = string(_longUrl)
}
// generate short key
if req.ShortKey == "" {
req.ShortKey = GenerateRandomString(defaultShortKeyLength)
}
// check whether short key exists
exists, err := CheckRedisKeyIfExist(c, req.ShortKey)
if err != nil {
resp.Code = ResponseCodeServerError
resp.Msg = "failed to check short key"
logger.Error("failed to check short key: ", err.Error())
c.JSON(200, resp)
return
}
if exists {
resp.Code = ResponseCodeParamsCheckError
resp.Msg = "short key already exists, please use another one or leave it empty to generate automatically"
logger.Info("short key already exists: ", req.ShortKey)
c.JSON(200, resp)
return
}
options := &LongToShortOptions{
ShortKey: req.ShortKey,
URL: req.LongUrl,
expiration: defaultTTL,
}
if err := LongToShort(c, options); err != nil {
resp.Code = ResponseCodeServerError
resp.Msg = "failed to create short URL"
logger.Warn("failed to create short URL: ", err.Error())
c.JSON(200, resp)
return
}
shortURL := proto + "://" + domain + "/" + options.ShortKey
// 兼容以前的返回结构体
respDataLegacy := gin.H{
"Code": ResponseCodeSuccessLegacy,
"ShortUrl": shortURL,
}
c.JSON(200, respDataLegacy)
}
}

112
logger.go
View File

@ -1,112 +0,0 @@
package main
import (
"os"
"path"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
var logger *zap.SugaredLogger
const (
logFileMaxSize = 50 // 日志文件最大大小MB
logFileMaxBackups = 10 // 最多保留的备份文件数量
logFileMaxAge = 7 // 日志文件最长保留天数
logFileCompress = false // 是否压缩备份文件
)
func InitLogger() {
// 创建 logs 目录
createLogPath()
// 初始化 zap logger
initZapLogger()
}
// createLogPath 创建 logs 目录
func createLogPath() error {
if dir, err := os.Getwd(); err == nil {
logFilePath := dir + "/logs/"
if err := os.MkdirAll(logFilePath, 0777); err != nil {
panic("create log path failed: " + err.Error())
}
}
return nil
}
// getLogPath 获取 logs 目录
func getLogPath() string {
if dir, err := os.Getwd(); err == nil {
return dir + "/logs/"
}
return ""
}
// 定义 zap logger
func initZapLogger() {
encoder := getEncoder()
core := zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel)
logger = zap.New(core).Sugar()
}
// getEncoder 获取 zap encoder
func getEncoder() zapcore.Encoder {
return zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
}
// initGinLogger 初始化 gin logger
func initGinLogger() *zap.Logger {
logPath := getLogPath()
logFileName := "access.log"
// 日志文件
logFile := path.Join(logPath, logFileName)
lumberJackLogger := &lumberjack.Logger{
Filename: logFile,
MaxSize: logFileMaxSize, // 日志文件最大大小MB
MaxBackups: logFileMaxBackups, // 最多保留的备份文件数量
MaxAge: logFileMaxAge, // 日志文件最长保留天数
Compress: logFileCompress, // 是否压缩备份文件
}
writeSyncer := zapcore.AddSync(lumberJackLogger)
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
encoderConfig.EncodeCaller = nil
encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
encoder := zapcore.NewConsoleEncoder(encoderConfig)
core := zapcore.NewCore(encoder, writeSyncer, zapcore.InfoLevel)
return zap.New(core, zap.AddCaller())
}
// initServiceLogger 初始化服务日志
func initServiceLogger() gin.HandlerFunc {
_logger := initGinLogger()
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next()
_logger.Info(
"request",
zap.String("time", start.Format(time.RFC3339)),
zap.String("method", c.Request.Method),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("path", path),
zap.Int("status", c.Writer.Status()),
zap.Duration("latency", time.Since(start)),
)
}
}

View File

@ -1,52 +0,0 @@
package main
import (
"context"
"time"
)
// ShortToLong gets the long URL from a short URL
func ShortToLong(ctx context.Context, shortKey string) string {
rc := GetRedisClient()
return rc.Get(ctx, shortKey).Val()
}
// LongToShortOptions are the options for the LongToShort function
type LongToShortOptions struct {
ShortKey string
URL string
expiration time.Duration
}
// LongToShort creates a short URL from a long URL
func LongToShort(ctx context.Context, options *LongToShortOptions) error {
rc := GetRedisClient()
return rc.SetEx(ctx, options.ShortKey, options.URL, options.expiration).Err()
}
// Renew updates the expiration time of a short URL
func Renew(ctx context.Context, shortKey string, expiration time.Duration) error {
rc := GetRedisClient()
rs := rc.TTL(ctx, shortKey)
if rs.Err() != nil {
return rs.Err()
}
ttl := rs.Val()
if ttl < 0 {
return nil
}
return rc.Expire(ctx, shortKey, ttl+expiration).Err()
}
func CheckRedisKeyIfExist(ctx context.Context, key string) (bool, error) {
rc := GetRedisClient()
rs := rc.Exists(ctx, key)
if rs.Err() != nil {
return false, rs.Err()
}
return rs.Val() > 0, nil
}

View File

@ -1,31 +0,0 @@
// FILEPATH: /root/CareyWang/MyUrls/logic_test.go
package main
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestLongToShortAndShortToLong(t *testing.T) {
ctx := context.Background()
initRedisClient(mockRedisOptions)
shortKey := "testKey"
longURL := "https://example.com"
err := LongToShort(ctx, &LongToShortOptions{
ShortKey: shortKey,
URL: longURL,
expiration: 60 * time.Second,
})
assert.NoError(t, err)
// delete test data from redis
defer GetRedisClient().Del(ctx, shortKey)
resultLongURL := ShortToLong(ctx, shortKey)
assert.Equal(t, longURL, resultLongURL)
}

449
main.go
View File

@ -1,102 +1,107 @@
package main package main
import ( import (
"context" "crypto/md5"
"encoding/base64"
"encoding/hex"
"flag" "flag"
"fmt" "fmt"
"log"
"math/rand"
"net/http" "net/http"
"os" "os"
"path"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/redis/go-redis/v9" "github.com/gomodule/redigo/redis"
"github.com/sirupsen/logrus"
) )
var helpFlag bool // Response is the response structure
type Response struct {
var ( Code int
port = "8080" Message string
domain = "localhost:8080" LongUrl string
proto = "https" ShortUrl string
redisAddr = "localhost:6379"
redisPassword = ""
)
func init() {
flag.BoolVar(&helpFlag, "h", false, "display help")
flag.StringVar(&port, "port", port, "port to run the server on")
flag.StringVar(&domain, "domain", domain, "domain of the server")
flag.StringVar(&proto, "proto", proto, "protocol of the server")
flag.StringVar(&redisAddr, "conn", redisAddr, "address of the redis server")
flag.StringVar(&redisPassword, "password", redisPassword, "password of the redis server")
} }
// redisPoolConf is the Redis pool configuration.
type redisPoolConf struct {
maxIdle int
maxActive int
maxIdleTimeout int
host string
password string
db int
handleTimeout int
}
// letterBytes is a string containing all the characters used in the short URL generation.
const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
// shortUrlLen is the length of the generated short URL.
const shortUrlLen = 7
// defaultPort is the default port number.
const defaultPort int = 8002
// defaultExpire is the redis ttl in days for a short URL.
const defaultExpire = 180
// defaultRedisConfig is the default Redis configuration.
const defaultRedisConfig = "127.0.0.1:6379"
// defaultLockPrefix is the default prefix for Redis locks.
const defaultLockPrefix = "myurls:lock:"
// defaultRenewal is the default renewal time for Redis locks.
const defaultRenewal = 1
// secondsPerDay is the number of seconds in a day.
const secondsPerDay = 24 * 3600
// redisPool is a connection pool for Redis.
var redisPool *redis.Pool
// redisPoolConfig is the Redis pool configuration.
var redisPoolConfig *redisPoolConf
// redisClient is a Redis client.
var redisClient redis.Conn
func main() { func main() {
flag.Parse()
if helpFlag {
flag.Usage()
os.Exit(0)
}
// 从环境变量中读取配置,且环境变量优先级高于命令行参数
parseEnvirons()
InitLogger()
// init and check redis
initRedisClient(&redis.Options{
Addr: redisAddr,
Password: redisPassword,
DB: 0,
})
ctx := context.Background()
rc := GetRedisClient()
rs := rc.Ping(ctx)
if rs.Err() != nil {
logger.Fatalln("redis ping failed: ", rs.Err())
}
logger.Info("redis ping success")
// GC optimize
ballast := make([]byte, 1<<30) // 预分配 1G 内存,不会实际占用物理内存,不可读写该变量
defer func() {
logger.Info("ballast len %v", len(ballast))
}()
// start http server
run()
}
func parseEnvirons() {
if p := os.Getenv("MYURLS_PORT"); p != "" {
port = p
}
if d := os.Getenv("MYURLS_DOMAIN"); d != "" {
domain = d
}
if p := os.Getenv("MYURLS_PROTO"); p != "" {
proto = p
}
if c := os.Getenv("MYURLS_REDIS_CONN"); c != "" {
redisAddr = c
}
if p := os.Getenv("MYURLS_REDIS_PASSWORD"); p != "" {
redisPassword = p
}
}
func run() {
// init and run server
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
router := gin.Default() router := gin.Default()
// logger // Log 收集中间件
router.Use(initServiceLogger()) router.Use(LoggerToFile())
// static files
router.LoadHTMLGlob("public/*.html") router.LoadHTMLGlob("public/*.html")
router.StaticFile("/logo.png", "public/logo.png")
port := flag.Int("port", defaultPort, "服务端口")
domain := flag.String("domain", "", "短链接域名,必填项")
ttl := flag.Int("ttl", defaultExpire, "短链接有效期,单位(天)默认180天。")
conn := flag.String("conn", defaultRedisConfig, "Redis连接格式: host:port")
passwd := flag.String("passwd", "", "Redis连接密码")
https := flag.Int("https", 1, "是否返回 https 短链接")
flag.Parse()
if *domain == "" {
flag.Usage()
log.Fatalln("缺少关键参数")
}
redisPoolConfig = &redisPoolConf{
maxIdle: 1024,
maxActive: 1024,
maxIdleTimeout: 30,
host: *conn,
password: *passwd,
db: 0,
handleTimeout: 30,
}
initRedisPool()
router.GET("/", func(context *gin.Context) { router.GET("/", func(context *gin.Context) {
context.HTML(http.StatusOK, "index.html", gin.H{ context.HTML(http.StatusOK, "index.html", gin.H{
@ -104,9 +109,283 @@ func run() {
}) })
}) })
router.POST("/short", LongToShortHandler()) // 短链接生成
router.GET("/:shortKey", ShortToLongHandler()) router.POST("/short", func(context *gin.Context) {
res := &Response{
logger.Infof("server running on :%s", port) Code: 1,
router.Run(fmt.Sprintf(":%s", port)) Message: "",
LongUrl: "",
ShortUrl: "",
}
longUrl := context.PostForm("longUrl")
shortKey := context.PostForm("shortKey")
if longUrl == "" {
res.Code = 0
res.Message = "longUrl为空"
context.JSON(200, *res)
return
}
_longUrl, _ := base64.StdEncoding.DecodeString(longUrl)
longUrl = string(_longUrl)
res.LongUrl = longUrl
// 根据有没有填写 short key分别执行
if shortKey != "" {
redisClient := redisPool.Get()
// 检测短链是否已存在
_exists, _ := redis.String(redisClient.Do("get", shortKey))
if _exists != "" && _exists != longUrl {
res.Code = 0
res.Message = "短链接已存在请更换key"
context.JSON(200, *res)
return
}
// 存储
_, _ = redisClient.Do("set", shortKey, longUrl)
} else {
shortKey = longToShort(longUrl, *ttl*secondsPerDay)
}
protocol := "http://"
if *https != 0 {
protocol = "https://"
}
res.ShortUrl = protocol + *domain + "/" + shortKey
// context.Header("Access-Control-Allow-Origin", "*")
context.JSON(200, *res)
})
// 短链接跳转
router.GET("/:shortKey", func(context *gin.Context) {
shortKey := context.Param("shortKey")
longUrl := shortToLong(shortKey)
if longUrl == "" {
context.String(http.StatusNotFound, "短链接不存在或已过期")
} else {
context.Redirect(http.StatusMovedPermanently, longUrl)
}
})
// GC 优化
ballast := make([]byte, 1<<30) // 分配 1G 内存,不会实际占用物理内存,不可读写该变量
defer func() {
log.Println("ballast len %v", len(ballast))
}()
router.Run(fmt.Sprintf(":%d", *port))
}
// 短链接转长链接
func shortToLong(shortKey string) string {
redisClient = redisPool.Get()
defer redisClient.Close()
longUrl, _ := redis.String(redisClient.Do("get", shortKey))
// 获取到长链接后续命1天。每天仅允许续命1次。
if longUrl != "" {
renew(shortKey)
}
return longUrl
}
// 长链接转短链接
func longToShort(longUrl string, ttl int) string {
redisClient = redisPool.Get()
defer redisClient.Close()
// 是否生成过该长链接对应短链接
longUrlMD5Bytes := md5.Sum([]byte(longUrl))
longUrlMD5 := hex.EncodeToString(longUrlMD5Bytes[:])
_existsKey, _ := redis.String(redisClient.Do("get", longUrlMD5))
if _existsKey != "" {
_, _ = redisClient.Do("expire", _existsKey, ttl)
log.Println("Hit cache: " + _existsKey)
return _existsKey
}
// 重试三次
var shortKey string
for i := 0; i < 3; i++ {
shortKey = generate(shortUrlLen)
_existsLongUrl, _ := redis.String(redisClient.Do("get", shortKey))
if _existsLongUrl == "" {
break
}
}
if shortKey != "" {
_, _ = redisClient.Do("mset", shortKey, longUrl, longUrlMD5, shortKey)
_, _ = redisClient.Do("expire", shortKey, ttl)
_, _ = redisClient.Do("expire", longUrlMD5, secondsPerDay)
}
return shortKey
}
// 续命
func renew(shortKey string) {
redisClient = redisPool.Get()
defer redisClient.Close()
// 加锁
lockKey := defaultLockPrefix + shortKey
lock, _ := redis.Int(redisClient.Do("setnx", lockKey, 1))
if lock == 1 {
// 设置锁过期时间
_, _ = redisClient.Do("expire", lockKey, defaultRenewal*secondsPerDay)
// 续命
ttl, err := redis.Int(redisClient.Do("ttl", shortKey))
if err == nil && ttl != -1 {
_, _ = redisClient.Do("expire", shortKey, ttl+defaultRenewal*secondsPerDay)
}
}
}
// generate is a function that takes an integer bits and returns a string.
// The function generates a random string of length equal to bits using the letterBytes slice.
// The letterBytes slice contains characters that can be used to generate a random string.
// The generation of the random string is based on the current time using the UnixNano() function.
func generate(bits int) string {
// Create a byte slice b of length bits.
b := make([]byte, bits)
// Create a new random number generator with the current time as the seed.
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// Generate a random byte for each element in the byte slice b using the letterBytes slice.
for i := range b {
b[i] = letterBytes[r.Intn(len(letterBytes))]
}
// Convert the byte slice to a string and return it.
return string(b)
}
// 定义 logger
func Logger() *logrus.Logger {
logFilePath := ""
if dir, err := os.Getwd(); err == nil {
logFilePath = dir + "/logs/"
}
if err := os.MkdirAll(logFilePath, 0777); err != nil {
fmt.Println(err.Error())
}
logFileName := "access.log"
//日志文件
fileName := path.Join(logFilePath, logFileName)
if _, err := os.Stat(fileName); err != nil {
if _, err := os.Create(fileName); err != nil {
fmt.Println(err.Error())
}
}
//写入文件
src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
fmt.Println("err", err)
}
//实例化
logger := logrus.New()
//设置输出
logger.SetOutput(src)
// logger.Out = src
//设置日志级别
logger.SetLevel(logrus.DebugLevel)
//设置日志格式
logger.Formatter = &logrus.JSONFormatter{}
return logger
}
// 文件日志
func LoggerToFile() gin.HandlerFunc {
logger := Logger()
return func(c *gin.Context) {
logMap := make(map[string]interface{})
// 开始时间
startTime := time.Now()
logMap["startTime"] = startTime.Format("2006-01-02 15:04:05")
// 处理请求
c.Next()
// 结束时间
endTime := time.Now()
logMap["endTime"] = endTime.Format("2006-01-02 15:04:05")
// 执行时间
logMap["latencyTime"] = endTime.Sub(startTime).Microseconds()
// 请求方式
logMap["reqMethod"] = c.Request.Method
// 请求路由
logMap["reqUri"] = c.Request.RequestURI
// 状态码
logMap["statusCode"] = c.Writer.Status()
// 请求IP
logMap["clientIP"] = c.ClientIP()
// 请求 UA
logMap["clientUA"] = c.Request.UserAgent()
//日志格式
// logJson, _ := json.Marshal(logMap)
// logger.Info(string(logJson))
logger.WithFields(logrus.Fields{
"startTime": logMap["startTime"],
"endTime": logMap["endTime"],
"latencyTime": logMap["latencyTime"],
"reqMethod": logMap["reqMethod"],
"reqUri": logMap["reqUri"],
"statusCode": logMap["statusCode"],
"clientIP": logMap["clientIP"],
"clientUA": logMap["clientUA"],
}).Info()
}
}
// redis 连接池
func initRedisPool() {
// 建立连接池
redisPool = &redis.Pool{
MaxIdle: redisPoolConfig.maxIdle,
MaxActive: redisPoolConfig.maxActive,
IdleTimeout: time.Duration(redisPoolConfig.maxIdleTimeout) * time.Second,
Wait: true,
Dial: func() (redis.Conn, error) {
con, err := redis.Dial("tcp", redisPoolConfig.host,
redis.DialPassword(redisPoolConfig.password),
redis.DialDatabase(redisPoolConfig.db),
redis.DialConnectTimeout(time.Duration(redisPoolConfig.handleTimeout)*time.Second),
redis.DialReadTimeout(time.Duration(redisPoolConfig.handleTimeout)*time.Second),
redis.DialWriteTimeout(time.Duration(redisPoolConfig.handleTimeout)*time.Second))
if err != nil {
return nil, err
}
return con, nil
},
}
} }

View File

@ -19,7 +19,7 @@
<el-header></el-header> <el-header></el-header>
<el-main> <el-main>
<div :class="[isPc ? 'body-center body-width-pc' : 'body-center body-width-mb']"> <div :class="[isPc ? 'body-center body-width-pc' : 'body-center body-width-mb']">
<img width="300" src="/logo.png" @click="goToGayHub"> <img width="300" src="https://cdn.jsdelivr.net/gh/CareyWang/MyUrls@master/public/logo.png" @click="goToGayHub">
<el-input ref="long" v-model="longUrl" size="medium" @keyup.enter.native="enterToDoShort"> <el-input ref="long" v-model="longUrl" size="medium" @keyup.enter.native="enterToDoShort">
<el-button slot="append" icon="el-icon-magic-stick" @click="doShort" :loading="loading"></el-button> <el-button slot="append" icon="el-icon-magic-stick" @click="doShort" :loading="loading"></el-button>
</el-input> </el-input>

View File

@ -1,28 +0,0 @@
package main
import (
"math/rand"
"time"
)
const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
// generate is a function that takes an integer bits and returns a string.
// The function generates a random string of length equal to bits using the letterBytes slice.
// The letterBytes slice contains characters that can be used to generate a random string.
// The generation of the random string is based on the current time using the UnixNano() function.
func GenerateRandomString(bits int) string {
// Create a byte slice b of length bits.
b := make([]byte, bits)
// Create a new random number generator with the current time as the seed.
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// Generate a random byte for each element in the byte slice b using the letterBytes slice.
for i := range b {
b[i] = letterBytes[r.Intn(len(letterBytes))]
}
// Convert the byte slice to a string and return it.
return string(b)
}

View File

@ -1,16 +0,0 @@
package main
import (
"github.com/redis/go-redis/v9"
)
var RedisClient *redis.Client
// initRedisClient is a function that takes a pointer to a RedisOptions struct and returns a pointer to a Redis client.
func initRedisClient(options *redis.Options) {
RedisClient = redis.NewClient(options)
}
func GetRedisClient() *redis.Client {
return RedisClient
}

View File

@ -1,41 +0,0 @@
package main
import (
"context"
"testing"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
)
var mockRedisOptions = &redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
}
func TestGetRedisClient(t *testing.T) {
client := GetRedisClient()
assert.Nil(t, client)
initRedisClient(mockRedisOptions)
client = GetRedisClient()
assert.NotNil(t, client)
// Test redis exec commands and response
ctx := context.Background()
rs := client.Ping(ctx)
assert.Nil(t, rs.Err())
assert.Equal(t, "PONG", rs.Val())
rsCmd := GetRedisClient().Do(ctx, "dbsize")
assert.Nil(t, rsCmd.Err())
}
func BenchmarkGetRedisClient(b *testing.B) {
initRedisClient(mockRedisOptions)
b.ResetTimer()
for i := 0; i < b.N; i++ {
GetRedisClient().Get(context.Background(), "key")
}
}